WPF+WriteableBitmap实现高性能曲线图的绘制
作者:流浪g
这篇文章主要为大家详细介绍了如何利用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<PointF> top_points1; private List<PointF> top_points2; private List<PointF> top_points3; private List<PointF> bottom_points; private List<PointF> labelPosition_up; private List<string> labelText_up; private List<PointF> labelPosition_down; private List<string> labelText_down; private List<PointF> 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<PointF> timePosition, List<string> timeText) { this.timePosition = timePosition; this.timeText = timeText; } public void UpdatePosition(List<PointF> fhr1_points, List<PointF> fhr2_points, List<PointF> fhr3_points, List<PointF> 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<PointF> labelPosition_up, List<string> labelText_up, List<PointF> 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 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> 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); } /// <summary> /// 根据滚动条偏移量更新需要绘制的数据 /// </summary> /// <param name="offset"></param> private void InitChartData(float offset) { y_scale = (float)((curve.ActualHeight - Center) / (Top_Val_Max - Top_Val_Min + Bottom)); //上图横线 List<PointF[]> horizontalList = new List<PointF[]>(); List<PointF[]> horizontalList_thin = new List<PointF[]>(); 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<PointF[]> verticals = new List<PointF[]>(); List<PointF[]> verticals_thin = new List<PointF[]>(); List<PointF> timePosition = new List<PointF>(); List<string> timeText = new List<string>(); List<PointF> labelPosition_up = new List<PointF>(); List<string> labelText_up = new List<string>(); List<PointF> labelPosition_down = new List<PointF>(); 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<PointF> top_points1 = new List<PointF>(); 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<PointF> top_points2 = new List<PointF>(); 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<PointF> top_points3 = new List<PointF>(); 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<PointF> bottom_points = new List<PointF>(); 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(); } } }
三、运行效果
运行效果如下,欢迎各位大佬指点
以上就是WPF+WriteableBitmap实现高性能曲线图的绘制的详细内容,更多关于WPF WriteableBitmap曲线图的资料请关注脚本之家其它相关文章!