Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS linear-gradient and Canvas linear-gradient aren't the same with opacity settings

I want to achieve the same linear gradient look defined by CSS on a canvas. Used a method that works great until no transparency setting is used. When there are rgba color values defined with the same linear gradient color settings the results doesn't look the same, please see the following link:

JSFiddle: Example

var canvas = document.getElementById("myCanvas");
var ctx = document.getElementById("myCanvas").getContext("2d");
var w = canvas.width;
var h = canvas.height;
var cssAng = Math.PI;
var dir = getDir(cssAng, w, h);
var gr = ctx.createLinearGradient(dir.x0,dir.y0,dir.x1,dir.y1);
gr.addColorStop(0, "rgb(255, 255, 255, 0)");
gr.addColorStop(0.87, "rgb(0, 0, 0, 1)");
ctx.fillStyle = gr;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);


function getDir(radian, width, height) {
        radian += Math.PI;
        const HALF_WIDTH = width * 0.5;
    const HALF_HEIGHT = height * 0.5;
    const lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian));
    const HALF_LINE_LENGTH = lineLength / 2;

    const x0 = HALF_WIDTH + Math.sin(radian) * HALF_LINE_LENGTH;
    const y0 = HALF_HEIGHT - Math.cos(radian) * HALF_LINE_LENGTH;
    const x1 = width - x0;
    const y1 = height - y0;

return {x0, x1, y0, y1};
}
<!DOCTYPE html>
<html>
<body>
<div style='background-color:gray;display:inline-block;max-height:300px'>
  <div id="myDiv" style="display:inline-block;width:300px;height:300px;border:1px solid #d3d3d3;background:linear-gradient(180deg,rgba(255,255,255, 0) 0%, rgba(0,0,0,1) 87%"> </div>
</div>
<canvas id="myCanvas" width="300" height="300" style="background-color: gray;border:1px solid #d3d3d3;"> </canvas>
</body>
</html>

Any idea why is this happening? Is there a package that can handle this issue?

like image 215
M.Ervin Avatar asked Jul 20 '20 10:07

M.Ervin


People also ask

How do I change the opacity of a linear gradient in CSS?

To add transparency, we use the rgba() function to define the color stops. The last parameter in the rgba() function can be a value from 0 to 1, and it defines the transparency of the color: 0 indicates full transparency, 1 indicates full color (no transparency).

Which setting is used to add a gradient to a canvas in CSS?

To use the gradient, set the fillStyle or strokeStyle property to the gradient, then draw the shape (rectangle, text, or a line).

How do you change a linear gradient in CSS?

The linear-gradient() function sets a linear gradient as the background image. To create a linear gradient you must define at least two color stops. Color stops are the colors you want to render smooth transitions among. You can also set a starting point and a direction (or an angle) along with the gradient effect.

What are the three main types of gradients in CSS?

There are 3 different CSS gradients: linear, conic, and radial. Each gradient uses a CSS function to pass in multiple color arguments. The colors can be in the format of hex, hsla, rgba or named colors. In addition, you can pass in direction and angles to specify the shape and transition of the gradient.


1 Answers

There's actually a difference in the specs of the CSS linear-gradient and canvas linear gradient. They look almost exactly the same, except for the way the color needs to be calculated regarding the alpha value. For the CSS linear-gradient, you have this:

3.4.2. Coloring the Gradient Line At each color stop position, the gradient line is the color of the color stop. Before the first color stop, the gradient line is the color of the first color stop, and after the last color stop, the gradient line is the color of the last color stop. Between two color stops, the gradient line’s color is interpolated between the colors of the two-color stops, with the interpolation taking place in premultiplied RGBA space.

See: https://drafts.csswg.org/css-images-3/#coloring-gradient-line

Whereas the canvas one:

Once a gradient has been created (see below), stops are placed along it to define how the colors are distributed along the gradient. The color of the gradient at each stop is the color specified for that stop. Between each such stop, the colors and the alpha component must be linearly interpolated over the RGBA space without pre multiplying the alpha value to find the color to use at that offset. Before the first stop, the color must be the color of the first stop.

See: https://html.spec.whatwg.org/multipage/canvas.html#interpolation

So the CSS version calculates the color stops by premultiplying their alpha values. I've changed your example to make it a bit more obvious. In the example below, the CSS version goes from rgba(255, 0, 0, 0) or rgba(0, 0, 0, 0) to rgba(0, 0, 0, 1). So at 50% the color calculated using premultiplied alpha is rgba(0, 0, 0, 0.5).

In the canvas version, the interpolation is calculated without pre multiplying. So at 50% you have rgba(127,5, 0, 0, 0.5). This is true for every point of the gradient-line.

See What does premultiplied means: https://drafts.csswg.org/css-images-3/#premultiplied

And the example:

var canvas = document.getElementById("myCanvas");
var ctx = document.getElementById("myCanvas").getContext("2d");
var w = canvas.width;
var h = canvas.height;
var cssAng = Math.PI;
var dir = getDir(cssAng, w, h);
var gr = ctx.createLinearGradient(dir.x0,dir.y0,dir.x1,dir.y1);
gr.addColorStop(0, "rgb(255, 0, 0, 0)");
gr.addColorStop(1, "rgb(0, 0, 0, 1)");
ctx.fillStyle = gr;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);


function getDir(radian, width, height) {
        radian += Math.PI;
        const HALF_WIDTH = width * 0.5;
    const HALF_HEIGHT = height * 0.5;
    const lineLength = Math.abs(width * Math.sin(radian)) + Math.abs(height * Math.cos(radian));
    const HALF_LINE_LENGTH = lineLength / 2;

    const x0 = HALF_WIDTH + Math.sin(radian) * HALF_LINE_LENGTH;
    const y0 = HALF_HEIGHT - Math.cos(radian) * HALF_LINE_LENGTH;
    const x1 = width - x0;
    const y1 = height - y0;

return {x0, x1, y0, y1};
}
<!DOCTYPE html>
<html>
<body>
<div style='display:inline-block;max-height:300px'>
  <div id="myDiv" style="display:inline-block;width:200px;height:300px;border:1px ;background:linear-gradient(180deg,rgba(255,0,0, 0) 0%, rgba(0,0,0,1) 100%"> </div>
   
</div>
<canvas id="myCanvas" width="200" height="300" > </canvas>
<div style="position: absolute;width:8px;height:8px;background:rgba(0,0,0,0.5); top: 144px; left: 0px; ">

</div>
<div style="position: absolute;width:10px;height:10px;background:rgba(127.5,0,0,0.5); top: 144px; left: 412px; ">

</div>
</body>
</html>

I don't think there's a way to make the 2 equivalent, except by calculating every point of the gradient line.

like image 124
Julien Grégoire Avatar answered Sep 30 '22 13:09

Julien Grégoire