JavaScript canvas make transparent image darker - javascript

I have an image I am drawing with ctx.drawImage, and it has transparency. I want to darken the image so I am using a fillRect with rgba(0,0,0,0.5) but this darkens the transparent parts of the image too. So I am looking into using ctx.globalCompositeOperation = 'destination atop' including using ctx.save and restore, but this now makes the entire canvas background white and only shows the background through the image/fillrect.
Before ctx.globalCompositeOperation = 'destination atop' or 'source-in':
After:
Here is my code:
/*above is drawing the background, I only want to merge the fillRect with the drawImage*/
ctx.save();
ctx.drawImage(o.image, x, y);
ctx.fillStyle = 'rgba(0,0,0,'+amount+')';
ctx.globalCompositeOperation = 'source-in';
ctx.fillRect(x, y, w, h);
ctx.globalCompositeOperation = 'source-over';
ctx.restore();

Not sure what you are after so just guessing.
Draw transparent image
ctx.globalCompositeOperation = "source-over"; // in case not set
ctx.drawImage(image,0,0);
Use "multiply" to darken image
ctx.globalCompositeOperation = "multiply"
ctx.fillStyle = "rgb(128,128,128)"; // dest pixels will darken by
// (128/255) * dest
ctx.fillRect(0,0,image.width,image.height)
Then to restore the alpha
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(image,0,0);
Then restore default comp state
ctx.globalCompositeOperation = "source-over";
An example of darkening and image. Image on left is darkened via method above. Right image is the original none darkened image. background colour is the colour under the canvas.
const ctx = canvas.getContext("2d");
// create an image to darken
const image = document.createElement("canvas");
image.width = 150;
const ctx1 = image.getContext("2d");
ctx1.beginPath()
ctx1.fillStyle = "#0F0";
ctx1.strokeStyle = "#FA0";
ctx1.lineWidth = 20;
ctx1.arc(75,75,50,0,Math.PI*2);
ctx1.fill();
ctx1.stroke();
ctx.globalCompositeOperation = "source-over"; // in case not set
ctx.drawImage(image,0,10);
ctx.globalCompositeOperation = "multiply"
ctx.fillStyle = "rgb(128,128,128)"; // dest pixels will darken by
// (128/255) * dest
ctx.fillRect(0,10,image.width,image.height)
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(image,0,10);
ctx.globalCompositeOperation = "source-over";
ctx.font = "16px arial";
ctx.fillStyle = "black";
ctx.fillText("Darkened image",10,14);
ctx.drawImage(image,150,10);
ctx.fillText("Original image",160,14);
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>
If you already have pixel content on the canvas you will need to use an offscreen canvas to darken the image and then use that image to draw to the canvas.
// create an image to hold darkened copy of image
const dImage - document.createElement("canvas");
dImage.width = image.width;
dImage.height - image.height;
const dctx = dImage.getContext("2d");
dctx.drawImage(image,0,0);
// apply darkening to dctx as shown in answer above.
ctx.drawImage(dImage,0,0); // draws darkened image on canvas

Related

Intersected semi-transparent stroke text

