Canvas Pixel Manipulation

JavaScript performance comparison

Revision 88 of this test case created by mattdesl

Info

Tests two different methods of manipulating pixels using the canvas.

NOTE: Now takes endianness into account.

Preparation code

<canvas id="canvas" height="256" width="256"></canvas>
<script>
Benchmark.prototype.setup = function() {
    var canvas = document.getElementById('canvas');
    var canvasWidth  = canvas.width;
    var canvasHeight = canvas.height;
    var ctx = canvas.getContext('2d');
    var imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
   
    var data = imageData.data;
   
    var buf = new ArrayBuffer(imageData.data.length);
    var buf8 = new Uint8ClampedArray(buf);
    var data32 = new Uint32Array(buf);
   
    // Determine whether Uint32 is little- or big-endian.
    data32[1] = 0x0a0b0c0d;
   
    var isLittleEndian = true;
    if (buf[4] === 0x0a && buf[5] === 0x0b && buf[6] === 0x0c &&
        buf[7] === 0x0d) {
        isLittleEndian = false;
    }
   
   
   
   
    !function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.ImageBuffer=e():"undefined"!=typeof global?global.ImageBuffer=e():"undefined"!=typeof self&&(self.ImageBuffer=e())}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){function isLittleEndian(){var a=new ArrayBuffer(4);var b=new Uint8Array(a);var c=new Uint32Array(a);b[0]=161;b[1]=178;b[2]=195;b[3]=212;if(c[0]==3569595041)return true;if(c[0]==2712847316)return false;else{return null}}function isUint8ClampedImageData(){if(typeof Uint8ClampedArray==="undefined")return false;var elem=document.createElement("canvas");var ctx=elem.getContext("2d");if(!ctx)return false;var image=ctx.createImageData(1,1);return image.data instanceof Uint8ClampedArray}var LITTLE_ENDIAN=isLittleEndian();var SUPPORTS_32BIT=typeof ArrayBuffer!=="undefined"&&typeof Uint8ClampedArray!=="undefined"&&typeof Int32Array!=="undefined"&&LITTLE_ENDIAN!==null&&isUint8ClampedImageData();var ImageBuffer=function(width,height){this.imageData=null;if(typeof width!=="number"){this.imageData=width;width=this.imageData.width;height=this.imageData.height}this.width=width;this.height=height;this.pixels=null;this.direct=false;this.uint8=null;if(this.imageData){this.direct=true;if(SUPPORTS_32BIT){this.uint8=this.imageData.data;this.pixels=new Int32Array(this.uint8.buffer)}else{this.pixels=this.uint8=this.imageData.data}}else{if(SUPPORTS_32BIT){this.uint8=new Uint8ClampedArray(width*height*ImageBuffer.NUM_COMPONENTS);this.pixels=new Int32Array(this.uint8.buffer)}else{this.pixels=this.uint8=new Array(width*height*ImageBuffer.NUM_COMPONENTS)}}};ImageBuffer.prototype.constructor=ImageBuffer;ImageBuffer.prototype.setPixelAt=function(x,y,r,g,b,a){var i=~~(x+y*this.width);this.setPixel(i,r,g,b,a)};ImageBuffer.prototype.getColorAt=function(x,y,out){var i=~~(x+y*this.width);return this.getPixel(i,out)};ImageBuffer.prototype.createImage=function(context){var canvas;if(!context){canvas=document.createElement("canvas");context=canvas.getContext("2d")}else{canvas=context.canvas}if(typeof canvas.toDataURL!=="function")throw new Error("Canvas.toDataURL is not supported");canvas.width=this.width;canvas.height=this.height;var imageData=this.imageData;if(!this.direct||!this.imageData){imageData=context.createImageData(this.width,this.height);this.apply(imageData)}context.clearRect(0,0,this.width,this.height);context.putImageData(imageData,0,0);var img=new Image;img.src=canvas.toDataURL.apply(canvas,Array.prototype.slice.call(arguments,1));imageData=null;context=null;canvas=null;return img};ImageBuffer.prototype.apply=function(imageData){if(this.imageData===imageData&&this.direct){return}if(SUPPORTS_32BIT){if(imageData instanceof ImageBuffer){imageData.pixels.set(this.pixels)}else{imageData.data.set(this.uint8)}}else{var data=imageData instanceof ImageBuffer?imageData.pixels:imageData.data;if(!data)throw new Error("imageData must be an ImageBuffer or Canvas ImageData object");var pixels=this.pixels;if(data.length!==pixels.length)throw new Error("the image data for apply() must have the same dimensions");for(var i=0;i<pixels.length;i++){data[i]=pixels[i]}}};ImageBuffer.NUM_COMPONENTS=4;ImageBuffer.SUPPORTS_32BIT=SUPPORTS_32BIT;ImageBuffer.LITTLE_ENDIAN=LITTLE_ENDIAN;if(SUPPORTS_32BIT){if(LITTLE_ENDIAN){ImageBuffer.prototype.setPixel=function(index,r,g,b,a){this.pixels[index]=a<<24|b<<16|g<<8|r}}else{ImageBuffer.prototype.setPixel=function(index,r,g,b,a){this.pixels[index]=r<<24|g<<16|b<<8|a}}}else{ImageBuffer.prototype.setPixel=function(index,r,g,b,a){var pixels=this.pixels;index*=4;pixels[index]=r;pixels[++index]=g;pixels[++index]=b;pixels[++index]=a}}ImageBuffer.prototype.getPixel=function(index,out){var pixels=this.uint8;index*=4;if(!out)out={r:0,g:0,b:0,a:0};out.r=pixels[index];out.g=pixels[++index];out.b=pixels[++index];out.a=pixels[++index];return out};if(LITTLE_ENDIAN){ImageBuffer.packPixel=function(r,g,b,a){return a<<24|b<<16|g<<8|r};ImageBuffer.unpackPixel=function(rgba,out){if(!out)out={r:0,g:0,b:0,a:0};out.a=(rgba&4278190080)>>>24;out.b=(rgba&16711680)>>>16;out.g=(rgba&65280)>>>8;out.r=rgba&255;return out}}else{ImageBuffer.packPixel=function(r,g,b,a){return r<<24|g<<16|b<<8|a};ImageBuffer.unpackPixel=function(rgba,out){if(!out)out={r:0,g:0,b:0,a:0};out.r=(rgba&4278190080)>>>24;out.g=(rgba&16711680)>>>16;out.b=(rgba&65280)>>>8;out.a=rgba&255;return out}}ImageBuffer.fromRGBA=function(rgba,out){if(!out)out={r:0,g:0,b:0,a:0};out.r=(rgba&4278190080)>>>24;out.g=(rgba&16711680)>>>16;out.b=(rgba&65280)>>>8;out.a=rgba&255;return out};ImageBuffer.toRGBA=function(r,g,b,a){return r<<24|g<<16|b<<8|a};ImageBuffer.createColor=function(r,g,b,a){return{r:r||0,g:g||0,b:b||0,a:a||0}};module.exports=ImageBuffer},{}]},{},[1])(1)});
   
    //our direct manipulation buffer...
    var buffer = new ImageBuffer(imageData);
};

