2013-11-24 41 views
6

是否可以直接讀寫WriteableBitmap的像素數據?我目前使用的是WriteableBitmapEx的SetPixel(),但速度很慢,我想直接訪問像素而沒有任何開銷。編輯WriteableBitmap的原始像素數據?

我沒有使用HTML5的畫布在一段時間,但如果我沒有記錯,你可以得到它的圖像數據作爲數字的單一陣列,這就是種什麼我正在尋找

在此先感謝

回答

22

要回答你的問題,你可以通過使用Lock更直接地訪問一個可寫的位圖的數據,寫,Unlock模式,如下面所示,但它是通常沒有必要,除非您將繪圖基於圖像的內容。更典型的情況是,你可以創建一個新的緩衝區並將其設置爲位圖,而不是相反。這就是說,在WPF中有許多可擴展點來執行創新繪圖而不借助像素操作。對於大多數控件來說,現有的WPF primitives(邊框,線條,矩形,圖像等)綽綽有餘 - 不要擔心使用它們中的許多,它們使用起來相當便宜。對於複雜的控件,可以使用DrawingContext來繪製D3D基元。對於圖像效果,您可以使用Effect class或使用內置效果(模糊和陰影)implement GPU assisted shaders

但是,如果您的情況需要直接像素訪問,請選擇pixel format並開始編寫。我建議BGRA32,因爲它很容易理解,可能是最常見的討論。

BGRA32表示像素數據以4個字節的順序存儲在內存中,代表圖像的藍色,綠色,紅色和alpha通道。這很方便,因爲每個像素都在4個字節的邊界上結束,並以32位整數存儲。在處理32位整數時,請記住大多數平臺上的順序都會顛倒過來(如果需要支持多個平臺,請在運行時檢查BitConverter.IsLittleEndian以確定正確的字節順序,x86和x86_64均爲小端)

圖像數據存儲在一個寬度爲1的寬度的水平條中,其構成單行寬度的圖像。寬度總是大於或等於圖像的像素寬度乘以所選格式中每個像素的字節數。某些情況會導致步幅比專用於某些體系結構的width * bytesPerPixel更長,因此您必須使用步幅寬度來計算行的起始位置,而不是乘以寬度。由於我們使用4字節寬的像素格式,因此我們的步幅確實是width * 4,但您不應該依賴它。

如前所述,我會建議使用WritableBitmap是,如果你正在訪問的現有圖像的唯一案例,所以這是下面的例子:

前/後:

image run through below algorithm

// must be compiled with /UNSAFE 
// get an image to draw on and convert it to our chosen format 
BitmapSource srcImage = JpegBitmapDecoder.Create(File.Open("img13.jpg", FileMode.Open), 
    BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames[0]; 

if (srcImage.Format != PixelFormats.Bgra32) 
    srcImage = new FormatConvertedBitmap(srcImage, PixelFormats.Bgra32, null, 0); 

// get a writable bitmap of that image 
var wbitmap = new WriteableBitmap(srcImage); 

int width = wbitmap.PixelWidth; 
int height = wbitmap.PixelHeight; 
int stride = wbitmap.BackBufferStride; 
int bytesPerPixel = (wbitmap.Format.BitsPerPixel + 7)/8; 

wbitmap.Lock(); 
byte* pImgData = (byte*)wbitmap.BackBuffer; 

// set alpha to transparent for any pixel with red < 0x88 and invert others 
int cRowStart = 0; 
int cColStart = 0; 
for (int row = 0; row < height; row++) 
{ 
    cColStart = cRowStart; 
    for (int col = 0; col < width; col++) 
    { 
     byte* bPixel = pImgData + cColStart; 
     UInt32* iPixel = (UInt32*)bPixel; 

     if (bPixel[2 /* bgRa */] < 0x44) 
     { 
      // set to 50% transparent 
      bPixel[3 /* bgrA */] = 0x7f; 
     } 
     else 
     { 
      // invert but maintain alpha 
      *iPixel = *iPixel^0x00ffffff; 
     } 

     cColStart += bytesPerPixel; 
    } 
    cRowStart += stride; 
} 
wbitmap.Unlock(); 
// if you are going across threads, you will need to additionally freeze the source 
wbitmap.Freeze(); 

但是,如果您不修改現有圖像,則確實沒有必要。例如,你可以使用所有安全代碼繪製棋盤圖案:

輸出:

checkerboard pattern at 25 percent zoom

// draw rectangles 
int width = 640, height = 480, bytesperpixel = 4; 
int stride = width * bytesperpixel; 
byte[] imgdata = new byte[width * height * bytesperpixel]; 

int rectDim = 40; 
UInt32 darkcolorPixel = 0xffaaaaaa; 
UInt32 lightColorPixel = 0xffeeeeee; 
UInt32[] intPixelData = new UInt32[width * height]; 
for (int row = 0; row < height; row++) 
{ 
    for (int col = 0; col < width; col++) 
    { 
     intPixelData[row * width + col] = ((col/rectDim) % 2) != ((row/rectDim) % 2) ? 
      lightColorPixel : darkcolorPixel; 
    } 
} 
Buffer.BlockCopy(intPixelData, 0, imgdata, 0, imgdata.Length); 

// compose the BitmapImage 
var bsCheckerboard = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, imgdata, stride); 

