DrawingContextExtension
DrawingContextExtensionDrawingContextExtension 源码详解 - WPF绘图扩展方法库这是一个WPF绘图扩展方法库为DrawingContext类添加了丰富的绘图辅助方法包括控制点、十字线、文本标注等常用功能。 文件头部和命名空间// 版权信息同前// Copyright (c) HeBianGu Authors. All Rights Reserved.// ...省略namespaceH.LabelImg.ShapeBox.Drawings;// 命名空间存放绘图相关的工具类 枚举定义控制点样式// 定义控制点手柄的样式类型publicenumPointStyleStype// 注意原代码拼写为 Stype应该是 Style{Cross,// 十字形加号形状Arrow,// 箭头形Circle,// 圆形None// 不显示}使用场景图像标注工具中选中形状后显示的控制点用于缩放、旋转。 静态类定义// 静态类不需要实例化直接使用 DrawingContextExtension.方法名()// this 参数 扩展方法可以像实例方法一样调用publicstaticclassDrawingContextExtension 绘制控制点方法1简化版// 绘制控制点使用画刷和粗细// 重载方法自动创建 Pen 对象publicstaticvoidDrawHandle(thisDrawingContextdc,Pointpoint,Brushstroke,doublestrokeThickness1,doublelen6){// 调用另一个重载方法传入创建的 Pendc.DrawHandle(point,newPen(stroke,strokeThickness),len);}参数说明dc绘图上下文point控制点中心位置stroke边框颜色strokeThickness边框粗细默认1像素len控制点大小默认6像素 绘制控制点方法2完整版// 绘制控制点使用 Pen 对象publicstaticvoidDrawHandle(thisDrawingContextdc,Pointpoint,Penpen,doublelen6){// 计算半长使矩形以 point 为中心doubleslen/2;// 被注释的代码原本可能是画圆形控制点// dc.DrawEllipse(null, pen, point, len, len);// 绘制矩形控制点白色填充 边框// 位置point.X - s 到 point.X s// 大小len x lendc.DrawRectangle(Brushes.White,pen,newRect(point.X-s,point.Y-s,len,len));}视觉效果len6 ←──────→ ┌──────┐ ← 白色填充 │ ● │ ← 中心点 └──────┘为什么用矩形而不是圆形矩形控制点在视觉上更清晰也更容易点击。✝️ 绘制十字线// 绘制十字线简单调用publicstaticvoidDrawCross(thisDrawingContextdc,Pointpoint,Brushstroke,doublestrokeThickness1,doublewlen6,doublehlen6,doubleangle0){// 调用通用方法指定样式为 Crossdc.DrawPointStyleStype(PointStyleStype.Cross,point,stroke,strokeThickness,wlen,hlen,angle);}十字线示例↑ hlen6 │ ← wlen6 → ● │ ↓ 绘制点样式核心方法// 绘制各种样式的控制点十字、箭头等publicstaticvoidDrawPointStyleStype(thisDrawingContextdc,PointStyleStypepointStyleStype,Pointpoint,Brushstroke,doublestrokeThickness1,doublewlen6,doublehlen6,doubleangle0){// 创建旋转变换围绕控制点中心旋转// 例如angle45度十字线会旋转45度RotateTransformrotateTransformnewRotateTransform(angle,point.X,point.Y);dc.PushTransform(rotateTransform);// 应用旋转变换// 创建画笔圆头端点圆角连接varpennewPen(stroke,strokeThickness){StartLineCapPenLineCap.Round,// 线条起点圆形EndLineCapPenLineCap.Round,// 线条终点圆形LineJoinPenLineJoin.Round// 线条连接处圆角};// 如果是十字样式if(pointStyleStypePointStyleStype.Cross){// 水平向量 (wlen, 0)varhvnewVector(wlen,0);// 垂直向量 (0, hlen)varvvnewVector(0,hlen);// 绘制垂直线从上到下dc.DrawLine(pen,point-vv,pointvv);// 绘制水平线从左到右dc.DrawLine(pen,point-hv,pointhv);}// 箭头样式代码中未实现预留// 圆形样式代码中未实现预留dc.Pop();// 恢复变换移除旋转}变换原理正常十字线 旋转45度后 │ ──●── ● │ PenLineCap.Round 效果普通端点 ─────┤ 圆头端点 ─────●⭕ 绘制圆形// 绘制圆形可选填充publicstaticvoidDrawCircle(thisDrawingContextdc,Pointpoint,Penpen,doubleradius,Brushfillnull){// DrawEllipse(填充, 边框, 中心点, X半径, Y半径)// 半径相等就是圆形dc.DrawEllipse(fill,pen,point,radius,radius);} 文本绘制方法系列基础文本绘制带偏移// 最通用的文本绘制方法// 参数文本内容、位置、画刷、字体大小、偏移计算函数、填充色、前置回调publicstaticRectDrawTextAt(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize,FuncFormattedText,VectorgetOffset,Brushfillnull,ActionFormattedText,RectbeforeActionnull){// 规范化字体大小NaN转为10fontSizefontSize.ToFontSize();// 创建格式化文本对象包含文本、字体、大小等信息FormattedTextformattedTexttext.ToForematedText(brush,fontSize);// 计算最终位置 鼠标位置 偏移量varppointgetOffset(formattedText);// 如果需要背景填充if(fill!null){// 构建文本的几何图形用于背景高亮varhgeoformattedText.BuildHighlightGeometry(p);// 绘制背景dc.DrawGeometry(fill,null,hgeo);}// 记录文本占用的矩形区域varresultnewRect(p,newSize(formattedText.Width,formattedText.Height));// 执行前置回调可以在绘制前修改文本样式beforeAction?.Invoke(formattedText,result);// 绘制文本dc.DrawText(formattedText,p);// 返回文本占用的矩形区域用于碰撞检测等returnresult;}偏移函数示例// 偏移 (5, 5)文本在鼠标右下方getOffsetxnewVector(5,5)// 居中文本中心对齐鼠标getOffsetxnewVector(-x.Width/2,-x.Height/2)各种预定义位置的文本绘制// 在鼠标位置偏移 (offset, offset) 处绘制文本// 默认偏移5像素右下角publicstaticRectDrawTextAt(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize10.0,Brushfillnull,doubleoffset5,ActionFormattedText,RectbeforeActionnull){returndc.DrawTextAt(text,point,brush,fontSize,xnewVector(offset,offset),fill,beforeAction);}// 文本居中于鼠标位置// 例如鼠标指向文本中心publicstaticRectDrawTextAtCenter(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize10.0,Brushfillnull,ActionFormattedText,RectbeforeActionnull){returndc.DrawTextAt(text,point,brush,fontSize,xnewVector(-x.Width/2,-x.Height/2),fill,beforeAction);}// 文本顶部居中于鼠标位置publicstaticRectDrawTextAtTopCenter(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize10.0,Brushfillnull,ActionFormattedText,RectbeforeActionnull){returndc.DrawTextAt(text,point,brush,fontSize,xnewVector(-x.Width/2,-x.Height),fill,beforeAction);}// 文本左上角对齐鼠标publicstaticRectDrawTextAtTopLeft(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize10.0,Brushfillnull,ActionFormattedText,RectbeforeActionnull){returndc.DrawTextAt(text,point,brush,fontSize,xnewVector(0,-x.Height),fill,beforeAction);}// 文本底部居中于鼠标位置publicstaticRectDrawTextAtBottomCenter(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize10.0,Brushfillnull,ActionFormattedText,RectbeforeActionnull){returndc.DrawTextAt(text,point,brush,fontSize,xnewVector(-x.Width/2,0),fill,beforeAction);}// 文本右侧垂直居中于鼠标位置publicstaticRectDrawTextAtRight(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize10.0,Brushfillnull,ActionFormattedText,RectbeforeActionnull){returndc.DrawTextAt(text,point,brush,fontSize,xnewVector(0,-x.Height/2),fill,beforeAction);}// 文本左侧垂直居中于鼠标位置publicstaticRectDrawTextAtLeft(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize10.0,Brushfillnull,ActionFormattedText,RectbeforeActionnull){returndc.DrawTextAt(text,point,brush,fontSize,xnewVector(-x.Width,-x.Height/2),fill,beforeAction);}文本位置图示TopLeft: TopCenter: TopRight需自定义: ┌─────────┐ ┌─────────┐ ┌─────────┐ │文本 │ │ 文本 │ │ 文本│ └─────────┘ └─────────┘ └─────────┘ ● ● ● (鼠标位置) (鼠标位置) (鼠标位置) Center: BottomCenter: Left: ┌─────┐ ┌─────┐ ┌─────┐ │文本 │ │文本 │ │文本 │ └─────┘ └─────┘ └─────┘ ● ● ● 私有辅助方法创建格式化文本// 将字符串转换为 FormattedTextWPF格式化文本对象privatestaticFormattedTextToForematedText(thisstringtext,Brushbrush,doublefontSize10.0){returnnewFormattedText(${text},// 文本内容System.Globalization.CultureInfo.CurrentCulture,// 当前区域文化FlowDirection.LeftToRight,// 从左到右流向newTypeface(Microsoft YaHei),// 字体微软雅黑fontSize,// 字体大小brush,// 颜色1.2// 像素与点的比例);}FormattedText 的作用测量文本占用的宽度和高度支持复杂的文本格式不同字体、颜色构建文本几何图形用于背景高亮规范化字体大小// 将 NaN 转换为默认字体大小10privatestaticdoubleToFontSize(thisdoublefontSize){returndouble.IsNaN(fontSize)?10:fontSize;} 总体设计思路设计目标这个扩展方法库为 WPF 的DrawingContext添加了标注工具常用的绘图功能让代码更简洁、易读。使用对比// ❌ 没有扩展方法时每次都要写一堆代码varpennewPen(Brushes.Red,2);pen.StartLineCapPenLineCap.Round;pen.EndLineCapPenLineCap.Round;varrectnewRect(point.X-3,point.Y-3,6,6);dc.DrawRectangle(Brushes.White,pen,rect);// ✅ 使用扩展方法一行搞定dc.DrawHandle(point,Brushes.Red,2,6);完整的文本位置示例// 在鼠标位置显示标注文本varmousePointe.GetPosition(box);// 右下角显示默认dc.DrawTextAt(猫,mousePoint,Brushes.Black,12,Brushes.Yellow);// 鼠标上方显示适合标注dc.DrawTextAtBottomCenter(置信度: 0.95,mousePoint,Brushes.White,10,Brushes.Black);// 跟随鼠标的标签dc.DrawTextAtCenter(拖拽中,mousePoint,Brushes.Red,14,Brushes.White);实际应用场景// 场景1绘制选中框的控制点publicvoidDrawSelection(ShapeBoxbox,DrawingContextdc,Rectbounds){// 四个角绘制控制点dc.DrawHandle(newPoint(bounds.Left,bounds.Top),Brushes.Blue,2,8);dc.DrawHandle(newPoint(bounds.Right,bounds.Top),Brushes.Blue,2,8);dc.DrawHandle(newPoint(bounds.Left,bounds.Bottom),Brushes.Blue,2,8);dc.DrawHandle(newPoint(bounds.Right,bounds.Bottom),Brushes.Blue,2,8);// 中心点绘制十字线varcenternewPoint(bounds.Xbounds.Width/2,bounds.Ybounds.Height/2);dc.DrawCross(center,Brushes.Red,2,10,10);}// 场景2绘制旋转控制点publicvoidDrawRotateHandle(DrawingContextdc,Pointcenter,doubleangle){varhandlePointnewPoint(center.X,center.Y-50);// 十字线旋转匹配形状的角度dc.DrawPointStyleStype(PointStyleStype.Cross,handlePoint,Brushes.Green,2,8,8,angle);}// 场景3标注信息显示publicvoidDrawLabel(DrawingContextdc,Rectbounds,stringtext){varlabelPointnewPoint(bounds.Left,bounds.Top);// 在标注框左上角显示文本带背景dc.DrawTextAtTopLeft(text,labelPoint,Brushes.White,12,Brushes.Black);}控制点样式效果图┌─────────────────────────────────┐ │ ● ← 控制点白色填充边框 │ │ │ │ ✝ ← 十字线旋转中心 │ │ │ │ ⬤ ← 圆形控制点 │ │ │ │ ┌─────────────────────────┐ │ │ │ 猫 ← 文本右下角 │ │ │ └─────────────────────────┘ │ └─────────────────────────────────┘扩展方法调用链示例// 一个完整的标注绘制流程publicvoidDrawShape(ShapeBoxbox,DrawingContextdc,IShapeshape){// 1. 绘制形状本体shape.Draw(box,dc,box.Stroke,box.StrokeThickness,box.Fill);// 2. 如果被选中绘制控制点if(isSelected){varboundsshape.GetBounds();// 绘制四个角控制点dc.DrawHandle(bounds.TopLeft,Brushes.Red,2,8);dc.DrawHandle(bounds.TopRight,Brushes.Red,2,8);dc.DrawHandle(bounds.BottomLeft,Brushes.Red,2,8);dc.DrawHandle(bounds.BottomRight,Brushes.Red,2,8);// 绘制中心十字线dc.DrawCross(bounds.Center,Brushes.Blue,1,12,12);// 显示标注信息dc.DrawTextAtTopLeft(shape.Label,bounds.TopLeft,Brushes.Black,10,Brushes.Yellow);}}设计模式识别扩展方法模式为现有类型添加新方法工厂模式ToForematedText创建FormattedText对象策略模式不同的PointStyleStype对应不同的绘制策略模板方法模式DrawTextAt定义文本绘制流程子方法提供具体偏移性能注意事项// ❌ 不好频繁创建 Pen 和 FormattedTextfor(inti0;i1000;i){dc.DrawHandle(point,Brushes.Red,2,6);}// ✅ 好重用 Pen 对象varpennewPen(Brushes.Red,2);for(inti0;i1000;i){dc.DrawHandle(point,pen,6);}// ✅ 好重用 FormattedTextvarformattedText文本.ToForematedText(Brushes.Black,12);// ... 使用 formattedText这个扩展方法库让 WPF 绘图代码更加简洁、优雅、易读是图像标注工具的基础设施