Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Canvas generated by canvg is blurry on retina screen

I'm using Raphael to draw an object, then transferring it to an HTML canvas element with canvg so that I can use toDataURL to save it as a PNG. But when I use canvg, the resulting image is blurry. The code below, for example, produces this (raphael on top, canvg on bottom):

enter image description here

<html>
    <head>
        <script src="lib/raphael-min.js"></script>
        <script type="text/javascript" src="http://canvg.googlecode.com/svn/trunk/rgbcolor.js"></script> 
        <script type="text/javascript" src="http://canvg.googlecode.com/svn/trunk/StackBlur.js"></script>
        <script type="text/javascript" src="http://canvg.googlecode.com/svn/trunk/canvg.js"></script> 
        <script src="lib/raphael.export.js"></script>
    </head>
    <body>

    <div id="raph_canvas"></div><br> 
    <canvas id="html_canvas" width="50px" height="50px"></canvas>

    <script language="JavaScript">
    var test=Raphael("raph_canvas",50,50);
    var rect=test.rect(0,0,50,50);
    rect.attr({fill: '#fff000', 'fill-opacity':1, 'stroke-width':1})

    window.onload = function() {
        var canvas_svg = test.toSVG();
        canvg('html_canvas',canvas_svg);
        var canvas_html = document.getElementById("html_canvas");
    }

    </script>
    </body>
</html>

The blurriness is evident in the png created by toDataURL as well. Any idea what is going on here? I don't think this has anything to do with re-sizing. I've tried setting ignoreDimensions: True and some other things.

Another datapoint. If I use raphael to output some text and then use canvg, it is not only blurry but the wrong font!

enter image description here

And here is the test.rect(0.5,0.5,50,50) suggested. Still blurry:

enter image description here

like image 952
AustinC Avatar asked Jun 24 '14 19:06

AustinC


2 Answers

Another solution described in this article, similar to the one posted here, except it's using scale() and it's taking into account the pixel ratio of the backing store (browser underlying storage of the canvas):

var devicePixelRatio = window.devicePixelRatio || 1,
    backingStoreRatio = context.webkitBackingStorePixelRatio ||
                        context.mozBackingStorePixelRatio ||
                        context.msBackingStorePixelRatio ||
                        context.oBackingStorePixelRatio ||
                        context.backingStorePixelRatio || 1,

    ratio = devicePixelRatio / backingStoreRatio;

// upscale the canvas if the two ratios don't match
if(devicePixelRatio !== backingStoreRatio){

   // adjust the original width and height of the canvas
   canvas.width = originalWidth * ratio;
   canvas.height = originalHeight * ratio;

   // scale the context to reflect the changes above
   context.scale(ratio, ratio);
}

// ...do the drawing here...

// use CSS to bring the entire thing back to the original size
canvas.style.width = originalWidth + 'px';
canvas.style.height = originalHeight + 'px';
like image 146
nice ass Avatar answered Oct 08 '22 08:10

nice ass


So it took me a while, but then it dawned on me. All your example images are twice the size the code claims they should be. So you're most likely on some sort of HDPI device (Retina MacBook Pro ect...) SVG is great because its resolution independent, canvas on the other hand is not. The issue you're seeing has to do with how canvas renders. To fix this, you need to prep the canvas so that your drawing will be done at the resolution of your screen.

http://jsbin.com/liquxiyi/3/edit?html,js,output

This jsbin example should look great on any screen.

The trick:

var cv = document.getElementById('box');
var ctx = cv.getContext("2d");

// SVG is resolution independent. Canvas is not. We need to make our canvas 
// High Resolution.

// lets get the resolution of our device.
var pixelRatio = window.devicePixelRatio || 1;

// lets scale the canvas and change its CSS width/height to make it high res.
cv.style.width = cv.width +'px';
cv.style.height = cv.height +'px';
cv.width *= pixelRatio;
cv.height *= pixelRatio;

// Now that its high res we need to compensate so our images can be drawn as 
//normal, by scaling everything up by the pixelRatio.
ctx.setTransform(pixelRatio,0,0,pixelRatio,0,0);


// lets draw a box
// or in your case some parsed SVG
ctx.strokeRect(20.5,20.5,80,80);

// lets convert that into a dataURL
var ur = cv.toDataURL();

// result should look exactly like the canvas when using PNG (default)
var result = document.getElementById('result');
result.src=ur;

// we need our image to match the resolution of the canvas
result.style.width = cv.style.width;
result.style.height = cv.style.height;

This should explain the issue you're having, and hopefully point you in a good direction to fix it.

like image 16
ericjbasti Avatar answered Oct 08 '22 09:10

ericjbasti