我只是想把它扔到这里以得到一些反馈,我称之为" 记得算零 "(感谢Andreas Rejbrand的链接.结果发现它被称为"一个问题")当处理像素时.记得零点是什么意思?好吧,如果你实现一个需要计算矩形运算中涉及的像素数的例程(例如FillRect或CopyRect),你必须记住零(0,0)是一个像素.但是将零视为像素而不是一些无值的规则似乎只涉及涉及<= 0值的坐标.举个例子:
mRect:=Rect(0,0,10,10);
mRectWidth:=mRect.right-mRect.left; // returns 10 - 0 = 10
Run Code Online (Sandbox Code Playgroud)
看到问题?矩形实际上在像素操作术语中定义了从位置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;
Run Code Online (Sandbox Code Playgroud)
现在这个工作,实际上它工作得很好,90%的图形库使用它作为计算矩形的宽度和高度的tecnique.但就像强大的英雄achillees一样,它有一个弱点,即空矩形返回的质量为1(如果你用一个阻击器,它也可以产生各种有趣的AV).
mRect:=Rect(0,0,0,0);
mRectWidth:=(mRect.right-mRect.left) + 1;
Run Code Online (Sandbox Code Playgroud)
其中大致相当于0 - 0 = 0:+ 1 = 1,这意味着如果你不注意盲点,将会渲染一个像素.令我困惑的是,Delphi XE实际上似乎有裁剪问题(?),或者说至少是一个矛盾.因为你实际上在底部会丢失一个像素,如果你绘制它就会丢失到最右边.ClientRect不能从第一个像素到最后一个像素返回完整的绘图范围吗? - 但如果你试试这个:
mRect:=getClientRect;
MoveTo(mRect.left,mRect.Bottom);
LineTo(mRect.right,mRect.bottom);
Run Code Online (Sandbox Code Playgroud)
你不会看到一件事!因为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个像素(我知道,这是主要的无聊事物).
在Mac上的C#下,使用GTK#和本机MonoMac绑定 - clientrect是绝对的.这意味着您可以绘制到mRect.bottom或mRect.right并获得可见结果.因此,我发现我很喜欢我最喜欢的语言和工具包Delphi,我们总是必须手动调整每个ownerdrawn或自定义控件的clientrect.
Dav*_*nan 13
这就是GDI的工作方式,而Delphi TCanvas仅仅反映了底层框架.
例如,考虑LineTo():
所述的LineTo函数绘制从当前位置的排队,但不包括指定点.
或者FillRect():
该FillRect函数通过使用指定的刷子填充的矩形.此功能包括左边框和上边框,但不包括矩形的右边框和下边框.
或者Rectangle():
绘制的矩形排除了底边和右边.
等等等等.
现在考虑API函数GetWindowRect().
检索指定窗口的边界矩形的尺寸.尺寸以屏幕坐标给出,相对于屏幕的左上角.
返回的bottom和right值RECT超出窗口边界1个像素.所以窗户的宽度确实width = right-left和高度相同.我猜这个大会是为了让这种平等成立.
您报告的行为不是Delphi TCanvas代码中的错误- 代码正确且完全按照设计工作.
到目前为止,使用Windows UI的开发人员的最佳方法是遵循相同的约定.试图采用自己的不同约定只会导致混乱和错误.
我为你为这种现象写一个完整的图书馆的努力感到非常抱歉,但你完全错了.
例如,您的代码应该是:
mRect := getClientRect;
MoveTo(mRect.left, mRect.Bottom - 1);
LineTo(mRect.right - 1, mRect.bottom - 1);
Run Code Online (Sandbox Code Playgroud)
总是考虑到例程喜欢FillRect()做什么X = Rect.Right也没有Y = Rect.Bottom.他们都画了直到Right - 1和Bottom - 1.它应该是这样的:对于带有Left = 10和的Width = 10最右边的像素的按钮,而X = 19不是X = 20.
也许这让人感到困惑,但你可以在纸方块上轻松地想象出来.