Disable Interpolation when Scaling a <canvas>

JavascriptHtmlCanvas

Javascript Problem Overview


NOTE: This has to do with how existing canvas elements are rendered when scaled up, not to do with how lines or graphics are rendered onto a canvas surface. In other words, this has everything to do with interpolation of scaled elements, and nothing to do with antialiasing of graphics being drawn on a canvas. I'm not concerned with how the browser draws lines; I care about how the browser renders the canvas element itself when it is scaled up.


Is there a canvas property or browser setting I can change programmatically to disable interpolation when scaling <canvas> elements? A cross-browser solution is ideal but not essential; Webkit-based browsers are my main target. Performance is very important.

This question is most similar but does not illustrate the problem sufficiently. For what it's worth, I have tried image-rendering: -webkit-optimize-contrast to no avail.

The application will be a "retro" 8-bit styled game written in HTML5+JS to make it clear what I need.


To illustrate, here is an example. (live version)

Suppose I have a 21x21 canvas...

<canvas id='b' width='21' height='21'></canvas>

...which has css that makes the element 5 times larger (105x105):

canvas { border: 5px solid #ddd; }
canvas#b { width: 105px; height: 105px; } /* 5 * 21 = 105 */

I draw a simple 'X' on the canvas like so:

$('canvas').each(function () {
    var ctx = this.getContext("2d");
    ctx.moveTo(0,0);
    ctx.lineTo(21,21);
    ctx.moveTo(0,21);
    ctx.lineTo(21,0);
    ctx.stroke();
});

The image on the left is what Chromium (14.0) renders. The image on the right is what I want (hand-drawn for illustrative purposes).

Chrome interpolates scaled canvas elements A non-interpolated version

Javascript Solutions


Solution 1 - Javascript

Last Updated: 2014-09-12

> Is there a canvas property or browser setting I can change programmatically to disable interpolation when scaling elements?

The answer is maybe some day. For now, you'll have to resort to hack-arounds to get what you want.


image-rendering

The working draft of CSS3 outlines a new property, image-rendering that should do what I want:

> The image-rendering property provides a hint to the user-agent about what aspects of an image are most important to preserve when the image is scaled, to aid the user-agent in the choice of an appropriate scaling algorithm.

The specification outlines three accepted values: auto, crisp-edges, and pixelated.

> pixelated: > > When scaling the image up, the "nearest neighbor" or similar algorithm must be used, so that the image appears to be simply composed of very large pixels. When scaling down, this is the same as auto.

Standard? Cross-browser?

Since this is merely a working draft, there's no guarantee that this will become standard. Browser support is currently spotty, at best.

The Mozilla Developer Network has a pretty thorough page dedicated to the current state of the art which I highly recommend reading.

The Webkit developers initially chose to tentatively implement this as -webkit-optimize-contrast, but Chromium/Chrome don't seem to be using a version of Webkit that implements this.

Update: 2014-09-12

Chrome 38 now supports image-rendering: pixelated!

Firefox has a bug report open to get image-rendering: pixelated implemented, but -moz-crisp-edges works for now.

Solution?

The most cross-platform, CSS-only solution so far is thus:

canvas {
  image-rendering: optimizeSpeed;             /* Older versions of FF          */
  image-rendering: -moz-crisp-edges;          /* FF 6.0+                       */
  image-rendering: -webkit-optimize-contrast; /* Safari                        */
  image-rendering: -o-crisp-edges;            /* OS X & Windows Opera (12.02+) */
  image-rendering: pixelated;                 /* Awesome future-browsers       */
  -ms-interpolation-mode: nearest-neighbor;   /* IE                            */
}

Sadly this wont work on all major HTML5 platforms yet (Chrome, in particular).

Of course, one could manually scale up images using nearest-neighbor interpolation onto high-resolution canvas surfaces in javascript, or even pre-scale images server-side, but in my case this will be forbiddingly costly so it is not a viable option.

ImpactJS uses a texture pre-scaling technique to get around all this FUD. Impact's developer, Dominic Szablewski, wrote a very in-depth article about this (he even ended up citing this question in his research).

See Simon's answer for a canvas-based solution that relies on the imageSmoothingEnabled property (not available in older browsers, but simpler than pre-scaling and pretty widely-supported).

Live Demo

If you'd like to test the CSS properties discussed in the MDN article on canvas elements, I've made this fiddle which should display something like this, blurry or not, depending on your browser: a 4:1 (64x64 to 256x256) image of an isometric pixel-art style TV

Solution 2 - Javascript

New answer 7/31/2012

This is finally in the canvas spec!

The specification has recently added a property called imageSmoothingEnabled, which defaults to true and determines if images drawn on non-integer coordinates or drawn scaled will use a smoother algorithm. If it is set to false then nearest-neighbor is used, producing a less smooth image and instead just making larger looking pixels.

Image smoothing has only recently been added to the canvas specification and isn’t supported by all browsers, but some browsers have implemented vendor-prefixed versions of this property. On the context there exists mozImageSmoothingEnabled in Firefox and webkitImageSmoothingEnabled in Chrome and Safari, and setting these to false will stop anti-aliasing from occurring. Unfortunately, at the time of writing, IE9 and Opera have not implemented this property, vendor prefixed or otherwise.


Preview: JSFiddle

Result:

enter image description here

Solution 3 - Javascript

Edit 7/31/2012 - This functionality is now in the canvas spec! See separate answer here:

https://stackoverflow.com/a/11751817/154112

Old answer is below for posterity.


Depending on your desired effect, you have this as one option:

var can = document.getElementById('b');
var ctx = can.getContext('2d');
ctx.scale(5,5);
$('canvas').each(function () {
    var ctx = this.getContext("2d");
    ctx.moveTo(0,0);
    ctx.lineTo(21,21);
    ctx.moveTo(0,21);
    ctx.lineTo(21,0);
    ctx.stroke();
});

http://jsfiddle.net/wa95p/

Which creates this:

enter image description here

Probably not what you want. But if you were merely looking to have zero blur then that would be the ticket so I'll offer it just in case.

A more difficult option is to use pixel manipulation and write an algorithm yourself for the job. Each pixel of the first image becomes a 5x5 block of pixels on the new image. It wouldn't be too hard to do with imagedata.

But Canvas and CSS alone won't help you here for scaling one to the other with the exact effect you desire.

Solution 4 - Javascript

In google chrome, canvas image patterns aren't interpolated.

Here is a working example edited from the namuol answer http://jsfiddle.net/pGs4f/

ctx.scale(4, 4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fillRect(0, 0, 64, 64);

Solution 5 - Javascript

Saviski's workaround explicated here is promising, because it works on:

  • Chrome 22.0.1229.79 Mac OS X 10.6.8
  • Chrome 22.0.1229.79 m Windows 7
  • Chromium 18.0.1025.168 (Developer Build 134367 Linux) Ubuntu 11.10
  • Firefox 3.6.25 Windows 7

But not works in the following, but the same effect can be achieved using CSS image-rendering:

  • Firefox 15.0.1 Mac OS X 10.6.8 (image-rendering:-moz-crisp-edges works in this )
  • Opera 12.02 Mac OS X 10.6.8 (image-rendering:-o-crisp-edges works in this )
  • Opera 12.02 Windows 7 (image-rendering:-o-crisp-edges works in this )

The problematic are these, because ctx.XXXImageSmoothingEnabled is not working and image-rendering is not working:

  • Safari 5.1.7 Mac OS X 10.6.8. (image-rendering:-webkit-optimize-contrast NOT works)

  • Safari 5.1.7 Windows 7 (image-rendering:-webkit-optimize-contrast NOT works)

  • IE 9 Windows 7 (-ms-interpolation-mode:nearest-neighbor NOT works)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnamuolView Question on Stackoverflow
Solution 1 - JavascriptnamuolView Answer on Stackoverflow
Solution 2 - JavascriptSimon SarrisView Answer on Stackoverflow
Solution 3 - JavascriptSimon SarrisView Answer on Stackoverflow
Solution 4 - JavascriptsaviskiView Answer on Stackoverflow
Solution 5 - JavascriptTimo KähkönenView Answer on Stackoverflow