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

news2024/11/25 20:29:45

文章目录

  • 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,向上偏移100
    var 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=10
    pathPen.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=10

pathPen.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.coloradmin.cn/o/1689197.html

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

相关文章

【Vue】Vue2中的Vuex

目录 Vuex介绍Vuex 中的核心概念 在vue2中使用Vuex安装 Vuex创建一个 Vuex Store在 Vue 实例中使用 Vuex编写 Vuex 的 state、mutations 和 actions在组件中使用 Vuex Vuex的核心State组件中获取 Vuex 的状态mapState 辅助函数对象展开运算符 Getter基本使用示例 通过属性访问通…

[协议]stm32读取AHT20程序示例

AHT20温度传感器使用程序&#xff1a; 使用i2c读取温度传感器数据很简单&#xff0c;但市面上有至少两个手册&#xff0c;我这个对应的手册贴出来&#xff1a; main: #include "stm32f10x.h" // Device header #include <stdint.h> #includ…

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

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

RabbitMQ - SimpleMessageListenerContainer的实现逻辑

RabbitMQ - SimpleMessageListenerContainer的实现逻辑 Queue&#xff08;队列&#xff09;&#xff1a;在 RabbitMQ 中用于存储消息的数据结构。生产者将消息发送到队列中&#xff0c;而消费者从队列中接收消息。 Connection&#xff08;连接&#xff09;&#xff1a;连接是应…

【代码随想录 二叉树】二叉树前序、中序、后序遍历的迭代遍历

文章目录 1. 二叉树前序遍历&#xff08;迭代法&#xff09;2. 二叉树后序遍历&#xff08;迭代法&#xff09;3. 二叉树中序遍历&#xff08;迭代法&#xff09; 1. 二叉树前序遍历&#xff08;迭代法&#xff09; 题目连接 &#x1f34e;因为处理顺序和访问顺序是一致的。所…

小程序开发的基本用法

一:基本组件 1.view和scroll-view view等同于div,view写在小程序显示和div一样的效果. srcoll-view scroll-x/scroll-y是div能移动的.但是小程序没有显示大的划的. 且scroll-view才能实现这个,要这个组件且要属性,内部基本结构才能实现. view没有属性实现. 2.swiper和swi…

【Centos7+JDK1.8】Jenkins安装手册

一、安装环境 Centos7 JDK1.8 Jenkins-2.346.3 JDK1.8安装以及网络配置等 自行搜索资料解决。 二、卸载历史安装的Jenkins&#xff0c;直接全部复制粘贴下面的命令 service jenkins stop yum -y remove jenkins rpm -e jenkins rpm -ql jenkins rm -rf /etc/sysconfig/je…

Vue从入门到实战Day12

一、Pinia快速入门 1. 什么是Pinia Pinia是Vue的最新状态管理工具&#xff0c;是Vuex的替代品 1. 提供更加简单的API&#xff08;去掉了mutation&#xff09; 2. 提供符合组合式风格的API&#xff08;和Vue3新语法统一&#xff09; 3. 去掉了modules的概念&#xff0c;每一…

【漏洞复现】懒人网址导航页 search.html SQL注入漏洞

0x01 产品简介 赖人网址导航系统是一种智能化的网址导航平台&#xff0c;旨在帮助用户快速找到所需的网址和资源。该系统提供了智能化的网址搜索和推荐功能&#xff0c;能够根据用户的搜索习惯和偏好推荐相关的网址和资源。同时&#xff0c;系统还提供了网址分类、网址收藏和网…

虚拟化技术[1]之服务器虚拟化

文章目录 虚拟化技术简介数据中心虚拟化 服务器虚拟化服务器虚拟化层次寄居虚拟化裸机虚拟化VMM无法直接捕获特权指令解决方案 服务器虚拟化底层实现CPU虚拟化内存虚拟化I/O设备虚拟化 虚拟机迁移虚拟机动态迁移迁移内容&#xff1a;内存迁移迁移内容&#xff1a;网络资源迁移迁…

二叉树实战演练

目录 1.二叉树前序遍历---leetcode 思路 画图解析&#xff1a; 2.相同的树的判断 思路&#xff1a; 3.对称二叉树 思路分析&#xff1a; 4.另一棵树的子树 思路&#xff1a; 5.二叉树的便利---牛客网 建立二叉树的逻辑图&#xff1a; 总结&#xff1a; 1.…

【嵌入式软件工程师面经】Socket,TCP,HTTP之间的区别

目录&#xff1a; 目录 目录&#xff1a; 一、Socket原理与TCP/IP协议 1.1 Socket概念&#xff1a; 1.2 建立Socket连接&#xff1a; 1.3 SOCKET连接与TCP/IP连接 二、HTTP连接&#xff1a; 2.1 HTTP原理 三、三者的区别和联系 前些天发现了一个巨牛的人工智能学习网站&#xf…

光伏电站在线监测智能诊断系统:开启无人值守新纪元

光伏电站在线监测智能诊断系统&#xff1a;开启无人值守新纪元 大家都知道光伏电站是通过汲取着太阳的光芒&#xff0c;为人类提供源源不断的电能源。然而&#xff0c;随着光伏电站规模的扩大和复杂性的增加&#xff0c;如何有效提高发电效率、减少人工维护成本&#xff0c;实…

力扣Hot100-73矩阵置零(标记数组)

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]示例 2&#xff1a; 输入&…

3、md5比较绕过

青少年ctf&#xff1a;EasyMD5 1、页面是一个上传页面 2、上传两个txt文件&#xff0c;bp抓包 3、go发现提示要PDF文件 4、将文件类型改成PDF类 5、改文件类型提示MD5&#xff0c;也看出它是将文件里的内容读取比较 6、改成s878926199a和QNKCDZO 猜测后端源码&#xff1a; if…

水下哨兵 智能守护——北斗人员落水报警与快速应急响应方案

随着科技的不断发展&#xff0c;人们对于安全的需求也越来越高&#xff0c;尤其是在水域活动中&#xff0c;落水事故时有发生&#xff0c;给人们的生命和财产安全带来了很大威胁。为了更好地保障水域活动者的安全&#xff0c;北斗短报文技术被广泛应用于落水报警系统中&#xf…

SpringBoot 实现私钥解密 前端的公钥加密内容

目录 一、前端公钥加密 二、后端私钥解密 一、前端公钥加密 Nextjs 前端实现RSA公钥加密 JSEncrypt加载问题解决-CSDN博客 二、后端私钥解密 import javax.crypto.Cipher; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.…

重新夺回控制权!原创始人从Synk回购FossID,致力于解决开源许可合规风险

FossID 于 2022 年 9 月被其原始创始人从 Snyk, Inc. 重新收购。为什么 Snyk 在 2021 年收购了 FossID&#xff0c;又在 2022 年将其分拆&#xff0c;以及为什么 FossID 的创始人&#xff08;Oskar Swirtun 和 Jon Aldama&#xff09;后来又回购了该公司&#xff1f; 公司背景 …

word-表格疑难杂症诊治

一、用表格进行排版图片、制作公文头 可以在插入图片时固定列宽 二、表格中的疑难杂症 问题一&#xff1a;表格超过页面&#xff0c;右侧文字看不见 解决&#xff1a;表格窗口-布局-自动调整-根据窗口自动调整表格 问题二&#xff1a;表格底部文字被遮挡 解决&#xff1a;布…

舵机(结构,原理,控制方法)

介绍 舵机&#xff0c;全称为伺服马达&#xff08;Servo Motor&#xff09;&#xff0c;是一种能够精确控制角度或位置的电动机。它广泛应用于模型制作、机器人技术、工业自动化等领域。舵机通过接收控制信号&#xff0c;将其转化为机械运动&#xff0c;从而实现精确的控制。 …