I have an issue with the painting context.stokeText that style contains an alpha. A big value of line width makes some effect of intersected strokes, as a result, the color is darker.
How I can avoid this?
ctx.strokeStyle ="rgba(0,0,0,0.3)";
ctx.lineWidth = 15;
ctx.lineJoin="round";
ctx.strokeText(text, x, y);
Image
That's a bit of an inconsistency in the specs since usually overlapping sub-pathes are painted only once.
However strokeText() does create one shape per glyph, and thus this method will indeed paint each glyphs on their own, creating this visible overlapping.
To overcome this, you'll to be a bit creative:
first draw your text fully opaque,
then redraw the produced pixels with the desired alpha level (many ways to do so).
draw that on your scene (or draw the background behind).
Here are a few ways (there are many others):
Probably the easiest, but which costs more memory: use a second disconnected canvas:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
// create a new canvas just for the text
const canvas2 = canvas.cloneNode();
const ctx2 = canvas2.getContext("2d");
ctx2.font = "60px sans-serif";
const text = "MY TEXT";
const x = canvas.width - ctx2.measureText(text).width - 20;
const y = canvas.height - 20;
// draw it fully opaque
ctx2.lineWidth = 15;
ctx2.lineJoin="round";
ctx2.strokeText(text, x, y);
// draw the background on the visible canvas
ctx.fillStyle = "#ffe97f";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// now draw our text canvas onto the visible one
// with the desired opacity
ctx.globalAlpha = 0.3;
ctx.drawImage(canvas2, 0, 0);
<canvas width="465" height="234"></canvas>
More memory friendly, but which requires you to rewrite your drawing logic in a different direction, use compositing:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.font = "60px sans-serif";
const text = "MY TEXT";
const x = canvas.width - ctx.measureText(text).width - 20;
const y = canvas.height - 20;
// first draw the text fully opaque
ctx.lineWidth = 15;
ctx.lineJoin="round";
ctx.strokeText(text, x, y);
// now apply the opacity
ctx.fillStyle ="rgba(0,0,0,0.3)";
ctx.globalCompositeOperation = "source-in";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// and the background
ctx.fillStyle = "#ffe97f";
ctx.globalCompositeOperation = "destination-over";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// if you want to keep drawing "normaly"
ctx.globalCompositeOperation = "source-over";
<canvas width="465" height="234"></canvas>
A mix of both, with different compositing rules:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.font = "60px sans-serif";
const text = "MY TEXT";
const x = canvas.width - ctx.measureText(text).width - 20;
const y = canvas.height - 20;
// first draw the text fully opaque
ctx.lineWidth = 15;
ctx.lineJoin="round";
ctx.strokeText(text, x, y);
// now redraw over itself with the desired opacity
ctx.globalAlpha = 0.3;
ctx.globalCompositeOperation = "copy";
ctx.drawImage(canvas, 0, 0);
ctx.globalAlpha = 1;
// and the background
ctx.fillStyle = "#ffe97f";
ctx.globalCompositeOperation = "destination-over";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// if you want to keep drawing "normaly"
ctx.globalCompositeOperation = "source-over";
<canvas width="465" height="234"></canvas>

How to have multiple globalCompositeOperation

My canvas is 512x256 pixels
Here is what I am trying to achieve :
Print a local image (image 1) on the canvas.
Fill this image with a specific color.
Print another local image(image 2) that should not overlap image 1.
So what I did is :
.drawImage(image1)
Create a rectangle of 512x256 ( ctx.rect(0, 0, 512, 256); )
ctx.globalCompositeOperation = "destination-in";
draw this rectangle ( ctx.fill() ) (with destination-in it should have for effect to fill the first image with the color of the rectangle)
ctx.globalCompositeOperation = "destination-out";
.drawImage(image2) (with destination-out it should make this image under the image 1)
But it doesn't display anything.
I figured it was because we can't have different globalCompositeOperation... But I'm sure it's possible somehow, I found people talking about it and fixing the issue but they're using specific code for their specific task and I simply don't understand. I would love an example for my script :
<script>
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var img1 = new Image();
img1.onload = function () {
ctx.drawImage(img1, 0, 0);
};
ctx.globalCompositeOperation = "destination-in";
ctx.rect(0, 0, 512, 256);
ctx.fillStyle = "green";
ctx.fill()
ctx.globalCompositeOperation = "destination-out";
img2.onload = function () {
ctx.drawImage(img2, 0, 0);
};
img1.src = "C:/Users/... file1.png" // replaced the path for this example
img2.src = "C:/Users/... file2.png";
</script>
And here is my html body :
<body>
<canvas id="canvas" width="512" height="256"></canvas>
</body>

How to Style Images in a canvas?

I'm using node-canvas and I was wonder how style an imported image in canvas similar to how you would an image in CSS.
For example, how would I crop a square image in canvas to a circle. In CSS, all you need to do is set border radius to 50%.
Well obviously you cannot use CSS in this case since CSS is applied to the DOM and not the the pixel based content of a Canvas element.
However the Canvas element has its own set of draw functions which allow to you replicate or at least approximate CSS rules.
Since you mentioned cropping an image to a circle I'll focus on this example. To achieve this effect you want to specify a clipping region before drawing the image. Every pixel outside of the clipped region will not be drawn. Effectively this will crop the image to the clipped region.
In code:
// Retrieve canvas and get context
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// Save the context so we can undo the clipping region at a later time
context.save();
// Define the clipping region as an 360 degrees arc at point x and y
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
// Clip!
context.clip();
// Draw the image at imageX, imageY.
context.drawImage(image, imageX, imageY);
// Restore context to undo the clipping
context.restore();
I'd advice taking a look at this page to give you an idea of what you can do with the Canvas element and the 2D rendering context.
I don't know if this would work in node, However you can do this with canvas;
The simplest way of doing it is using, as you intended, border-radius:
canvas{border-radius:50%;}
An other way of doing it is by using the ctx.clip() method.
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(125,120,100,0,2*Math.PI);
// you clip the context
ctx.clip();
let img = document.querySelector("#testImg");
ctx.drawImage(img, 0, 20);
<canvas width="250" height="240" >
<img id="testImg" src="theImage.jpg">
</canvas>
Yet an other way of doing it is by using ctx.globalCompositeOperation = "destination-atop"in this way:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 400,
cx = cw / 2;
let ch = canvas.height = 400,
cy = ch / 2;
ctx.globalCompositeOperation = "destination-atop";
let img = document.querySelector("#testImg");
ctx.drawImage(img, 0, 0);
ctx.beginPath();
ctx.fillStyle = "#f00";
ctx.arc(cx, cx, 100, 0, 2 * Math.PI);
ctx.fill();

