Delphi的TCanvas中有错误吗?

时间:2011-05-22 10:16:55

标签: delphi graphics

我只是想把它扔到这里以获得一些反馈,我称之为“remember to count zero”(感谢Andreas Rejbrand的链接。结果发现它被称为“一个问题”)当使用像素。记得零点是什么意思?好吧,如果你实现一个需要计算矩形运算中涉及的像素数的例程(例如FillRect或CopyRect),你必须记住零(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%的图形库都使用它作为计算矩形宽度和高度的tecnique。但就像强大的英雄achillees一样,它有一个弱点,即空矩形返回的质量为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#编写的库时,我也注意到很多人都喜欢我 - 并确保客户端是您可以使用的区域的全部范围。当他们在剪辑区域外进行blitting并使用重叠的矩形时,他们也为“盲点”占据了一席之地。

盲点的情况

我们假设您正在将一个矩形从一个位图复制到另一个位图。你的blit的目标是Rect(-10,-10,10,10)。为了在这里正确地“剪切”目标,所以你不要在内存缓冲区外写入​​访问冲突,你必须计算X1 / Y1和你的剪辑之间的距离(这里被认为是0,0,宽度-1 ,身高-1)。

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

现在,这取决于你如何实现这个目的。但是有很多库不考虑零。当X1和X2具有相同的值时发生盲点,但x1为负。因为人们通常写:mOffset:= x2 - abs(x1)。在我们的情况下,它变为10-10 = 0.只要cliprect设置为0,0它就可以正常工作。但是当你的剪辑进入正空间的那一刻 - 你会偏离一个像素。如果你自动包含你的getRectWidth中的值(例如mWidth:= aRect.right-aRect.left +1) - 根据源矩形,你将偏离2个像素(我知道,这是主要的无聊事物)。 / p>

在Mac上的C#下,使用GTK#和本机MonoMac绑定 - clientrect是绝对的。这意味着您可以绘制到mRect.bottom或mRect.right并获得可见结果。因此,我发现我很喜欢我最喜欢的语言和工具包Delphi,当我们使用它时,我们总是必须手动调整每个所有者绘制或自定义控件的客户端。

2 个答案:

答案 0 :(得分:13)

这就是GDI的工作方式,而Delphi的TCanvas仅仅反映了底层框架。

例如,考虑LineTo()

  

LineTo 功能从当前位置绘制一条直线,但不包括指定的点。

FillRect()

  

FillRect 功能使用指定的画笔填充矩形。此功能包括左边框和上边框,但不包括矩形的右边框和下边框。

Rectangle()

  

绘制的矩形排除了底边和右边。

依旧等等。

现在考虑API函数GetWindowRect()

  

检索指定窗口的边界矩形的尺寸。尺寸以屏幕坐标给出,相对于屏幕的左上角。

返回的bottom中的rightRECT值超出窗口边界1个像素。所以窗口的宽度确实是width = right-left,同样对于高度。我猜这个大会是为了让这种平等成立。

您报告的行为不是Delphi的TCanvas代码中的错误 - 代码正常且完全符合设计。

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

答案 1 :(得分:8)

我非常抱歉你为这个现象编写了一个完整的库,但你完全错了。

例如,您的代码应该是:

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

始终考虑FillRect()之类的例程对X = Rect.RightY = Rect.Bottom不执行任何操作。他们都画到Right - 1Bottom - 1。这应该是这样的:对于Left = 10Width = 10的按钮,最右边的像素位于X = 19,而不是X = 20

也许这会让人感到困惑,但你可以在纸方块上轻松地想象出来。