Change color Image in Canvas - javascript

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.

Related

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>

Multi color image into single color image in canvas JavaScript [duplicate]

I want to change the background color of this image while keeping the form, the effects and the contour of
the image.
<canvas id="canvas01" width="1200" height="800"></canvas>
<script>
function drawImage(imageObj,x, y, width, height){
var canvas = document.getElementById('canvas01');
var context = canvas.getContext('2d');
context.drawImage(imageObj, x, y, width, height);
}
var image = new Image();
image.onload = function(){
drawImage(this, 400, 100, 320, 450);
};
image.src ="images/658FFBC6.png";
</script>
Luma preservation
At the risk of looking similar to the existing answer, I would like to point out a small but important difference using a slightly different approach.
The key is to preserve the luma component in an image (ie. shadow details, wrinkles etc. in this case) so two steps are needed to control the look using blending modes via globalCompositeOperation (or alternatively, a manual approach using conversion between RGB and the HSL color-space if older browsers must be supported):
"saturation": will alter the chroma (intensity, saturation) from the next drawn element and apply it to the existing content on the canvas, but preserve luma and hue.
"hue": will grab the chroma and luma from the source but alter the hue, or color if you will, based on the next drawn element.
As these are blending modes (ignoring the alpha channel) we will also need to clip the result using composition as a last step.
The color blending mode can be used too but it will alter luma which may or may not be desirable. The difference can be subtle in many cases, but also very obvious depending on target chroma and hue where luma/shadow definition is lost.
So, to achieve a good quality result preserving both luma and chroma, these are more or less the main steps (assumes an empty canvas):
// step 1: draw in original image
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, 0, 0);
// step 2: adjust saturation (chroma, intensity)
ctx.globalCompositeOperation = "saturation";
ctx.fillStyle = "hsl(0," + sat + "%, 50%)"; // hue doesn't matter here
ctx.fillRect(0, 0);
// step 3: adjust hue, preserve luma and chroma
ctx.globalCompositeOperation = "hue";
ctx.fillStyle = "hsl(" + hue + ",1%, 50%)"; // sat must be > 0, otherwise won't matter
ctx.fillRect(0, 0, c.width, c.height);
// step 4: in our case, we need to clip as we filled the entire area
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(img, 0, 0);
// step 5: reset comp mode to default
ctx.globalCompositeOperation = "source-over";
50% lightness (L) will keep the original luma value.
Live Example
Click the checkbox to see the effect on the result. Then test with different chroma and hue settings.
var ctx = c.getContext("2d");
var img = new Image(); img.onload = demo; img.src = "//i.stack.imgur.com/Kk1qd.png";
function demo() {c.width = this.width>>1; c.height = this.height>>1; render()}
function render() {
var hue = +rHue.value, sat = +rSat.value, l = +rL.value;
ctx.clearRect(0, 0, c.width, c.height);
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, 0, 0, c.width, c.height);
if (!!cColor.checked) {
// use color blending mode
ctx.globalCompositeOperation = "color";
ctx.fillStyle = "hsl(" + hue + "," + sat + "%, 50%)";
ctx.fillRect(0, 0, c.width, c.height);
}
else {
// adjust "lightness"
ctx.globalCompositeOperation = l < 100 ? "color-burn" : "color-dodge";
// for common slider, to produce a valid value for both directions
l = l >= 100 ? l - 100 : 100 - (100 - l);
ctx.fillStyle = "hsl(0, 50%, " + l + "%)";
ctx.fillRect(0, 0, c.width, c.height);
// adjust saturation
ctx.globalCompositeOperation = "saturation";
ctx.fillStyle = "hsl(0," + sat + "%, 50%)";
ctx.fillRect(0, 0, c.width, c.height);
// adjust hue
ctx.globalCompositeOperation = "hue";
ctx.fillStyle = "hsl(" + hue + ",1%, 50%)";
ctx.fillRect(0, 0, c.width, c.height);
}
// clip
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(img, 0, 0, c.width, c.height);
// reset comp. mode to default
ctx.globalCompositeOperation = "source-over";
}
rHue.oninput = rSat.oninput = rL.oninput = cColor.onchange = render;
body {font:16px sans-serif}
<div>
<label>Hue: <input type=range id=rHue max=359 value=0></label>
<label>Saturation: <input type=range id=rSat value=100></label>
<label>Lightness: <input type=range id=rL max=200 value=100></label>
<label>Use "color" instead: <input type=checkbox id=cColor></label>
</div>
<canvas id=c></canvas>
Global composite operations
The 2D context property ctx.globalCompositeOperation is very useful for a wide range of image processing tasks. For more on globalCompositeOperation at MDN
You can convert the image into a canvas, that way you can edit it.
function imageToCanvas(image){
const c = document.createElement("canvas");
c.width = image.width;
c.height = image.height;
c.ctx = c.getContext("2d"); // attach context to the canvas for eaasy reference
c.ctx.drawImage(image,0,0);
return c;
}
You can use the globalCompositeOperation = "color" to colour the image
function colorImage(image,color){ // image is a canvas image
image.ctx.fillStyle = color;
image.ctx.globalCompositeOperation = "color";
image.ctx.fillRect(0,0,image.width,image.height);
image.ctx.globalCompositeOperation = "source-over";
return image;
}
Unfortunately this also overwrites the alpha pixels so you need to use the original image as a mask to restore the alpha pixels.
function maskImage(dest,source){
dest.ctx.globalCompositeOperation = "destination-in";
dest.ctx.drawImage(source,0,0);
dest.ctx.globalCompositeOperation = "source-over";
return dest;
}
And then you have a coloured image
Example.
In he example I colour the image in a range of colours and added a function to restore the canvas copy of the image back to the original. If you get the image from the page as an element then use naturalWidth and naturalHeight as the width and height properties may not match the image resolution.
const ctx = canvas.getContext("2d");
const image = new Image;
var colCopy;
image.src = "https://i.stack.imgur.com/Kk1qd.png";
image.onload = () => {
colCopy = imageToCanvas(image);
const scale = canvas.height / image.naturalHeight;
ctx.scale(scale, scale);
ctx.drawImage(colCopy, 0, 0);
for (var i = 32; i < 360; i += 32) {
restoreImage(colCopy, image);
colorImage(colCopy, "hsl(" + i + ",100%,50%)");
maskImage(colCopy, image);
ctx.drawImage(colCopy, 150 * i / 16, 0);
}
}
function imageToCanvas(image) {
const c = document.createElement("canvas");
c.width = image.naturalWidth;
c.height = image.naturalHeight;
c.ctx = c.getContext("2d"); // attach context to the canvas for easy reference
c.ctx.drawImage(image, 0, 0);
return c;
}
function restoreImage(dest, source) {
dest.ctx.clearRect(0, 0, dest.width, dest.height);
dest.ctx.drawImage(source, 0, 0);
return dest;
}
function colorImage(dest, color) { // image is a canvas image
dest.ctx.fillStyle = color;
dest.ctx.globalCompositeOperation = "color";
dest.ctx.fillRect(0, 0, dest.width, dest.height);
dest.ctx.globalCompositeOperation = "source-over";
return dest;
}
function maskImage(dest, source) {
dest.ctx.globalCompositeOperation = "destination-in";
dest.ctx.drawImage(source, 0, 0);
dest.ctx.globalCompositeOperation = "source-over";
return dest;
}
canvas {
border: 2px solid black;
}
<canvas id="canvas" width=600></canvas>
The image can get a little washed out in some situations, you can convert the image to a higher contrast black and white image using composite operations similar to shown above, and use the high contrast image as the template to colour.
Using Filters
Most of the common browsers now support canvas filters which has a hue shift filter. You can use that to shift the hue to the value you want, though first you will need to know what the image original hue is. (see below example on how to find HUE)
See Canvas filters at MDN for compatibility and how to use canvas filters.
The following function will preserve the saturation and just shift the hue.
// dest canvas to hold the resulting image
// source the original image
// hue The hue to set the dest image to
// sourceHue the hue reference point of the original image.
function colorImage(dest,source, hue , sourceHue) { // image is a canvas image
dest.ctx.clearRect(0,0,dest.width, dest.height);
dest.ctx.filter="hue-rotate("+((hue - sourceHue) | 0)+"deg)";
dest.ctx.drawImage(source,0, 0, dest.width, dest.height);
return dest;
}
Filters example.
The following uses ctx.filter = "hue-rotate(30deg)" to rotate the hue. I have not included any code to find the image original hue so manually set it by eye to 120.
const ctx = canvas.getContext("2d");
const image = new Image;
var colCopy;
const sourceHue = 120;
image.src = "https://i.stack.imgur.com/Kk1qd.png";
image.onload = () => {
colCopy = imageToCanvas(image);
const scale = canvas.height / image.naturalHeight;
ctx.scale(scale, scale);
ctx.drawImage(colCopy, 0, 0);
for (var i = 32; i < 360; i += 32) {
colorImage(colCopy,image,i,sourceHue);
ctx.drawImage(colCopy, 150 * i / 16, 0);
}
}
function imageToCanvas(image) {
const c = document.createElement("canvas");
c.width = image.naturalWidth;
c.height = image.naturalHeight;
c.ctx = c.getContext("2d"); // attach context to the canvas for easy reference
c.ctx.drawImage(image, 0, 0);
return c;
}
function colorImage(dest,source, hueRotate , sourceHue) { // image is a canvas image
dest.ctx.clearRect(0,0,dest.width, dest.height);
dest.ctx.filter="hue-rotate("+((hueRotate - sourceHue) | 0)+"deg)";
dest.ctx.drawImage(source,0, 0, dest.width, dest.height);
return dest;
}
canvas {
border: 2px solid black;
}
<canvas id="canvas" width=600></canvas>
RGB to Hue
There are plenty of answers to help find the hue of a pixel here on SO. Here is a particularly detailed one RGB to HSL conversion.
Filters example White.
The following uses ctx.filter = "grayscale(100%)" to remove saturation and then ctx.filter = "brightness(amount%)" to change the brightness. This gives a range of gray colours from black to white. You can also do the same with the colour, by reducing the grayscale amount.
const ctx = canvas.getContext("2d");
const image = new Image;
var colCopy;
const sourceHue = 120;
image.src = "https://i.stack.imgur.com/Kk1qd.png";
image.onload = () => {
colCopy = imageToCanvas(image);
const scale = canvas.height / image.naturalHeight;
ctx.scale(scale, scale);
ctx.drawImage(colCopy, 0, 0);
for (var i = 40; i < 240; i += 20) {
grayImage(colCopy,image,i);
ctx.drawImage(colCopy, 150 * ((i-40) / 12), 0);
}
}
function imageToCanvas(image) {
const c = document.createElement("canvas");
c.width = image.naturalWidth;
c.height = image.naturalHeight;
c.ctx = c.getContext("2d"); // attach context to the canvas for easy reference
c.ctx.drawImage(image, 0, 0);
return c;
}
function grayImage(dest,source, brightness) { // image is a canvas image
dest.ctx.clearRect(0,0,dest.width, dest.height);
dest.ctx.filter = "grayscale(100%)";
dest.ctx.drawImage(source,0, 0, dest.width, dest.height);
dest.ctx.filter = "brightness(" + brightness +"%)";
dest.ctx.drawImage(dest,0, 0, dest.width, dest.height);
return dest;
}
canvas {
border: 2px solid black;
}
<canvas id="canvas" width=600></canvas>
You can combine filters on a single line of code before performing your draw operation, like this:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById('source');
ctx.filter = 'hue-rotate(120deg) grayscale(10%) brightness(150%)';
ctx.drawImage(image, 10, 10, 180, 120);
<canvas id="canvas"></canvas>
<div style="display:none;">
<img id="source"
src="https://interactive-examples.mdn.mozilla.net/media/examples/gecko-320-213.jpg">
</div>

