【问题标题】:Drawing 1-pixel thick, aliased lines in real-time实时绘制 1 像素粗的锯齿线
【发布时间】:2010-12-28 21:21:50
【问题描述】:

简要背景:我正在开发一个基于 Web 的绘图应用程序,我正在实施的工具之一是 1 像素厚的铅笔。该工具允许用户在画布上绘制 1px 的别名线。

为了确定用户在画布上绘制的位置,会监视鼠标坐标。如果 mouse1 被按住,光标所在的像素将会改变。本质上它就像 Photoshop 中的铅笔工具一样工作。

注意:Bresenham 的算法不适用于这种情况。我的输入是实时提交的,所以我没有画一条从 P0 到 P1 的线,因为 P0 和 P1 之间的距离很多像素。一般来说,P1 是 P0 的邻居。

我遇到的问题是我得到的线条没有完全干净的 1px 粗细。这是一个例子:

请注意,两条线都是手绘的,因此存在一些差异。有趣的是,Photoshop 能够对我绘制的线条进行更清晰的 1px 表示。我的线看起来更脏的原因是这样的:

在我的应用程序中使用该工具绘图时,红色像素被填充。在 Photoshop 中,红色像素未被填充。这是有道理的,因为为了从给定像素移动到它的东南部邻居,无论是东邻还是南邻,都可能会被忽略。光标将恰好越过角落进入东南邻域的可能性非常小,从而避免绘制红色像素,但这通常不会发生。

所以,我剩下的问题是 Photoshop 如何能够跳过出现在我的线条中的红色像素。我唯一能想到的就是等到两个像素排队后再绘制它们中的任何一个,这样我就会知道是否传递了“角落邻居”。在这种情况下,我不会绘制两个像素中的第一个,因为它相当于我图中的一个红色像素。如果用户绘制一个像素,将光标向南移动一个像素,然后向东移动一个像素,这将存在未绘制预期像素的风险。两个像素都应该被绘制,但算法会说不然。

有什么想法吗? Photoshop 会如何处理这个问题?

【问题讨论】:

  • 快速移动鼠标会发生什么?它会跳过像素吗?
  • @Hans Passant 是的,当快速绘制时,我得到的线条类似于 Photoshop 生成的线条。奇怪的是,您不必在 Photoshop 中快速绘制即可跳过那些不需要的像素。
  • 它将鼠标点存储在折线中。当它可以简单地扩展前一个段时,它不会向其中添加新段。
  • @Hans Passant 我不确定我是否完全遵循。如果我画一条穿过 (50,50)、(50,51) 和 (51,51) 的线,理想的结果将是跳过 (50,51),因为我正在向下移动到 (51,51)。但是折线中的点会说这条线应该穿过 (50,51)...?

标签: algorithm graphics drawing paint


【解决方案1】:

这两行之间的区别基本上是 Photoshop 在绘制像素 n 之后重新考虑绘制的 n-1 像素,如果它使用以下掩码之一给出正值,则将其擦除:

 x 1 x       x 1 x
 1 o x   or  x o 1
 x x x       x x x

 x x x       x x x
 1 o x   or  x o 1
 x 1 x       x 1 x

x=别介意
o= n-1 像素
1=绘制像素n后标记的像素

或者写成逻辑:

假设像素 n-1 在 a[i,j] 处,所以,标记像素 n 后,检查:

If ( (a[i-1,j  ]==1 && a[i  ,j-1]==1) ||
     (a[i-1,j  ]==1 && a[i  ,j+1]==1) ||
     (a[i  ,j-1]==1 && a[i+1,j  ]==1) ||
     (a[i  ,j+1]==1 && a[i+1,j  ]==1))
Then
     Unmark a[i,j];

你可能希望你的画线延迟一个像素,只是为了不显示你的“消失”像素(尽管我怀疑它在那个比例下会很明显)。


.

