41

So I ran across this recently: http://www.nicalis.com/

And I was curious: Is there a way to do this sort of thing with smaller images? I mean, it's pixel art, and rather than using an image with each pixel quadrupled in size couldn't we stretch them with the code? So I started trying to make it happen.

I tried CSS, Javascript, and even HTML, none of which worked. They all blur up really badly (like this: http://jsfiddle.net/nUVJt/2/), which brings me to my question: Can you stretch an image in-browser without any antialiasing?

I'm open to any suggestions, whether it's using a canvas, jQuery, CSS3, or whatever.

Thanks for the help!

EDIT: There's a better way to do this now! A slightly less hackish way! Here's the magic:

.pixelated {
    image-rendering: -moz-crisp-edges;
    image-rendering: -o-crisp-edges;
    image-rendering: -webkit-optimize-contrast;
    -ms-interpolation-mode: nearest-neighbor;
    image-rendering: pixelated;
}

And that'll stop the anti-aliasing in all the modern browsers. It'll even work in IE7-8, but not in 9, and I don't know of any way of doing it in 9, unfortunately (aside from the canvas hack outlined below). It's not even funny how much faster it is doing it this way than with JS. Here's more info on those: https://developer.mozilla.org/en-US/docs/CSS/Image-rendering

EDIT2: Because this isn't an official spec yet, it's not very reliable. Chrome and FF both seem to have stopped supporting it since I wrote the above (according to this article which was mentioned below), which is really annoying. We're probably going to have to wait a few more years before we really can start using this in CSS, which is really unfortunate.

FINAL EDIT: There is an official way to do this now! There's a new property called image-rendering. It's in the CSS3 spec. Support is super spotty right now, but Chrome just added support so before too long we’ll be able to just say image-rendering: pixelated; and it’ll work all the places (yaayy evergreen browsers!)

Timothy Miller
  • 2,391
  • 1
  • 27
  • 34
  • Anti-aliasing is a technique used to smooth pixelated edges. It sounds like you do want to use anti-aliasing. – James Dec 21 '11 at 23:19
  • 8
    @James the entire point is to _preserve_ the pixelated edges, for a retro effect. – Alnitak Dec 21 '11 at 23:34
  • What about the `scale()` method? http://www.w3schools.com/TAGS/canvas_scale.asp.would that work? – Jess Feb 16 '14 at 02:06

5 Answers5

19

The Canvas documentation explicitly does not specify a scaling method - in my own tests it did indeed anti-alias the image quite badly in Firefox.

The code below copies pixel by pixel from a source image (which must be from the same Origin or from a Data URI) and scales it by the specified factor.

An extra off-screen canvas (src_canvas) is required to receive the original source image, and its image data is then copied pixel by pixel into an on-screen canvas.

var img = new Image();
img.src = ...;
img.onload = function() {

    var scale = 8;

    var src_canvas = document.createElement('canvas');
    src_canvas.width = this.width;
    src_canvas.height = this.height;

    var src_ctx = src_canvas.getContext('2d');
    src_ctx.drawImage(this, 0, 0);
    var src_data = src_ctx.getImageData(0, 0, this.width, this.height).data;

    var dst_canvas = document.getElementById('canvas');
    dst_canvas.width = this.width * scale;
    dst_canvas.height = this.height * scale;
    var dst_ctx = dst_canvas.getContext('2d');

    var offset = 0;
    for (var y = 0; y < this.height; ++y) {
        for (var x = 0; x < this.width; ++x) {
            var r = src_data[offset++];
            var g = src_data[offset++];
            var b = src_data[offset++];
            var a = src_data[offset++] / 100.0;
            dst_ctx.fillStyle = 'rgba(' + [r, g, b, a].join(',') + ')';
            dst_ctx.fillRect(x * scale, y * scale, scale, scale);
        }
    }
};

Working demo at http://jsfiddle.net/alnitak/LwJJR/

EDIT a more optimal demo is available at http://jsfiddle.net/alnitak/j8YTe/ that also uses a raw image data array for the destination canvas.

Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • 1
    @Timothy the essential part is the call to `.getImageData()` - the result includes a memory buffer (`.data`) with RGBA values for each pixel currently in the canvas. The two nested loops just read that data and draw appropriately sized squares in the destination canvas. – Alnitak Dec 21 '11 at 23:33
  • 3
    Not to discount your solution (it's exactly what I suggested), but this should be a last resort if you're doing anything remotely complicate as it's going to be _slow_ –  Dec 22 '11 at 02:44
  • 1
    Also, looking at how you did this, you'll likely get better performance by re-creating the pixel array instead of thousands of `fillRect` calls. Loop the array and duplicate every 4 values. –  Dec 22 '11 at 02:49
  • @cwolves yes, it's taking around 120ms on my system for the OP's large image. The `fillRect` method is easier to understand, though. – Alnitak Dec 22 '11 at 08:15
  • @cwolves I made a pixel array copy version - http://jsfiddle.net/2Fkpc/1/ - runs in 30ms vs 120ms. – Alnitak Dec 22 '11 at 08:31
  • you might be a lot better off pre-defining the array as `new Array( source_ary.length * size * size )` as the array is going to be constantly dynamically redefined since it's a compact array. And there's no need to call `getImageData` twice. In any case, there's another advantage of doing it that way -- you have the pixel data in memory so if you need to draw the image multiple times, there's no overhead for the subsequent ones. –  Dec 23 '11 at 02:30
  • @cwolves I don't think you can use a plain `Array` with `putImageData` - but I shall try a version where I do put the entire image in one go - Firefox seems to have performance problems with repeated calls to `putImageData`. – Alnitak Dec 23 '11 at 07:35
  • @cwolves I've done that now - it's at http://jsfiddle.net/alnitak/j8YTe/ and works far better under Firefox now. – Alnitak Dec 23 '11 at 09:44
4

I've gotten this to work for canvas

var canvas = document.getElementById("canvas"),
    context = canvas.getContext('2d');
context.webkitImageSmoothingEnabled = context.imageSmoothingEnabled = context.mozImageSmoothingEnabled = context.oImageSmoothingEnabled = false;
Leonard Meagher
  • 139
  • 1
  • 2
  • In which browsers? It doesn't do anything for me in Chrome unfortunately. Also there's `msImageSmoothingEnabled` to add to your list. – Drew Noakes Jul 13 '14 at 12:43
  • messy solution - inadvertently adds prefixed properties to every browser (for the wrong prefix). Will break any other code that subsequently accesses that context and tries to feature detect the correct prefix. – Alnitak Jul 12 '15 at 20:57
1

The only way I can think of is via canvas. You can try simply drawing the image to a canvas and then scaling the canvas -- I think that you won't get anti-aliasing this way. If you still do, you can try either scaling the image in the draw call or if that doesn't work, you could use getImageData and putImageData to scale the image "by hand"

update: first method works: http://jsfiddle.net/nUVJt/3/

  • I've tried your fiddle on Chrome, Firefox and MSIE - it doesn't work correctly - the image is antialiased in every case. – Alnitak Dec 21 '11 at 23:16
  • Works here, Chrome 18 Mac OSX. It may be a browser-specific thing. The other two techniques are still worth looking at if the first doesn't work. –  Dec 22 '11 at 02:43
0

Nope. The browser does it however it does it, and you get no control over scaling algorithms.

However, I'm sure there is a canvas based solution you could make, but it would probably involve copying pixels out of a source image and strategically drawing them into the canvas. It would be tricky, but possible (and slow).

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
0

im not sure but maybe you can use imagedata. try this function..

function imageToImageData(image) {
     var canvas = document.createElement("canvas");
     canvas.width = image.width;
     canvas.height = image.height;
     var ctx = canvas.getContext("2d");
     ctx.drawImage(image, 0, 0);
     return ctx.getImageData(0, 0, canvas.width, canvas.height);
}
Salt Hareket
  • 764
  • 7
  • 18