HTML5 Canvas Pixel Interpolation (v8)

Revision 8 of this benchmark created on


Description

Compare methods to do canvas pixel interpolation / filtering. Compares stuff like nearest-neighbor, bilinear, etc and various optimizations. Please add any optimizations or other interpolation methods to this page.

Preparation HTML

<canvas id="canvas" width="100" height="100"></canvas>

Setup

var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');
    var imageData = context.getImageData(0, 0, 3300, 2550);
    var red, green, blue;
    
    function clamp(lo, value, hi) {
        return value < lo ? lo : value > hi ? hi : value;
    }
    
    function nearest(pixels, x, y, offset, width) {
        return pixels[offset + Math.round(y) * width * 4 + Math.round(x) * 4];
    }
    
    function nearest_unrolled(pixels, x, y, width) {
        var yw4x4 = ((y + 0.5) ^ 0) * width * 4 + ((x + 0.5) ^ 0) * 4;
        return [
            pixels[yw4x4],
            pixels[yw4x4 + 1],
            pixels[yw4x4 + 2]
        ]
    }
    
    function bilinear(pixels, x, y, offset, width) {
        var percentX = 1.0 - (x - Math.floor(x));
        var percentY = y - Math.floor(y);
    
        var top = pixels[offset + Math.ceil(y) * width * 4 + Math.floor(x) * 4] * percentX + pixels[offset + Math.ceil(y) * width * 4 + Math.ceil(x) * 4] * (1.0 - percentX);
        var bottom = pixels[offset + Math.floor(y) * width * 4 + Math.floor(x) * 4] * percentX + pixels[offset + Math.floor(y) * width * 4 + Math.ceil(x) * 4] * (1.0 - percentX);
    
        return top * percentY + bottom * (1.0 - percentY);
    }
    
    function bilinear_optimized(pixels, x, y, offset, width) {
        var percentX = x - (x ^ 0);
        var percentX1 = 1.0 - percentX;
        var percentY = y - (y ^ 0);
        var fx4 = (x ^ 0) * 4;
        var cx4 = fx4 + 4;
        var fy4 = (y ^ 0) * 4;
        var cy4wo = (fy4 + 4) * width + offset;
        var fy4wo = fy4 * width + offset;
    
        var top = pixels[cy4wo + fx4] * percentX1 + pixels[cy4wo + cx4] * percentX;
        var bottom = pixels[fy4wo + fx4] * percentX1 + pixels[fy4wo + cx4] * percentX;
    
        return top * percentY + bottom * (1.0 - percentY);
    }
    
    function bilinear_unrolled(pixels, x, y, width) {
        var percentX = x - (x ^ 0);
        var percentX1 = 1.0 - percentX;
        var percentY = y - (y ^ 0);
        var percentY1 = 1.0 - percentY;
        var fx4 = (x ^ 0) * 4;
        var cx4 = fx4 + 4;
        var fy4 = (y ^ 0) * 4;
        var cy4wr = (fy4 + 4) * width;
        var fy4wr = fy4 * width;
        var cy4wg = cy4wr + 1;
        var fy4wg = fy4wr + 1;
        var cy4wb = cy4wr + 2;
        var fy4wb = fy4wr + 2;
        var top, bottom, r, g, b;
    
        top = pixels[cy4wr + fx4] * percentX1 + pixels[cy4wr + cx4] * percentX;
        bottom = pixels[fy4wr + fx4] * percentX1 + pixels[fy4wr + cx4] * percentX;
        r = top * percentY + bottom * percentY1;
        
        top = pixels[cy4wg + fx4] * percentX1 + pixels[cy4wg + cx4] * percentX;
        bottom = pixels[fy4wg + fx4] * percentX1 + pixels[fy4wg + cx4] * percentX;
        g = top * percentY + bottom * percentY1;
        
        top = pixels[cy4wb + fx4] * percentX1 + pixels[cy4wb + cx4] * percentX;
        bottom = pixels[fy4wb + fx4] * percentX1 + pixels[fy4wb + cx4] * percentX;
        b = top * percentY + bottom * percentY1;
    
        return [r, g, b];
    }
    
    function bicubic_value(x, a, b, c, d) {
        return clamp(0, 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * x) * x) * x + b, 255);
    }
    
    function bicubic(pixels, x, y, offset, width) {
        var v = [0, 0, 0, 0];
        var fx = Math.floor(x);
        var fy = Math.floor(y);
        var percentX = x - fx;
        var percentY = y - fy;
    
        for (var i = -1; i < 3; i++) {
            var yw4o = (fy + i) * width * 4 + offset;
            v[i + 1] = (bicubic_value(percentX, pixels[(fy + i) * width * 4 + (fx - 1) * 4 + offset], pixels[(fy + i) * width * 4 + fx * 4 + offset], pixels[(fy + i) * width * 4 + (fx + 1) * 4 + offset], pixels[(fy + i) * width * 4 + (fx + 2) * 4 + offset]));
        }
    
        return Math.floor(bicubic_value(percentY, v[0], v[1], v[2], v[3]));
    }
    
    function bicubic_optimized(pixels, x, y, offset, width) {
        var a, b, c, d, v0, v1, v2, v3;
        var fx = x ^ 0;
        var fy = y ^ 0;
        var percentX = x - fx;
        var percentY = y - fy;
    
        var fx14 = fx * 4;
        var fx04 = fx14 - 4;
        var fx24 = fx14 + 4;
        var fx34 = fx14 + 8;
        var w4 = width * 4;
        var yw14o = fy * w4 + offset;
        var yw04o = yw14o - w4;
        var yw24o = yw14o + w4;
        var yw34o = yw14o + w4 + w4;
        
        a = pixels[yw04o + fx04];
        b = pixels[yw04o + fx14];
        c = pixels[yw04o + fx24];
        d = pixels[yw04o + fx34];
        v0 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v0 = v0 > 255 ? 255 : v0 < 0 ? 0 : v0;
        
        a = pixels[yw14o + fx04];
        b = pixels[yw14o + fx14];
        c = pixels[yw14o + fx24];
        d = pixels[yw14o + fx34];
        v1 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v1 = v1 > 255 ? 255 : v1 < 0 ? 0 : v1;
        
        a = pixels[yw24o + fx04];
        b = pixels[yw24o + fx14];
        c = pixels[yw24o + fx24];
        d = pixels[yw24o + fx34];
        v2 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v2 = v2 > 255 ? 255 : v2 < 0 ? 0 : v2;
        
        a = pixels[yw34o + fx04];
        b = pixels[yw34o + fx14];
        c = pixels[yw34o + fx24];
        d = pixels[yw34o + fx34];
        v3 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v3 = v3 > 255 ? 255 : v3 < 0 ? 0 : v3;
        
        a = v0;
        b = v1;
        c = v2;
        d = v3;
        a = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentY) * percentY) * percentY + b;
        return a > 255 ? 255 : a < 0 ? 0 : a ^ 0;
    }
    
    function bicubic_unrolled(pixels, x, y, width) {
        var a, b, c, d, v0, v1, v2, v3, r, g, b;
        var fx = x ^ 0;
        var fy = y ^ 0;
        var percentX = x - fx;
        var percentY = y - fy;
    
        var fx14 = fx * 4;
        var fx04 = fx14 - 4;
        var fx24 = fx14 + 4;
        var fx34 = fx14 + 8;
        var w4 = width * 4;
        var yw14r = fy * w4;
        var yw04r = yw14r - w4;
        var yw24r = yw14r + w4;
        var yw34r = yw14r + w4 + w4;
        var yw14g = yw14r + 1;
        var yw04g = yw04r + 1;
        var yw24g = yw24r + 1;
        var yw34g = yw34r + 1;
        var yw14b = yw14r + 2;
        var yw04b = yw04r + 2;
        var yw24b = yw24r + 2;
        var yw34b = yw34r + 2;
        
        // Red
        a = pixels[yw04r + fx04];
        b = pixels[yw04r + fx14];
        c = pixels[yw04r + fx24];
        d = pixels[yw04r + fx34];
        v0 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v0 = v0 > 255 ? 255 : v0 < 0 ? 0 : v0;
        
        a = pixels[yw14r + fx04];
        b = pixels[yw14r + fx14];
        c = pixels[yw14r + fx24];
        d = pixels[yw14r + fx34];
        v1 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v1 = v1 > 255 ? 255 : v1 < 0 ? 0 : v1;
        
        a = pixels[yw24r + fx04];
        b = pixels[yw24r + fx14];
        c = pixels[yw24r + fx24];
        d = pixels[yw24r + fx34];
        v2 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v2 = v2 > 255 ? 255 : v2 < 0 ? 0 : v2;
        
        a = pixels[yw34r + fx04];
        b = pixels[yw34r + fx14];
        c = pixels[yw34r + fx24];
        d = pixels[yw34r + fx34];
        v3 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v3 = v3 > 255 ? 255 : v3 < 0 ? 0 : v3;
    
        a = v0;
        b = v1;
        c = v2;
        d = v3;
        r = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentY) * percentY) * percentY + b;
        r = r > 255 ? 255 : r < 0 ? 0 : r ^ 0;
        
        // Green
        a = pixels[yw04g + fx04];
        b = pixels[yw04g + fx14];
        c = pixels[yw04g + fx24];
        d = pixels[yw04g + fx34];
        v0 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v0 = v0 > 255 ? 255 : v0 < 0 ? 0 : v0;
        
        a = pixels[yw14g + fx04];
        b = pixels[yw14g + fx14];
        c = pixels[yw14g + fx24];
        d = pixels[yw14g + fx34];
        v1 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v1 = v1 > 255 ? 255 : v1 < 0 ? 0 : v1;
        
        a = pixels[yw24g + fx04];
        b = pixels[yw24g + fx14];
        c = pixels[yw24g + fx24];
        d = pixels[yw24g + fx34];
        v2 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v2 = v2 > 255 ? 255 : v2 < 0 ? 0 : v2;
        
        a = pixels[yw34g + fx04];
        b = pixels[yw34g + fx14];
        c = pixels[yw34g + fx24];
        d = pixels[yw34g + fx34];
        v3 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v3 = v3 > 255 ? 255 : v3 < 0 ? 0 : v3;
    
        a = v0;
        b = v1;
        c = v2;
        d = v3;
        g = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentY) * percentY) * percentY + b;
        g = g > 255 ? 255 : g < 0 ? 0 : g ^ 0;
        
        // Blue
        a = pixels[yw04b + fx04];
        b = pixels[yw04b + fx14];
        c = pixels[yw04b + fx24];
        d = pixels[yw04b + fx34];
        v0 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v0 = v0 > 255 ? 255 : v0 < 0 ? 0 : v0;
        
        a = pixels[yw14b + fx04];
        b = pixels[yw14b + fx14];
        c = pixels[yw14b + fx24];
        d = pixels[yw14b + fx34];
        v1 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v1 = v1 > 255 ? 255 : v1 < 0 ? 0 : v1;
        
        a = pixels[yw24b + fx04];
        b = pixels[yw24b + fx14];
        c = pixels[yw24b + fx24];
        d = pixels[yw24b + fx34];
        v2 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v2 = v2 > 255 ? 255 : v2 < 0 ? 0 : v2;
        
        a = pixels[yw34b + fx04];
        b = pixels[yw34b + fx14];
        c = pixels[yw34b + fx24];
        d = pixels[yw34b + fx34];
        v3 = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentX) * percentX) * percentX + b;
        v3 = v3 > 255 ? 255 : v3 < 0 ? 0 : v3;
    
        a = v0;
        b = v1;
        c = v2;
        d = v3;
        b = 0.5 * (c - a + (2.0 * a - 5.0 * b + 4.0 * c - d + (3.0 * (b - c) + d - a) * percentY) * percentY) * percentY + b;
        b = b > 255 ? 255 : b < 0 ? 0 : b ^ 0;
        
        return [r, g, b];
    }