HTML5 Canvas - Hide text parts when it's out of my rectangle

My problem is simple. I just want to make invisible text parts when it's out of my rectangle. This image will help to understand what I want. How can I make invisible gray parts of text at canvas? Thanks for help!
After drawing your rectangle, invoke
ctx.clip();
Then draw the parts that should be only inside it.
Source
I would use ctx.globalCompositeOperation = "source-atop"
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 400;
let ch = canvas.height = 110;
ctx.fillStyle = "#ccc";
ctx.beginPath();
ctx.fillRect(100,25,200,60);
ctx.globalCompositeOperation = "source-atop"
ctx.font="2em Verdana";
ctx.fillStyle = "#f00";
ctx.fillText("This is some text",110,65);
canvas{border:1px solid;}
<canvas id="canvas"></canvas>

Change color Image in Canvas

I want to change color a Image in canvas
this is the Image
You can see there is a Image transparent I was try using PutImgData but my transparent is changing color
Is there anyway to change color the car and money only ?
I was using this code :
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
image = document.getElementById("testImage");
canvas.height = canvas.width = 100;
ctx.fillStyle = 'red';
ctx.fillRect(10,10,20,10);
ctx.drawImage(image,0,0);
var imgd = ctx.getImageData(0, 0, 45, 45),
pix = imgd.data;
for (var i = 0, n = pix.length; i <n; i += 4) {
if(pix[i+3]==0)
{continue;}
pix.length[i]=r|pix.length[i];
pix.length[i+1]=g|pix.length[i+1];
pix.length[i+2]=b|pix.length[i+2];
pix[i + 3] = 255;
}
ctx.putImageData(imgd, 0, 0);
To mix manually you would have to apply a different formula to mix foreground (new color) and background (image) to preserve anti-aliased pixels (and just in case: the image included in the question is not actually transparent, but I guess you just tried to illustrate transparency using the solid checkerboard background?).
I would suggest a different approach which is CORS safe and much faster (and simpler) -
There are a couple of ways to do this: one is to draw in the color you want, then set composite mode to destination-in and then draw the image, or draw the image, set composite mode to source-in and then draw the color.
Example using the first approach coloring the following image blue:
var img = new Image; img.onload = draw; img.src = "//i.stack.imgur.com/cZ0gC.png";
var ctx = c.getContext("2d");
function draw() {
// draw color
ctx.fillStyle = "#09f";
ctx.fillRect(0, 0, c.width, c.height);
// set composite mode
ctx.globalCompositeOperation = "destination-in";
// draw image
ctx.drawImage(this, 0, 0);
}
<canvas id=c></canvas>
Example using second approach:
var img = new Image; img.onload = draw; img.src = "//i.stack.imgur.com/cZ0gC.png";
var ctx = c.getContext("2d");
function draw() {
// draw image
ctx.drawImage(this, 0, 0);
// set composite mode
ctx.globalCompositeOperation = "source-in";
// draw color
ctx.fillStyle = "#09f";
ctx.fillRect(0, 0, c.width, c.height);
}
<canvas id=c></canvas>
To reset comp. mode back to normal use:
// reset comp. mode
ctx.globalCompositeOperation = "source-over";
As with getImageData(), the drawback with this technique is that your canvas must only hold the content of this image while doing this process. A workaround if the image needs to be colored and mixed with other content is to use an off-screen canvas to do the processing, then draw that canvas back onto the main canvas.

Categories