在使用WPF框架进行图像绘制的时候,您是不是也碰到过类似问题,当需要将大量的图形信息绘制到屏幕上时,常常感觉图形绘制的速度心有余而力不足,绘制卡顿、用户体验效果很差,其中的原因在本文中不做过多的分析,但是我们可以通过使用WriteableBitmap对象显著更改图形绘制效率。
    WriteableBitmap继承至BitmapSource对象,可以将WriteableBitmap的内容作为Image的Souce从而实现图形内容的可视化输出。WriteableBitmap 类使用两个缓冲区:前台及后台缓冲器,两个缓冲区均在系统内存中分配。“后台缓冲区”可以累积当前未显示的内容。“前台缓冲区”为直接输出显示的内容。

    当有大量的图形信息需要绘制到屏幕且图形内容更新速度很快,完全可以利用WriteableBitmap对象进行图形绘制。首先将WriteableBitmap对象锁定,然后更新WriteableBitmap对象的BackBuffer,完成后台缓冲区内容更新后解锁WriteableBitmap对象,实现前台内容的刷新。但在实际使用中,尤其是需要大量更新图形内容时,多采用后台线程的方法进行数据的获取和计算,并将计算完成后的内容填写到后台缓冲区,但在使用WriteableBitmap对象的过程中,如果在线程中定义WriteableBitmap对象,在线程中更新WriteableBitmap的后台缓冲区,再通过线程同步的方式将更新后的WriteableBitmap对象作为窗口Image控件的source对象来更新图像,却发现达不到目的,图像内容不会有任何的更改,原因是因为WriteableBitmap是在线程中定义的无法通过线程同步的方式把其它线程中刷新的内容同步更新到主窗体线程。

    如果利用WriteableBitmap通过多线程的方式既可以实现内容的后台更新,又能达到刷新图形显示内容的目的呢?可以使用下面的方法:

    1、在主窗体中创建WriteableBitmap对象,将Image的source 设置为创建的WriteableBitmap对象;

    2、创建线程时,将WriteableBitmap的BackBuffer属性传递给线程,BackBuffer属性是一个整型指针,可以利用其创建Bitmap对象,通过绘制Bitmap内容,从而间接达到更新WriteableBitmap图形缓冲区的目的;

    3、通过线程同步的方式,在每次需要更新图形内容时锁定WriteableBitmap对象;

    4、在线程中利用GraphicsPath绘制需要的内容;

    5、完成图形绘制后通过线程同步的方式解锁WriteableBitmap对象;

    通过上述步骤即可实现图形内容的后台更新,前台重绘。

    示例代码:

    1、在主窗体中创建 WriteableBitmap对象,启动线程将 WriteableBitmap 对象的BackBuffer、BackBufferStride两个属性传递给后台线程备用;

    1. WriteableBitmap wBitmap = new WriteableBitmap(600, 400, 96, 96, PixelFormats.Pbgra32, null);
    2. protected void StartDrawGraph(System.Windows.Controls.Image GraphPanel)
    3. {
    4. Thread drawThread = new Thread(new ParameterizedThreadStart(DoDraw));
    5. //wBitmap.Lock();
    6. drawThread.Start(new object[] { wBitmap.BackBuffer,wBitmap.BackBufferStride });
    7. }

    2、在线程中绘制图形内容

    1. protected void FillGraphToBitmap(IntPtr WBmpBackBuffer,int WBmpBackBufferStride)
    2. {
    3. //=====================产生随机数,并完成随机数的坐标表示转换===
    4. List<int> yAxisData = null, xAxisData = null;
    5. List<double> data = Common.Randoms.NormalDistribution.NormalRandom.CreateNormalDatas(100);
    6. int PanelWidth=600, PanelHeight = 400;
    7. //============坐标转换==================
    8. CreateAxisData(data, PanelHeight, PanelWidth, ref yAxisData, ref xAxisData);
    9. int pixelWidth = PanelWidth, pixelHeight = PanelHeight;
    10. //=====创建位图===================
    11. Bitmap backBitmap = new Bitmap(pixelWidth, pixelHeight, WBmpBackBufferStride, System.Drawing.Imaging.PixelFormat.Format32bppPArgb, WBmpBackBuffer);
    12. //===========获取与位图关联的图形绘制对象============
    13. Graphics graphics = Graphics.FromImage(backBitmap);
    14. graphics.Clear(System.Drawing.Color.White);
    15. //=======使用图形路径的方式进行图形绘制============
    16. GraphicsPath path = new GraphicsPath();
    17. path.FillMode = FillMode.Winding;
    18. DrawLines(ref path, yAxisData, xAxisData);
    19. graphics.DrawPath(new System.Drawing.Pen(System.Drawing.Color.Green, 1f), path);
    20. graphics.Flush();
    21. path.Dispose();
    22. path = null;
    23. graphics.Dispose();
    24. graphics = null;
    25. backBitmap.Dispose();
    26. backBitmap = null;
    27. }

    3、绘制线程与主窗口线程同步\

    1. protected void DoDraw(object Parameter)
    2. {
    3. //double[] domain = (double[])Parameter;
    4. //WriteableBitmap wBitmap = new WriteableBitmap((int)domain[0], (int)domain[1], 96, 96, PixelFormats.Pbgra32, null);
    5. //FillGraphToBitmap(wBitmap,(int)domain[0], (int)domain[1]);
    6. //img.Source = wBitmap;
    7. object[] domain = (object[])Parameter;
    8. while (MainWindow.Continued)
    9. {
    10. //=================可以在线程中获取数据、生成图像内容,但是 显示图像内容的WriteableBitmap必须和主窗体属于同一线程,否则图像内容不能正常显示=============
    11. //=================采用的方法是:将WriteableBitmap的内存指针传递给线程=====================
    12. this.Dispatcher.BeginInvoke(new Action(()=> {
    13. wBitmap.Lock();
    14. }));
    15. FillGraphToBitmap( (IntPtr)domain[0], (int)domain[1]);
    16. this.Dispatcher.BeginInvoke(new Action(()=> {
    17. //img.Source = d;
    18. wBitmap.AddDirtyRect(new System.Windows.Int32Rect(0, 0, (int)img.Width, (int)img.Height));
    19. wBitmap.Unlock();
    20. img.Source = wBitmap;
    21. }));
    22. Thread.Sleep(100);
    23. }
    24. }