【轉】【WPF】WriteableBitmap應用及圖片數據格式轉換


使用 WriteableBitmap 類基於每個框架來更新和呈現位圖。這對於生成算法內容(如分形圖像)和數據可視化(如音樂可視化工具)很有用。

WriteableBitmap 類使用兩個緩沖區。“后台緩沖區”在系統內存中分配,它可以累計當前未顯示的內容。“前台緩沖區”在系統內存中分配,它包含當前顯示的內容。呈現系統將前台緩沖區復制到視頻內存中以便顯示。

兩個線程使用這兩個緩沖區。“用戶界面 (UI) 線程”生成 UI 但不將其呈現在屏幕上。UI 線程響應用戶輸入、計時器以及其他事件。一個應用程序可以具有多個 UI 線程。“呈現線程”撰寫和呈現 UI 線程的變化。每個應用程序只有一個呈現線程。

UI 線程將內容寫入后台緩沖區。呈現線程從前台緩沖區讀取內容,然后將其復制到視頻內存中。將使用更改的矩形區域跟蹤對后台緩沖區的更改。

WriteableBitmap使用兩個線程來使用這兩個緩沖區。UI 線程響應用戶輸入、計時器以及其他事件。“呈現線程”撰寫和呈現 UI 線程的變化。在WriteableBitmap中,UI 線程將內容寫入后台緩沖區。呈現線程從前台緩沖區讀取內容,然后將其復制到視頻內存中,最后切換兩個緩存區。

WritePixel方法

可以使用WritePixel方法來更新緩沖區:

1. public void WritePixels(Int32Rect sourceRect, Array pixels, int stride, int offset);
2. public void WritePixels(Int32Rect sourceRect, IntPtr buffer, int bufferSize, int stride);
3. public void WritePixels(Int32Rect sourceRect, Array sourceBuffer, int sourceBufferStride,
      int destinationX, int destinationY);
4. public void WritePixels(Int32Rect sourceRect, IntPtr sourceBuffer, int sourceBufferSize,
      int sourceBufferStride, int destinationX, int destinationY);

關於Stride

Stride是Bitmap里一個令人頭痛的東西,它代表着一張圖片每一行的掃描寬度(跨距)。跨距總是大於或等於實際像素寬度。如果跨距為正,則位圖自頂向下。如果跨距為負,則位圖顛倒。Stride是指圖像每一行需要占用的字節數。根據BMP格式的標准,Stride一定要是4的倍數。據個例子,一幅1024*768的24bppRgb的圖像,每行有效的像素信息應該是1024*3 = 3072。因為已經是4的倍數,所以Stride就是3072。那么如果這幅圖像是35*30,那么一行的有效像素信息是105,但是105不是4的倍數,所以填充空字節(也就是0),Stride應該是108。

在WritableBitmap中,可以使用:

.BackBufferStride 

屬性來獲得Stride值。

明白了Stride的意義,讓我們來看看以下代碼:

WriteableBitmap bitmap = new WriteableBitmap(15, 2, 72, 72, PixelFormats.Bgr24, null);
Byte[] buffer = new Byte[bitmap.BackBufferStride * bitmap.PixelHeight];
for (byte i = 0; i < buffer.Length; i++)
{
    buffer[i] = i;
}
bitmap.WritePixels(new Int32Rect(0, 0, 15, 2), buffer, bitmap.BackBufferStride, 0);

Marshal.Copy(bitmap.BackBuffer, buffer, 0, buffer.Length);

在上面的代碼里,我們新建了一個15X2的24位位圖,它的行跨距是(15*3+3)/4*4=48,沒行多了3個填充字節。我們把一個由0到95的連續數組復制到該位圖中,然后重新復制回來,可以看到,第45、46、47個元素值是0:

WritePixel函數中的Stride

在WritePixel的四個重載函數里,同樣有一個stride的參數(后兩個重載函數用的是sourceBufferStride),該參數表示源數組(函數里的sourceBuffer參數)的跨度。

舉個例子,假設WriteableBitmap是8位的,源數組是一個長度為12的一維數組,如果stride為4,則表示填充時,該一維數組代表的一個4X3的圖像:

如果stride為3,則該數組代表的是一個3X4的圖像:

WritePixel方法就是用這個圖像來填充緩存區域:

sourceRect和stride

剛才說到,在WritePixel函數里,通過傳入參數stride來設定填充源數組(sourceBuffer參數)的尺寸,把它看做一個“二維數組”,WritePixel把這個“二維數組”的值寫入WriteableBitmap對象的緩沖區中。那么,如果我只想寫這個“二維數組”的一部分,該怎么做呢?

於是第一個參數sourceRect就是設定這個區域。

