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:
Pro no scale:
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:
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.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/
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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With