而且你並不真的甚至不需要一個Int32中間,如果你寫入字節數組直接。

輸出:

Gradient produced from below

// draw using byte array 
int width = 640, height = 480, bytesperpixel = 4; 
int stride = width * bytesperpixel; 
byte[] imgdata = new byte[width * height * bytesperpixel]; 

// draw a gradient from red to green from top to bottom (R00 -> ff; Gff -> 00) 
// draw a gradient of alpha from left to right 
// Blue constant at 00 
for (int row = 0; row < height; row++) 
{ 
    for (int col = 0; col < width; col++) 
    { 
     // BGRA 
     imgdata[row * stride + col * 4 + 0] = 0; 
     imgdata[row * stride + col * 4 + 1] = Convert.ToByte((1 - (col/(float)width)) * 0xff); 
     imgdata[row * stride + col * 4 + 2] = Convert.ToByte((col/(float)width) * 0xff); 
     imgdata[row * stride + col * 4 + 3] = Convert.ToByte((row/(float)height) * 0xff); 
    } 
} 
var gradient = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, imgdata, stride); 

編輯:顯然,你要使用WPF作出某種圖像編輯器。我仍然會使用WPF原始圖形和源位圖,然後實現翻譯,縮放,旋轉(如RenderTransform的位圖效果,如Effect),並將所有內容保留在WPF模型中。但是,如果這不適合你,我們有很多其他選擇。

你可以使用WPF的圖元渲染到RenderTargetBitmap已選定PixelFormatWritableBitmap如下使用:

Canvas cvRoot = new Canvas(); 
// position primitives on canvas 

var rtb = new RenderTargetBitmap(width, height, dpix, dpiy, PixelFormats.Bgra32); 
var wb = new WritableBitmap(rtb); 

你可以使用WPF DrawingVisual發出GDI樣式的命令然後渲染爲位圖作爲在RenderTargetBitmap頁面上的樣本上進行了演示。

您可以使用GDI使用InteropBitmap創建使用System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmapHBITMAPBitmap.GetHBitmap方法檢索。儘管如此,確保你不會泄漏HBITMAP

+0

感謝您的答案,我一直在嘗試測試你的代碼,但我必須使用Pbgra32格式,我試圖讓你的轉換代碼工作,但由於我的存儲爲WriteableBitmap而不是我無法獲得的BitmapSource它工作。你知道我可以將WriteableBitmap轉換爲Bgra32嗎? – Oztaco

+0

@ leaf68,'WritableBitmap'繼承自'BitmapSource',您可以使用'FormatConvertedBitmap'類在像素格式之間進行轉換,但同樣不需要。你能解釋你想做什麼嗎? PBGRA通常僅用於存儲圖像,而不用於編輯或創建。 – Mitch

+0

我正在使用WriteableBitmapEx庫來繪製圖形,我只是發現它只接受Pbgra作爲輸入。嗯,是否可以像Bgra一樣編輯Pbgra的原始數據,因爲它的alpha值是預先混合到顏色中的? – Oztaco

1

您可以通過調用Lock()方法並使用BackBuffer屬性來訪問原始像素數據。完成後,請不要忘記撥打AddDirtyRectUnlock。 舉個簡單的例子,你可以看看這個:http://cscore.codeplex.com/SourceControl/latest#CSCore.Visualization/WPF/Utils/PixelManipulationBitmap.cs

+0

謝謝!我使用'*((int *)pBackBuffer)= color_data;'從你的鏈接(和其他代碼一起)來設置它。但有一個問題:我怎樣才能讀取它? – Oztaco

+0

而且,我該如何設置alpha值? – Oztaco

+0

@ leaf68,你直接使用指針運算來訪問位圖,所以你按照你寫它的方式來閱讀它('color_data = *((int *)pBackBuffer)')。在ARGB格式中,MSB保存8位阿爾法值。你可以通過算位來設置它。例如:'currentValue&0x00ffffff | (newAValue << 24)'將其設置爲任意字節。請參閱MSDN有關不安全代碼的更多信息,請訪問http://msdn.microsoft.com/zh-cn/library/aa664774(v=vs.71).aspx。 – Mitch

1

一個不錯的長期頭痛後,我發現this article解釋的方式來做到這一點,而不使用位算術,並讓我把它當作一個數組來代替:

unsafe 
{ 
    IntPtr pBackBuffer = bitmap.BackBuffer; 

    byte* pBuff = (byte*)pBackBuffer.ToPointer(); 

    pBuff[4 * x + (y * bitmap.BackBufferStride)] = 255; 
    pBuff[4 * x + (y * bitmap.BackBufferStride) + 1] = 255; 
    pBuff[4 * x + (y * bitmap.BackBufferStride) + 2] = 255; 
    pBuff[4 * x + (y * bitmap.BackBufferStride) + 3] = 255; 
}