【讨论】:

  • 感谢您再次帮助解决我的一个问题。我运行它的环境非常有利于抢先解决方案而不是反应性解决方案(“删除”像素是一个涉及的过程,尤其是当您考虑绘制已经存在的东西时)。这里的问题是决定是否应该真的删除一个像素。如果用户打算在您的图表中绘制“L”形怎么办?该算法会盲目地删除它。我很难相信 Adob​​e 的实现是极其复杂的预测和轨迹跟踪......
  • @Xenethyl 如果用户对 Photoshop 中的角落很挑剔,他可能会使用其他工具,而不是手绘铅笔。但我同意 PShop 可能有更好的算法。至于画出预先存在的东西,我以为你有一个“撤消”命令,所以取消标记!=删除。
  • 同意用户偏好会涉及到这一点。不幸的是,这个问题是我的任何其他绘图/绘画工具(即具有不同笔尖的铅笔、画笔等)的绘图算法的核心。用户绘制的每条线都被视为 1px 线,如果正在使用提示(例如星形),则它只是在每个像素坐标处标记。撤消尚未实现,但该过程将类似地涉及。不幸的是,更改像素数据需要在这种环境中进行复制、读取、更改和写入。
  • @Xenethyl:在你仔细绘制 L 形的情况下,Photoshop 实际上做了什么?我怀疑您需要多次单击才能执行此操作(即,您将无法在 Photoshop 中从头到尾按住鼠标按钮来绘制 L),这意味着 Photoshop 正在处理鼠标的每一段时间按钮作为一个单独的实体被按下——你的程序也可以这样做。
  • @j_random_hacker 实际上,在 Photoshop 中绘制一个 3 像素的 L 形不是问题——它可以正常工作。
【解决方案2】:

原文:看看 Bresenham 的线算法。它可以很容易地扩展平滑等。

编辑:关于问题的发展和讨论(尤其是下面的cmets),我想提炼一些有趣的点:Photoshop的铅笔工具也确实画出了与“东”非常相似的线条”和“东南”像素,如果鼠标移动相对较慢,从而为所有这些像素提供许多样本。一旦鼠标移动得更快,轨迹就不会为所有直接相邻的像素提供样本。结果将是所需的 1 像素粗线。总之,我们注意到:Photoshop 的铅笔工具背后没有“魔法”;它似乎只是扫描了更少的样本。有关详细信息,请参阅下面的讨论/cmets。

【讨论】:

  • 我同意;比如在wikipedia上查一下Xiaolin Wu的线算法。
  • @WebMonster 吴晓琳的线条算法是用于绘制抗锯齿线条。
  • @Xenethyl 我刚刚看了我们正在谈论的 Photoshop 的铅笔工具。如果我移动鼠标的速度相对较慢并因此为所有这些像素提供样本,它也会用“东”“东南”像素绘制与您自己的解决方案相比外观相同的线条。只要我更快地移动鼠标,因此不会为所有直接相邻的像素提供样本,结果确实与您想要的 Photoshop 线相似。你也观察到了吗?也许你的程序太快了,扫描了太多的样本...... ^^
  • @Flinsch 我想你可能刚刚帮助我意识到了我当前实施的一个问题。现在,只要光标移动到新像素,我就会触发我的绘制事件。我并没有真正考虑这么多,因为我认为它会更有效率(如果用户不移动鼠标,就没有什么新东西可以绘制了)。如果我切换到轮询而不是事件处理,那么我就有能力提供随着时间的推移建立油漆的画笔(例如喷枪)。明天我将对此进行试验,但我认为轮询会很好地工作。我只需要找出正确的间隔。
  • @Flinsch 我刚刚完成将我的代码移动到 10 毫秒轮询而不是鼠标移动事件处理。不仅性能更好(浏览器不会丢弃任何鼠标移动事件),而且我的线条质量更高(与 Photoshop 相媲美)。我从来没有想过我最初的方法准确到我的线条质量下降的程度。油漆堆积也是这种变化的免费赠品,我对此感到非常高兴。如果您可以编辑您的答案以记录下面的讨论和/或投票,我会将其标记为已接受。非常感谢!
【解决方案3】:

我猜你终于解决了你的问题(6 年过去了......),但我只是想说,在这种特殊情况下,你的应用程序似乎比 Photoshop 运行得更好。虽然我想我知道你的目标是什么(避免那些像素聚类),但你的应用程序可以更好地实现“恒定厚度”(尽管有那些不需要的分组),而 Photoshop 制作了一个可能更聪明的“香肠绳”图案,但没有如此宽度恒定,因此不是那么“真实”。这可能是由于对十进制值(舍入)的不同处理以及选择要填充或跳过的像素。但是,这又是一个老话题了。

【讨论】:

    猜你喜欢
    • 2015-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多