FMX:TCanvas.DrawPath 出现奇怪的故障。为什么?
我绘制了一条由两条线组成的路径,先向上然后向下返回到同一位置或几乎同一位置,但第一条线画得太高。如果我然后使用 DrawLine 绘制相同的线条,我就看不到问题。为什么会发生这种情况?
下面是一个例子。只需将 400x400 TImage 放在空白的多平台表单上即可。该代码绘制了 2 条红色路径,其中一条线之间的角度接近 180 度,另一条线之间的角度较小。然后使用蓝色的 DrawLine 绘制相同的线条。如果 DrawPath 函数正常工作,那么蓝线应该完全覆盖红线,但事实并非如此。在此比例为 1.5 的示例中,路径比第一条路径高出 7 个像素。随着线的距离越来越远,误差的程度会减小。当比例为 1 时,该问题仍然会发生,但不太明显。
procedure TForm1.FormActivate(Sender: TObject);
var
LPath1, LPath2 : TPathData;
i : Integer;
begin
// A path of 2 lines going up and then back down to almost the same spot
LPath1 := TPathData.Create;
LPath1.MoveTo(PointF(100,200));
LPath1.LineTo(PointF(100,50 ));
LPath1.LineTo(PointF(105,200));
// A path of 2 lines going up and then back down at a wider angle
LPath2 := TPathData.Create;
LPath2.MoveTo(PointF(200,200));
LPath2.LineTo(PointF(200,50 ));
LPath2.LineTo(PointF(260,200));
Image1.Bitmap.BitmapScale := 1.5; // The issue shows up more at larger scales
Image1.Bitmap.SetSize(Trunc(Image1.Width), Trunc(Image1.Height));
with Image1.Bitmap.Canvas do if BeginScene then begin
Clear(TAlphaColorRec.White);
// Draw the paths using DrawPath in red
Stroke.Color := TAlphaColorRec.Red;
Stroke.Thickness := 1;
DrawPath(LPath1, 1);
DrawPath(LPath2, 1);
// Draw the paths using DrawLine in blue over the top
// The red lines should be completely hidden under the blue
Stroke.Color := TAlphaColorRec.Blue;
for i := 1 to LPath1.Count - 1 do
DrawLine(LPath1.Points[i-1].Point, LPath1.Points[i].Point, 1);
for i := 1 to LPath2.Count - 1 do
DrawLine(LPath2.Points[i-1].Point, LPath2.Points[i].Point, 1);
EndScene;
end;
LPath1.Free;
LPath2.Free;
Image1.Bitmap.SaveToFile('test.png');
end;
在 Windows 10 中运行代码的结果。我使用的是 Delphi 11,但 Delphi 10 也出现同样的问题。我尝试过切换 GPU,但出现同样的问题。
放大视图:
I draw a path consisting of 2 lines going up and then back down to the same spot, or almost the same spot, but the first line is drawn too high. If I then draw the same lines using DrawLine I don't see the issue. Why is this happening?
Below is an example. Just drop a 400x400 TImage on a blank multiplatform form. The code draws 2 red paths, one with close to a 180 degree angle between the lines and one with less of an angle. The same lines are then drawn using DrawLine in blue. If the DrawPath function works correctly then the blue lines should completely cover the red lines, but they don't. In this example with a scale of 1.5 the path extends 7 pixels too high for the first path. The extent of the error reduces as the lines get further apart. The issue still happens with a scale of 1, but is less obvious.
procedure TForm1.FormActivate(Sender: TObject);
var
LPath1, LPath2 : TPathData;
i : Integer;
begin
// A path of 2 lines going up and then back down to almost the same spot
LPath1 := TPathData.Create;
LPath1.MoveTo(PointF(100,200));
LPath1.LineTo(PointF(100,50 ));
LPath1.LineTo(PointF(105,200));
// A path of 2 lines going up and then back down at a wider angle
LPath2 := TPathData.Create;
LPath2.MoveTo(PointF(200,200));
LPath2.LineTo(PointF(200,50 ));
LPath2.LineTo(PointF(260,200));
Image1.Bitmap.BitmapScale := 1.5; // The issue shows up more at larger scales
Image1.Bitmap.SetSize(Trunc(Image1.Width), Trunc(Image1.Height));
with Image1.Bitmap.Canvas do if BeginScene then begin
Clear(TAlphaColorRec.White);
// Draw the paths using DrawPath in red
Stroke.Color := TAlphaColorRec.Red;
Stroke.Thickness := 1;
DrawPath(LPath1, 1);
DrawPath(LPath2, 1);
// Draw the paths using DrawLine in blue over the top
// The red lines should be completely hidden under the blue
Stroke.Color := TAlphaColorRec.Blue;
for i := 1 to LPath1.Count - 1 do
DrawLine(LPath1.Points[i-1].Point, LPath1.Points[i].Point, 1);
for i := 1 to LPath2.Count - 1 do
DrawLine(LPath2.Points[i-1].Point, LPath2.Points[i].Point, 1);
EndScene;
end;
LPath1.Free;
LPath2.Free;
Image1.Bitmap.SaveToFile('test.png');
end;
Result of the code when run in Windows 10. I'm using Delphi 11, but the same issue happens with Delphi 10. I've tried switching GPU but the same issue occurs.
Enlarged view:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我得出的结论是,这根本不是一个故障。这是因为 TCanvas.Stroke.Join 的默认设置是 TStrokeJoin.Miter。所看到的文物只是斜角的尖刺。构造路径时在每个线段之前使用 MoveTo 确实可以解决问题(因为单独的线段之间没有连接),但将 TCanvas.Stroke.Join 参数设置为 TStrokeJoin.Round 或 TStrokeJoin.Bevel 也可以解决问题。
请注意,在接近 180 度的非常尖锐的角度处,斜接连接将变得无限大。然而,它似乎受到某种限制,也许与笔画粗细成比例。我不认为有办法改变德尔福中的斜接限制。
I've come to the conclusion that this isn't a glitch at all. It's because the default setting of TCanvas.Stroke.Join is TStrokeJoin.Miter. The artefact seen is just the sharp spike of the mitred corner. Using MoveTo before each line segment when constructing the path does solve the issue (because there's no join between the separate line segments) but so does setting the TCanvas.Stroke.Join parameter to TStrokeJoin.Round or TStrokeJoin.Bevel.
Note that at very sharp angles approaching 180 degrees, the miter join would become infinite. However, it appears to be limited somehow, perhaps in proportion to the stroke thickness. I don't think there's a way to change this miter limit in delphi.
这是因为默认情况下
TPath
在不同路径段之间进行平滑过渡。我猜测它可能使用二次插值来实现这些平滑过渡。是的,在两条线之间进行平滑过渡似乎不合逻辑,但看起来这就是它的实现方式。
现在,您可以通过告诉
TPath
您的两条线没有连接来避免这种情况,因此即使实际上它们是连接的,也应该将其视为两条单独的线。您可以通过简单地调用 Path.MoveTo 来完成此操作,该函数旨在移动位置,以便您可以创建另一条不从最后一个路径点继续的未连接线。以下是第一条尖角线的修改后的代码:
请注意,我为
MoveTo
命令指定了与用于渲染上一条路径线的完全相同的位置,因为您不希望新线从新位置开始。This is because by default
TPath
is making smooth transitions between different path segments. I'm guessing it might be using Quadratic interpolation for making these smooth transitions.Yes making smooth transition between two lines doesn't seem logical but it looks this is how it is implemented.
Now you can avoid this by telling the
TPath
that your two lines are not connected and thus should be treated as two separate lines even thou in reality they are connected. And you can do this by simply callingPath.MoveTo
which is intended to shift position so you can create another unconnected line that dos not continue from your last path point.Here is how modified code for your first sharp cornered line would look like:
NOTE that I'm specifying the exact same position for
MoveTo
command that was used for rendering of previous path line since you don't want the new line to start at new position.