I am using this jQuery plugin to make free drawing over a canvas.
I want to clear the canvas for redrawing but after I do and when I click inside canvas for redrawing the old drawing that I cleared appears again
$('#canvasFirst').sketch();
$('button').on("click", (evt) => {
var canvas = document.getElementById('canvasFirst');
let context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
});
canvas { border: 1px solid; vertical-align: top }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/mobomo/sketch.js#master/lib/sketch.min.js"></script>
<p>Draw on the below canvas, then press "clear" and try to draw again.</p>
<canvas id="canvasFirst">
</canvas>
<button>clear</button>
This plugin stores all the drawing commands in an actions array, and at each redraw it will go through that whole list and draw the full thing again. (Which allows to avoid having holes in your stroke).
The docs are very hard to grasp, but you can set this options's content through the .sketch("options", value) method.
As hinted in this issue, setting it to an empty Array will thus remove all the previous commands. All you have to do then is to redraw the whole scene, now empty:
const $canvas = $('#canvasFirst');
$canvas.sketch();
$('button').on("click", (evt) => {
$canvas.sketch("actions", []);
$canvas.sketch("redraw");
});
canvas { border: 1px solid; vertical-align: top }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/mobomo/sketch.js#master/lib/sketch.min.js"></script>
<p>Draw on the below canvas, then press "clear" and try to draw again.</p>
<canvas id="canvasFirst">
</canvas>
<button>clear</button>
You can try to save the state of the canvas as a blob. For example, as image/png.
There are two bad things here:
A small inconvenience. Methods for converting a blob to an image and back are asynchronous. Promises have to be used.
Speed. I strongly doubt that this solution is suitable for tasks that require speed - games, videos, etc.
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const saveCanvasState = (canvas) => {
const ctx = canvas.getContext('2d');
ctx.save(); // save state
let blob;
return new Promise((r) => {
canvas.toBlob( // toBlob is async method
(b) => {
blob = b;
r(() =>
new Promise((r) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
const img = new Image();
img.addEventListener('load', () => {
URL.revokeObjectURL(img.src);
ctx.drawImage(img, 0, 0);
r();
});
img.src = URL.createObjectURL(blob);
}));
},
'image/png',
1
);
});
};
ctx.fillStyle = 'green';
ctx.fillRect(10, 10, 100, 100);
saveCanvasState(canvas).then((restore) => {
ctx.fillStyle = 'black';
ctx.fillRect(150, 40, 100, 100);
ctx.fillRect(100, 40, 100, 100);
restore().then(() => {
console.log('restored, can draw');
});
});
<canvas id="canvas"></canvas>
Related
I want to save canvas image as a bitmap to be able to later use it in drawImage.
Goal is to be able to change (eg. resize) canvas and keep it's contents (eg. scaled - canvas is always a square).
I tried:
var tmp = createImageBitmap(canvas)
ctx.drawImage(tmp,0,0)
What I got was an error that said 'tmp' is not a bitmap.
createImageBitmap returns a Promise, so the result is asynchronous and you have to wait for it with await:
const img = await createImageBitmap(canvas);
ctx.drawImage(img, 0, 0);
Or if you can't use await because you target ES5 then do it the old fashioned way:
createImageBitmap(canvas).then(img => {
ctx.drawImage(img, 0, 0);
});
An HTMLCanvasElement is part of the CanvasImageSource mixin, meaning you can directly use it with drawImage():
const [canvas1, canvas2] = document.querySelectorAll("canvas");
const ctx1 = canvas1.getContext("2d");
const ctx2 = canvas2.getContext("2d");
ctx1.fillStyle = "green"
ctx1.fillRect(0, 0, 50, 50);
ctx2.fillStyle = "red"
ctx2.fillRect(0, 0, 50, 50);
// draw canvas1 at (60, 0), with a 2x scale
ctx2.drawImage(canvas1, 60, 0, 600, 300);
canvas { border: 1px solid }
<canvas></canvas>
<canvas></canvas>
l believe to have a logic error in the way of which my logic is meant to find the previous coordinates of my canvas object, a moving image, and delete the frame drawn before it so that it does not duplicated the image every time it is drawn onto the canvas.
There is a reproducible demo below and l added comments where l believe the problem occurs.
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var the_background = document.getElementById("the_background");
var imgTag = new Image();
var X_POS = canvas.width;
var Y_POS = 0;
imgTag.src = "http://i.stack.imgur.com/Rk0DW.png"; // load image
function animate() {
/* Error resides from here */
var coords = {};
coords.x = Math.floor(this.X_POS - imgTag);
coords.y = Math.floor(this.Y_POS - imgTag);
ctx.clearRect(coords.x, coords.y, X_POS, Y_POS);
/* To here */
ctx.drawImage(imgTag, X_POS, Y_POS);
X_POS -= 5;
if (X_POS > 200) requestAnimationFrame(animate)
}
window.onload = function() {
ctx.drawImage(the_background, 0, 0, canvas.width, canvas.height);
animate();
}
html,
body {
width: 100%;
height: 100%;
margin: 0px;
border: 0;
overflow: hidden;
display: block;
}
<html>
<canvas id="c" width="600" height="400"></canvas>
<img style="display: none;" id="the_button" src="https://i.imgur.com/wO7Wc2w.png" />
<img style="display: none;" id="the_background" src="https://img.freepik.com/free-photo/hand-painted-watercolor-background-with-sky-clouds-shape_24972-1095.jpg?size=626&ext=jpg" />
</html>
It seems logical to only clear the subset of the canvas that's changing, but the normal approach is to clear and redraw the entire canvas per frame. After the car moves, the background that's cleared underneath it needs to be filled in, and trying to redraw only that subset will lead to visual artifacts and general fussiness. Clearing small portions of the screen is a premature optimization.
Canvases can't keep track of much of anything other than pixels, so an animation is more like a flipbook of stationary frames and less like a cardboard cutout animation, where the same pieces of cardboard move along and overlap one another.
Math.floor(this.X_POS - imgTag) looks wrong -- imgTag is an image object, which doesn't make sense to subtract from a number. You may have meant to grab the x property from this.
Use image.onload to ensure the image is actually loaded before running the loop. It's a bit odd to use image tags in the DOM just to load images for a canvas. I'd do that programmatically from JS which saves some typing and makes it easier to manage the data.
const loadImg = url => new Promise((resolve, reject) => {
const img = new Image();
img.onerror = reject;
img.onload = () => resolve(img);
img.src = url;
});
const images = [
"https://img.freepik.com/free-photo/hand-painted-watercolor-background-with-sky-clouds-shape_24972-1095.jpg?size=626&ext=jpg",
"http://i.stack.imgur.com/Rk0DW.png",
];
Promise
.all(images.map(loadImg))
.then(([backgroundImg, carImg]) => {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const car = {x: canvas.width, y: 0};
(function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(
backgroundImg, 0, 0, canvas.width, canvas.height
);
ctx.drawImage(
carImg, car.x, car.y, carImg.width, carImg.height
);
car.x -= 5;
if (car.x > 200) {
requestAnimationFrame(update);
}
})();
})
.catch(err => console.error(err))
;
<canvas id="canvas" width="600" height="400"></canvas>
I want save canvas in every 2 minutes.
document.getElementById('canvasUrl').value = canvas.toDataURL();
I try:
document.getElementById('canvasUrl').value += canvas.toDataURL();
//or
var canvasValue += canvas.toDataURL();
document.getElementById('canvasUrl').value = canvasValue;
both can't work! Why?
You can but you need to combine the image data first prior to using a dataURL here is an example. Please note it's very verbose I imagine you'd want multiple canvases in an array or something along those lines.
How this works is it takes the data from the first 3 canvases then draws them to the 4th, the 4th canvases dataURL is what's used which has the data from the previous 3 canvases combined.
const canvas1 = document.querySelector('#first');
const ctx1 = canvas1.getContext('2d');
const canvas2 = document.querySelector('#second');
const ctx2 = canvas2.getContext('2d');
const canvas3 = document.querySelector('#third');
const ctx3 = canvas3.getContext('2d');
ctx1.fillStyle = 'red';
ctx1.fillRect(0, 0, 100, 100);
ctx2.fillStyle = 'blue';
ctx2.fillRect(100, 100, 100, 100);
ctx3.fillStyle = 'green';
ctx3.fillRect(10, 10, 100, 10);
const saveCanvas = document.querySelector('#saved');
const ctxSave = saveCanvas.getContext('2d');
// Draw each of our other canvases onto the canvas we're going to save from.
ctxSave.drawImage(canvas1, 0, 0);
ctxSave.drawImage(canvas2, 0, 0);
ctxSave.drawImage(canvas3, 0, 0);
document.getElementById('canvasUrl').value = saveCanvas.toDataURL();
canvas {
border: 1px solid black;
}
<div>
<label>Saved Canvas Data</label>
<input id="canvasUrl" type="text">
</div>
<div>
<canvas id="first"></canvas>
<canvas id="second"></canvas>
<canvas id="third"></canvas>
<canvas id="saved"></canvas>
</div>
I have 2 images of a pirate, one with the shape, and one with the pirate itself. I want to merge that 2 pictures together to create an image with transparant borders to use into my canvas. The only problem is i cant find a way to actually manage this.
The images itself are naturally not transparant at all, so i played with the Composite options in javascript, to try to make it transparant with the 'lighter' option, but i cant find a way to get rid of the white borders around the image.
This is a part of the code i currently have:
ctx.canvas.width = pirate_shape.width;
ctx.canvas.height = pirate_shape.height;
ctx.clearRect(0, 0, pirate_shape.width, pirate_shape.height);
// Draw the shape in reversed colors so i can draw the picture inside the shape.
ctx.drawImage(pirate_shape, 0, 0);
ctx.globalCompositeOperation='difference';
ctx.fillStyle='white';
ctx.fillRect(0, 0, pirate_shape.width, pirate_shape.height);
// Draw the pirate itself
ctx.globalCompositeOperation = 'lighter';
ctx.drawImage(pirate, 0, 0);
The complete code: https://jsfiddle.net/0kbj2Leg/
The result im trying to get:
https://imgur.com/uL0Pf4T
The 2 images i used:
(the pirate)
https://imgur.com/8R1qutU
(the shape)
https://imgur.com/qemOzU2
Do you guys know maybe an way to get that result?
Thanks!
You want to use compositing, not blending.
To do this, you need transparency. Your images don't contain any.
Since your border image is black and white, there is actually an easy way in modern browsers to convert the white to transparent pixels: svg filters.
Once done, we can use compositing to achieve our goal.
// just the assets loader
(async () => {
const [face, border, back] = await Promise.all(
[
'https://i.imgur.com/8R1qutU.png',
'https://imgur.com/qemOzU2.png',
'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png'
].map(url => {
const img = new Image();
img.src = url;
return new Promise(res => {
img.onload = e => res(img);
});
})
);
const ctx = document.getElementById('canvas').getContext('2d');
// generate the border transparency based on luminance
// use the brightness to control how much red we keep
ctx.filter = 'brightness(180%) url(#trans)';
ctx.drawImage(border, 10, 10);
ctx.filter = 'none';
await wait(1000); // only for demo
// draw the face only where we already have the border drawn
ctx.globalCompositeOperation = 'source-in';
ctx.drawImage(face, 10, 10);
await wait(1000); // only for demo
// draw the background behind
ctx.globalCompositeOperation = 'destination-over';
ctx.drawImage(back, -100,0, 400,300);
// reset
ctx.globalCompositeOperation = 'source-over';
})();
// only for demo, so we can see the different steps
function wait(ms) { return new Promise(res => setTimeout(res, ms)) }
canvas {
background-color: ivory;
border: 1px solid lightgray;
}
<canvas id="canvas" width="200" height="200"></canvas>
<svg style="width:0px;height:0px;position:absolute;z-index:-1">
<defs>
<filter id="trans">
<feColorMatrix type="luminanceToAlpha"/>
</filter>
</defs>
</svg>
Now, it would obviously be a lot easier if you can prepare your assets correctly from the beginning:
// just the assets loader
(async () => {
const [face, border, back] = await Promise.all(
[
'https://i.imgur.com/8R1qutU.png',
'https://i.stack.imgur.com/778ZM.png', // already wth transparency
'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png'
].map(url => {
const img = new Image();
img.src = url;
return new Promise(res => {
img.onload = e => res(img);
});
})
);
const ctx = document.getElementById('canvas').getContext('2d');
ctx.drawImage(border, 10, 10);
// draw the face only where we already have the border drawn
ctx.globalCompositeOperation = 'source-in';
ctx.drawImage(face, 10, 10);
// draw the background behind
ctx.globalCompositeOperation = 'destination-over';
ctx.drawImage(back, -100,0, 400,300);
// reset
ctx.globalCompositeOperation = 'source-over';
})();
canvas {
background-color: ivory;
border: 1px solid lightgray;
}
<canvas id="canvas" width="200" height="200"></canvas>
But you could probably even get rid of the border image entirely by drawing a shadow over a well prepared pirate image:
// just the assets loader
(async () => {
const [face,back] = await Promise.all(
[
'https://i.stack.imgur.com/1hzcD.png',
'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png'
].map(url => {
const img = new Image();
img.src = url;
return new Promise(res => {
img.onload = e => res(img);
});
})
);
const ctx = document.getElementById('canvas').getContext('2d');
// draw the background
ctx.drawImage(back, -100,0, 400,300);
// draw the face with a shadow
ctx.shadowBlur = 15;
ctx.shadowColor = "red";
// little hack to make shadow more opaque
// first move your shadow away from what you wish to draw
ctx.shadowOffsetX = face.width + 10; // + 10 because we'll draw the last one at x:10
ctx.shadowOffsetY = face.height + 10;
// now draw your shape outside of the visible area
ctx.drawImage(face, -face.width, -face.height);
await wait(1000); // just for demo
ctx.drawImage(face, -face.width, -face.height);
// we now have to shadows overlapping but not our shape
await wait(1000); // just for demo
// reset the shadowOffset
ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
// adn draw the last one
ctx.drawImage(face, 10, 10);
ctx.shadowBlur = 0;
ctx.shadowColor = 'transparent';
})();
// only for demo, so we can see the different steps
function wait(ms) { return new Promise(res => setTimeout(res, ms)) }
canvas {
background-color: ivory;
border: 1px solid lightgray;
}
<canvas id="canvas" width="200" height="200"></canvas>
Images are not drawing to bg_canvas with a z-index of 0, only my main game canvas overlay is showing, i have only included the start of the code.
// HTML
<canvas id="bg-canvas" width="768" height="600"></canvas>
<canvas id="canvas" width="768" height="600"></canvas>
// CSS
canvas {
position: absolute;
top: 0;
left: 0;
}
#canvas {
z-index: 1
}
#bg-canvas {
z-index: 0
}
// Javascript
const bg_canvas = document.getElementById('bg-canvas');
const bg_ctx = bg_canvas.getContext("2d");
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const map = {
object: {
brickwall: new Image(),
woodenbox: new Image()
}
};
map.object.brickwall.src = "objects/brickwall.jpeg";
map.object.woodenbox.src = "objects/boximage.jpg";
ctx.clearRect(0,0,canvas.width,canvas.height);
bg_ctx.clearRect(0,0,bg_canvas.width,bg_canvas.height);
// var currentItemEquipped;
// drawImage(imgsrc, xcoord, ycoord, sizex, sizey) // drawImage parameters
map.object.brickwall.onload = () => bg_ctx.drawImage(map.object.brickwall, 100, 100, 100, 100);
map.object.woodenbox.onload = () => bg_ctx.drawImage(map.object.woodenbox, 200, 200, 100, 100);
The player is drawing and the rest of the code is working completely fine however, i am not able to draw these images to the canvas, it may be a problem with z-indexes or that the overlaying canvas is not transparent.
Have you tried "window.addEventListener" ?
Because sometimes i got the same issue in some browsers...
try this:
function LoadImages(){
bg_ctx.drawImage(map.object.brickwall, 100, 100, 100, 100);
bg_ctx.drawImage(map.object.woodenbox, 200, 200, 100, 100);
}
window.addEventListener("load", LoadImages);
Had to call the function inside the draw method, not sure why though since the background canvas is never cleared.