21

I have a small image, which I am rendering on a canvas, like this:

ctx.drawImage(img, 0, 0, img.width*2, img.height*2);

I would like this to show a sharp upsized image (4 identical pixels for each image pixel). However, this code (in Chrome 29 on Mac) makes a blurry image. In Photoshop terms, it looks like it's using "Bicubic" resampling, instead of "Nearest Neighbour".

In a situation where it would be useful (eg. a retro game), Is it possible to produce a sharp upsized image, or do I need to have a seperate image file for each size of the image on the server?

JJJollyjim
  • 5,837
  • 19
  • 56
  • 78
  • best way to do this is to have separate images for each size (fastest, good quality), another way is to have the largest then resize them on the go (minimal storage, good quality). – slash197 Aug 31 '13 at 10:26
  • @slash197 If I downscale, it has the same blur. – JJJollyjim Aug 31 '13 at 10:28
  • 1
    it shouldn't be blurred, do you keep the aspect ratio? – slash197 Aug 31 '13 at 10:30
  • Check out the answer on this question http://stackoverflow.com/questions/2303690/resizing-an-image-in-an-html5-canvas. Actually your question seems to be a duplicate of this. – slash197 Aug 31 '13 at 10:35
  • @slash197 My mistake. Upon further testing, it is actually downscaling properly. It seems quite a waste of bandwidth to do it this way though, I would still love a solution. – JJJollyjim Aug 31 '13 at 10:36
  • @slash197 That question is the opposite of mine. He is getting nearest-neighbour when he wants bicubic, I *want* nearest-neighbour – JJJollyjim Aug 31 '13 at 10:37
  • Use an offscreen canvas and resize there the sprites manually using getImageData. – Pedro L. Aug 31 '13 at 10:38
  • Could you elaborate on that, @PedroL.? – JJJollyjim Aug 31 '13 at 10:44
  • http://phoboslab.org/log/2012/09/drawing-pixels-is-hard – Ian Hern Apr 03 '16 at 04:48

3 Answers3

36

Simply turn off canvas' anti-aliasing for images - unfortunately this property is still vendor prefixed so here are the variations:

context.webkitImageSmoothingEnabled = false;
context.mozImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;

then draw the image.

Optionally for older versions and browsers which hasn't implemented this yet, you can use CSS instead:

canvas {
    image-rendering: optimizeSpeed;             // Older versions of FF
    image-rendering: -moz-crisp-edges;          // FF 6.0+
    image-rendering: -webkit-optimize-contrast; // Webkit (non standard naming)
    image-rendering: -o-crisp-edges;            // OS X & Windows Opera (12.02+)
    image-rendering: crisp-edges;               // Possible future browsers.
    -ms-interpolation-mode: nearest-neighbor;   // IE (non standard naming)
}

ONLINE TEST HERE

Neuron
  • 5,141
  • 5
  • 38
  • 59
  • 1
    Fantastic, a much better (and more elegant) solution than downscaling large files, or `getImageData`. – JJJollyjim Sep 01 '13 at 08:07
  • 2
    The un-prefixed `context.imageSmoothingEnabled` works in Chrome as of at least ver 33. – Luke Mar 20 '14 at 20:17
4

Check this fiddle: http://jsfiddle.net/yPFjg/

It loads the image into a canvas, then creates a resized copy and uses that as sprite.

With few modifications, you can implement an image loader that resizes images on the fly.

var ctx = document.getElementById('canvas1').getContext('2d');
var img = new Image();
var original = document.createElement("canvas");
var scaled = document.createElement("canvas");

img.onload = function() {
    var oc = original.getContext('2d');
    var sc = scaled.getContext('2d');
    oc.canvas.width = oc.canvas.height = 16;
    sc.canvas.width = sc.canvas.height = 32;
    oc.drawImage(this, 0, 0);    
    var od = oc.getImageData(0,0,16,16);
    var sd = sc.getImageData(0,0,32,32);
    for (var x=0; x<32; x++) {
        for (var y=0; y<32; y++) {
            for (var c=0; c<4; c++) {        
                // you can improve these calculations, I let them so for clarity        
                sd.data[(y*32+x)*4+c] = od.data[((y>>1)*16+(x>>1))*4+c];
            }
        }
    }
    sc.putImageData(sd, 0, 0);
    ctx.drawImage(scaled, 0, 0);    
}

img.src = document.getElementById('sprite').src;

Some notes about getImageData: it returns an object with an array. The array has a height*width*4 size. The color components are stored in RGBA order (red, green, blue, alpha, 8 bits each value).

Pedro L.
  • 7,376
  • 3
  • 25
  • 27
0

To resize an image with sharp/pixelated/non-smooothing quality, one resizing method is to use createImageBitmap with the resizeQuality option.

let resizeWidth = imageSource.width * 2;
let resizeHeight = imageSource.height * 2;
let resizeQuality = 'pixelated';
let bitmap = await createImageBitmap(imageSource, {resizeWidth, resizeHeight, resizeQuality});

Then draw the bitmap on a new canvas.

Another resizing method is to use drawImage with the imageSmoothingEnabled, the imageSmoothingQuality option.

let canvas = Object.assign(document.createElement('canvas'), {width: resizeWidth, height: resizeHeight});
let context = canvas.getContext('2d', {colorSpace: 'srgb', pixelFormat: 'unorm8'});
if (resizeQuality === 'pixelated') {
  context.imageSmoothingEnabled = false;
}else{
  context.imageSmoothingEnabled = true;
  context.imageSmoothingQuality = resizeQuality;
}
if (bitmap.width === resizeWidth) { // check if the resizeWith/resizeHeight option is supported
  context.drawImage(bitmap, 0, 0); // draw resized bitmap without resizing
}else{
  context.drawImage(bitmap, 0, 0, resizeWidth, resizeHeight); // draw bitmap with resizing
}
bitmap.close();

See CodePen demo Image Pixelated/Non-smoothing Resizing

fuweichin
  • 1,398
  • 13
  • 14