news 2026/5/27 22:58:21

DrawingContextExtension

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DrawingContextExtension

DrawingContextExtension

DrawingContextExtension 源码详解 - 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,doublestrokeThickness=1,doublelen=6){// 调用另一个重载方法,传入创建的 Pendc.DrawHandle(point,newPen(stroke,strokeThickness),len);}

参数说明

  • dc:绘图上下文
  • point:控制点中心位置
  • stroke:边框颜色
  • strokeThickness:边框粗细(默认1像素)
  • len:控制点大小(默认6像素)

🎮 绘制控制点(方法2:完整版)

// 绘制控制点(使用 Pen 对象)publicstaticvoidDrawHandle(thisDrawingContextdc,Pointpoint,Penpen,doublelen=6){// 计算半长(使矩形以 point 为中心)doubles=len/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));}

视觉效果

len=6 ←──────→ ┌──────┐ ← 白色填充 │ ● │ ← 中心点 └──────┘

为什么用矩形而不是圆形?矩形控制点在视觉上更清晰,也更容易点击。


✝️ 绘制十字线

// 绘制十字线(简单调用)publicstaticvoidDrawCross(thisDrawingContextdc,Pointpoint,Brushstroke,doublestrokeThickness=1,doublewlen=6,doublehlen=6,doubleangle=0){// 调用通用方法,指定样式为 Crossdc.DrawPointStyleStype(PointStyleStype.Cross,point,stroke,strokeThickness,wlen,hlen,angle);}

十字线示例

↑ hlen=6 │ ← wlen=6 → ● │ ↓

🎨 绘制点样式(核心方法)

// 绘制各种样式的控制点(十字、箭头等)publicstaticvoidDrawPointStyleStype(thisDrawingContextdc,PointStyleStypepointStyleStype,Pointpoint,Brushstroke,doublestrokeThickness=1,doublewlen=6,doublehlen=6,doubleangle=0){// 创建旋转变换(围绕控制点中心旋转)// 例如:angle=45度,十字线会旋转45度RotateTransformrotateTransform=newRotateTransform(angle,point.X,point.Y);dc.PushTransform(rotateTransform);// 应用旋转变换// 创建画笔(圆头端点,圆角连接)varpen=newPen(stroke,strokeThickness){StartLineCap=PenLineCap.Round,// 线条起点:圆形EndLineCap=PenLineCap.Round,// 线条终点:圆形LineJoin=PenLineJoin.Round// 线条连接处:圆角};// 如果是十字样式if(pointStyleStype==PointStyleStype.Cross){// 水平向量 (wlen, 0)varhv=newVector(wlen,0);// 垂直向量 (0, hlen)varvv=newVector(0,hlen);// 绘制垂直线(从上到下)dc.DrawLine(pen,point-vv,point+vv);// 绘制水平线(从左到右)dc.DrawLine(pen,point-hv,point+hv);}// 箭头样式(代码中未实现,预留)// 圆形样式(代码中未实现,预留)dc.Pop();// 恢复变换(移除旋转)}

变换原理

正常十字线: 旋转45度后: │ \ ──●── \● │ \

PenLineCap.Round 效果

普通端点: ─────┤ 圆头端点: ─────●

⭕ 绘制圆形

// 绘制圆形(可选填充)publicstaticvoidDrawCircle(thisDrawingContextdc,Pointpoint,Penpen,doubleradius,Brushfill=null){// DrawEllipse(填充, 边框, 中心点, X半径, Y半径)// 半径相等就是圆形dc.DrawEllipse(fill,pen,point,radius,radius);}

📝 文本绘制方法系列

基础文本绘制(带偏移)

// 最通用的文本绘制方法// 参数:文本内容、位置、画刷、字体大小、偏移计算函数、填充色、前置回调publicstaticRectDrawTextAt(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize,Func<FormattedText,Vector>getOffset,Brushfill=null,Action<FormattedText,Rect>beforeAction=null){// 规范化字体大小(NaN转为10)fontSize=fontSize.ToFontSize();// 创建格式化文本对象(包含文本、字体、大小等信息)FormattedTextformattedText=text.ToForematedText(brush,fontSize);// 计算最终位置 = 鼠标位置 + 偏移量varp=point+getOffset(formattedText);// 如果需要背景填充if(fill!=null){// 构建文本的几何图形(用于背景高亮)varhgeo=formattedText.BuildHighlightGeometry(p);// 绘制背景dc.DrawGeometry(fill,null,hgeo);}// 记录文本占用的矩形区域varresult=newRect(p,newSize(formattedText.Width,formattedText.Height));// 执行前置回调(可以在绘制前修改文本样式)beforeAction?.Invoke(formattedText,result);// 绘制文本dc.DrawText(formattedText,p);// 返回文本占用的矩形区域(用于碰撞检测等)returnresult;}

偏移函数示例

// 偏移 (5, 5):文本在鼠标右下方getOffset=x=>newVector(5,5)// 居中:文本中心对齐鼠标getOffset=x=>newVector(-x.Width/2,-x.Height/2)

各种预定义位置的文本绘制

// 在鼠标位置偏移 (offset, offset) 处绘制文本// 默认偏移5像素(右下角)publicstaticRectDrawTextAt(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize=10.0,Brushfill=null,doubleoffset=5,Action<FormattedText,Rect>beforeAction=null){returndc.DrawTextAt(text,point,brush,fontSize,x=>newVector(offset,offset),fill,beforeAction);}// 文本居中于鼠标位置// 例如:鼠标指向文本中心publicstaticRectDrawTextAtCenter(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize=10.0,Brushfill=null,Action<FormattedText,Rect>beforeAction=null){returndc.DrawTextAt(text,point,brush,fontSize,x=>newVector(-x.Width/2,-x.Height/2),fill,beforeAction);}// 文本顶部居中于鼠标位置publicstaticRectDrawTextAtTopCenter(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize=10.0,Brushfill=null,Action<FormattedText,Rect>beforeAction=null){returndc.DrawTextAt(text,point,brush,fontSize,x=>newVector(-x.Width/2,-x.Height),fill,beforeAction);}// 文本左上角对齐鼠标publicstaticRectDrawTextAtTopLeft(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize=10.0,Brushfill=null,Action<FormattedText,Rect>beforeAction=null){returndc.DrawTextAt(text,point,brush,fontSize,x=>newVector(0,-x.Height),fill,beforeAction);}// 文本底部居中于鼠标位置publicstaticRectDrawTextAtBottomCenter(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize=10.0,Brushfill=null,Action<FormattedText,Rect>beforeAction=null){returndc.DrawTextAt(text,point,brush,fontSize,x=>newVector(-x.Width/2,0),fill,beforeAction);}// 文本右侧(垂直居中)于鼠标位置publicstaticRectDrawTextAtRight(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize=10.0,Brushfill=null,Action<FormattedText,Rect>beforeAction=null){returndc.DrawTextAt(text,point,brush,fontSize,x=>newVector(0,-x.Height/2),fill,beforeAction);}// 文本左侧(垂直居中)于鼠标位置publicstaticRectDrawTextAtLeft(thisDrawingContextdc,stringtext,Pointpoint,Brushbrush,doublefontSize=10.0,Brushfill=null,Action<FormattedText,Rect>beforeAction=null){returndc.DrawTextAt(text,point,brush,fontSize,x=>newVector(-x.Width,-x.Height/2),fill,beforeAction);}

文本位置图示

TopLeft: TopCenter: TopRight(需自定义): ┌─────────┐ ┌─────────┐ ┌─────────┐ │文本 │ │ 文本 │ │ 文本│ └─────────┘ └─────────┘ └─────────┘ ● ● ● (鼠标位置) (鼠标位置) (鼠标位置) Center: BottomCenter: Left: ┌─────┐ ┌─────┐ ┌─────┐ │文本 │ │文本 │ │文本 │ └─────┘ └─────┘ └─────┘ ● ● ●

🔧 私有辅助方法

创建格式化文本

// 将字符串转换为 FormattedText(WPF格式化文本对象)privatestaticFormattedTextToForematedText(thisstringtext,Brushbrush,doublefontSize=10.0){returnnewFormattedText($"{text}",// 文本内容System.Globalization.CultureInfo.CurrentCulture,// 当前区域文化FlowDirection.LeftToRight,// 从左到右流向newTypeface("Microsoft YaHei"),// 字体:微软雅黑fontSize,// 字体大小brush,// 颜色1.2// 像素与点的比例);}

FormattedText 的作用

  • 测量文本占用的宽度和高度
  • 支持复杂的文本格式(不同字体、颜色)
  • 构建文本几何图形(用于背景高亮)

规范化字体大小

// 将 NaN 转换为默认字体大小(10)privatestaticdoubleToFontSize(thisdoublefontSize){returndouble.IsNaN(fontSize)?10:fontSize;}

🎯 总体设计思路

设计目标

这个扩展方法库为 WPF 的DrawingContext添加了标注工具常用的绘图功能,让代码更简洁、易读。

使用对比

// ❌ 没有扩展方法时(每次都要写一堆代码)varpen=newPen(Brushes.Red,2);pen.StartLineCap=PenLineCap.Round;pen.EndLineCap=PenLineCap.Round;varrect=newRect(point.X-3,point.Y-3,6,6);dc.DrawRectangle(Brushes.White,pen,rect);// ✅ 使用扩展方法(一行搞定)dc.DrawHandle(point,Brushes.Red,2,6);

完整的文本位置示例

// 在鼠标位置显示标注文本varmousePoint=e.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);// 中心点绘制十字线varcenter=newPoint(bounds.X+bounds.Width/2,bounds.Y+bounds.Height/2);dc.DrawCross(center,Brushes.Red,2,10,10);}// 场景2:绘制旋转控制点publicvoidDrawRotateHandle(DrawingContextdc,Pointcenter,doubleangle){varhandlePoint=newPoint(center.X,center.Y-50);// 十字线旋转,匹配形状的角度dc.DrawPointStyleStype(PointStyleStype.Cross,handlePoint,Brushes.Green,2,8,8,angle);}// 场景3:标注信息显示publicvoidDrawLabel(DrawingContextdc,Rectbounds,stringtext){varlabelPoint=newPoint(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){varbounds=shape.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);}}

设计模式识别

  1. 扩展方法模式:为现有类型添加新方法
  2. 工厂模式ToForematedText创建FormattedText对象
  3. 策略模式:不同的PointStyleStype对应不同的绘制策略
  4. 模板方法模式DrawTextAt定义文本绘制流程,子方法提供具体偏移

性能注意事项

// ❌ 不好:频繁创建 Pen 和 FormattedTextfor(inti=0;i<1000;i++){dc.DrawHandle(point,Brushes.Red,2,6);}// ✅ 好:重用 Pen 对象varpen=newPen(Brushes.Red,2);for(inti=0;i<1000;i++){dc.DrawHandle(point,pen,6);}// ✅ 好:重用 FormattedTextvarformattedText="文本".ToForematedText(Brushes.Black,12);// ... 使用 formattedText

这个扩展方法库让 WPF 绘图代码更加简洁、优雅、易读,是图像标注工具的基础设施!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/27 22:58:16

word批量删除空格

按【CtrlH】组合键&#xff0c;打开“查找与替换”对话框 在查找内容里面填&#xff1a; ^w 替换为那里不用填&#xff0c;最后直接点全部替换即可

作者头像 李华
网站建设 2026/5/23 1:53:50

二叉树的递归遍历详解:前序、中序与后序

二叉树是数据结构中非常重要的非线性结构&#xff0c;其遍历是操作二叉树的基础&#xff0c;指按照一定的顺序访问二叉树中的所有节点&#xff0c;且每个节点仅被访问一次。递归遍历因实现逻辑简洁、符合二叉树的递归定义&#xff0c;成为入门二叉树遍历的首选方式。本文将详细…

作者头像 李华
网站建设 2026/5/23 1:53:50

第三方软件测评机构中CMA与CNAS资质对软件验收的重要性

CMA与CNAS资质的重要性 在软件项目验收过程中&#xff0c;第三方软件测评机构的CMA&#xff08;中国计量认证&#xff09;与CNAS&#xff08;中国合格评定国家认可委员会&#xff09;资质至关重要。这些资质不仅是机构专业能力的体现&#xff0c;更是确保测试结果公正、准确、可…

作者头像 李华
网站建设 2026/5/25 6:00:45

新一代高端工业 HMI 如何重塑现场交互体验?

繁易 FPADX 系列电容触摸屏支持 3D 可视化、多点触控、Web 远程访问与大型工程承载&#xff0c;帮助工业设备实现更高效、更直观、更智能的人机交互体验。在工业自动化持续升级的今天&#xff0c;触摸屏早已不再只是设备上的一个操作界面。对于设备制造商、系统集成商和终端工厂…

作者头像 李华
网站建设 2026/5/23 1:53:52

OpenClaw技能组合:Qwen3.5-9B实现跨境电商价格监控

OpenClaw技能组合&#xff1a;Qwen3.5-9B实现跨境电商价格监控 1. 为什么选择OpenClaw做跨境电商监控&#xff1f; 去年我开始尝试在Etsy和Shopify上销售手工皮具&#xff0c;很快发现了一个痛点&#xff1a;竞品价格波动频繁&#xff0c;但手动跟踪耗时费力。尝试过几个SaaS…

作者头像 李华