How to draw high resolution canvas on Chrome? And why, if devicePixelRatio === webkitBackingStorePixelRatio does scaling to 2x, improves resolution?

I am trying to draw a 300 dots image on a canvas, but in Chrome it shows very poor quality. When I used the code below, it did not improve, but it was because devicePixelRatio was the same as backingStoreRatio (both were 1 ).

Then I tried to change some relationships and found the following:

  • if I change the ratio to 2 and force the scaling code to run, then it will draw on canvas with better resolution.
  • If I changed the ratio to something greater than 2 (e.g. 3 , 4 , 5 , 6 , etc.), then it had poor resolution!

All this was done on a desktop computer.

How can I ensure that the canvas is painting in high resolution?

(Code from: http://www.html5rocks.com/en/tutorials/canvas/hidpi/ )

 /** * Writes an image into a canvas taking into * account the backing store pixel ratio and * the device pixel ratio. * * @author Paul Lewis * @param {Object} opts The params for drawing an image to the canvas */ function drawImage(opts) { if(!opts.canvas) { throw("A canvas is required"); } if(!opts.image) { throw("Image is required"); } // get the canvas and context var canvas = opts.canvas, context = canvas.getContext('2d'), image = opts.image, // now default all the dimension info srcx = opts.srcx || 0, srcy = opts.srcy || 0, srcw = opts.srcw || image.naturalWidth, srch = opts.srch || image.naturalHeight, desx = opts.desx || srcx, desy = opts.desy || srcy, desw = opts.desw || srcw, desh = opts.desh || srch, auto = opts.auto, // finally query the various pixel ratios devicePixelRatio = window.devicePixelRatio || 1, backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1, ratio = devicePixelRatio / backingStoreRatio; // ensure we have a value set for auto. // If auto is set to false then we // will simply not upscale the canvas // and the default behaviour will be maintained if (typeof auto === 'undefined') { auto = true; } // upscale the canvas if the two ratios don't match if (auto && devicePixelRatio !== backingStoreRatio) { var oldWidth = canvas.width; var oldHeight = canvas.height; canvas.width = oldWidth * ratio; canvas.height = oldHeight * ratio; canvas.style.width = oldWidth + 'px'; canvas.style.height = oldHeight + 'px'; // now scale the context to counter // the fact that we've manually scaled // our canvas element context.scale(ratio, ratio); } context.drawImage(pic, srcx, srcy, srcw, srch, desx, desy, desw, desh); } 

Making only the changes below results in high resolution images (why?):

  //WE FORCE RATIO TO BE 2 ratio = 2; //WE FORCE IT TO UPSCALE (event though they're equal) if (auto && devicePixelRatio === backingStoreRatio) { 

If we change the above as relation 3 , it is no longer a high resolution!

EDIT: one extra observation - even with a 2x ratio, although it is noticeably better, it's still not as sharp as displaying an image in the img tag)

+8
javascript html5 google-chrome html5-canvas canvas
source share
2 answers

The HTML5Rocks article related to the question makes things more difficult than they should be, but it makes the same basic mistake as the other resources I saw ( 1 , 2 , 3 , 4 ). These links give some variation on this formula:

 var rect = canvas.getBoundingClientRect(); canvas.width = Math.round (devicePixelRatio * rect.width); // WRONG! 

The formula is incorrect. Best formula

 var rect = canvas.getBoundingClientRect(); canvas.width = Math.round (devicePixelRatio * rect.right) - Math.round (devicePixelRatio * rect.left); 

The fact is that it makes no sense to scale the width or height (i.e. the difference in two positions) using devicePixelRatio. You should only scale the absolute position. I cannot find a link to this exact point, but I think it is obvious as soon as you receive it.

Sentence.

It is not possible to calculate the physical width and height of a rectangle (in device pixels) from its width and CSS height (in pixels independent of the device).

Evidence.

Suppose you have two elements bounding rectangles in device-independent pixels

 { left: 0, top: 10, right: 8, bottom: 20, width: 8, height: 10 }, { left: 1, top: 20, right: 9, bottom: 30, width: 8, height: 10 }. 

Now, assuming devicePixelRatio is 1.4, the elements will span these device pixel rectangles:

 { left: 0, top: 14, right: 11, bottom: 28, width: 11, height: 14 }, { left: 1, top: 28, right: 13, bottom: 42, width: 12, height: 14 }, 

where left, top, right, and bottom were multiplied by devicePixelRatio and rounded to the nearest integer (using Math.round ()).

You will notice that the two rectangles have the same width in device-independent pixels, but differ in width in the device pixels. β–―

Testing.

Here is sample code for testing. Download it in a browser, then zoom in and out with the mouse. There should always be clear lines on the last canvas. The other three will be blurred at some resolutions.

Tested on desktop Firefox, IE, Edge, Chrome and Android Chrome and Firefox. (Note that this does not work on JSfiddle , because getBoundingClientRect returns invalid values ​​there.)

 <!DOCTYPE html> <html> <head> <script> function resize() { var canvases = document.getElementsByTagName("canvas"); var i, j; for (i = 0; i != canvases.length; ++ i) { var canvas = canvases[i]; var method = canvas.getAttribute("method"); var dipRect = canvas.getBoundingClientRect(); var context = canvas.getContext("2d"); switch (method) { case "0": // Incorrect: canvas.width = devicePixelRatio * dipRect.width; canvas.height = devicePixelRatio * dipRect.height; break; case "1": // Incorrect: canvas.width = Math.round(devicePixelRatio * dipRect.width); canvas.height = Math.round(devicePixelRatio * dipRect.height); break; case "2": // Incorrect: canvas.width = Math.floor(devicePixelRatio * dipRect.width); canvas.height = Math.floor(devicePixelRatio * dipRect.height); break; case "3": // Correct: canvas.width = Math.round(devicePixelRatio * dipRect.right) - Math.round(devicePixelRatio * dipRect.left); canvas.height = Math.round(devicePixelRatio * dipRect.bottom) - Math.round(devicePixelRatio * dipRect.top); break; } console.log("method " + method + ", devicePixelRatio " + devicePixelRatio + ", client rect (DI px) (" + dipRect.left + ", " + dipRect.top + ")" + ", " + dipRect.width + " x " + dipRect.height + ", canvas width, height (logical px) " + canvas.width + ", " + canvas.height); context.clearRect(0, 0, canvas.width, canvas.height); context.fillStyle = "cyan"; context.fillRect(0, 0, canvas.width, canvas.height); context.fillStyle = "black"; for (j = 0; j != Math.floor (canvas.width / 2); ++ j) { context.fillRect(2 * j, 0, 1, canvas.height); } } }; addEventListener("DOMContentLoaded", resize); addEventListener("resize", resize); </script> </head> <body> <canvas method="0" style="position: absolute; left: 1px; top: 10px; width: 8px; height: 10px"></canvas> <canvas method="1" style="position: absolute; left: 1px; top: 25px; width: 8px; height: 10px"></canvas> <canvas method="2" style="position: absolute; left: 1px; top: 40px; width: 8px; height: 10px"></canvas> <canvas method="3" style="position: absolute; left: 1px; top: 55px; width: 8px; height: 10px"></canvas> </body> </html> 
+9
source share

One trick you can use is to set the width and height of the canvas to the height and width of the photo pixel, and then use css to resize the image. The following is an example of sudo-code.

 canvasElement.width = imgWidth; canvasElement.height = imgHeight; canvasElement.getContext("2d").drawImage(ARGS); 

Then you can use the width and height adjustment or 3D transformation.

 canvasElement.style.transform = "scale3d(0.5,0.5,0)"; 

or

 canvasElement.style.width = newWidth; canvasElement.style.height = newWidth * imgHeight / imgWidth; 

I suggest you use 3D conversion, because when you use 3D conversion, the image data of the element is copied to the GPU, so you don’t need to worry about any quality degradation.

Hope this helps!

+3
source share

All Articles