【问题标题】:Draw stroke on HTML canvas with different levels of opacity在具有不同不透明度级别的 HTML 画布上绘制描边
【发布时间】:2014-01-22 10:45:23
【问题描述】:

问题

我正在尝试创建一个带有不透明度抖动的画笔工具(就像在 Photoshop 中一样)。具体问题是:

在具有不同不透明度的 HTML 画布上绘制笔触。不透明度较高的像素应替换不透明度较低的像素;否则,像素保持不变。

透明度不应在此过程中丢失。笔画在单独的画布上绘制,然后与背景画布合并。

结果应该类似于 this。所有代码和相应的输出都可以在 here (JSFiddle) 中找到。

因为你不能用不同的不透明度来描画一条路径(如果我错了,请纠正我)我的代码为每个段创建了一个具有不同不透明度的路径。

非解决方案 1,使用“变暗”混合模式

变暗混合模式在使用不透明像素时会产生所需的结果,但似乎不适用于透明度。失去透明度会破坏交易。

使用不透明像素:

带有透明像素:

非解决方案 2,使用 'destination-out' 合成运算符

在绘制新的笔画段之前,使用“destination-out”合成运算符从下面的像素中减去其不透明度。然后使用“source-over”添加新的笔画段。这几乎可以工作,但有点不对劲。

寻找解决方案

我想避免手动操作每个像素(我过去做过)。我错过了一些明显的东西吗?这个问题有简单的解决方案吗?

"Links to jsfiddle.net must be accompanied by code."

【问题讨论】:

    标签: html canvas blending compositing


    【解决方案1】:

    因为你不能用不同程度的不透明度描画一条路径(如果我错了,请纠正我)

    你错了=)

    当您使用globalCompositeOperation = 'destination-out'(您在lineDestinationOut)时,您需要将strokeStyle 不透明度设置为1 以删除所有内容。

    但是,由于路径构建的顺序,简单地在小提琴中更改它不会产生所需的效果。先建 10% 透明的,全长,然后删除并绘制两个 40% 透明的位。

    Here's a jsfiddle of the code below

    var canvas = document.getElementById('canvas');
    var cx = canvas.getContext('2d');
    cx.lineCap = 'round';
    cx.lineJoin = 'round';
    cx.lineWidth = 40;
    
    // Create the first line, 10% transparency, the whole length of the shape.
    cx.strokeStyle = 'rgba(0,0,255,0.1)';
    cx.beginPath();
    cx.moveTo(20,20);
    cx.lineTo(260,20);
    cx.lineTo(220,60);
    cx.stroke();
    cx.closePath();
    
    // Create the first part of the second line, first by clearing the first
    // line, then 40% transparency.
    cx.strokeStyle = 'black';
    cx.globalCompositeOperation = 'destination-out';
    cx.beginPath();
    cx.moveTo(20,20);
    cx.lineTo(100,20);
    cx.stroke();
    cx.strokeStyle = 'rgba(0,0,255,0.4)';
    cx.globalCompositeOperation = 'source-over';
    cx.stroke();
    cx.closePath();
    
    // Create the second part of the second line, same as above.
    cx.strokeStyle = 'black';
    cx.globalCompositeOperation = 'destination-out';
    cx.beginPath();
    cx.moveTo(180,20);
    cx.lineTo(260,20);
    cx.stroke();
    cx.strokeStyle = 'rgba(0,0,255,0.4)';
    cx.globalCompositeOperation = 'source-over';
    cx.stroke();
    cx.closePath();
    

    【讨论】:

    • +1 用于创建所需的效果。创建一个更广泛有用的工具会很有趣,它不需要为每组线条+alphas进行自定义编码。
    • 谢谢 =) 你的意思是简化上面的方法,还是查询画布的像素,确定它们是否更透明/更不透明,分别在上方或下方绘制?我考虑了后者并找到了this answer,但是当已经绘制了多个路径/形状时,它会变得非常复杂。
    • 我在想这样的事情,所以所有较高的 alpha 像素都将替换较低的 alpha 像素,而不是与它们混合:(1) 将每行+alpha 放入一个数组中。 (2) 按 alpha 升序对数组进行排序。 (3) 将您的 draw/erase/draw 方法应用于数组中的每个连续元素以构建最终结果。
    • 那行得通!如果在你的方法之前已经画了一些东西,那会变得非常复杂,你需要考虑到这一点——这种问题让我的大脑停止运转,因为复杂性增加得如此之快!
    • @markE 谢谢,亨利。谢谢,马克E。虽然使用完全不透明的目的地输出存在问题(我已经意识到):它留下了一个小阴影。造成这种情况的原因可能是笔划的抗锯齿边缘。这些像素被destination-out完全擦除,留下一个间隙。看看thisJSFiddle。您可以尝试通过添加/减去校正值来进行补偿,但结果永远不会完美。但它还没有被搁置……这条评论达到了它的字符数限制,所以我将在另一条评论中继续。
    【解决方案2】:

    使用两个图层绘制到:

    • 首先计算顶层不透明度 40% - 10% 并将其设置为顶层的 alpha
    • 将底层设置为 10%
    • 用虚线设置顶层 (lineDash)(根据尺寸要求计算虚线图案尺寸)
    • 在两层上画线,底层将是一条长线,顶层在描边时会在顶部画一条虚线。
    • 完成后将两个图层复制到主画布。

    【讨论】:

    • 谢谢。有一个警告:我的例子是相当人为的。我应该指出,真正的笔画具有更多的不透明度和不同长度的片段。我的错。
    【解决方案3】:

    @HenryBlyth 的答案可能是你能得到的最好的答案;没有原生 API 可以执行您被要求执行的操作(在我看来,这有点奇怪......不透明度并不是真的应该替换像素)。

    在一段中阐明解决方案:将您的“笔画”拆分为具有不同不透明度的单独路径。像往常一样绘制最低不透明度的路径。然后,使用“destination-out”绘制较高的不透明度以删除重叠的低不透明度路径。然后,像往常一样使用“source-over”绘制高不透明度路径,以创建所需的效果。

    正如 cmets 对该答案的建议,@markE 关于使每个路径成为在绘制之前预先排序的对象的评论是一个很好的建议。由于您想要执行本机 API 无法执行的手动绘制逻辑,因此将每条路径转换为对象并以这种方式处理它们将比手动操作每个像素容易得多(尽管该解决方案可行,但它也可以驱使您疯了。)

    您提到每个笔划都是在另一个画布上完成的,这很好,因为您可以记录在绘制该线时触发的鼠标事件,创建一个对象来表示该路径,然后使用该对象和其他对象您的“合并”画布,而不必担心像素操作或其他任何事情。如果可能,我强烈建议切换到@markE 建议的面向对象的方法。

    【讨论】:

    • 感谢您的全面回复。使用destination-out 有一个问题,我已经在上面的评论中解释过,但我仍然会追求Henry Blyth 和markE 建议的解决方案。更多的测试将不得不证明这是一个可以容忍的烦恼或破坏交易。操作每个像素在编码方面并不是什么大问题,但就性能而言,这是一场噩梦。所以我想不惜一切代价避免它。是的,不用担心,我会尽可能坚持使用面向对象的方法。 ;)
    猜你喜欢
    • 2012-05-07
    • 2016-05-12
    • 1970-01-01
    • 2020-05-22
    • 2015-03-28
    • 2019-04-16
    • 1970-01-01
    • 2015-10-20
    相关资源
    最近更新 更多