Benchmark.prototype.teardown = function() {
    ctx.putImageData(imageData, 0, 0);
};
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
8-bit Pixel Manipulation
for (var y = 0; y < canvasHeight; ++y) {
    for (var x = 0; x < canvasWidth; ++x) {
        var index = (y * canvasWidth + x) * 4;

        var value = x * y & 0xff;

        data[index]   = value;    // red
        data[++index] = value;    // green
        data[++index] = value;    // blue
        data[++index] = 255;      // alpha
    }
}
pending…
32-bit Pixel Manipulation
if (isLittleEndian) {
    for (var y = 0; y < canvasHeight; ++y) {
        for (var x = 0; x < canvasWidth; ++x) {
            var value = x * y & 0xff;

            data32[y * canvasWidth + x] =
                (255   << 24) |    // alpha
                (value << 16) |    // blue
                (value <<  8) |    // green
                 value;            // red
        }
    }
} else {
    for (y = 0; y < canvasHeight; ++y) {
        for (x = 0; x < canvasWidth; ++x) {
            value = x * y & 0xff;

            data32[y * canvasWidth + x] =
                (value << 24) |    // red
                (value << 16) |    // green
                (value <<  8) |    // blue
                 255;              // alpha
        }
    }
}

imageData.data.set(buf8);
pending…
imagebuffer-test
for (var y = 0; y < canvasHeight; ++y) {
        for (var x = 0; x < canvasWidth; ++x) {
            var value = x * y & 0xff;
            buffer.setPixel(y * canvasWidth + x, value, value, value, 255);
        }
    }
pending…
imagebuffer-manual

    var pixels = buffer.pixels;

if (ImageBuffer.LITTLE_ENDIAN) {
    for (var y = 0; y < canvasHeight; ++y) {
        for (var x = 0; x < canvasWidth; ++x) {
            var value = x * y & 0xff;
            pixels[y * canvasWidth + x] =
                (255   << 24) |    // alpha
                (value << 16) |    // blue
                (value <<  8) |    // green
                 value;            // red
        }
    }
} else {

    for (var y = 0; y < canvasHeight; ++y) {
        for (var x = 0; x < canvasWidth; ++x) {
            var value = x * y & 0xff;
            pixels[y * canvasWidth + x] =
                (value << 24) |    // red
                (value << 16) |    // green
                (value <<  8) |    // blue
                 255;              // alpha
        }
    }
}
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment