如何使用 NSBezierPath 获得 1 像素线?

发布于 2024-12-13 22:40:20 字数 301 浏览 2 评论 0 原文

我正在开发一个自定义控件。要求之一是画线。虽然这有效,但我注意到我的 1 像素宽线看起来并不像 1 像素宽线 - 我知道,它们不是真正的像素,但你知道我的意思。它们看起来更像是两三个像素宽。当我绘制一条具有 1 像素虚线和 2 像素间隙的虚线时,这一点变得非常明显。 1 像素的虚线实际上看起来像细线而不是点。

我读过 Cocoa Drawing 文档,尽管 Apple 提到了 setLineWidth 方法,但将线宽更改为小于 1.0 的值只会使线看起来更模糊而不是更细。

所以,我怀疑还有其他因素影响了我的线条的外观。

有什么想法吗?

I'm developing a custom control. One of the requirements is to draw lines. Although this works, I noticed that my 1 pixel wide lines do not really look like 1 pixel wide lines - I know, they're not really pixels but you know what I mean. They look more like two or three pixels wide. This becomes very apparent when I draw a dashed line with a 1 pixel dash and a 2 pixel gap. The 1 pixel dashes actually look like tiny lines in stead of dots.

I've read the Cocoa Drawing documentation and although Apple mentions the setLineWidth method, changing the line width to values smaller than 1.0 will only make the line look more vague and not thinner.

So, I suspect there's something else influencing the way my lines look.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

酷炫老祖宗 2024-12-20 22:40:20

贝塞尔曲线路径以其路径为中心绘制,因此如果您沿 X 坐标绘制 1 像素宽的路径,则该线实际上沿 Y 坐标绘制 { -0.5, 0.5 } 解决方案通常是将坐标偏移 0.5,以便该线未绘制在子像素边界中。您应该能够将边界框移动 0.5,以获得更清晰的绘图行为。

Bezier paths are drawn centered on their path, so if you draw a 1 pixel wide path along the X-coordinate, the line actually draws along Y-coordinates { -0.5, 0.5 } The solution is usually to offset the coordinate by 0.5 so that the line is not drawn in the sub pixel boundaries. You should be able to shift your bounding box by 0.5 to get sharper drawing behavior.

夏日落 2024-12-20 22:40:20

Francis McGrew 已经给出了正确的答案,但由于我曾经对此进行过演示,所以我想添加一些图片。

这里的问题是 Quartz 中的坐标位于像素之间的交叉点处。这在填充矩形时很好,因为位于坐标内的每个像素都会被填充。但线条在技术上(数学上!)是不可见的。为了绘制它们,Quartz 必须实际绘制一个具有给定线宽的矩形。这个矩形以坐标为中心:

当您指定整数坐标时您告诉 Quartz 绘制的矩形

因此,当您要求 Quartz用整数坐标绘制矩形,它存在只能绘制整个像素的问题。但在这里你可以看到我们有半个像素。所以它的作用是平均颜色。对于 50% 黑色(线条颜色)和 50% 白色(背景)线条,它只是将每个像素绘制为灰色:

这就是褪色绘图的来源。现在解决方法很明显:不要在像素之间绘制,您可以通过将点移动半个像素来实现这一点,这样您的坐标就以所需像素为中心:

坐标向右下偏移 0.5

当然,现在仅仅偏移可能不是您想要的。因为如果将填充变体与描边变体进行比较,会发现描边向右下方大 1 个像素。例如,如果您要剪切到矩形,这将切断右下角:

由于人们通常期望矩形在指定矩形内进行描边,因此您通常所做的就是向中心偏移 0.5,因此右下角实际上向上移动了一个像素。或者,许多绘图应用程序会从中心偏移 0.5,以避免边框和填充之间的重叠(当您使用透明度进行绘图时,这可能看起来很奇怪)。

请注意,这仅适用于 1x 屏幕。 2x Retina 屏幕实际上以不同的方式表现出这个问题,因为下面的每个像素实际上都是由 4 个 Retina 像素绘制的,这意味着它们实际上可以绘制半像素。然而,如果您想要一条清晰的 0.5pt 线,您仍然会遇到同样的问题。另外,由于 Apple 将来可能会推出其他 Retina 屏幕,例如每个像素都由 9 个 Retina 像素 (3x) 或其他什么组成,因此您真的不应该依赖于此。相反,现在有 API 调用将矩形转换为“支持对齐”,无论您运行的是 1x、2x 还是虚构的 3x,它都会为您执行此操作。

PS - 由于我费尽心思把这一切写下来,所以我把它放在我的网站上:http://orangejuiceliberationfront.com/are-your-rectangles-blurry-pale-and-have-rounded-corners/ 其中我将更新和修改此描述并添加更多图像。

Francis McGrew already gave the right answer, but since I did a presentation on this once, I thought I'd add some pictures.

The problem here is that coordinates in Quartz lie at the intersections between pixels. This is fine when filling a rectangle, because every pixel that lies inside the coordinates gets filled. But lines are technically (mathematically!) invisible. To draw them, Quartz has to actually draw a rectangle with the given line width. This rectangle is centered over the coordinates:

The rectangle you tell Quartz to draw when you specify integral coordinates

So when you ask Quartz to stroke a rectangle with integral coordinates, it has the problem that it can only draw whole pixels. But here you see that we have half pixels. So what it does is it averages the color. For a 50% black (the line color) and 50% white (the background) line, it simply draws each pixel in grey:

Half pixels averaged out when drawing between pixels

This is where your washed-out drawings come from. The fix is now obvious: Don't draw between pixels, and you achieve that by moving your points by half a pixel, so your coordinate is centered over the desired pixel:

Coordinates offset by 0.5 towards lower right

Now of course just offsetting may not be what you wanted. Because if you compare the filled variant to the stroked one, the stroke is one pixel larger towards the lower right. If you're e.g. clipping to the rectangle, this will cut off the lower right:

Comparison between offset stroked and non-offset filled rectangle

Since people usually expect the rectangle to stroke inside the specified rectangle, what you usually do is that you offset by 0.5 towards the center, so the lower right effectively moves up one pixel. Alternately, many drawing apps offset by 0.5 away from the center, to avoid overlap between the border and the fill (which can look odd when you're drawing with transparency).

Note that this only holds true for 1x screens. 2x Retina screens actually exhibit this problem differently, because each of the pixels below is actually drawn by 4 Retina pixels, which means they can actually draw the half-pixels. However, you still have the same problem if you want a sharp 0.5pt line. Also, since Apple may in the future introduce other Retina screens where e.g. every pixel is made up of 9 Retina pixels (3x), or whatever, you should really not rely on this. Instead, there are now API calls to convert rectangles to "backing aligned", which does this for you, no matter whether you're running 1x, 2x, or a fictitious 3x.

PS - Since I went to the hassle of writing this all up, I've put this up on my web site: http://orangejuiceliberationfront.com/are-your-rectangles-blurry-pale-and-have-rounded-corners/ where I'll update and revise this description and add more images.

苍风燃霜 2024-12-20 22:40:20

答案(隐藏)在 Apple 文档中:

为了避免在绘制单点宽的水平或垂直线时出现抗锯齿,如果该线的宽度为奇数个像素,您必须将该位置向整数位置的两侧偏移 0.5 个点

隐藏在 iOS 绘图和打印指南:iOS 绘图概念,尽管在​​当前标准 (OS X) ..

href="https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CocoaDrawingGuide/Introduction/Introduction.html" rel="noreferrer">Cocoa 绘图指南 调用 setDefaultLineWidth: 的效果 docs 还指出:

宽度 0 被解释为可以渲染的最细线特定设备。实际渲染的线条宽度可能与指定宽度相差最多 2 个设备像素,具体取决于线条相对于像素网格的位置和当前的抗锯齿设置。线的宽度还可能受到活动图形上下文的当前变换矩阵中指定的缩放因子的影响。"

The answer is (buried) in the Apple Docs:

"To avoid antialiasing when you draw a one-point-wide horizontal or vertical line, if the line is an odd number of pixels in width, you must offset the position by 0.5 points to either side of a whole-numbered position"

Hidden in Drawing and Printing Guide for iOS: iOS Drawing Concepts, though nothing that specific to be found in the current, standard (OS X) Cocoa Drawing Guide..

As for the effects of invoking setDefaultLineWidth: the docs also state that:

"A width of 0 is interpreted as the thinnest line that can be rendered on a particular device. The actual rendered line width may vary from the specified width by as much as 2 device pixels, depending on the position of the line with respect to the pixel grid and the current anti-aliasing settings. The width of the line may also be affected by scaling factors specified in the current transformation matrix of the active graphics context."

三人与歌 2024-12-20 22:40:20

我发现一些信息表明这是由抗锯齿引起的。暂时关闭抗锯齿很容易:

[[NSGraphicsContext currentContext] setShouldAntialias: NO];

这会产生清晰的 1 像素线。绘制完成后,只需再次打开它即可。

我尝试了 Francis McGrew 建议的解决方案,将 x 坐标偏移 0.5,但这对我的线条的外观没有任何影响。

编辑:
更具体地说,我单独更改了 x 和 y 坐标,并加上 0.5 的偏移量。

编辑2:
我肯定做错了什么,因为用 0.5 的偏移量更改坐标实际上确实有效。最终结果比关闭抗锯齿所获得的结果更好,因此我将 Francis MsGrew 的答案作为公认的答案。

I found some info suggesting that this is caused by anti aliasing. Turning anti aliasing off temporarily is easy:

[[NSGraphicsContext currentContext] setShouldAntialias: NO];

This gives a crisp, 1 pixel line. After drawing just switch it on again.

I tried the solution suggested by Francis McGrew by offsetting the x coordinate with 0.5, however that did not make any difference to the appearance of my line.

EDIT:
To be more specific, I changed x and y coordinates individually and together with an offset of 0.5.

EDIT 2:
I must have done something wrong, as changing the coordinates with an offset of 0.5 actually does work. The end result is better than the one obtained by switching off the anti aliasing so I'll make Francis MsGrew's answer the accepted answer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文