Window GDI+ API有BUG?GetBounds测不准?

文章目录

  • GraphicsPath的GetBounds测不准?
  • 方法一:GetBounds ()
    • 实战
  • 方法二:GetBounds(Matrix)
    • 实战
  • GraphicsPath的GetBounds测不准?
    • 实战
  • .NET 版本的问题?
  • C++也一样,不是.NET的问题
  • 怀疑人生
  • MiterLimit惹得祸
  • 完美结果
  • 结束语

最近,在学习系统了解 Windows GDI+ 绘图,并尝试复现大部分函数,看似一帆风顺的过程,也让我遇到了怀疑人生的困惑。

无论是前面的GDI+绘制基础、坐标系和坐标转换、还是矩阵Matrix详解,都能不太费力地实现各函数、方法的示例,并继续推进。

然而,事件总是不会这么顺风顺水的,就如人生多少都会有些波折吧!那么,下面让我们开始本篇的主题吧,

GraphicsPath的GetBounds测不准?

这个要从学习GDI+的GraphicsPath的GetBounds方法说起,GetBounds无法准确获取路径的外接矩形?Microsoft会有这么大的Bug吗?

方法一:GetBounds ()

原型:

public System.Drawing.RectangleF GetBounds ();

作用:
返回GraphicsPath的外接矩形。

实战

要求:通过绘制一个椭圆,并计算这个椭圆的外接矩形。
代码如下:

//定义一个矩形,用于确定一个椭圆
var rect = new Rectangle(200, 200, 300, 200);//定义一个GraphicsPath,用于添加路径
using (var path = new GraphicsPath())
{//根据矩形,添加一个椭圆path.AddEllipse(rect);//绘制椭圆e.Graphics.DrawPath(Pens.Red,path);//计算路径(椭圆)的外接矩形var boxRect = path.GetBounds();//绘制该外接矩形e.Graphics.DrawRectangle(Pens.LightGreen, boxRect.X, boxRect.Y, boxRect.Width, boxRect.Height);
}

GetBounds
一切都是这么的顺利,结果也是这么的完美。

方法二:GetBounds(Matrix)

原型:

public System.Drawing.RectangleF GetBounds (System.Drawing.Drawing2D.Matrix? matrix);

作用:
返回一个经过矩阵变换后的外接矩形。

实战

绘制一个椭圆,返回一个有偏移的外接矩形。

//定义一个矩形,用于确定一个椭圆
var rect = new Rectangle(200, 200, 300, 200);//定义一个GraphicsPath,用于添加路径
using (var path = new GraphicsPath())
{//根据矩形,添加一个椭圆path.AddEllipse(rect);//绘制椭圆e.Graphics.DrawPath(Pens.Red, path);//计算路径(椭圆)的外接矩形,结果向左偏移150,向上偏移100var boxRect = path.GetBounds(new Matrix(1,0,0,1,-150,-100));//绘制该偏移后的外接矩形e.Graphics.DrawRectangle(Pens.LightGreen, boxRect.X, boxRect.Y, boxRect.Width, boxRect.Height);
}

GetBounds_Matrix
事件还是很顺利,此场景应该是有时需要获取一个相对偏移的结果,这样就不需要在获取的矩形上,再做其它坐标的加、减法了。

如果,一切都是这么顺利,我就不会单独另一起篇来记录,这个惊天BUG了(我承认,多少有点标题党了,但当时,确实是怀疑这个API是不是有BUG)。

GraphicsPath的GetBounds测不准?

怀疑这个BUG,要从GetBounds(Matrix, Pen)说起,这个方法比前一个多了个Pen参数,表示,有些路径会用特定的Pen绘制,然后需要计算路径与Pen一起构成的图形的外接矩形,这个功能也很实用吧。所以,也让我急切地想知道,为何一开始测不准了。

原型:

public System.Drawing.RectangleF GetBounds (System.Drawing.Drawing2D.Matrix? matrix, System.Drawing.Pen? pen);

