3

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.stack.imgur.com/eWDSw.png"
<canvas width="944" height="596" id="crisp-canvas" ></canvas>
<canvas width="944" height="596" id="blurry-canvas" ></canvas>
slaviboy
  • 1,407
  • 17
  • 27
  • Strange, when I run your snippet in Chrome, I can't really tell any difference. Are you sure the difference in the two is the initial draw with scale=1, and not the timing? If you move your blurryContext draw into the setInterval (and just for kicks, up the interval to 1000ms), does the difference disappear? – Elliot Nelson Sep 05 '18 at 13:58
  • By the way i am using the latest chrome version - " Version 69.0.3497.81 (Official Build) (64-bit) ", my screen resolution is 1366x768. The only reason i use setTimeout is to let time to first draw the blurry-canvas and after that draw the crisp-canvas, since once i draw the full scale image the problem is fixed and after that i can draw the the scaled image just fine in every canvas. – slaviboy Sep 05 '18 at 16:08
  • Yes, if i set my blurryContext draw in setTimeout with (1s -1000ms) interval, it fixes the problem, but that is expected, since we draw the image in the blurryContext after we first draw the full scaled image. So the problem is why i first need to draw the image in full scale, to get crisp image when i want to scale it. In firefox there is no blurriness, but in Opera the effect also appears since opera and chrome both are using the same rendering engine. – slaviboy Sep 05 '18 at 16:25

1 Answers1

5

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.stack.imgur.com/eWDSw.png";
<canvas id="crisp-canvas" ></canvas>
Decoded
  • 1,057
  • 13
  • 17
slaviboy
  • 1,407
  • 17
  • 27