• WPF开发随笔收录-WriteableBitmap绘制高性能曲线图


    一、前言

    之前分享过一期关于DrawingVisual来绘制高性能曲线的博客,今天再分享一篇通过另一种方式来绘制高性能曲线的方法,也就是通过WriteableBitmap的方式;具体的一些细节这里就不啰嗦了,同样是局部绘制的思想,滚动条拖动到哪里,就只绘制那一部分的曲线,直接贴代码;(该程序在英特尔11代CPU的电脑可能会遇到拖动滚动条曲线图卡住不动的情况,这个是显卡驱动的问题,官方已经修复了,遇到这问题的记得更新一下驱动)

    二、正文

    1、新建一个类,继承FrameworkElement,然后在里面实现一下绘图的逻辑;

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Text;
    using System.IO;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Resources;
    using _Font = System.Drawing.Font;
    using GDI = System.Drawing;
    
    namespace WriteableBitmapDemo.Controls
    {
        public class CruveWriteableBitmap : FrameworkElement
        {
            private static PrivateFontCollection pfc = new PrivateFontCollection();
            private WriteableBitmap bitmap;
    
            private int bitmap_width = 0;
            private int bitmap_height = 0;
    
            private static _Font font = null;
            private static _Font time_font = null;
    
            private PointF[][] horizontals = null;
            private PointF[][] horizontals_thin = null;
            private PointF[][] verticals = null;
            private PointF[][] verticals_thin = null;
    
            private List top_points1;
            private List top_points2;
            private List top_points3;
            private List bottom_points;
    
            private List labelPosition_up;
            private List<string> labelText_up;
            private List labelPosition_down;
            private List<string> labelText_down;
    
            private List timePosition;
            private List<string> timeText;
    
            private GDI.Pen blackPen = new GDI.Pen(GDI.Color.Black, 1.5f);
            private GDI.Pen grayPen = new GDI.Pen(GDI.Color.Gray, 1f);
    
            private GDI.Pen top_pen1 = new GDI.Pen(GDI.Color.Black, 2);
            private GDI.Pen top_pen2 = new GDI.Pen(GDI.Color.Orange, 2);
            private GDI.Pen top_pen3 = new GDI.Pen(GDI.Color.Purple, 2);public float scaleX { get; set; } = 1f;
            private float _ScaleY { get; set; } = 1f;
            public float ScaleY
            {
                get { return _ScaleY; }
                set
                {
                    _ScaleY = value;
                }
            }
    
            static CruveWriteableBitmap()
            {
                var appRootDataDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "msyh.ttf");
                if (!File.Exists(appRootDataDir))
                {
                    var key = $"/CurveChartDemo;component/Fonts/msyh.ttf";
                    StreamResourceInfo info = Application.GetResourceStream(new Uri(key, UriKind.Relative));
                    using (var stream = info.Stream)
                    {
                        byte[] bytes = new byte[stream.Length];
                        int len = stream.Read(bytes, 0, bytes.Length);
                        File.WriteAllBytes(appRootDataDir, bytes);
                    }
                }
                pfc.AddFontFile(appRootDataDir);
            }
    
            public CruveWriteableBitmap()
            {
                time_font = new _Font(pfc.Families[0], 10);
                font = new _Font(pfc.Families[0], 8);
            }
    
            public void DrawPoints()
            {
                //InitBitmap();
                if (this.bitmap == null)
                {
                    return;
                }
    
                this.bitmap.Lock();
                using (Bitmap backBufferBitmap = new Bitmap(this.bitmap_width, this.bitmap_height,
                    this.bitmap.BackBufferStride, GDI.Imaging.PixelFormat.Format24bppRgb,
                    this.bitmap.BackBuffer))
                {
                    using (Graphics backBufferGraphics = Graphics.FromImage(backBufferBitmap))
                    {
                        backBufferGraphics.SmoothingMode = SmoothingMode.AntiAlias;
                        backBufferGraphics.CompositingQuality = CompositingQuality.HighSpeed;
    
                        backBufferGraphics.Clear(GDI.Color.White);
    
                        //粗横线
                        if (this.horizontals != null)
                        {
                            foreach (var horizontal in this.horizontals)
                            {
                                backBufferGraphics.DrawLine(blackPen, horizontal[0], horizontal[1]);
                            }
                        }
                        //细横线
                        if (this.horizontals_thin != null)
                        {
                            foreach (var horizontal in this.horizontals_thin)
                            {
                                backBufferGraphics.DrawLine(grayPen, horizontal[0], horizontal[1]);
                            }
                        }
                        //粗竖线
                        if (this.verticals != null)
                        {
                            foreach (var vertical in this.verticals)
                            {
                                backBufferGraphics.DrawLine(blackPen, vertical[0], vertical[1]);
                            }
                        }
                        //细竖线
                        if (this.verticals_thin != null)
                        {
                            foreach (var vertical in this.verticals_thin)
                            {
                                backBufferGraphics.DrawLine(grayPen, vertical[0], vertical[1]);
                            }
                        }
                        //上图曲线1
                        if (this.top_points1 != null && this.top_points1.Count > 0)
                        {
                            backBufferGraphics.DrawLines(top_pen1, top_points1.ToArray());
                        }
                        //上图曲线2
                        if (this.top_points2 != null && this.top_points2.Count > 0)
                        {
                            backBufferGraphics.DrawLines(top_pen2, this.top_points2.ToArray());
                        }
                        //上图曲线3
                        if (this.top_points3 != null && this.top_points3.Count > 0)
                        {
                            backBufferGraphics.DrawLines(top_pen3, this.top_points3.ToArray());
                        }
                        //下图曲线
                        if (this.bottom_points != null && this.bottom_points.Count > 0)
                        {
                            backBufferGraphics.DrawLines(top_pen1, this.bottom_points.ToArray());
                        }
    
                        //文本
                        if (labelPosition_up != null && labelPosition_up.Count > 0)
                        {
                            SizeF fontSize = backBufferGraphics.MeasureString(labelText_up[0], font);
                            for (int i = 0; i < labelPosition_up.Count; ++i)
                            {
                                backBufferGraphics.DrawString(labelText_up[i], font, GDI.Brushes.Black, labelPosition_up[i].X, labelPosition_up[i].Y - fontSize.Height);
                            }
                        }
                        if (labelPosition_down != null && labelPosition_down.Count > 0)
                        {
                            for (int i = 0; i < labelPosition_down.Count; ++i)
                            {
                                backBufferGraphics.DrawString(labelText_down[i], font, GDI.Brushes.Black, labelPosition_down[i].X, labelPosition_down[i].Y);
                            }
                        }
                        if (timePosition != null && timePosition.Count > 0)
                        {
                            for (int i = 0; i < timePosition.Count; ++i)
                            {
                                if (i == 0)
                                    backBufferGraphics.DrawString(timeText[i], time_font, GDI.Brushes.Black, timePosition[i].X, timePosition[i].Y);
                                else
                                {
                                    SizeF fontSize = backBufferGraphics.MeasureString(timeText[i], time_font);
                                    backBufferGraphics.DrawString(timeText[i], time_font, GDI.Brushes.Black, timePosition[i].X - fontSize.Width / 2, timePosition[i].Y);
                                }
    
                            }
                        }
    
                        backBufferGraphics.Flush();
                    }
                }
                this.bitmap.AddDirtyRect(new Int32Rect(0, 0, this.bitmap_width, this.bitmap_height));
                this.bitmap.Unlock();
            }public void UpdateTimeLabel(List timePosition, List<string> timeText)
            {
                this.timePosition = timePosition;
                this.timeText = timeText;
            }
            public void UpdatePosition(List fhr1_points, List fhr2_points, List fhr3_points, List toco_points)
            {
                this.top_points1 = fhr1_points;
                this.top_points2 = fhr2_points;
                this.top_points3 = fhr3_points;
                this.bottom_points = toco_points;
            }
    
            public void UpdateLabelPosition(List labelPosition_up, List<string> labelText_up, List labelPosition_down, List<string> labelText_down)
            {
                this.labelPosition_up = labelPosition_up;
                this.labelText_up = labelText_up;
                this.labelPosition_down = labelPosition_down;
                this.labelText_down = labelText_down;
            }
    
            public void UpdateHorizontalLine(PointF[][] horizontals, PointF[][] horizontals_thin)
            {
                this.horizontals = horizontals;
                this.horizontals_thin = horizontals_thin;
            }
    
            public void UpdateVerticalLine(PointF[][] verticals, PointF[][] verticals_thin)
            {
                this.verticals = verticals;
                this.verticals_thin = verticals_thin;
            }
    
            protected override void OnRender(DrawingContext dc)
            {
                InitBitmap();
                if (this.bitmap != null)
                {
                    dc.DrawImage(bitmap, new Rect(0, 0, RenderSize.Width, RenderSize.Height));
                }
                base.OnRender(dc);
            }
    
            private void InitBitmap()
            {
                if (bitmap == null || this.bitmap.Width != (int)this.ActualWidth || this.bitmap.Height != (int)this.ActualHeight)
                {
                    if ((int)this.ActualWidth > 0 && (int)this.ActualHeight > 0)
                    {
                        this.bitmap_width = (int)this.ActualWidth;
                        this.bitmap_height = (int)this.ActualHeight;
                        this.bitmap = new WriteableBitmap(bitmap_width, bitmap_height, 96, 96, PixelFormats.Bgr24, null);
                        this.bitmap.Lock();
                        using (Bitmap backBufferBitmap = new Bitmap(bitmap_width, bitmap_height,
                            this.bitmap.BackBufferStride, GDI.Imaging.PixelFormat.Format24bppRgb,
                            this.bitmap.BackBuffer))
                        {
                            using (Graphics backBufferGraphics = Graphics.FromImage(backBufferBitmap))
                            {
                                backBufferGraphics.SmoothingMode = SmoothingMode.HighSpeed;
                                backBufferGraphics.CompositingQuality = CompositingQuality.HighSpeed;
                                backBufferGraphics.Clear(GDI.Color.White);
                                backBufferGraphics.Flush();
                            }
                        }
                        this.bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap_width, bitmap_height));
                        this.bitmap.Unlock();
                    }
                }
            }
        }
    }
    复制代码

    2、主窗口添加该控件,并添加滚动条那些

    复制代码
    <Window
        x:Class="WriteableBitmapDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ct="clr-namespace:WriteableBitmapDemo.Controls"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WriteableBitmapDemo"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="1500"
        Height="450"
        Loaded="Window_Loaded"
        mc:Ignorable="d">
        <Grid>
            <ct:CruveWriteableBitmap x:Name="curve" Margin="0,0,0,20" />
            <ScrollViewer
                Name="scroll"
                HorizontalScrollBarVisibility="Auto"
                ScrollChanged="ScrollViewer_ScrollChanged"
                VerticalScrollBarVisibility="Disabled">
                <Canvas x:Name="canvas" Height="1" />
            ScrollViewer>
            <Canvas
                x:Name="CanvasPanel"
                Margin="0,0,0,20"
                Background="Transparent" />
        Grid>
    Window>
    复制代码

    3、主窗口后台添加曲线数值生成方法和更新视图数据方法

    复制代码
    using System.Collections.Generic;
    using System.Drawing;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    
    namespace WriteableBitmapDemo
    {
        /// 
        /// MainWindow.xaml 的交互逻辑
        /// 
        public partial class MainWindow : Window
        {
            private bool isAdd = true;
    
            private Dictionary<int, int> dicTopPoints = new Dictionary<int, int>();
            private Dictionary<int, int> dicBottomPoints = new Dictionary<int, int>();
    
            private float y_scale;
    
            private static int Top_Val_Max = 240;
            private static int Top_Val_Min = 30;
            private static int Top_X_Sex = 20;
            private static int Bottom = 100;
            private static int Center = 25;
            private static int BottomOffset = 0;
    
            private double offset = -1;
    
            public MainWindow()
            {
                InitializeComponent();
    
                CanvasPanel.MouseMove += delegate (object sender, MouseEventArgs e)
                {
                    if (e.LeftButton == MouseButtonState.Pressed)
                    {
                        if (Mouse.Captured == null) Mouse.Capture(CanvasPanel);
    
                        if (offset >= 0 && offset <= CanvasPanel.ActualWidth)
                        {
                            scroll.ScrollToHorizontalOffset(scroll.HorizontalOffset - (e.GetPosition(this).X - offset));
                        }
                        offset = e.GetPosition(this).X;
                    }
                    else
                    {
                        offset = -1;
                        Mouse.Capture(null); // 释放鼠标捕获
                    }
                };
            }
    
            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                //生成曲线数据
                int temp = 50;
                for (int i = 0; i < 24 * 60 * 60 * 4; i++)
                {
                    if (isAdd)
                    {
                        dicTopPoints.Add(i, temp);
                        temp += 2;
                    }
                    else
                    {
                        dicTopPoints.Add(i, temp);
                        temp -= 2;
                    }
    
                    if (temp == 210) isAdd = false;
                    if (temp == 50) isAdd = true;
                }
                temp = 0;
                for (int i = 0; i < 24 * 60 * 60 * 4; i++)
                {
                    if (isAdd)
                    {
                        dicBottomPoints.Add(i, temp);
                        temp += 2;
                    }
                    else
                    {
                        dicBottomPoints.Add(i, temp);
                        temp -= 2;
                    }
    
                    if (temp == 100) isAdd = false;
                    if (temp == 0) isAdd = true;
                }
                //初始化滚动条和触发曲线绘制
                canvas.Width = dicTopPoints.Count;
                scroll.ScrollToLeftEnd();
            }
    
            private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
            {
                InitChartData((float)scroll.HorizontalOffset);
            }
    
            /// 
            /// 根据滚动条偏移量更新需要绘制的数据
            /// 
            /// 
            private void InitChartData(float offset)
            {
                y_scale = (float)((curve.ActualHeight - Center) / (Top_Val_Max - Top_Val_Min + Bottom));
    
                //上图横线
                List horizontalList = new List();
                List horizontalList_thin = new List();
                for (int y = 0; y <= Top_Val_Max - Top_Val_Min; y += 10)
                {
                    float currentHeight = (float)(curve.ActualHeight - (y + Bottom) * y_scale - Center);
                    PointF point1 = new PointF(0, currentHeight);
                    PointF point2 = new PointF((float)curve.ActualWidth, currentHeight);
                    if (y % 30 == 0)
                        horizontalList.Add(new PointF[] { point1, point2 });
                    else
                        horizontalList_thin.Add(new PointF[] { point1, point2 });
                }
                for (int y = 0; y <= Bottom; y += 10)
                {
                    float currentHeight = (float)(curve.ActualHeight - y * y_scale - BottomOffset);
                    PointF point1 = new PointF(0, currentHeight);
                    PointF point2 = new PointF((float)curve.ActualWidth, currentHeight);
                    if (y % 20 == 0)
                        horizontalList.Add(new PointF[] { point1, point2 });
                    else
                        horizontalList_thin.Add(new PointF[] { point1, point2 });
                }
    
                //竖线与文字
                List verticals = new List();
                List verticals_thin = new List();
    
                List timePosition = new List();
                List<string> timeText = new List<string>();
    
                List labelPosition_up = new List();
                List<string> labelText_up = new List<string>();
    
                List labelPosition_down = new List();
                List<string> labelText_down = new List<string>();
    
                for (int i = 0; i < offset + curve.ActualWidth; i += Top_X_Sex * 2)
                {
                    if (i < offset) continue;
                    //下竖线
                    PointF point1 = new PointF(i - offset, (float)(curve.ActualHeight - BottomOffset));
                    PointF point2 = new PointF(i - offset, (float)(curve.ActualHeight - Bottom * y_scale - BottomOffset));
                    //上竖线
                    PointF point3 = new PointF(i - offset, 0);
                    PointF point4 = new PointF(i - offset, (float)(curve.ActualHeight - Bottom * y_scale - Center));
    
                    if ((i + (60 * 2)) % (60 * 2) == 0)
                    {
                        verticals.Add(new PointF[] { point1, point2 });
                        verticals.Add(new PointF[] { point3, point4 });
                    }
                    else
                    {
                        verticals_thin.Add(new PointF[] { point1, point2 });
                        verticals_thin.Add(new PointF[] { point3, point4 });
                    }
    
                    if (i % 240 == 0)
                    {
                        timeText.Add(i + "");
                        timePosition.Add(new PointF(i - offset, (float)(curve.ActualHeight - Bottom * y_scale - Center)));
                    }
    
                    if ((i + (60 * 2)) % (120 * 2) == 0)
                    {
                        for (int y = Top_Val_Min; y <= Top_Val_Max; y += 10)
                        {
                            if (y % 30 == 0)
                            {
                                labelText_up.Add(y + "");
                                labelPosition_up.Add(new PointF(i - offset, (float)(curve.ActualHeight - (Bottom + y - Top_Val_Min) * y_scale - Center)));
                            }
                        }
                        for (int y = 20; y <= 100; y += 10)
                        {
                            if (y % 20 == 0)
                            {
                                labelText_down.Add(y + "");
                                labelPosition_down.Add(new PointF(i - offset, (float)(curve.ActualHeight - y * y_scale)));
                            }
                        }
                    }
                }
    
                List top_points1 = new List();
                for (int i = (int)offset, j = 0; i < dicTopPoints.Count && j < curve.ActualWidth; i++, j++)
                {
                    top_points1.Add(new PointF(j, (float)(curve.ActualHeight - (dicTopPoints[i] + 100 - Top_Val_Min) * y_scale) - Center));
                }
    
                List top_points2 = new List();
                for (int i = (int)offset, j = 0; i < dicTopPoints.Count && j < curve.ActualWidth; i++, j++)
                {
                    top_points2.Add(new PointF(j, (float)(curve.ActualHeight - (dicTopPoints[i] + 20 + 100 - Top_Val_Min) * y_scale) - Center));
                }
    
                List top_points3 = new List();
                for (int i = (int)offset, j = 0; i < dicTopPoints.Count && j < curve.ActualWidth; i++, j++)
                {
                    top_points3.Add(new PointF(j, (float)(curve.ActualHeight - (dicTopPoints[i] - 20 + 100 - Top_Val_Min) * y_scale) - Center));
                }
    
                List bottom_points = new List();
                for (int i = (int)offset, j = 0; i < dicBottomPoints.Count && j < curve.ActualWidth; i++, j++)
                {
                    bottom_points.Add(new PointF(j, (float)(curve.ActualHeight - dicBottomPoints[i] * y_scale - BottomOffset)));
                }
    
                curve.UpdateHorizontalLine(horizontalList.ToArray(), horizontalList_thin.ToArray());
                curve.UpdateVerticalLine(verticals.ToArray(), verticals_thin.ToArray());
                curve.UpdatePosition(top_points1, top_points2, top_points3, bottom_points);
                curve.UpdateTimeLabel(timePosition, timeText);
                curve.UpdateLabelPosition(labelPosition_up, labelText_up, labelPosition_down, labelText_down);
                curve.DrawPoints();
            }
        }
    }
    复制代码

    4、运行效果如下,欢迎各位大佬指点

     

  • 相关阅读:
    Zynq UltraScale+ XCZU15EG 纯VHDL解码 IMX214 MIPI 视频,2路视频拼接输出,提供vivado工程源码和技术支持
    飞书面试经验
    Win10怎么设置待机时间
    【操作系统】错题库
    开源白板工具 Excalidraw 架构解读
    智能马达地球仪芯片-DLT8P64SC-杰力科创
    Java基础
    实用新型专利和发明专利的区别是什么
    Prometheus+Grafana监控MySQL
    【工作篇】软件工程师的知识基础(持续更新)
  • 原文地址:https://www.cnblogs.com/cong2312/p/16553695.html