設想一下,如下圖,我們使用stride把一個長度為81的sourceBuffer分割成9X9的“二維數組”,因為這里使用的WriteableBitmap是24位的,因此在圖上我用三種顏色表示RGB值:

顯然,該數組一次最多可以填充一個3X9的像素的緩沖區(因為WriteableBitmap是24位的,所以填充時三個字節才能填充一個像素),如果需要填充的區域大於3X9,會拋出異常:ArgumentException。

如果我打算填充的區域是2X4的區域呢?這意味着僅僅使用一部分sourceBuffer:

這時,可以把sourceRect設置為(1,2,2,4)

總之,WritePixel方法的本質就是把sourceBuffer看做一副圖片,使用stride來設定該圖片的寬度,利用sourceRect來確定使用該圖片的哪部分填充,而destinationX、destinationY則是指定把圖片填充到緩沖區的位置坐標。函數調用完成后,會自動調用更新,重新繪制屏幕上的圖像。

Lock、Unlock和AddDirtyRect

一般的,WritePixel方法使用於快速填充圖像,如果我們僅僅想對圖像進行“像素級”的改變,那么,WritePixel未免過於粗獷。這時候,需要使用Lock、AddDirtyRect、Unlock方法了。

以下代碼,通過使用上述函數,把一副圖片“反色”:

unsafe
{
    var bytes = (byte*)m_Bitmap.BackBuffer.ToPointer();
    m_Bitmap.Lock();
    for (int i = 0; i < m_Bitmap.BackBufferStride * m_Bitmap.PixelHeight; i++)
    {
        bytes[i] = (byte)(255-bytes[i]);
    }
    m_Bitmap.AddDirtyRect(new Int32Rect(0, 0, m_Bitmap.PixelWidth, m_Bitmap.PixelHeight));
    m_Bitmap.Unlock();
}

在進行操作前,先使用Lock方法,鎖定后台緩沖區,這時,呈現系統得不到后台緩沖區的數據,因此不發送更新,屏幕不發生變化。

其次,直接修改圖片內存,c#不推薦使用指針,因此該方法是unsafe的。

再次,調用AddDirtyRect方法,指示代碼對后台緩沖區所做的更改,通知呈現系統,緩沖區哪部分發生了改變。

最后,使用Unlock方法,解除對后台緩沖區的鎖定。

一般來說,為了提高效率,不需要更新整幅圖片,因此,可以通過調用多次AddDirtyRect方法,進行局部修改:

m_Bitmap.AddDirtyRect(new Int32Rect(0, 0, 100, 100));
m_Bitmap.AddDirtyRect(new Int32Rect(150, 150, 100, 100));
m_Bitmap.AddDirtyRect(new Int32Rect(300, 300, 100, 100));

 

WPF Image控件 Source: Byte[] ,BitmapImage 相互轉換

 文件轉為byte[]

FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read);
byte[] desBytes = new byte[fs.Length];
fs.Read(desBytes, 0, desBytes.Length);
fs.Close();  

byte[]轉換為BitmapImage:

public static BitmapImage ByteArrayToBitmapImage(byte[] byteArray) 
{ 
    BitmapImage bmp = null;  
    try 
    { 
        bmp = new BitmapImage(); 
        bmp.BeginInit(); 
        bmp.StreamSource = new MemoryStream(byteArray); 
        bmp.EndInit(); 
    } 
    catch 
    { 
        bmp = null; 
    }  
    return bmp; 
}

BitmapImage轉換為byte[]:

public static byte[] BitmapImageToByteArray(BitmapImage bmp) 
{ 
    byte[] byteArray = null;  
    try 
    { 
        Stream sMarket = bmp.StreamSource;  
        if (sMarket != null && sMarket.Length > 0) 
        { 
            //很重要,因為Position經常位於Stream的末尾,導致下面讀取到的長度為0。 
            sMarket.Position = 0; 

            using (BinaryReader br = new BinaryReader(sMarket)) 
            { 
                byteArray = br.ReadBytes((int)sMarket.Length); 
            } 
        } 
    } 
    catch 
    { 
        //other exception handling 
    }  
    return byteArray; 
}
WriteableBitmap wb = new WriteableBitmap(img.Source as BitmapSource);//將Image對象轉換為WriteableBitmap 
byte[] b = Convert.FromBase64String(GetBase64Image(wb));//得到byte數組

WriteableBitmap轉為BitmapImage對象

var bi= new BitmapImage(); 
bi.SetSource(wb.ToImage().ToStream()); //其中wb是WriteableBitmap對象。

BitmapImage轉為WriteableBitmap對象

WriteableBitmap wb = new WriteableBitmap(bi.Source as BitmapSource);