作用:
获取一个外接矩形,包围着由指定画笔绘制的路径。

实战

用10像素宽的画笔绘制一个椭圆,并计算其外接矩形。

//定义一个矩形,用于确定一个椭圆
var rect = new Rectangle(200, 200, 300, 200);//定义一个GraphicsPath,用于添加路径
using (var path = new GraphicsPath())
{//根据矩形,添加一个椭圆path.AddEllipse(rect);//定义一个10像素宽的亮绿画笔var pathPen = new Pen(Color.LightGreen, 10);//用10像素宽的画笔绘制椭圆e.Graphics.DrawPath(pathPen, path);//计算路径及画笔的外接矩形var boxRect = path.GetBounds(new Matrix(), pathPen);//绘制外接矩形,结果?e.Graphics.DrawRectangle(Pens.LightGreen, boxRect.X, boxRect.Y, boxRect.Width, boxRect.Height);
}

代码没有特别之处,只是在获取Bounds时,加了画笔参数。
可结果,结果。。。
GetBounds_Matrix_Pen
没理由呀,前面他们不是还好好的吗?怎么突然就两个离的十万八千里呢?

.NET 版本的问题?

首先怀疑会不会Graphics的PageUnit的问题?默认是Display,将其改为Pixel。结果依旧。

再次怀疑会不会Graphics的Scale的问题呢?默认是1,也没问题。

尝试着将.NET Framewor 版本从4.8一直换到3.5结果,还是依旧。外接矩形与椭圆还是离得那么远。但是,他们之间的距离,也是随着画笔的宽度越大,越的越远。

