Resizing a canvas image without blurring it
Asked Answered
F

3

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?

Farly answered 31/8, 2013 at 10:20 Comment(9)
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).Pleo
@Pleo If I downscale, it has the same blur.Farly
it shouldn't be blurred, do you keep the aspect ratio?Pleo
Check out the answer on this question #2304190. Actually your question seems to be a duplicate of this.Pleo
@Pleo 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.Farly
@Pleo That question is the opposite of mine. He is getting nearest-neighbour when he wants bicubic, I want nearest-neighbourFarly
Use an offscreen canvas and resize there the sprites manually using getImageData.Racemose
Could you elaborate on that, @PedroL.?Farly
phoboslab.org/log/2012/09/drawing-pixels-is-hardMantra
E
38

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

Eriha answered 1/9, 2013 at 7:10 Comment(2)
Fantastic, a much better (and more elegant) solution than downscaling large files, or getImageData.Farly
The un-prefixed context.imageSmoothingEnabled works in Chrome as of at least ver 33.Wolver
R
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).

Racemose answered 31/8, 2013 at 12:2 Comment(2)
This doesn't work for me in Firefox (no errors in the JS console). i.imgur.com/DDqlxTS.pngFarly
fixed for FF. I've changed the image creation (line 2)Racemose
B
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

Beatrisbeatrisa answered 25/8, 2023 at 11:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.