zoukankan      html  css  js  c++  java
  • 【转】【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

  • 相关阅读:
    ECharts之柱状图 饼状图 折线图
    Vue自定义指令(directive)
    HDU 1231 最大连续子序列
    POJ 2533 Longest Ordered Subsequence
    HDU 1163 Eddy's digital Roots
    HDU 2317 Nasty Hacks
    HDU 2571 命运
    HDU 4224 Enumeration?
    HDU 1257 最少拦截系统
    HDU 2740 Root of the Problem
  • 原文地址:https://www.cnblogs.com/mqxs/p/5707620.html
Copyright © 2011-2022 走看看