有没有办法禁用 html 画布中的颜色混合/重叠

Gia*_*ley 1 html javascript canvas

我有一个画布,当我绘制两个低不透明度重叠的东西时,它们重叠的部分的不透明度会变高。即使两件事重叠,有没有一种方法可以使其具有相同的不透明度。

const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

ctx.fillStyle = "rgba(0, 0, 255, 0.2)";

ctx.fillRect(10, 10, 50, 50);
ctx.fillRect(20, 20, 50, 60);
ctx.fillRect(40, 5, 50, 40)
Run Code Online (Sandbox Code Playgroud)
canvas {
  width: 100vw;
  height: 100vh;
}
Run Code Online (Sandbox Code Playgroud)
<p>all of it should be the same color but the overlapping parts are darker</p>
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)

Kai*_*ido 5

对于简单的情况,通常的技巧是使用第二个画布作为图层:在独立的画布上绘制不应与完全不透明混合的部分,然后在可见画布上使用预期的 alpha 绘制此画布:

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

const detached = canvas.cloneNode();
const ctx2 = detached.getContext("2d");

// draw at full opacity
ctx2.fillStyle = "rgb(0, 0, 255)";

ctx2.fillRect(10, 10, 50, 50);
ctx2.fillRect(20, 20, 50, 60);
ctx2.fillRect(40, 5, 50, 40)

// draw something in the background of the visible canvas
// where we want the blue rects to mix with
ctx.fillStyle = "green";
ctx.fillRect(50, 65, 30, 30);

// now draw the blue rects in a single pass with the expected alpha
ctx.globalAlpha = 0.2
ctx.drawImage(detached, 0, 0);
Run Code Online (Sandbox Code Playgroud)
/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
    background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
    background-size: 2em 2em;
}
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)

请注意,这个确切的示例可以使用单个画布完成:如果所有形状确实共享相同的颜色,则可以使它们全部成为同一子路径的一部分,并在一次调用中将它们全部填充。

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

ctx.fillStyle = "green";
ctx.fillRect(50, 65, 30, 30);

ctx.fillStyle = "rgb(0, 0, 255, 0.2)";
ctx.beginPath();
ctx.rect(10, 10, 50, 50);
ctx.rect(20, 20, 50, 60);
ctx.rect(40, 5, 50, 40);
ctx.fill();
Run Code Online (Sandbox Code Playgroud)
/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
    background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
    background-size: 2em 2em;
}
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)

现在,只有当所有要绘制的形状的透明度都相同时,这些才起作用。如果多个形状具有不同的 alpha 值,它们仍然会混合。

下面是这种情况的一个例子。

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

ctx.fillStyle = "green";
ctx.fillRect(50, 10, 50, 60);

ctx.fillStyle = "rgba(0, 0, 255, .2)";
ctx.fillRect(40, 50, 70, 60);

ctx.fillStyle = "rgba(0, 0, 255, .8)";
ctx.fillRect(10, 20, 60, 70);
Run Code Online (Sandbox Code Playgroud)
/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
    background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
    background-size: 2em 2em;
}
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)

如果我们希望第一个蓝色矩形位于第二个矩形之上而不混合,但仍与绿色矩形混合,我们需要使用第一个解决方案的变体,但使用更多步骤:

  • 首先,计算较透明的矩形相对于较不透明的矩形的 alpha 值。这里有 0.2 和 0.8,因此如果我们希望 0.8 为 1,则 0.2 必须变为 0.25。
  • 我们在 0.25 处绘制更透明的矩形,然后在 1 处绘制更不透明的矩形。
  • 我们使用目标 0.8 alpha 值重新绘制构图。
  • 我们将其绘制在绿色背景上。

不过,我将利用这个新片段的机会来展示,通过对该globalCompositeOperation属性的一些创造性使用,我们可以在一个画布上完成所有这些。

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

// To use a single canvas, we will draw the green "background" later

// target color is rgb(0, 0, 255, 0.2) & max alpha is 0.8
// 0.2 x (1 / 0.8) -> 0.25
ctx.fillStyle = "rgb(0, 0, 255, 0.25)";
ctx.fillRect(40, 50, 70, 60);
ctx.fillStyle = "rgba(0, 0, 255, 1)";
ctx.fillRect(10, 20, 60, 70);
ctx.globalAlpha = 0.8;
// gCO "copy" will clear whatever was on the context before the next paint
ctx.globalCompositeOperation = "copy"; 
ctx.drawImage(ctx.canvas, 0, 0);
// we could continue over like this if more such opacities were required

ctx.globalAlpha = 1;

// gCO "destination-over" will draw behind what's already painted on the context
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = "green";
ctx.fillRect(50, 10, 50, 60);
Run Code Online (Sandbox Code Playgroud)
/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
    background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
    background-size: 2em 2em;
}
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)


现在,有些人可能会对此感兴趣,但是在 WHATWG/html 上,我们开始了关于为 canvas2D 启用图层 API 的讨论,虽然还远未达成共识,但我编写了一个相当小的CanvasLayer 接口原型,它将允许我们在画布中使用图层。

这个想法是创建一个CanvasLayer对象,该对象记录绘图操作,当使用上下文的当前设置在上下文中渲染该对象时,将在该对象上执行这些操作。基本上再现了分离的画布技巧,但自动处理分离的画布的大小,并且(如果本地实现),而不分配完整的位图缓冲区。

在我非常有偏见的观点中(我是该提案和原型的作者),当我们必须处理画布上的多个层时,这将允许更清晰的代码。

/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
    background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
    background-size: 2em 2em;
}
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
<script type="module">
  import CanvasLayer from "https://cdn.jsdelivr.net/gh/Kaiido/CanvasLayer/bundles/CanvasLayer.min.mjs";

  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  ctx.fillStyle = "green";
  ctx.fillRect(50, 10, 50, 60);

  const backLayer = new CanvasLayer();
  backLayer.fillStyle = "rgba(0, 0, 255)"; // fully opaque
  backLayer.fillRect(40, 50, 70, 60);
  
  const frontLayer = new CanvasLayer();
  frontLayer.fillStyle = "rgba(0, 0, 255)";
  frontLayer.fillRect(10, 20, 60, 70);
  frontLayer.globalAlpha = 0.2;
  frontLayer.renderLayer(backLayer);

  ctx.globalAlpha = 0.8;
  ctx.renderLayer(frontLayer);
</script>
Run Code Online (Sandbox Code Playgroud)