delphi - Delphi的TCanvas中有错误吗?

标签 delphi graphics

我将在这里扔掉它以获得一些反馈,我称之为“remember to count zero”(感谢Andreas Rejbrand提供的链接。事实证明,使用像素时被称为“一个问题解决”)。记住要数零是什么意思?好吧,如果您实现了一个例程,该例程需要计算矩形运算中涉及的像素数(例如FillRectCopyRect),则必须记住零(0,0)是一个像素。但是,将零视为像素而不是无值数量的规则似乎只在涉及<= 0值的坐标时起作用。举个例子:

mRect:=Rect(0,0,10,10);
mRectWidth:=mRect.right-mRect.left; // returns 10 - 0 = 10

看到问题了吗?矩形实际上以像素运算的方式定义了一个从位置0,0延伸到位置10、10的区域。该区域实际上是11个步长,而不是10个步长(对于x:= 0到10,实际上是11个步长)。为了弥补丢失的像素(当您将其移动到正或负空间中时零没有质量并且消失。我似乎还记得上帝的毕达哥拉斯定理),大多数人只会在最终结果中加1,如下所示:

function getRectWidth(const aRect:TRect):Integer;
Begin
  result:=(aRect.right-aRect.left) +1;
End;

现在可以正常工作了,实际上所有90%的图形库都将其用作计算矩形的宽度和高度的技术。但是,就像强大的英雄阿基里斯一样,它也有一个弱点,那就是质量为1的空矩形会返回(如果您将它与挡块一起使用,它还会创建各种有趣的AV)。

mRect:=Rect(0,0,0,0);
mRectWidth:=(mRect.right-mRect.left) + 1;

大致等于0 – 0 = 0 : +1 = 1,这意味着如果您不注意盲点,将渲染像素。让我感到困惑的是,Delphi XE实际上似乎存在一个裁剪问题(?),或者至少在矛盾上是矛盾的。因为您实际上会在底部和最右边损失一个像素(如果绘制到该像素)。 ClientRect是否不应该从第一个像素到最后一个像素返回完整的绘图范围? –但是,如果您尝试这样做:

mRect:=getClientRect;
MoveTo(mRect.left,mRect.Bottom);
LineTo(mRect.right,mRect.bottom);

你什么也看不见!因为Delphi会裁剪最终像素(错误?)。似乎很好奇,当您要求clientrect时,您必须手动对其进行调整?

我已经从头开始编写了自己的图形库(用于快速的dib访问和屏幕外渲染,与这种特殊情况无关),因此我在这些方法中研究了很长时间。关于编码,总是有一些新的东西要学习,但是没有人能告诉我,本文中没有盲点。

当我将VCL与其他库(尤其是用C#编写的库)的工作方式进行比较时,我还注意到其中很多人都喜欢我-并确保clientrect是可以使用的整个区域范围。当在裁剪区域外滑动并使用重叠的矩形时,它们还为“盲点”增加了高度。

盲点案例

假设您正在将一个矩形从一个位图复制到另一个位图。 blit的目标是Rect(-10,-10,10,10)。为了在此处正确地“剪切”目标,以免在外部写入内存缓冲区时遇到访问冲突,您必须计算X1/Y1与cliprect(此处为0,0,Width-1,Height-1)之间的距离。

这为您提供了必须添加到目标矩形和源矩形的偏移量。否则,您将在缓冲区外写入​​,但也会从源缓冲区中的错误位置读取数据。

现在,这取决于您如何实现这一偏离目标。但是,有很多图书馆没有考虑零。当X1和X2具有相同的值,但x1为负值时,出现盲点。因为人们通常写:mOffset:= x2 - abs(x1)。在我们的情况下,它变为10-10 =0。只要将cliprect设置为0,0,它就可以正常工作。但是,当您的cliprect移动到正数空间时,您将偏离一个像素。而且,如果您自动增加getRectWidth(例如mWidth:=aRect.right-aRect.left +1)中的值-根据源矩形,您将偏离2个像素(我知道,这是很无聊的事情)。

在Mac上的C#下,使用GTK#以及 native MonoMac绑定(bind)-clientrect是绝对的。这意味着您可以绘制到mRect.bottommRect.right并获得可见的结果。因此,我感到奇怪的是,我最喜欢的语言和工具包Delphi在使用它时总是必须手动调整每个clientrect或自定义控件的ownerdrawn

最佳答案

这就是GDI的工作方式,而Delphi的TCanvas仅反射(reflect)了基础框架。

例如,考虑 LineTo() :

The LineTo function draws a line from the current position up to, but not including, the specified point.



FillRect() :

The FillRect function fills a rectangle by using the specified brush. This function includes the left and top borders, but excludes the right and bottom borders of the rectangle.



Rectangle() :

The rectangle that is drawn excludes the bottom and right edges.



等等等等。

现在考虑API函数 GetWindowRect()

Retrieves the dimensions of the bounding rectangle of the specified window. The dimensions are given in screen coordinates that are relative to the upper-left corner of the screen.



返回的bottom中的rightRECT值超出窗口边界1个像素。因此,窗口的宽度实际上是width = right-left,高度也是如此。我猜想选择惯例是为了保持这种平等。

您报告的行为不是Delphi的TCanvas代码中的错误-该代码可以正确且完全按设计工作。

到目前为止,使用Windows UI的开发人员的最佳方法是遵循相同的约定。尝试采用自己的不同约定只会导致困惑和错误。

关于delphi - Delphi的TCanvas中有错误吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6087286/

相关文章:

delphi - 获取 IHTMLElement.body.innerHTML 作为 ansi 字符串

windows - 在 Delphi 窗体上递归更新字体

Delphi:FastMM 使您的可执行文件更大?

java - 为什么我的绘制方法仅在某些时候有效?

java - 用 Java 在屏幕/窗口上写入每个像素的最快方法

java - java中的慢速 map

windows - 当我从 CreateProcess 运行 NETSH 时得到 "The system cannot find the file specified"但它在命令提示符下工作正常?

excel - Delphi 7 with..do 语句不适用于变体变量

java - java中的放大和缩小?

html - 如何使用 CSS 显示具有不同前景的相同图像