//定义一个矩形,用于确定一个椭圆
var rect = new Rectangle(200, 200, 300, 200);//定义一个GraphicsPath,用于添加路径
using (var path = new GraphicsPath())
{//根据矩形,添加一个椭圆path.AddEllipse(rect);Random random = new Random();for (int width = 21; width >= 1; width -= 2){var color = Color.FromArgb(random.Next(0, 256), random.Next(0, 256), random.Next(0, 256));//定义一个10像素宽的亮绿画笔var pathPen = new Pen(color, width);//用10像素宽的画笔绘制椭圆e.Graphics.DrawPath(pathPen, path);//计算路径及画笔的外接矩形var boxRect = path.GetBounds(new Matrix(), pathPen);//绘制外接矩形,结果?e.Graphics.DrawRectangle(new Pen(color,1), boxRect.X, boxRect.Y, boxRect.Width, boxRect.Height);}

笔宽越大,偏离越远

如上图,当画笔的宽度从1到21时,外接矩形逐浙远离,当画笔的宽度为1时,几乎接近我们要计算的外接矩形的结果,可这不是我们要的结果呀!

C++也一样,不是.NET的问题

我怀疑你在开车,但我没有证据
我怀疑是.NET封装的问题,但我没有证据!

为了证据,我尝试着用C++来实现两样的GDI+绘制。(隔语言,如隔山,对不熟悉的语言,想要实现个简单的功能,却花了不少时间。)

void OnPaint(HDC hdc)
{Graphics graphics(hdc);// 创建矩形RectF rect(200, 200, 300, 200);// 创建路径GraphicsPath path;path.AddEllipse(rect);// 创建一个画笔对象,指定颜色和宽度Gdiplus::Pen pathPen(Color::LightGreen, 10);// 绘制椭圆graphics.DrawPath(&pathPen, &path);// 创建路径的外包矩形RectF mPenBBox;path.GetBounds(&mPenBBox, nullptr, &pathPen);// 绘制路径的外包矩形Gdiplus::Pen pen(Color::Red, 1);graphics.DrawRectangle(&pen, mPenBBox.X, mPenBBox.Y, mPenBBox.Width, mPenBBox.Height);
}

事实,啪啪啪打脸,C++的结果和.NET的结果是一样的,其椭圆路径与其外接矩形也是相隔那么远!!!
C++实现

怀疑人生

Microsoft会有一个惊天Bug让我遇上了?不可能,绝对不可能!那为什么计算出来的外接矩形会有这么大的误差呢?

山重水复疑无路,柳暗花明又一村!(其实这中间过程,还是挺折腾的,甚至尝试 IDA ProOllyDbg来静态分析与动态调试,但还是水平有限,不知如何入手,而放弃!)

于是返回到官网对GetBounds(Matrix, Pen)函数的详细说明,既然是踏破铁鞋无觅处,得来全不费工夫。

The size of the returned bounding rectangle is influenced by the type of end caps, pen width, and pen miter limit, and therefore produces a “loose fit” to the bounded path. The approximate formula is: the initial bounding rectangle is inflated by pen width, and this result is multiplied by the miter limit, plus some additional margin to allow for end caps.

大概意思是:返回的外接矩形会受到画笔的笔帽(end caps)、笔宽(pen width)还有斜接(pen miter limit)影响,因此会产生看似“松弛”的外接矩形。近似计算公式是:初始边界矩形按笔宽膨胀,并将结果乘以MiterLimt和加上额外的笔帽边距。

MiterLimit惹得祸

原来,一切都是MiterLimit惹的祸。先上代码,来验证下吧。

//定义一个矩形,用于确定一个椭圆
var rect = new Rectangle(200, 200, 300, 200);//定义一个GraphicsPath,用于添加路径
using (var path = new GraphicsPath())
{//根据矩形,添加一个椭圆path.AddEllipse(rect);//定义一个10像素宽的亮绿画笔var pathPen = new Pen(Color.Red, 21);//未修改Pen的MiterLimit前var boxRect = path.GetBounds(new Matrix(), pathPen);//未修改Pen的MiterLimit前的外接矩形e.Graphics.DrawRectangle(pathPen, boxRect.X, boxRect.Y, boxRect.Width, boxRect.Height);//原来的MiterLimit=10pathPen.MiterLimit = 1;//用10像素宽的画笔绘制椭圆e.Graphics.DrawPath(pathPen, path);//计算路径及画笔的外接矩形boxRect = path.GetBounds(new Matrix(), pathPen);//绘制外接矩形,结果?e.Graphics.DrawRectangle(pathPen, boxRect.X, boxRect.Y, boxRect.Width, boxRect.Height);
}

原来默认的是因为Pen的LineJong默认值为Miter,而Pen的MiterLimit默认值又10,所以导致画笔宽度每加1,差不多增加10像素。
MiterLimit惹的祸

完美结果

知道为什么导致结果会“松弛”,那么,要计算出实际贴切的外接矩形就不难了。
//定义一个矩形,用于确定一个椭圆
var rect = new Rectangle(200, 200, 300, 200);

//定义一个GraphicsPath,用于添加路径
using (var path = new GraphicsPath())
{
//根据矩形,添加一个椭圆
path.AddEllipse(rect);

//定义一个10像素宽的亮绿画笔
var pathPen = new Pen(Color.Red, 21);//原来的MiterLimit=10pathPen.MiterLimit = 1;//用10像素宽的画笔绘制椭圆
e.Graphics.DrawPath(pathPen, path);
//绘制没有笔为1的椭圆
e.Graphics.DrawEllipse(Pens.Black, rect);//计算路径及画笔的外接矩形
var boxRect = path.GetBounds(new Matrix(), pathPen);var halfWidth = pathPen.Width / 2;
//贴切的外接矩形
e.Graphics.DrawRectangle(Pens.Black, boxRect.X + halfWidth, boxRect.Y + halfWidth, boxRect.Width - pathPen.Width, boxRect.Height - pathPen.Width);

}
贴切的外接矩形

结束语

回顾为什么会遇到这个疑似GraphicsPath的GetBounds的Bug问题,且兜兜转转花费了那么多时间,起因是急了,而没有花更多的时间去细读函数说明。

所以,当遇到问题时,慢下来,细读下,重新梳理下,或许就能找到答案了。

最后,回头看了看IDA Pro,也找到了答案。
逆向Gdiplus.dll
在Github上也找到GetMaximumCapWidth与GetMaximumJoinWidth的函数原码,有兴趣的可以看看。

https://github.com/ufwt/windows-XP-SP1/blob/d521b6360fcff4294ae6c5651c539f1b9a6cbb49/XPSP1/NT/windows/advcore/gdiplus/engine/entry/pen.cpp#L484

如果你对编程有兴趣,对细节处的魔鬼着迷,给个赞,求关注,一起探索未来吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/14606.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis-Plus介绍及Spring Boot 3集成指南

我们每个Java开发者都在使用springbootmybatis开发时,我们经常发现自己需要为每张数据库表单独编写XML文件,并且为每个表都需要编写一套增删改查的方法,较为繁琐。为了解决这一问题,MyBatis-Plus应运而生。在本文中,我…

第七节:带你全面理解vue3: 其他响应式进阶API

前言: 针对vue3官网中, 响应式:进阶API 中, 我们在上一章中给大家讲解了shallowRef, shallowReactive, shallowReadonly几个API的使用. 本章主要对剩下的API 进行讲解, 我们先看一下官网中进阶API 都有那些 对于剩下这些API, 你需要了解他们创建目的, 是为了解决之前的API存在…

LLM多模态——GPT-4o改变人机交互的多模式 AI 模型应用

1. 概述 OpenAI 发布了迄今为止最新、最先进的语言模型 – GPT-4o也称为“全“ 模型。这一革命性的人工智能系统代表了一次巨大的飞跃,其能力模糊了人类和人工智能之间的界限。 GPT-4o 的核心在于其原生的多模式特性,使其能够无缝处理和生成文本、音频…

AWPortrait1.4更新,人物的生成更加趋近真实感,将SD1.5人像的真实感提升到了一个新的高度

AWPortrait1.4更新,人物的生成更加趋近真实感,将SD1.5人像的真实感提升到了一个新的高度 经过5个月,AWPortrait终于迎来了1.4。 本次更新基于1.3训练,使得人物的生成更加趋近真实感,将SD1.5人像的真实感提升到了一个新…

uview1.0 u-form表单回显校验不通过

提交到后端的数据,回显后不做任何修改无法通过表单校验 原因,u-form表单校验的类型默认为string,但是后端返回的是integer类型,导致无法通过校验 解决,既然后端返回的是整数形,那么我们就将校验规则的type…

【企业动态】东胜物联成为AWS硬件合作伙伴,助力实现边缘智能

近日,AIoT硬件设备供应商东胜物联与全球领先的云计算服务提供商亚马逊云(AWS)达成合作关系,共同致力于推动物联网技术的发展,为企业客户提供更智能、灵活的硬件解决方案,助力智能化升级和数字化转型。 作为…

Android studio关闭自动更新

Windows下: 左上角file - setting - Appearance & Behavier - system setting - update - 取消勾选

图书管理系统(Java版本)

文章目录 前言要求1.设置对象1.1.图书1.2.书架2.管理员3.功能的实现 2.搭建框架2.1.登录(login)2.2.菜单2.3.操作方法的获取 3.操作方法的实现3.1.退出系统(ExitOperation)3.2.显示图书(ShowOperation)3.3.查阅图书(FindOperation)3.4.新增图书(AddOperation)3.5.借出图书(Borr…

链游:区块链技术的游戏新纪元

随着区块链技术的快速发展,越来越多的行业开始探索与其结合的可能性,其中,游戏行业与区块链的结合尤为引人注目。链游,即基于区块链技术的游戏,正以其独特的优势,为玩家带来全新的游戏体验。本文将对链游进…

QQ技术导航源码附带交易系统

网站功能 QQ登录 友联自助交换 友情链接交易功能 多功能搜索 ico小图标本地化 网站图片本地化 蜘蛛日志 文章评论 网站评论 自助链接匿名提交站点,添加友链访问网站自动审核通过 VIP 会员等级 VIP 付费升级 单个文章或者站点付费快审 多背景图片可自定义背景图片…

200smart【编程入门】

说明 编程时,遇到困难就按【F1】 【I】输入 200smart 上限 i0.0~i31.7 255bit【255个输入点】 i0.0~i31.7 八进制 【布尔 bool 】 ib0~ib127 【单字节】 8bit iw0~iw127 …

springBoot+springSecurity基本认证流程

springBootspringSecurity认证流程 整合springSecurity 对应springboot版本&#xff0c;直接加依赖&#xff0c;这样版本不会错 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId…

SpringMVC接收请求参数的方式:

接收简单变量的请求参数 直接使用简单变量作为形参进行接收&#xff08;这里简单变量名称需要与接收的参数名称保持一致&#xff0c;否则需要加上RequestParam注解&#xff09;&#xff1a; 细节&#xff1a; 1&#xff1a;SpringMVC会针对常见类型&#xff08;八种基本类型及…

MQTT到串口的转发(node.js)

本文针对以下应用场景&#xff1a;已有通过串口通信的设备或软件&#xff0c;想要实现跨网的远程控制。 node.js安装 从 Node.js — Run JavaScript Everywhere下载LTS版本安装包&#xff0c;运行安装程序。&#xff08;傻瓜安装&#xff0c;按提示点击即可&#xff09; 设置环…

网络传输层

叠甲&#xff1a;以下文章主要是依靠我的实际编码学习中总结出来的经验之谈&#xff0c;求逻辑自洽&#xff0c;不能百分百保证正确&#xff0c;有错误、未定义、不合适的内容请尽情指出&#xff01; 文章目录 1.端口号的基础2.传输层两协议2.1.UDP 协议2.1.1.协议结构2.1.2.封…

CPP Con 2020:Type Traits I

先谈谈Meta Programming 啥是元编程呢&#xff1f;很简单&#xff0c;就是那些将其他程序当作数据来进行处理和传递的编程&#xff08;私人感觉有点类似于函数式&#xff1f;&#xff09;这个其他程序可以是自己也可以是其他程序。元编程可以发生在编译时也可以发生在运行时。…

LAMDA面试准备(2024-05-23)

有没有学习过机器学习&#xff0c;提问了 FP-Growth 相比 Apriori 的优点 1. 更高的效率和更少的计算量&#xff08;时间&#xff09; FP-Growth 通过构建和遍历 FP-树 (Frequent Pattern Tree) 来挖掘频繁项集&#xff0c;而不需要像 Apriori 那样生成和测试大量的候选项集。具…

5.23.2 深度学习提高乳房 X 光检查中乳腺癌的检测率

开发了一种深度学习算法&#xff0c;该算法可以使用“端到端”训练方法在筛查乳房 X 光检查中准确检测出乳腺癌&#xff0c;该方法有效地利用了具有完整临床注释或仅具有整个图像的癌症 标签 的训练数据集。 在这种方法中&#xff0c;仅在初始训练阶段才需要病变注释&#xff…

springboot vue 开源 会员收银系统 (2) 搭建基础框架

前言 完整版演示 前面我们对会员系统https://blog.csdn.net/qq_35238367/article/details/126174288进行了分析 确定了技术选型 和基本的模块 下面我们将从 springboot脚手架开发一套收银系统 使用脚手架的好处 不用编写基础的rabc权限系统将工作量回归业务本身生成代码 便于…

Tensorflow入门实战 P01-实现手写数字识别mnist

目录 1、背景&#xff1a;MNIST手写数字识别 2、完整代码&#xff08;Tensorflow&#xff09;&#xff1a; 3、运行过程及结果&#xff1a; 4、小结&#xff08;还是很清晰的&#xff09; 5、 展望 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客…