Test runner

Ready to run.

Testing in
TestOps/sec
Nearest Neighbor
var red = nearest(imageData.data, 10.2, 4.6, 0, canvas.width);
var green = nearest(imageData.data, 10.2, 4.6, 1, canvas.width);
var blue = nearest(imageData.data, 10.2, 4.6, 2, canvas.width);
ready
Nearest Neighbor Optimized Unrolled
var rgb = nearest_unrolled(imageData.data, 10.2, 4.6, canvas.width);
ready
Bilinear
var red = bilinear(imageData.data, 10.2, 4.6, 0, canvas.width);
var green = bilinear(imageData.data, 10.2, 4.6, 1, canvas.width);
var blue = bilinear(imageData.data, 10.2, 4.6, 2, canvas.width);
ready
Bilinear Optimized
var red = bilinear_optimized(imageData.data, 10.2, 4.6, 0, canvas.width);
var green = bilinear_optimized(imageData.data, 10.2, 4.6, 1, canvas.width);
var blue = bilinear_optimized(imageData.data, 10.2, 4.6, 2, canvas.width);
ready
Bilinear Optimized Unrolled
var rgb = bilinear_unrolled(imageData.data, 10.2, 4.6, canvas.width);
ready
Bicubic
var red = bicubic(imageData.data, 10.2, 4.6, 0, canvas.width);
var green = bicubic(imageData.data, 10.2, 4.6, 1, canvas.width);
var blue = bicubic(imageData.data, 10.2, 4.6, 2, canvas.width);
ready
Bicubic Optimized
var red = bicubic_optimized(imageData.data, 10.2, 4.6, 0, canvas.width);
var green = bicubic_optimized(imageData.data, 10.2, 4.6, 1, canvas.width);
var blue = bicubic_optimized(imageData.data, 10.2, 4.6, 2, canvas.width);
ready
Bicubic Optimized Unrolled
var rgb = bicubic_unrolled(imageData.data, 10.2, 4.6, canvas.width);
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.