Search code examples
htmlgoogle-chromeoperablurry

HTML5 - scaled image is blurry when drawn on canvas in chrome & opera


When i draw scaled image on canvas using the drawImage() function it looks slightly blurry in Chrome & Opera, but if i draw the full size image first and then the scaled one it looks crisp. What causes the blurriness and how can i fix it?

Here is the original image: enter image description here

Here is the result in Chrome & Opera: enter image description here

const img = new Image();

const crisptCanvas = document.getElementById('crisp-canvas');
const crispContext = crisptCanvas.getContext('2d');

const blurryCanvas = document.getElementById('blurry-canvas');
const blurryContext = blurryCanvas.getContext('2d');

const sx = 0, sy = 0, sWidth = 1980, sHeight = 1251;
const dx = 0, dy = 0;
const scaleFactor = 0.4762626262626263;

// Draw an image on canvas
function scaleImage(scale, context)
{
	const dWidth = (sWidth*scale);
	const dHeight = (sHeight*scale);
	context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
};
        
// When image is loaded draw it on both canvases
img.onload = function(){

	// First draw the source image in full scale and then using the -scaleFactor
	setTimeout(()=> {
		scaleImage(1, crispContext);
		scaleImage(scaleFactor, crispContext);
	}, 0);

	// Draw the image using the -scaleFactor
	scaleImage(scaleFactor, blurryContext); 
}

img.src = "https://i.sstatic.net/eWDSw.png"
<canvas width="944" height="596" id="crisp-canvas" ></canvas>
<canvas width="944" height="596" id="blurry-canvas" ></canvas>


Solution

  • Well after one day trying everything i can think of, i was not able to find a solution when i draw the scaled image on canvas.

    Here are some of the things i have tried:
    1) I have tried using the scale() method, but results were the same.
    2) I have tried setting the imageSmoothingEnabled property, that work well with pixelated art games, but for high resolution images the quality was awful.
    3) I have tried using the window.requestAnimationFrame() method than on the first request draw the full scale image, on hidden canvas and after that draw the scaled image on my main canvas. That work well, but when i change the tab(focus on another tab), after a few minutes the images on my main canvas became blurry again. Then i added a method to check when the user focus on my tab, using the Page Visibility API, and again redraw the full scale image on the hidden canvas, but that did not work. Only the last drawn image on the hidden canvas was crisp and all images drawn before the last one were blurry. So the only solution was to create hidden canvases for each image and that is not practical, so i had to try another approach.

    So here is the solution came up with:
    1) Set width and height canvas properties to my original image size 1980x1251
    2) I scaled the canvas, using its style width & height properties

    Have in mind that using this method everything drawn on the canvas like shapes, text, lines... will also be scaled. So for example if you draw a rectangle (10x10) pixels. It will have width and height each equal to 10px, only when the canvas width & height style properties match the canvas width & height properties.
    canvas.style.width = canvas.width + 'px';
    canvas.style.height = canvas.height + 'px';

    const img = new Image();
    
    const crispCanvas = document.getElementById('crisp-canvas');
    const crispContext = crispCanvas.getContext('2d');
    
    const sx = 0, sy = 0, sWidth = 1980, sHeight = 1251;
    const dx = 0, dy = 0;
    const scaleFactor = 0.4762626262626263;
    
    // Set crisp canvas width & height properties to match the source image
    crispCanvas.width = sWidth;
    crispCanvas.height = sHeight;
    
    // Scale the source canvas using width & height style properties 
    function scaleCanvas(scale, canvas) {
    
    	const dWidth =  (sWidth*scale);
    	const dHeight =  (sHeight*scale);
    
    	canvas.style.width = dWidth + 'px';
    	canvas.style.height = dHeight + 'px';
    }
    
    // When image is loaded, scale the crisp canvas and draw the full size image
    img.onload = function(){ 
    	scaleCanvas(scaleFactor, crispCanvas);
    	crispContext.drawImage(img, sx, sy);
    }
    
    img.src = "https://i.sstatic.net/eWDSw.png";
    <canvas id="crisp-canvas" ></canvas>