I found this code to tint an canvas image file. I'm wondering what ist ctx.save and ctx.restore is used for in this tinting context? Why is it needed here?
JS FIDDLE
function recolor(color) {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(pic, 0, 0);
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = color;
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill();
ctx.restore();
var img = new Image();
img.src = canvas.toDataURL();
return (img);
}
save and restore are used to save and restore all context state, such as fillStyle, lineWidth, globalCompositeOperation, the clipping region, the current context transformation matrix, and so on.
The only necessary purpose of the save and restore in your fiddle is to reset the globalCompositeOperation.
You can do this manually instead:
function recolor(color) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(pic, 0, 0);
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = color;
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill();
//instead of save and restore:
ctx.globalCompositeOperation = "source-over";
var img = new Image();
img.src = canvas.toDataURL();
return (img);
}
In general you should avoid using save and restore unless you absolutely must, as it can get computationally expensive.
ctx save and restore are used to save the current context state. This is commonly used in functions so that you can call them anywhere and not worry that it will change the draw state outside of the function.
So for instance, in this code you change the fillStyle. Once ctx.restore is called, the fill style will return to what it was when the ctx.save was called.
Related
My challenge is to add color filter to dark part of image and another color filter to light part of image. To achieve effect like this https://imgur.com/a/cGmJbs9
I am using canvas with globalCompositeOperation effects, but I am able to apply only one filter without affect the other one.
ctx.drawImage(image, 0, 0, 380, 540);
ctx.globalCompositeOperation = 'darken';
ctx.fillStyle = overlayFillColor;
ctx.fillRect(0, 0, 380, 540);
this works great to apply color filter to dark or light areas, based on the globalCompositeOperation, but if I add another filter, it change colors of the previous filter as well.
any idea?
thanks Ales
There is a nice SVG filter component which does map luminance to alpha: <feColorMatrix type="luminanceToAlpha"/>
Since we can use SVG filters in canvas, this allows us to separate the dark area from the light one and use compositing instead of blending.
This way, your input colors are preserved.
(async () => {
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = "https://picsum.photos/500/500";
await img.decode();
canvas.width = img.width;
canvas.height = img.height;
// first we create our alpha layer
ctx.filter = "url(#lumToAlpha)";
ctx.drawImage(img, 0, 0);
ctx.filter = "none";
const alpha = await createImageBitmap(canvas);
// draw on "light" zone
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// save into an ImageBitmap
// (note that we could also use a second canvas to do this all synchronously)
const light = await createImageBitmap(canvas);
// clean canvas
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw on "dark" zone
ctx.drawImage(alpha, 0, 0);
ctx.globalCompositeOperation = "source-out";
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// reintroduce "light" zone
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(light, 0, 0);
})().catch(console.error);
<svg width="0" height="0" style="visibility:hidden;position:absolute">
<filter id="lumToAlpha">
<feColorMatrix type="luminanceToAlpha" />
</filter>
</svg>
<canvas></canvas>
<!--
If you don't like having an element in the DOM just for that
you could also directly set the context's filter to a data:// URI
url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cfilter%20id%3D%22f%22%3E%3CfeColorMatrix%20type%3D%22luminanceToAlpha%22%2F%3E%3C%2Ffilter%3E%3C%2Fsvg%3E#f");
but you'd have to wait a least a task (setTimeout(fn, 0))
because setting filters this way is async...
Hopefully CanvasFilters will solve this soon enough
-->
Note that hopefully we'll have CanvasFilters objects in a near future, which will make SVG filters to canvas easier to use, and accessible in Workers (they currently aren't...).
So for the ones from the future (or from the present on Canary with web-features flag on), this would look like:
// typeof CanvasFilter === "function"
// should be enough for detecting colorMatrix
// but see below for how to "correctly" feature-detect
// a particular CanvasFilter
if (supportsColorMatrixCanvasFilter()) {
(async () => {
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = "https://picsum.photos/500/500";
await img.decode();
canvas.width = img.width;
canvas.height = img.height;
// first we create our alpha layer
ctx.filter = new CanvasFilter({
filter: "colorMatrix",
type: "luminanceToAlpha"
});
ctx.drawImage(img, 0, 0);
ctx.filter = "none";
const alpha = await createImageBitmap(canvas);
// draw on "light" zone
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// save into an ImageBitmap
// (note that we could also use a second canvas to do this all synchronously)
const light = await createImageBitmap(canvas);
// clean canvas
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw on "dark" zone
ctx.drawImage(alpha, 0, 0);
ctx.globalCompositeOperation = "source-out";
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// reintroduce "light" zone
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(light, 0, 0);
})().catch(console.error);
}
else {
console.error("your browser doesn't support CanvasFilters yet");
}
// Feature detection is hard...
// see https://gist.github.com/Kaiido/45d189c110d29ac2eda25a7762c470f2
// to get the list of all supported CanvasFilters
// below only checks for colorMatrix
function supportsColorMatrixCanvasFilter() {
if(typeof CanvasFilter !== "function") {
return false;
}
let supported = false;
try {
new CanvasFilter({
filter: "colorMatrix",
// "type" will be visited for colorMatrix
// we throw in to avoid actually creating the filter
get type() { supported = true; throw ""; }
});
} catch(err) {}
return supported;
}
<canvas></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.
Seems like there are other questions like this and I'd like to avoid a buffer and/or requestAnimationFrame().
In a recent project the player is flickering but I cannot find out the reason. You can find the project on JSFiddle: https://jsfiddle.net/90wjetLa/
function gameEngine() {
timer += 1;
timer = Math.round(timer);
// NEWSHOOT?
player.canShoot -= 1;
// MOVE:
movePlayer();
shootEngine(); // Schussbewegung & Treffer-Abfrage
// DRAW:
ctx.beginPath();
canvas.width = canvas.width;
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fill();
drawField();
drawPlayer();
drawShoots();
setTimeout(gameEngine, 1000 / 30);
}
Each time you write to a visible canvas the browser want's to update the display. Your drawing routines might be out of sync with the browsers display update. The requestAnimationFrame function allows you to run all your drawing routines before the display refreshes. Your other friend is using an invisible buffer canvas. Draw everything to the buffer canvas and then draw the buffer to the visible canvas. The gameEngine function should only run once per frame and if it runs multiple times you could see flicker. Try the following to clear multiple runs in the same frame.
(edit): You might also want to clear the canvas instead of setting width.
(edit2): You can combine the clearRect, rect, and fill to one command ctx.fillRect(0, 0, canvas.width, canvas.height);.
var gameEngineTimeout = null;
function gameEngine() {
// clear pending gameEngine timeout if it exists.
clearTimeout(gameEngineTimeout);
timer += 1;
timer = Math.round(timer);
// NEWSHOOT?
player.canShoot -= 1;
// MOVE:
movePlayer();
shootEngine(); // Schussbewegung & Treffer-Abfrage
// DRAW:
ctx.beginPath();
//canvas.width = canvas.width;
//ctx.clearRect(0, 0, canvas.width, canvas.height);
//ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
//ctx.fill();
drawField();
drawPlayer();
drawShoots();
gameEngineTimeout = setTimeout(gameEngine, 1000 / 30);
}
I've tried googling the answer to this but i'm just going around in circles....
If I clear the rect (using clearRect) then the image doesn't redraw after.
However, if I don't clear the images just stack.
What I want it to clear the current image and then draw with new one.
What am I missing?
Does it have something to do with the image Load ?
Sorry if this is a question repeat, I couldn't find the exact answer to this- and I tried what others suggested but results were poor.
http://jsfiddle.net/bxeuhh4h/
function clear() {
var canvasTemp = document.getElementById(imgSection);
var ctxTemp = canvasTemp.getContext("2d");
ctxTemp.clearRect(0, 0, 500, 500);
}
function fillColorOrPattern(imgSection,currentcolor){
if ((oldcolor !== currentcolor) || (oldxImgToBeFilled !== xImgToBeFilled)){
clear();
}
imgFill.onload = function () {
imgToBeFilled.onload = function () {
if ((oldcolor !== currentcolor) || (oldxImgToBeFilled !== xImgToBeFilled)){
fill(imgSection,currentcolor)
}
};
imgToBeFilled.src = xImgToBeFilled;
}
imgFill.src = xImgFill;
}
function fill(imgSection,currentcolor){
canvas = document.getElementById(imgSection);
ctx = canvas.getContext("2d");
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.globalCompositeOperation = "source-atop";
console.log(isItColorOrPattern);
if (isItColorOrPattern == "color"){
ctx.rect(0, 0, canvas.width, canvas.height);
console.log("currentcolor: " + currentcolor);
ctx.fillStyle = getColor(currentcolor);
console.log(getColor(currentcolor));
ctx.fill();
}else{
var pattern = ctx.createPattern(imgFill, 'repeat');
console.log("canvas.width: " + canvas.width);
console.log("xImgFill: " + xImgFill);
console.log(canvas.getContext);
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = pattern;
ctx.fill();
}
ctx.globalAlpha = .10;
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.drawImage(imgToBeFilled, 0, 0);
oldcolor = currentcolor;
oldxImgToBeFilled = xImgToBeFilled;
}
$(window).load(function(){
imgToBeFilled = new Image();
imgFill = new Image();
fillColorOrPattern(imgSection,currentcolor);
}
You need to add a beginPath() in there. rect() will accumulate rectangles to the path, clearRect() won't clear those. Also reset comp. mode and alpha as they are sticky.
You could avoid beginPath() if you use fillRect() instead of rect() + fill() (added example below) as fillRect() does not add to the path.
function fill(imgSection,currentcolor){
// these should really be initialized outside the loop
canvas = document.getElementById(imgSection);
ctx = canvas.getContext("2d");
// clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// clear path
ctx.beginPath();
// use default comp. mode
ctx.globalCompositeOperation = "source-over";
// reset alpha
ctx.globalAlpha = 1;
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.globalCompositeOperation = "source-atop";
if (isItColorOrPattern === "color"){
// rect() accumulates on path
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = getColor(currentcolor);
ctx.fill();
// instead of rect() + fill() you could have used:
// fillRect() does not accumulate on path
// fillRect(0, 0, canvas.width, canvas.height);
}
else {
var pattern = ctx.createPattern(imgFill, 'repeat');
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = pattern;
ctx.fill();
}
ctx.globalAlpha = .1;
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.drawImage(imgToBeFilled, 0, 0);
oldcolor = currentcolor;
oldxImgToBeFilled = xImgToBeFilled;
}
Canvas workflow goes like this:
Draw some things on the canvas.
Calculate changes to the position of those things.
Clear the canvas.
Redraw all the things in their new positions.
Canvas does not "remember" where it drew your things so you cannot directly order your things to move.
But you can save the definition of your things in javascript object:
var myCircle={
centerX:50,
centerY:50,
radius:25,
fill:'blue'
}
Then you can "move" your things using the javascript objects:
myCircle.centerX += 5;
And then redraw the things at their new positions. Putting the redraw code in a function makes redrawing easier:
function redraw(){
// clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
// redraw one or more things based on their javascript objects
ctx.beginPath();
ctx.arc( myCircle.centerX, myCircle.centerY, myCircle.radius, 0, Math.PI*2 );
ctx.closePath();
ctx.fillStyle=myCircle.fill;
ctx.fill();
}
Putting it all together:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var myCircle={
centerX:50,
centerY:50,
radius:25,
fill:'blue'
}
redraw();
document.getElementById('move').addEventListener('click',function(){
myCircle.centerX+=5;
redraw();
});
function redraw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.beginPath();
ctx.arc( myCircle.centerX, myCircle.centerY, myCircle.radius, 0, Math.PI*2 );
ctx.closePath();
ctx.fillStyle=myCircle.fill;
ctx.fill();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<button id=move>Move</button>
<br>
<canvas id="canvas" width=300 height=300></canvas>
I am just getting started with Canvas programming and trying to build a small game. Below is a sample code that I am trying out. My intention is to:
Create a canvas.
Fill it with some background color.
Draw a circle.
Clear the canvas.
Draw another circle in different location.
Here's the code:
var canvas = document.createElement('canvas');
canvas.width= 400;
canvas.height = 400;
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
// 2. Fill background
ctx.fillStyle = 'rgb(30,0,0)';
ctx.fillRect(0,0,400,400);
// 3. Draw circle
ctx.save();
ctx.fillStyle = 'rgba(256,30,30,.8)';
ctx.arc(50,50, 20, 0, Math.PI*2, true);
ctx.fill();
ctx.restore();
// 4. Clear Canvas
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
// 5. Draw another circle
ctx.save();
ctx.fillStyle = 'rgba(256,30,30,.8)';
ctx.arc(150,150, 20, 0, Math.PI*2, true);
ctx.fill();
ctx.restore();
But as you can see, only the background color gets cleared and the first circle remains as it is.
Why is the above code fails to clear the canvas completely before drawing second circle?
If you don't use beginPath before starting a new path, all draw command keeps stacking in the current path.
What's happening here is that when you fill() the second time, the first circle is still in the current path, so even if the screen was in deed cleared, there are two circles drawn with this single fill() command.
==>> use beginPath() before starting a new path.