• 【转】【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

  • 相关阅读:
    第十五周总结
    第二阶段冲刺
    课堂练习
    第十四周总结
    WP8 NavigationInTransition实现页面切换效果
    WP8学习笔记:如何在页面显示前自动转向到其他页面
    WP8简单的计算器
    WP8滑动条(Slider)控件的使用
    Wp8滚动区域(ScrollViewer)控件的使用
    C#中ToString格式大全
  • 原文地址:https://www.cnblogs.com/mqxs/p/5707620.html
Copyright © 2020-2023  润新知