0

I have two transparent <canvas> elements, a regular 2d and a webgl and both are filled with the same semi-transparent color e.g. 0x00008080.

Now what finally is composited inside the browser window looks different for both, most likely because the webgl canvas uses premultiplied while the regular canvas straight alpha.

let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(0,0,127,0.5)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
document.body.appendChild(canvas);

canvas = document.createElement("canvas");
ctx = canvas.getContext("webgl");
ctx.clearColor(0, 0, 0.5, 0.5);
ctx.clear(ctx.COLOR_BUFFER_BIT);
document.body.appendChild(canvas);

What I need though is that the regular canvas is composited like the webgl canvas.

If I'd know the background color of the browser window upfront, I could paint the whole canvas with it and set the globalCompositeOperation to hard-light before drawing my actual content. e.g.

let canvas = document.createElement("canvas");
ctx = canvas.getContext("webgl");
ctx.clearColor(0, 0, 0.5, 0.5);
ctx.clear(ctx.COLOR_BUFFER_BIT);
document.body.appendChild(canvas);

canvas = document.createElement("canvas");
ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(255,255,255,1)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "hard-light"
ctx.fillStyle = "rgba(0,0,127,0.5)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
document.body.appendChild(canvas);

But as I said I'd need to know the background color and the canvas loses it's tranparency.

What else could I do?

2 Answers 2

0

You can enable premultipliedAlpha on the webgl context

A boolean value that indicates that the page compositor will assume the drawing buffer contains colors with pre-multiplied alpha.

let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(0,0,127,0.5)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
document.body.appendChild(canvas);

canvas = document.createElement("canvas");
ctx = canvas.getContext("webgl", { premultipliedAlpha: false });
ctx.clearColor(0, 0, 0.5, 0.5);
ctx.clear(ctx.COLOR_BUFFER_BIT);
document.body.appendChild(canvas);
canvas:first-of-type { border-right: 1px solid; }

Sign up to request clarification or add additional context in comments.

2 Comments

Unfortunately I need the regular canvas to look like the webgl, not the other way round.
Ahh shame, might want to clarify that in your OP. I'll remove my answer in a sec
0

There is no such option in the 2D context. For alpha higher than 0.3 or so, you can approximate it by premultiplying the color yourself:

let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
const b = 128 * 1 / 0.5; // c * 1/alpha
ctx.fillStyle = `rgba(0,0,${b},0.5)`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
document.body.appendChild(canvas);

canvas = document.createElement("canvas");
ctx = canvas.getContext("webgl");
ctx.clearColor(0, 0, 0.5, 0.5);
ctx.clear(ctx.COLOR_BUFFER_BIT);
document.body.appendChild(canvas);
body {
  background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
  background-size: 2em 2em;
}

But, I suppose because sRGB isn't linear, when using alpha values below 0.3 it's just wrong over non-white colors:

let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
const b = 128 * 1 / 0.15; // c * 1/alpha
ctx.fillStyle = `rgba(0,0,${b},0.15)`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
document.body.appendChild(canvas);

canvas = document.createElement("canvas");
ctx = canvas.getContext("webgl");
ctx.clearColor(0, 0, 0.5, 0.15);
ctx.clear(ctx.COLOR_BUFFER_BIT);
document.body.appendChild(canvas);
body {
  background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
  background-size: 2em 2em;
}

I tried to grasp my head around the sRGB gamma correction and transfer functions but I couldn't figure how to inverse it for these values. Maybe there is a way, I'm just not good at that.

But one workaround for you might be to use a webGL context as source on your 2D context directly. You can obviously use drawImage(), but if you need a fillStyle, you can also create a CanvasPattern from it. This will conserve the premultiplyAlpha settings of the webGL bitmap even on the 2D context. However, note that if you try to read the values there using getImageData(), you'll get the unpremultiplied ones back.

const [r,g,b,a] = [0.52, 0.02, 0.25, 0.19];

const canvasGL = document.createElement("canvas");
const ctxGL = canvasGL.getContext("webgl2");
ctxGL.clearColor(r,g,b,a);
ctxGL.clear(ctxGL.COLOR_BUFFER_BIT);
document.body.appendChild(canvasGL);

const canvas2D = document.createElement("canvas");
const ctx2D = canvas2D.getContext("2d");
document.body.appendChild(canvas2D);
// Use the webGL canvas as a fillStyle
ctx2D.fillStyle = ctx2D.createPattern(canvasGL, "repeat");
ctx2D.arc(150, 75, 50, 0, Math.PI*2);
ctx2D.fill();
// actual color
ctx2D.fillStyle = `rgba(${[r, g, b].map(c => c*255)},${a})`;
ctx2D.fillRect(0, 0, 50, 50);
// premultiplied
ctx2D.fillStyle = `rgba(${[r, g, b].map(c => (c*1/a)*255)},${a})`;
ctx2D.fillRect(55, 0, 50, 50);
// dark magic...
onclick = e => {
  ctx2D.putImageData(ctx2D.getImageData(0, 0, canvas2D.width, canvas2D.height), 0, 0);
}
body {
  background: repeating-conic-gradient(rgba(0,0,0,0.2) 0deg 25%, white 0deg 50%);
  background-size: 2em 2em;
}
<!--
  Top canvas is webGL,
  Below is 2D:
    Left square is basic rgba(color)
    Right square is premult
    Circle is pattern using webGL context
  Click to draw the canvas2D ImageData over itself.
 -->

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.