best practice in drawing outline of image

I've tried 3 ways to make it, but the effect doesn't looks well.
copy and fill image then make offset. The demo is
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = "http://i.stack.imgur.com/UFBxY.png";
function draw() {
var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
s = 20, // thickness scale
i = 0, // iterator
x = 5, // final position
y = 5;
// draw images at offsets from the array scaled by s
for(; i < dArr.length; i += 2)
ctx.drawImage(img, x + dArr[i]*s, y + dArr[i+1]*s);
// fill with color
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "red";
ctx.fillRect(0,0,canvas.width, canvas.height);
// draw original image in normal mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
<canvas id=canvas width=500 height=500></canvas>
. When the outline width is large, the outline result will be wrong.
check the edge of image base on the Marching Squares algorithm. When the image shape is circle, the outline is with sawtooth. If make the outline more smoother, it won't fit the sharp shape like star.
copy and fill the image then scale it. When a image width is not equal with height, it doesn't work.
You can try with a math approach, without the offset array
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = "http://i.stack.imgur.com/UFBxY.png";
function draw() {
var s = 20, // thickness scale
x = 5, // final position
y = 5;
for (i=0; i < 360; i++)
ctx.drawImage(img, x + Math.sin(i) * s, y + Math.cos(i) * s);
// fill with color
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// draw original image in normal mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
<canvas id=canvas width=500 height=500></canvas>
My idea comes from the way we draw a circle using a string:
https://www.wikihow.com/Draw-a-Perfect-Circle-Using-a-Pin
Imagine that instead of a pencil at the end of the string we just have a shape
Here is a visual comparison of my approach and yours, also I'm showing a third approach scaling the image, there is really not a best one, it's just a matter of personal preference.
You could create a hybrid mode, if the hairline is important to you, get that portion of the image scaling it, then use a different way for the rest of the body.

JavaScript canvas make transparent image darker

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

Gradient map as opacity in a HTML5 canvas element?

Is there a way to create an opacity map on a canvas element
I am trying to fade a generated image as shown below.
EXAMPLE:
I don't believe there is any way to directly draw an image with a gradiant mask, but you could pre-draw the image to a separate canvas, and use globalCompositeOperation to draw a masking linear gradient, then draw that canvas using drawImage to the main canvas.
Working Example:
var cvs = document.getElementById('cvs');
var ctx = cvs.getContext('2d');
// Draw some background colors.
ctx.fillStyle = "#FF6666";
ctx.fillRect(0, 0, 150, 200);
ctx.fillStyle = "#6666FF";
ctx.fillRect(150, 0, 150, 200);
// Load the image.
img = new Image();
img.onload = function() {
// Create a canvas in memory and draw the image to it.
var icvs = document.createElement('canvas');
icvs.width = img.width;
icvs.height = img.height;
var ictx = icvs.getContext('2d');
ictx.drawImage(img, 0, 0);
// For masking.
ictx.globalCompositeOperation = 'destination-out';
// Draw the masking gradient.
var gradient = ictx.createLinearGradient(0, 0, 0, icvs.height);
gradient.addColorStop(0, "transparent");
gradient.addColorStop(1, "white");
ictx.fillStyle = gradient;
ictx.fillRect(0, 0, icvs.width, icvs.height);
// Draw the separate canvas to the main canvas.
ctx.drawImage(icvs, 25, 25, 250, 150);
};
img.src = '//i.stack.imgur.com/dR8i9.jpg';
<canvas id="cvs" width="300" height="200"></canvas>

Categories