Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding HTML Retina Canvas Support

Recently I got into HTML canvas drawing and the retina support for it. Without further configuration lines drawn on a canvas-element look a little bit blurry on retina displays.

I do understand that the retina display has four times as much pixels and therefore has to fill some of the device pixels by default (otherwise the picture would be only half the size intended to).

Example:
Draw an HTML canvas with width: 50px and height: 50px.

<canvas height="50px" width="50px">

The browser on a normal screen just draws it (50*50 pixels). The browser on a retina display receives the 50x50px canvas, but since it is retina 50*50 device pixels would be smaller then intended. Therefore the browser draws it 100*100 pixels on retina.

A clear line (single pixel line black/white) on the canvas now becomes a little blurry, since the browser has to fill the nearby pixels and uses the nearby surrounding - some pixels turn out grey.

How to fix it:
Th piece of code should fix the problem:

// Returns: 1 on 'normal' screens, 2 on retina displays
var PIXEL_RATIO = (function () {
    var ctx = document.createElement("canvas").getContext("2d"),
        dpr = window.devicePixelRatio || 1,
        bsr = ctx.webkitBackingStorePixelRatio ||
              ctx.mozBackingStorePixelRatio ||
              ctx.msBackingStorePixelRatio ||
              ctx.oBackingStorePixelRatio ||
              ctx.backingStorePixelRatio || 1;

    return dpr / bsr;
})();


function makeCanvasHiPPI(canvas) {
  canvas.style.width  = canvas.width  + "px";
  canvas.style.height = canvas.height + "px";

  canvas.width  *= PIXEL_RATIO;
  canvas.height *= PIXEL_RATIO;

  var context = canvas.getContext('2d');
  context.scale(PIXEL_RATIO, PIXEL_RATIO)
}

What I think the code does:
With this piece of code used on the canvas the browser is informed to use 50*50px (that is what the canvas.style is set to) as in the example above - 50*50px on normal screens, 100*100px on retinas. But it internally handles the canvas as an canvas of twice the size (at least on retina devices)and therefore is able to draw the pixels which are normally filled by the browser.

The tricky part (I don't quite understand):
The scaling. Double 'canvas.width'? Fine, now we have more pixels we can write to - more information. Good.
But now it is scaled, therefore we don't really write to the extra pixel (only 0 - 50 will show up on the canvas) - who fills the device pixels this time? I tried it without the scaling and it allows me to draw thinner lines on retina (~1 device pixel) on the range 0 - 100 - these pixels are still a little bit blurry though, the scaled lines are thicker, but crystal clear. Fuzzy only visible with a lot of zoom.

Pro scale:

  • crystal lines
  • No scale somehow didn't produce a strong black in my example (visible under strong zoom)

Pro no scale:

  • ability to draw thin lines
  • canvas.width really represents the pixels I can write to. For example:
context.moveTo(0, context.canvas.height/2);
context.lineTo(context.canvas.width, context.canvas.height/2);

works. This doesn't work with scaling, since context.canvas.width returns unscaled values.

My questions:

  • Why is the line clear with scaling, even though I don't even fill the extra pixels by myself?
  • How would I go about the misleading return value?
  • Should I always use context.canvas.width / PIXEL_RATIO (with scaling)? This does fix the problem, but doesn't look nice in the code (OCD kicks in). I will go with this until I got something better though.
like image 948
LastSecondsToLive Avatar asked Mar 05 '16 22:03

LastSecondsToLive


1 Answers

  1. The line might look clear without scaling because canvas has a high dpi backing store in your case: http://www.html5rocks.com/en/tutorials/canvas/hidpi/

  2. I don't understand the question about misleading return values. Everything seems perfectly consistent. Have you tried to set the line width to 0.5 in the "unscaled" case?

  3. It really depends on your use case. If "unscaled" is good enough, go for it. Otherwise, if you need control over DPI for your application, go for the high DPI approach.

like image 51
Stefan Haustein Avatar answered Oct 05 '22 13:10

Stefan Haustein