一、前言
之前分享过一期关于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、主窗口后台添加曲线数值生成方法和更新视图数据方法
[code]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 dicTopPoints = new Dictionary(); private Dictionary dicBottomPoints = new Dictionary(); 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 |