5

I'm hoping someone out there can help with this as I can't seem to find a solid solution.

Is there a framework or general setup to get HTML canvas rendering nicely, and at the correct size at varying pixel densities?

I understand why the issue exists. I've searched pretty thoroughly so apologies if this has been asked many times before but I still haven't seen a robust solution.

ADDITION: To clarify, canvas renderings look fuzzy on a retina screen, I was thinking there would be a way to get the renderings looking sharp no matter where they are being viewed. The fabric framework looks amazing but their demos still look fuzzy on my retina screen.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Brad
  • 97
  • 1
  • 10
  • JavaScript canvas / WebGL libraries usually implement some kind of resize method. For example, in three.js this method is `renderer.setSize(w, h)`. (http://threejs.org/docs/#Reference/Renderers/WebGLRenderer) maybe vanilla canvas / WebGL has something like this? – Swiffy Dec 16 '15 at 10:17
  • If you don't use CSS, canvas pixel density will be the same as document's one. So just set your canvas `width`and `height` to what you want (e.g. if you want it to take half of the screen : `canvas.width = window.innerWidth/2; canvas.innerHeight = window.innerHeight/2;`) – Kaiido Dec 16 '15 at 10:37
  • probably the demo are running on a different version with no retina scaling added yet, Please checks my links for a test. I got from a retina user confirmation it does not look fuzzy. – AndreaBogazzi Dec 17 '15 at 13:19

4 Answers4

8

fabricjs implement a scaling hack to render the canvas nicely on screens with different ratio between pixel on screen and "logic pixels",

As you tagged your question as retina-scaling I think you are asking about that.

if you do not want to use that framework this is basically how it works in pure javascript ( same implementation of fabricjs more or less)

if( window.devicePixelRatio !== 1 ){
          var c = document.getElementById('mycanvas') // your canvas
          var w = c.width, h = c.height;
    
          // scale the canvas by window.devicePixelRatio
          c.setAttribute('width', w*window.devicePixelRatio);
          c.setAttribute('height', h*window.devicePixelRatio);
    
          // use css to bring it back to regular size
          c.setAttribute('style', 'width:'+w+'px; height:'+h+'px;')
    
          // set the scale of the context
          c.getContext('2d').scale(window.devicePixelRatio, window.devicePixelRatio);
 }

be aware that a browser has to be retina enabled.

Plese check if those images looks different on your retina screen. First is retina enabled, secondo not. (be aware, scary images)

var c = new fabric.StaticCanvas('canvas');
var c2 = new fabric.StaticCanvas('canvas2', {enableRetinaScaling: false});
fabric.Image.fromURL('http://www.free-desktop-backgrounds.net/free-desktop-wallpapers-backgrounds/free-hd-desktop-wallpapers-backgrounds/535693007.jpg', function(img) {
  img.scale(0.5);
  var circle = new fabric.Circle({radius: 40, fill: 'red', stroke: 'blue', top: 2, left: 2});
  
  c.add(img);
  c.add(circle);
  c2.add(img);
  c2.add(circle);
});
<script src="http://www.deltalink.it/andreab/fabric/fabric.js"></script>
<canvas id="canvas" width="800" height="600"></canvas>

<canvas id="canvas2" width="800" height="600"></canvas>

Link with other picture: http://www.deltalink.it/andreab/fabric/retina.html

Edit: Added a vector geometric shape to see if there is more evidence.

Holger Ludvigsen
  • 2,320
  • 1
  • 26
  • 31
AndreaBogazzi
  • 14,323
  • 3
  • 38
  • 63
  • Thanks for answering, to clarify, canvas renderings look fuzzy on a retina screen, I was thinking there would be a way to get the renderings looking sharp no matter where they are being viewed. The fabric framework looks amazing but their demos still look fuzzy on my retina screen e.g. http://fabricjs.com/static_canvas/ – Brad Dec 17 '15 at 05:19
  • i ll link you a demo with retina enabled. this only solution anyway, make a big image, pretend it is smaller, so a retina aware browser will render it high res. – AndreaBogazzi Dec 17 '15 at 07:00
2

If you look at, for example three.js source, it implements resizing like this:

this.setSize = function ( width, height, updateStyle ) {

        _width = width;
        _height = height;

        _canvas.width = width * pixelRatio;
        _canvas.height = height * pixelRatio;

        if ( updateStyle !== false ) {

            _canvas.style.width = width + 'px';
            _canvas.style.height = height + 'px';

        }

        this.setViewport( 0, 0, width, height );

    };

this.setViewport = function ( x, y, width, height ) {

        _viewportX = x * pixelRatio;
        _viewportY = y * pixelRatio;

        _viewportWidth = width * pixelRatio;
        _viewportHeight = height * pixelRatio;

        _gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight );

    };

Where _gl seems to be the canvas context.

It seems like they are just taking a width and height (as in screen width and height for example) and multiplying it with a pixel ratio, which is some integer between like 1-4 as far as I know.

Swiffy
  • 4,401
  • 2
  • 23
  • 49
1

Here is the trick to fixing the blurry canvas issue with higher dpi (devicePixelRatio) screens.

Scale the canvas element dimensions: dpr x css dimensions.

Here is what a dpr of 2.0 would look like

<canvas class="small" width="400" height="400"></canvas> 

CSS

.small {
    width: 200px;
    height: 200px;
}

Use Javascript to detect the dpr and the canvas element dimensions.

const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect(); // css

Calculate the new dimension

canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;

Now you can use CSS classes, @media rules, etc. to set the relative dimensions. Javascript will detect the element size and dpr and return the correct relative size.

Matt
  • 1,388
  • 15
  • 16
0

To create a canvas with given resolution

function createCanvas (width, height) {
    var canvas = document.createElement("canvas");
    canvas.width = width;
    canvas.height = height;
    return canvas;
}
var canvas = createCanvas(1024,1024); // create a canvas that is 1024by1024 pixels

To add it to an element

var element = document.getElementById("uniqueId");
if(element !== null){
   element.appendChild(canvas);
}
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • Thanks for answering, to clarify, canvas renderings look fuzzy on a retina screen, I was thinking there would be a way to get the renderings looking sharp no matter where they are being viewed. The fabric framework looks amazing but their demos still look fuzzy on my retina screen e.g. http://fabricjs.com/static_canvas/ – Brad Dec 17 '15 at 05:20
  • @Brad You need to match the canvas resolution with its display size. See http://stackoverflow.com/q/294250/3877726 on how to get an elements display width and height. Thus get the size of the element that will contain the canvas, adjust that size for any padding, etc, then create the canvas at the calculated resolution and add it to the page. – Blindman67 Dec 17 '15 at 06:29