I'm trying to draw any amount of images onto a canvas while being able to zoom in/out using the mousewheel event, but when adjusting the canvas scale, sometimes the images overlap and the canvas doesn't clear.
Here I get the canvas, create the starting scale, image, and add the eventlistener when it is loaded:
var canvas = document.getElementById("Canvas") // Canvas
var ctx = canvas.getContext("2d") // Context
var scale = 1 // Stored Canvas Scale
var image = new Image() // Random Image
image.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f1/Heart_coraz%C3%B3n.svg/1200px-Heart_coraz%C3%B3n.svg.png"
image.onload = () => {console.log("Running..."); canvas.addEventListener("mousewheel", MouseWheelHandler)}
Next, the reDraw() method clears the canvas and draws any number of images (between 10, or so) onto the canvas, while compensating for the positioning of the images:
function reDraw() { // Draw Image(s)
ctx.clearRect(0, 0, canvas.width, canvas.height) // Clear Canvas
var maxX = parseInt(Math.random()*10) // Set Max Length
var maxY = parseInt(Math.random()*10) // Set Max Height
var size = 32; // Set Image Size
for (var i=0; i<maxX; i++) {
ctx.drawImage(image, canvas.width/2, canvas.height/2-(size*(maxY-i-1)), size, size)
for (var c=0; c<maxY; c++) {
ctx.drawImage(image, canvas.width/2-(size*(maxX-i-1)), canvas.height/2-(size*(maxY-i-1)), size, size)
}
}
}
Finally, the MouseWheelHandler() method gets the mousewheel data and adjusts the canvas scale accordingly:
function MouseWheelHandler(e) {
var e = window.event || e;
var delta = e.wheelDelta || -e.detail
if (scale+(delta/Math.abs(delta*10)) >= 0.1) { // Prevent zooming out further than 10% of view
var zoom = (1+(delta/Math.abs(delta*10))) // Set the zoom
scale += (delta/Math.abs(delta*10)) // Set the scale (No less than 0.1)
ctx.transform(zoom,0,0,zoom,-(zoom-1)*canvas.width/2,-(zoom-1)*canvas.height/2)
reDraw()
}
}
My problem is that if I zoom too far out, the canvas isn't getting cleared and the images appear to kind of echo, or layer on top of one another. I've tried doing ctx.clearRect() with numbers larger and smaller than the canvas size, but it still results in overlapping images.
I just want to be able to zoom in/out and have the canvas only display what was drawn after the MouseWheelHandler runs.
Related
I think this is a chrome issue as it only seems to Chrome, Edge, and OBS. However, that is a problem because that is most of my intended audience. There is no flicker in Firefox so I believe I just need a work around for Chromium based browsers.
The flickering only seems to last for 1 rotation through the animation, however, it will occasionally come back at random times. It also comes back whenever we change the sprite animation
Prior to the below code running we have already imported the image as a Javascript Object. Each image has multiple states we can cycle between. Each state has 10 frames which are loaded as DataURLs and switched between so the flickering would not be caused by loading the images.
Currently we are testing with very few images so the total object size is only a few Kb.
//Set up Canvas Javascript Elements
const canvas = document.getElementById('mainCanvas');
const bufferCanvas = document.getElementById('bufferCanvas');
//Get Canavas 2d context
const ctx = canvas.getContext('2d');
const bctx = bufferCanvas.getContext('2d');
//Set Canvas Size - Broken up into multiple lines to more easily enable and disable elements during testing
const CANVAS_WIDTH = 364;
const CANVAS_HEIGHT = 444;
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
bufferCanvas.width = CANVAS_WIDTH;
bufferCanvas.height = CANVAS_HEIGHT;
//Sets current animation, initial frame, and cycle count
var currentAnimation = 'idle'
var currentFrame = 0;
var cycle = 0;
function animate(){
//Only incrementing frames when the cycle mod 10 is 0 slows it down
if(cycle % 10 == 0){
currentFrame++;
if(currentFrame >= currentImage[currentAnimation].length){
currentFrame = 0;
}
playerImage.src = currentImage[currentAnimation][currentFrame];
}
//Draws the buffer canvas prior to clearing the main canvas
bctx.drawImage(playerImage, 0, 0);
//Clears main Canvas
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
//Draws new image to main Canvas
ctx.drawImage(bufferCanvas, 0, 0);
//Clears buffer canvas once main canvas has been drawn
bctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
//Resets cycle to maintain a manageable number
cycle++;
if(cycle >= 10000){
cycle = 0;
}
//Calls itself to loop
requestAnimationFrame(animate);
}
I have created a basic shape in HTML canvas element which works fine.
The problem occurs when I resize the canvas, all the drawing in the canvas disappears. Is this the normal behavior? or is there a function that can be used to stop this?
One way to fix this could be to call drawing function again on canvas resize however this may not be very efficient if there is huge content to be drawn.
What's the best way?
Here is the link to sample code https://gist.github.com/2983915
You need to redraw the scene when you resize.
setting the width or height of a canvas, even if you are setting it to the same value as before, not only clears the canvas but resets the entire canvas context. Any set properties (fillStyle, lineWidth, the clipping region, etc) will also be reset.
If you do not have the ability to redraw the scene from whatever data structures you might have representing the canvas, you can always save the entire canvas itself by drawing it to an in-memory canvas, setting the original width, and drawing the in-memory canvas back to the original canvas.
Here's a really quick example of saving the canvas bitmap and putting it back after a resize:
http://jsfiddle.net/simonsarris/weMbr/
Everytime you resize the canvas it will reset itself to transparant black, as defined in the spec.
You will either have to:
redraw when you resize the canvas, or,
don't resize the canvas
One another way is to use the debounce if you are concerned with the performance.
It doesnt resize or redraw every position you are dragging. But it will resize only when the it is resized.
// Assume canvas is in scope
addEventListener.("resize", debouncedResize );
// debounce timeout handle
var debounceTimeoutHandle;
// The debounce time in ms (1/1000th second)
const DEBOUNCE_TIME = 100;
// Resize function
function debouncedResize () {
clearTimeout(debounceTimeoutHandle); // Clears any pending debounce events
// Schedule a canvas resize
debounceTimeoutHandle = setTimeout(resizeCanvas, DEBOUNCE_TIME);
}
// canvas resize function
function resizeCanvas () { ... resize and redraw ... }
I had the same problem. Try following code
var wrapper = document.getElementById("signature-pad");
var canvas = wrapper.querySelector("canvas");
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
It keeps the drawing as it is
One thing that worked for me was to use requestAnimationFrame().
let height = window.innerHeight;
let width = window.innerWidth;
function handleWindowResize() {
height = window.innerHeight;
width = window.innerWidth;
}
function render() {
// Draw your fun shapes here
// ...
// Keep this on the bottom
requestAnimationFrame(render);
}
// Canvas being defined at the top of the file.
function init() {
ctx = canvas.getContext("2d");
render();
}
I had the same problem when I had to resize the canvas to adjust it to the screen.
But I solved it with this code:
var c = document.getElementById('canvas');
ctx = c.getContext('2d');
ctx.fillRect(0,0,20,20);
// Save canvas settings
ctx.save();
// Save canvas context
var dataURL = c.toDataURL('image/jpeg');
// Resize canvas
c.width = 50;
c.height = 50;
// Restore canvas context
var img = document.createElement('img');
img.src = dataURL;
img.onload=function(){
ctx.drawImage(img,20,20);
}
// Restote canvas settings
ctx.restore();
<canvas id=canvas width=40 height=40></canvas>
I also met this problem.but after a experiment, I found that Resizing the canvas element will automatically clear all drawings off the canvas!
just try the code below
<canvas id = 'canvas'></canvas>
<script>
var canvas1 = document.getElementById('canvas')
console.log('canvas size',canvas1.width, canvas1.height)
var ctx = canvas1.getContext('2d')
ctx.font = 'Bold 48px Arial'
var f = ctx.font
canvas1.width = 480
var f1 = ctx.font
alert(f === f1) //false
</script>
When I try to draw an image on a canvas with a pre-loaded image with css like:
img.style.backgroundColor="red";
ctx.drawImage(img,0,0,100,100);
I find that the image is being drawn as it would appear without the css. Do HTML canvases support css? If not is there a way to overlay a png with transparency with a color only on the pixels that are not transparent?
Can you specify more about our requirement?
You can not set the background colour of an image which is going to draw directly from the canvas. if you altering a color, it will reflect in the source image.
You have to make it from the source element. If you want to fill the box size with some color, you could use fillStyle of the ctx before draw.
Have a look at the example here on w3schools that will get you started on how to load the image and copy it into the canvas. If you don't need the original image to be shown on the page then add 'style="display:none;"' to the img tag. To tint the image have a look at globalAlpha in combination with a filled rect - something along these lines:
window.onload = function() {
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var img = document.getElementById("scream");
ctx.globalAlpha = 0.5;
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#ff0000";
ctx.fill();
ctx.drawImage(img, 10, 10);
};
Canvas do not render css no matter what css you use for image it will always render plain image unless you draw border or background by yourself
It is possible to overlay the image drawn on a canvas by analyzing each pixel of the drawn image and then overlaying a 1x1 rectangle with the desired color. Here is an example of how to do so:
function overlayColor(img,x,y,a,r,g,b,drawWidth,drawHeight){
//create a canvas in memory and draw the image there to analyze it
var tmpCanvas = document.createElement('canvas');
var context = tmpCanvas.getContext('2d');
tmpCanvas.width = drawWidth;
tmpCanvas.height = drawHeight;
context.drawImage(img,0,0,drawWidth,drawHeight);
let pData = new Array();
for (let i = 0; i < tmpCanvas.width; i++){
for (let j = 0; j < tmpCanvas.height; j++){
if (context.getImageData(i, j, 1, 1).data[3] != 0){//if the pixel is not transparent then add the coordinates to the array
pData.push([i,j]);
}
}
}
//then every pixel that wasn't transparent will be overlayed with a 1x1 rectangle of the inputted color
//drawn at the (x,y) origin which was also given by the user
//this uses ctx, the context of the canvas visible to the user
ctx.fillStyle="rgba(" + r + "," + g + "," + b + "," + a + ")";
for (let i = 0; i < pData.length; i++){
ctx.fillRect(x + pData[i][0],y + pData[i][1],1,1);
}
}
Since the function takes an x and y value the image given by the user will be analyzed and overlayed only on pixels that are not transparent at the coordinate given by the user with the rgba value also given. I have found that this process can result in some lag but it could be overcome by saving the pData array and using the second half of the function to draw the array on screen again.
If the source svg is in a responsive environment, how do I use drawImage() to draw a to a given canvas size?
Example: How do I get the svg drawn to a 412.5 x 487.5 canvas, if the original svg is 550 x 650 and it is being viewed on a mobile device (so obviously the svg will be seen smaller than the original size)?
Fiddle
svgToImage(svg2, function(img2){
ctx2.drawImage(img2, 0, 0);
});
function svgToImage(svg2, callback) {
var nurl = "data:image/svg+xml;utf8," + encodeURIComponent(svg2),
img2 = new Image;
img2.onload = function() {
callback(img2);
}
img2.src = nurl;
}
When you SVG has loaded, simply set the canvas size, then draw in the image using the width and height arguments of drawImage.
Note that a bitmap can only take integer values so your canvas has to be either 413x488 or 412x487. If you don't set the canvas size it will default to 300x150 and then stretch to the size you use for style/CSS:
svgToImage(svg2, function(img2){
ctx2.canvas.width = 413; // set canvas size
ctx2.canvas.height = 488;
ctx2.drawImage(img2, 0, 0, 413, 488); // draw SVG/image at same size
});
Updated fiddle
window.onload = function() {
var Can1 = document.getElementById('canvas2');
var ctx = Can1.getContext("2d");
Can1.width=960;
Can1.height=720;
//ctx.fillStyle = "rgba(0,255,0,0)";
//ctx.fillRect(0,0,960,720);
var imgData=ctx.getImageData(0,0,960,720);
for (var i=0;i<imgData.data.length;i+=4)
{
imgData.data[i]=255/*-imgData.data[i]*/;
imgData.data[i+1]=0/*255-imgData.data[i+1]*/;
imgData.data[i+2]=0/*255-imgData.data[i+2]*/;
imgData.data[i+3]=255-imgData.data[i+3];
}
ctx.putImageData(imgData,0,0);
}
This will change every pixel of your canvas to red with alpha unchanged;
var Can1 = document.getElementById("canvas2");
var ctx = Can1.getContext("2d");
// get all canvas pixel data
imgData = ctx.getImageData(0, 0, Can1.width, Can1.height);
// change every pixel on the canvas to rgba(255,0,0,255);
for (var i=0;i<imgData.data.length;i+=4){
imgData.data[i]=255 // this is the red component of this pixel
imgData.data[i+1]=0 // this is the green component of this pixel
imgData.data[i+2]=0 // this is the blue component of this pixel
// To keep the alpha the same, just leave .data[i+3] unchanged
//imgData.data[i+3]; // this is the alpha component of this pixel
}
// replace all the canvas pixels with your modified pixels
ctx.putImageData(imgData,0,0);