14

I'm trying to draw an image on a canvas, then use css to fit the canvas within a certain size. It turns out that many browsers don't scale the canvas down very nicely. Firefox on OS X seems to be one of the worst, but I haven't tested very many. Here is a minimal example of the problem:

HTML

<img>
<canvas></canvas>

CSS

img, canvas {
  width: 125px;
}

JS

var image = document.getElementsByTagName('img')[0],
    canvas = document.getElementsByTagName('canvas')[0];

image.onload = function() {
  canvas.width = image.width;
  canvas.height = image.height;

  var context = canvas.getContext('2d');
  context.drawImage(image, 0, 0, canvas.width, canvas.height);
}

image.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Helvetica_Neue_typeface_weights.svg/783px-Helvetica_Neue_typeface_weights.svg.png"

Running in a codepen: http://codepen.io/ford/pen/GgMzJd

Here's the result in Firefox (screenshot from a retina display):

canvas_scaling_firefox.png

What's happening is that both the <img> and <canvas> start at the same size and are scaled down by the browser with css (the image width is 783px). Apparently, the browser does some nice smoothing/interpolation on the <img>, but not on the <canvas>.

I've tried:

How can I make the image on the right look like the image on the left? Preferably in as little code as possible (I'd rather not implement bicubic interpolation myself, for example).

Cœur
  • 37,241
  • 25
  • 195
  • 267
ford
  • 10,687
  • 3
  • 47
  • 54
  • Browsers have a very simple down sampling for the HTML 5 Canvas, [see this answer here](http://stackoverflow.com/questions/18922880/html5-canvas-resize-downscale-image-high-quality?rq=1). – Spencer Wieczorek Feb 04 '15 at 16:36
  • That answer is a perfect example of what I'm trying to avoid: an incredibly complex hack with (what looks like) serious performance implications. – ford Feb 04 '15 at 16:44
  • Perhaps use media queries to deliver an image that's closer to the display size so the resizing isn't so noticable? – markE Feb 04 '15 at 17:49

4 Answers4

7

You can fix the pixelation issue by scaling the canvas's backing store by the window.devicePixelRatio value. Unfortunately, the shoddy image filtering seems to be a browser limitation at this time, and the only reliable fix is to roll your own.

Replace your current onload with:

image.onload = function() {
  var dpr = window.devicePixelRatio;
  canvas.width = image.width * dpr;
  canvas.height = image.height * dpr;

  var context = canvas.getContext('2d');
  context.drawImage(image, 0, 0, canvas.width, canvas.height);
}

Results:

Results

Tested on Firefox 35.0.1 on Windows 8.1. Note that your current code doesn't handle browser zoom events, which could reintroduce pixelation. You can fix this by handling the resize event.

Community
  • 1
  • 1
mm201
  • 526
  • 4
  • 15
4

Canvas is not quite meant to be css zoomed : Try over-sampling : use twice the required canvas size, and css scaling will do a fine job in down-scaling the canvas.
On hi-dpi devices you should double yet another time the resolution to reach the same quality.

(even on a standard display, X4 shines a bit more).

Image, canvas 1X, 2X and 4X (Image, canvas 1X, 2X and 4X)


var $ = document.getElementById.bind(document);
var image = $('fntimg');

image.onload = function() {
  drawAllImages();
}

image.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Helvetica_Neue_typeface_weights.svg/783px-Helvetica_Neue_typeface_weights.svg.png"

function drawAllImages() {
  drawImage(1);
  drawImage(2);
  drawImage(4);
}

function drawImage(x) {
  console.log('cv' + x + 'X');
  var canvas = $('cv' + x + 'X');
  canvas.width = x * image.width;
  canvas.height = x * image.height;
  var context = canvas.getContext('2d');
  context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
img,
canvas {
  width: 125px;
}
<br>
<img id='fntimg'>
<canvas  id='cv1X'></canvas>
<canvas  id='cv2X'></canvas>
<canvas  id='cv4X'></canvas>
<br>
GameAlchemist
  • 18,995
  • 7
  • 36
  • 59
  • 1
    OK, it looks a bit better when oversampled. But consider the price. You are buying a bit better image at the price of a 2X or 4X canvas. And the most expensive 4X price is likely paid on a mobile device that can least afford it. IMHO, oversampling here is a bad purchase and it's better to create several pre-sized images delivered appropriately with media queries. – markE Feb 07 '15 at 17:44
  • 1
    @markE : I agree, downsizing by hand in an appropriate software will both provide a better result and less battery strain. One might also downscale the image with the right code after loading the image. But if we listen to the O.P. requirement : 'in as little code as possible', i quite only see this solution. – GameAlchemist Feb 07 '15 at 19:10
0

It's not good idea to scale canvas and think that you solved the image scale problem.you can pass your dynamic value to canvas,and then draw with that size whatever you want. here is link of canvas doc: http://www.w3docs.com/learn-javascript/canvas.html

Hazarapet Tunanyan
  • 2,809
  • 26
  • 30
0

Simple answer, you can't do it. The canvas is just like a bitmap, nothing more.

My idea: You should redraw the whole surface on zooming, and make sure you scale the image you're drawing to the canvas. As it is a vector graphic, this should work. But you're going to have to redraw the canvas for sure.

David
  • 1,227
  • 12
  • 23