15

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).

3
  • Browsers have a very simple down sampling for the HTML 5 Canvas, see this answer here. Commented Feb 4, 2015 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. Commented Feb 4, 2015 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? Commented Feb 4, 2015 at 17:49

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

Comments

5

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>

2 Comments

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 : 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.
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

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.