├── package.json ├── README ├── filters.js └── workers_test_heavy.html /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canvasfilters", 3 | "author": "Ilmari Heikkinen ", 4 | "version": "0.0.1", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/kig/canvasfilters" 8 | }, 9 | "main": "./filters.js" 10 | } 11 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Canvas filters 2 | -------------- 3 | 4 | This library implements a few image processing filters using the canvas element. 5 | 6 | The filters operate on ImageData objects. The filters do not modify the 7 | source ImageData. 8 | 9 | Based on http://www.html5rocks.com/en/tutorials/canvas/imagefilters/ 10 | Smoke tests online at http://fhtr.org/canvasfilters/ 11 | 12 | LICENSE 13 | ------- 14 | MIT 15 | 16 | API Documentation 17 | ----------------- 18 | 19 | Filters : { 20 | 21 | 22 | // 23 | // Convenience functions 24 | // 25 | 26 | // filterImage applies a filter function to an image or canvas element. 27 | // Arguments from the third onwards are passed as extra arguments to the filter function. 28 | ImageData filterImage(Function filter, Image_or_Canvas image, Filter_arguments var_args, ...) 29 | 30 | // getPixels returns the ImageData object for an image or a canvas element. 31 | ImageData getPixels(Image_or_Canvas img) 32 | 33 | // toCanvas returns a new canvas filled with the given ImageData object. 34 | Canvas toCanvas(ImageData pixels) 35 | 36 | // getCanvas creates a canvas of the wanted dimensions 37 | Canvas getCanvas(int width, int height) 38 | 39 | // createImageData creates an ImageData object of the wanted dimensions 40 | ImageData createImageData(int width, int height) 41 | 42 | // createImageData creates an ImageData-like object backed by a Float32Array 43 | // of the wanted dimensions 44 | ImageDataFloat32 createImageDataFloat32(int width, int height) 45 | 46 | // bilinearSample bilinearly samples the image at the given coordinates. 47 | // The result is computed by linear blending of the four pixels around x,y. 48 | [r,g,b,a] bilinearSample(ImageData pixels, float x, float y) 49 | 50 | // 51 | // Distort filters 52 | // 53 | 54 | // identity returns a copy of the ImageData 55 | ImageData identity(ImageData pixels) 56 | 57 | // horizontalFlip flips the image left-right 58 | ImageData horizontalFlip(ImageData pixels) 59 | 60 | // verticalFlip flips the image upside down 61 | ImageData verticalFlip(ImageData pixels) 62 | 63 | // distortSine distorts the image by pinching / punching it by the given amount. 64 | // The distort amounts should be between -0.5 and 0.5. 65 | ImageData distortSine(ImageData pixels, float xAmount, float yAmount) 66 | 67 | 68 | // 69 | // Color filters 70 | // 71 | 72 | // luminance converts the image to grayscale using the CIE luminance 73 | // (0.2126*r + 0.7152*g + 0.0722*b) 74 | ImageData luminance(ImageData pixels) 75 | 76 | // grayscale converts the image to grayscale using 77 | // (0.3*r + 0.59*g + 0.11*b) 78 | ImageData grayscale(ImageData pixels) 79 | 80 | // grayscaleAvg converts the image to grayscale using 81 | // (r+g+b) / 3 82 | ImageData grayscaleAvg(ImageData pixels) 83 | 84 | // threshold converts the image to a two-color image with 85 | // pixels brighter than or equal to the threshold value rendered white and 86 | // pixels darker than the threshold rendered black 87 | // The filter uses grayscale to compute the value of a pixel. 88 | // (0.3*r + 0.59*g + 0.11*b) 89 | ImageData threshold(ImageData pixels, int threshold) 90 | 91 | // invert inverts the RGB channels of the image. 92 | // The inverted version of a pixel is [255-r, 255-g, 255-b, a] 93 | ImageData invert(ImageData pixels) 94 | 95 | // invert inverts the RGB channels of the image. 96 | // The inverted version of a pixel is [255-r, 255-g, 255-b, a] 97 | ImageData invert(ImageData pixels) 98 | 99 | // brightnessContrast adjusts the brightness and contrast of the image. 100 | // The brightness value ranges between -1 .. 1, with 0 being neutral. 101 | // The contrast value ranges between 0 .. 127, with 1 being neutral. 102 | ImageData brightnessContrast(ImageData pixels, float brightness, float contrast) 103 | 104 | // applyLUT applies a color lookup table to the image. 105 | // The lookup table is an object of form 106 | // {r:Uint8[256], g:Uint8[256], b:Uint8[256], a:Uint8[256]} 107 | // Result pixel values are calculated by looking up the current value from 108 | // the corresponding lookup table: [lut.r[r], lut.g[g], lut.b[b], lut.a[a]] 109 | ImageData applyLUT(ImageData pixels, LookUpTable lut) 110 | 111 | // 112 | // Convolution filters 113 | // 114 | 115 | // convolve convolves the image using the weights array as a square 116 | // row-major convolution matrix. 117 | // If the opaque argument is set to true the result image will have 118 | // an opaque alpha channel. 119 | ImageData convolve(ImageData pixels, Array weights, bool opaque) 120 | 121 | // horizontalConvolve convolves the image using a horizontal weights vector. 122 | // If the opaque argument is set to true the result image will have 123 | // an opaque alpha channel. 124 | ImageData horizontalConvolve(ImageData pixels, Array weights, bool opaque) 125 | 126 | // verticalConvolve convolves the image using a vertical weights vector. 127 | // If the opaque argument is set to true the result image will have 128 | // an opaque alpha channel. 129 | ImageData verticalConvolve(ImageData pixels, Array weights, bool opaque) 130 | 131 | // separableConvolve convolves the image using vertically and horizontally 132 | // using the supplied vectors. Faster than convolve for separable kernels. 133 | ImageData separableConvolve(ImageData pixels, 134 | Array horizWeights, 135 | Array vertWeights, 136 | bool opaque) 137 | 138 | // convolveFloat32 is a version of convolve that operates on ImageData-like 139 | // objects with a Float32Array storing the pixels 140 | // {width:int, height:int, data:Float32Array}. 141 | // Useful when you need a high value range or negative values in pixels. 142 | ImageDataFloat32 convolveFloat32(ImageData pixels, Array weights, bool opaque) 143 | 144 | // horizontalConvolveFloat32 convolves the image using a horizontal weights 145 | // vector. 146 | // If the opaque argument is set to true the result image will have 147 | // an opaque alpha channel. 148 | ImageDataFloat32 horizontalConvolveFloat32(ImageData pixels, 149 | Array weights, 150 | bool opaque) 151 | 152 | // verticalConvolveFloat32 convolves the image using a vertical weights 153 | // vector. 154 | // Returns a ImageDataFloat32. 155 | // If the opaque argument is set to true the result image will have 156 | // an opaque alpha channel. 157 | ImageDataFloat32 verticalConvolveFloat32(ImageData pixels, 158 | Array weights, 159 | bool opaque) 160 | 161 | // separableConvolveFloat32 convolves the image using vertically and 162 | // horizontally using the supplied vectors. Faster than convolve for separable 163 | // kernels. 164 | // Returns a ImageDataFloat32. 165 | // If the opaque argument is set to true the result image will have 166 | // an opaque alpha channel. 167 | ImageDataFloat32 separableConvolveFloat32(ImageData pixels, 168 | Array horizWeights, 169 | Array vertWeights, 170 | bool opaque) 171 | 172 | 173 | // 174 | // Pre-defined convolution filters 175 | // 176 | 177 | // gaussianBlur applies a gaussian blur kernel of the wanted diameter on the image. 178 | ImageData gaussianBlur(ImageData pixels, float diameter) 179 | 180 | // laplace applies a Laplace edge detection kernel on the image. 181 | ImageData laplace(ImageData pixels) 182 | 183 | // sobel applies a Sobel filter on the image. 184 | // This filter is purely for looks, the red channel encodes absolute vertical 185 | // gradient and the green channel absolute horizontal gradient. 186 | ImageData sobel(ImageData pixels) 187 | 188 | // sobelVectors computes the signed horizontal and vertical gradients of the image 189 | // and returns the array of resulting 2-vectors, packed tightly into a Float32Array 190 | Float32Vec2ImageData sobelVectors(ImageData pixels) 191 | 192 | // sobelVerticalGradient computes the signed vertical gradient of the image 193 | ImageDataFloat32 sobelVerticalGradient(ImageData pixels) 194 | 195 | // sobelHorizontalGradient computes the signed horizontal gradient of the image 196 | ImageDataFloat32 sobelHorizontalGradient(ImageData pixels) 197 | 198 | 199 | // 200 | // Blend operations 201 | // 202 | 203 | // darkenBlend blends b on top of a, replacing a with b whenever b is darker. 204 | // The filter operates on a per-channel basis, the result pixels 205 | // are computed as [min(a.r, b.r), min(a.g, b.g), min(a.b, b.b), alpha(a.a, b.a)] 206 | // where alpha(a, b) = a + (255-a)*b/255. 207 | ImageData darkenBlend(ImageData a, ImageData b) 208 | 209 | // lightenBlend blends b on top of a, replacing a with b whenever b is lighter. 210 | // The filter operates on a per-channel basis, the result pixels 211 | // are computed as [max(a.r, b.r), max(a.g, b.g), max(a.b, b.b), alpha(a.a, b.a)] 212 | // where alpha(a, b) = a + (255-a)*b/255. 213 | ImageData lightenBlend(ImageData a, ImageData b) 214 | 215 | // addBlend blends b on top of a, adding b's values to a. 216 | // [a.r+b.r, a.g+b.g, a.b+b.b, alpha(a.a, b.a)] 217 | // where alpha(a, b) = a + (255-a)*b/255. 218 | ImageData addBlend(ImageData a, ImageData b) 219 | 220 | // subBlend blends b on top of a, subtracting b's values to a. 221 | // [a.r-(255-b.r), a.g-(255-b.g), a.b-(255-b.b), alpha(a.a, b.a)] 222 | // where alpha(a, b) = a + (255-a)*b/255. 223 | ImageData subBlend(ImageData a, ImageData b) 224 | 225 | // multiplyBlend blends b on top of a, multiplying b with a. 226 | // [a.r*b.r/255, a.g*b.g/255, a.b*b.b/255, alpha(a.a, b.a)] 227 | // where alpha(a, b) = a + (255-a)*b/255. 228 | ImageData multiplyBlend(ImageData a, ImageData b) 229 | 230 | // screenBlend blends b on top of a with the screen blend mode. 231 | // Makes a brighter by an amount determined by b. 232 | // [255 - (255 - b.c)*(255 - a.c)/255, ..., alpha(a.a, b.a)] 233 | // where alpha(a, b) = a + (255-a)*b/255. 234 | ImageData screenBlend(ImageData a, ImageData b) 235 | 236 | // differenceBlend blends b on top of a by taking the absolute difference 237 | // between the images. 238 | // [Math.abs(a.c-b.c), alpha(a.a, b.a)] 239 | // where alpha(a, b) = a + (255-a)*b/255. 240 | ImageData differenceBlend(ImageData a, ImageData b) 241 | 242 | } 243 | -------------------------------------------------------------------------------- /filters.js: -------------------------------------------------------------------------------- 1 | Filters = {}; 2 | 3 | if (typeof Float32Array == 'undefined') { 4 | Filters.getFloat32Array = 5 | Filters.getUint8Array = function(len) { 6 | if (len.length) { 7 | return len.slice(0); 8 | } 9 | return new Array(len); 10 | }; 11 | } else { 12 | Filters.getFloat32Array = function(len) { 13 | return new Float32Array(len); 14 | }; 15 | Filters.getUint8Array = function(len) { 16 | return new Uint8Array(len); 17 | }; 18 | } 19 | 20 | if (typeof document != 'undefined') { 21 | Filters.tmpCanvas = document.createElement('canvas'); 22 | Filters.tmpCtx = Filters.tmpCanvas.getContext('2d'); 23 | 24 | Filters.getPixels = function(img) { 25 | var c,ctx; 26 | if (img.getContext) { 27 | c = img; 28 | try { ctx = c.getContext('2d'); } catch(e) {} 29 | } 30 | if (!ctx) { 31 | c = this.getCanvas(img.width, img.height); 32 | ctx = c.getContext('2d'); 33 | ctx.drawImage(img, 0, 0); 34 | } 35 | return ctx.getImageData(0,0,c.width,c.height); 36 | }; 37 | 38 | Filters.createImageData = function(w, h) { 39 | return this.tmpCtx.createImageData(w, h); 40 | }; 41 | 42 | Filters.getCanvas = function(w,h) { 43 | var c = document.createElement('canvas'); 44 | c.width = w; 45 | c.height = h; 46 | return c; 47 | }; 48 | 49 | Filters.filterImage = function(filter, image, var_args) { 50 | var args = [this.getPixels(image)]; 51 | for (var i=2; i= threshold) ? high : low; 205 | dst[i] = dst[i+1] = dst[i+2] = v; 206 | dst[i+3] = d[i+3]; 207 | } 208 | return output; 209 | }; 210 | 211 | Filters.invert = function(pixels) { 212 | var output = Filters.createImageData(pixels.width, pixels.height); 213 | var d = pixels.data; 214 | var dst = output.data; 215 | for (var i=0; i 255 ? 255 : c); 283 | } 284 | return lut; 285 | }; 286 | 287 | Filters.convolve = function(pixels, weights, opaque) { 288 | var side = Math.round(Math.sqrt(weights.length)); 289 | var halfSide = Math.floor(side/2); 290 | 291 | var src = pixels.data; 292 | var sw = pixels.width; 293 | var sh = pixels.height; 294 | 295 | var w = sw; 296 | var h = sh; 297 | var output = Filters.createImageData(w, h); 298 | var dst = output.data; 299 | 300 | var alphaFac = opaque ? 1 : 0; 301 | 302 | for (var y=0; y b[i] ? a[i] : b[i]; 698 | dst[i+1] = a[i+1] > b[i+1] ? a[i+1] : b[i+1]; 699 | dst[i+2] = a[i+2] > b[i+2] ? a[i+2] : b[i+2]; 700 | dst[i+3] = a[i+3]+((255-a[i+3])*b[i+3])*f; 701 | } 702 | return output; 703 | }; 704 | 705 | Filters.multiplyBlend = function(below, above) { 706 | var output = Filters.createImageData(below.width, below.height); 707 | var a = below.data; 708 | var b = above.data; 709 | var dst = output.data; 710 | var f = 1/255; 711 | for (var i=0; i 2 | 3 | Canvas filters smoke tests 4 | 5 | 31 | 32 | 33 |

Canvas filters smoke tests

34 |
35 |
36 |
37 |

Test results

38 |

Test image

39 | 40 |
41 | 97 | 182 | 183 | 184 | --------------------------------------------------------------------------------