將WriteableBitmap轉為字節數組

byte[] b = Convert.FromBase64String(GetBase64Image(wb));
//這里通過base64間接處理,效率不是很高。

將字節數組/Stream流 轉為BitmapImage對象

MemoryStream ms = new MemoryStream(b); // b為byte[]
using (var stream = new MemoryStream(data))// data為byte[]
{
    var bitmap = new BitmapImage();
    bitmap.BeginInit();
    bitmap.StreamSource = stream;
    bitmap.CacheOption = BitmapCacheOption.OnLoad;
    bitmap.EndInit();
    bitmap.Freeze();
}
//以下方法為Stream轉BitmapFrame
using (var stream = new MemoryStream(data))
{
    var bi = BitmapFrame.Create(stream , BitmapCreateOptions.IgnoreImageCache, BitmapCacheOption.OnLoad);
}

Bitmap 轉 Imagesource

[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool DeleteObject(IntPtr hObject);   

IntPtr hBitmap = bitmap.GetHbitmap(); 
ImageSource wpfBitmap = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
(
                hBitmap,
                IntPtr.Zero,
                Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions()
); 
DeleteObject(hBitmap);

 補充:2016-7-26

WriteableBitmap wtbBmp;//Image對象
RenderTargetBitmap rtbitmap = new RenderTargetBitmap(wtbBmp.PixelWidth, wtbBmp.PixelHeight, wtbBmp.DpiX, wtbBmp.DpiY, PixelFormats.Default);  
DrawingVisual drawingVisual = new DrawingVisual();  
using (DrawingContext context = drawingVisual.RenderOpen())  
{  
    context.DrawImage(wtbBmp, new Rect(0, 0, wtbBmp.Width, wtbBmp.Height));  
}  
rtbitmap.Render(drawingVisual);  
JpegBitmapEncoder bitmapEncoder = new JpegBitmapEncoder();  
bitmapEncoder.Frames.Add(BitmapFrame.Create(rtbitmap)); 
using (System.IO.FileStream fs = new System.IO.FileStream("D:\\mq.jpg", System.IO.FileMode.Create))
jpeg.Save(fs);

 再次補充:2019-6-22:

將GDI的Bitmap顯示到WriteableBitmap上(兩張圖都是32位)

private unsafe void DrawBitmap(WriteableBitmap dst, Bitmap src, System.Drawing.Rectangle rect)
        {
            int srcw = src.Width;
            int srch = src.Height;
            int left = rect.Left < 0 ? 0 : rect.Left;
            int top = rect.Top < 0 ? 0 : rect.Top;
            int right = rect.Right > srcw ? srcw : rect.Right;
            int bottom = rect.Bottom > srch ? srch : rect.Bottom;
            if (left >= right || top >= bottom) return;
            BitmapData srcdata = src.LockBits(new System.Drawing.Rectangle(0, 0, srcw, srch), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            dst.Lock();
            var srcints = (int*)srcdata.Scan0;
            var dstints = (int*)dst.BackBuffer.ToPointer();
            for (int j = top; j < bottom; ++j)
            {
                int y = j * srcw;
                for (int i = left; i < right; ++i)
                {
                    int index = y + i;
                    dstints[index] = srcints[index];
                }
            }
            dst.AddDirtyRect(new Int32Rect(left, top, right - left, bottom - top));
            src.UnlockBits(srcdata);
            dst.Unlock();
        }

 反向繪制當然也是可以的:

        private unsafe void DrawWriteableBitmap(Bitmap dst, WriteableBitmap src, System.Drawing.Rectangle rect)
        {
            int dstw = dst.Width;
            int dsth = dst.Height;
            int left = rect.Left < 0 ? 0 : rect.Left;
            int top = rect.Top < 0 ? 0 : rect.Top;
            int right = rect.Right > dstw ? dstw : rect.Right;
            int bottom = rect.Bottom > dsth ? dsth : rect.Bottom;
            if (left >= right || top >= bottom) return;
            BitmapData dstdata = dst.LockBits(new System.Drawing.Rectangle(0, 0, dstw, dsth), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            var dstints = (int*)dstdata.Scan0;
            var srcints = (int*)src.BackBuffer.ToPointer();
            for (int j = top; j < bottom; ++j)
            {
                int y = j * dstw;
                for (int i = left; i < right; ++i)
                {
                    int index = y + i;
                    dstints[index] = srcints[index];
                }
            }
            dst.UnlockBits(dstdata);
        }

 

 

 

 參考文章:http://www.cnblogs.com/carekee/articles/2039142.html

               http://www.oschina.net/question/54100_39186?fromerr=uHZl9PH1


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM