首页 > 其他分享 >Window GDI+ API有BUG?GetBounds测不准?

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

时间:2024-05-24 23:01:16浏览次数:22  
标签:外接 boxRect Window API GetBounds new path 矩形

文章目录


最近,在学习系统了解 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

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

标签:外接,boxRect,Window,API,GetBounds,new,path,矩形
From: https://blog.csdn.net/TyroneKing/article/details/139159546

相关文章

  • Windows环境变量的优先级?
    起因今天用python的时候查了一下version,发现并不是我想用的版本,之前装的是3.10,但是跳出来一个3.11。眉头一皱,事情并不简单。第一个想法就是有什么程序偷偷给我装了其他版本的python还覆盖了路径。寻找去环境变量里寻摸一圈,发现事情并不简单,系统变量和用户变量里只有我自己配的......
  • Windows Server 2008 R2安装VMtools-安装失败的解决方法
    VMware安装WindowsServer2008R2可以参考这篇文章:https://blog.csdn.net/2301_77225571/article/details/139113923?spm=1001.2014.3001.55011.下载补丁https://www.catalog.update.microsoft.com/search.aspx?q=kb4474419远控虚拟机查看虚拟机IP地址回到本机,【Win......
  • VMware 安装Windows Server 2008 R2
    1.下载镜像迅雷:ed2k://|file|cn_windows_server_2008_r2_standard_enterprise_datacenter_and_web_with_sp1_x64_dvd_617598.iso|3368839168|D282F613A80C2F45FF23B79212A3CF67|/2.安装过程自定义名字,点击【浏览】选择安装路径点击【浏览】选择前面下载的镜......
  • Windows Server 2022 安装
    获取WindowsServer2022https://www.microsoft.com/zh-cn/evalcenter/evaluate-windows-server-2022查看WindowsServer2022发行说明和系统要求。注册,然后下载并安装。(注意:此评估版将在180天后过期。)接收包含资源和指导的电子邮件,帮助完成评估。 https://next.ite......
  • .net Framework Web Api 实现多国语
    首先,在项目下创建一个Resources文件夹,在Resources文件夹中添加如下资源文件:Message.resx【默认英语】Message.ja.resx 【日语】Message.zh-Hans.resx【简体中文】Message.zh-Hant.resx 【繁体中文】在文件中添加名称和值,例如在简体中文的文件中 在日语文件中 然......
  • 记录Nginx开机自动启动(Windows环境)
    参考:Nginx配置及开机自启动(Windows环境)_nginx开机自启动windows-CSDN博客winsw下载地址Indexofreleases/com/sun/winsw/winsw或者参考Nginx安装、配置以及开机启动(Win10篇)_win10怎么查看nginx启动成功-CSDN博客......
  • 已经阻止此发布者在你的计算机上运行软件/ 为了对电脑进行保护,已经阻止此应用 / windo
    需求场景svg文件的图标不直接显示内容,不如其他文件直观,但是svg文件的体积极小,因此适合网页开发中使用。最终效果解决过程https://download.cnet.com/svg-explorer-extension/3000-2248_4-78237543.htmlSVGExplorerExtensionforWindows这个软件可以解决这个问题。但......
  • 总结一下windows 运行窗口的一些常用命令
    cmd:打开命令提示符窗口,可以在其中执行各种命令和操作系统功能。msconfig:打开系统配置实用程序,可以配置系统启动项、服务和启动选项。appwiz.cpl:打开“程序和功能”窗口,可以卸载或更改安装的程序。control:打开控制面板,可以对系统设置和功能进行管理。devmgmt.msc:打开......
  • .Net6 web API (ResouceFilter-ActionFilter)
    前沿net中的ResouceFilter和ActionFilter 有啥主要区别呢其实2者都是过滤器 在ASP.NETCore中,ResourceFilter和ActionFilter是两种不同类型的过滤器,用于在请求处理过程中执行额外的逻辑。它们可以用于对请求和响应进行处理、修改或拦截,以实现诸如授权、日志记录......
  • Windows下分卷压缩后到Linux进行解压的方法
    windows分卷压缩后linux解压缩 Linux服务器在内网,中途隔了一层堡垒机。文件太大,堡垒机对此有限制,需要在Windows上分包,然后上送到Linux上进行合并解压。我探索出来的办法有两个,以下依次介绍:1、WinRAR+7za命令WinRAR上需要选择压缩格式为zip,输入分卷大小: 然后将分卷都传......