├── LICENSE ├── README.md ├── filter list ├── index.html └── style │ └── style.css └── script ├── jsmanipulate.js ├── minified └── jsmanipulate.min.js └── separate filters ├── blur.js ├── brightness.js ├── bump.js ├── circlesmear.js ├── contrast.js ├── crosssmear.js ├── diffusion.js ├── dither.js ├── edge.js ├── emboss.js ├── exposure.js ├── filterutils.js ├── gain.js ├── gamma.js ├── grayscale.js ├── hue.js ├── invert.js ├── kaleidoscope.js ├── lensdistortion.js ├── linesmear.js ├── maximum.js ├── median.js ├── minimum.js ├── noise.js ├── oil.js ├── opacity.js ├── pinch.js ├── pixelation.js ├── posterize.js ├── rgbadjust.js ├── saturation.js ├── sawtoothripple.js ├── sepia.js ├── sharpen.js ├── sineripple.js ├── solarize.js ├── sparkle.js ├── squaresmear.js ├── threshold.js ├── triangleripple.js ├── twirl.js ├── vignette.js └── waterripple.js /LICENSE: -------------------------------------------------------------------------------- 1 | JSManipulate is licensed under the MIT License: 2 | http://www.opensource.org/licenses/mit-license.php 3 | 4 | Copyright (c) 2011 Joel Besada 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSManipulate 2 | ============ 3 | JSManipulate is an image filter and effects library written in Javascript for 4 | client-side manipulation of images on a web page. 5 | 6 | Version: v1.0 (2011-08-01) 7 | 8 | Developed by Joel Besada (http://www.joelb.me) 9 | 10 | Demo page: http://www.joelb.me/jsmanipulate 11 | 12 | MIT LICENSED (http://www.opensource.org/licenses/mit-license.php) 13 | 14 | Copyright (c) 2011, Joel Besada 15 | 16 | Usage 17 | ------ 18 | 19 | The filters are javascript objects all with a commonly named filter function. 20 | To use any of the filters, you need to input the ImageData object to be manipulated, and any 21 | optional extra parameters taken by the filter. To get the ImageData of an image, 22 | it needs to first be drawn on an HTML5 canvas object, from which you can use the 23 | getImageData function to retrieve the data. 24 | 25 | Here is an example of a common usage scenario: 26 | 27 | var canvas = document.getElementById('#your-canvas-element'); 28 | var context = canvas.getContext("2d"); 29 | 30 | //Get data for the entire image 31 | var data = context.getImageData(0,0,canvas.width, canvas.height) 32 | 33 | //Apply a Lens Distortion effect, with 3.0 refraction and a radius of 75. 34 | //The filter has two more parameters, centerX and centerY, but 35 | //all filters have default values for every parameter, so we can choose not 36 | //to specify these, leaving centerX and centerY at the default 0.5 value. 37 | JSManipulate.lensdistortion.filter(data, {refraction: 3.0, radius: 75}); 38 | 39 | //Now finally put the data back into the context, which will render 40 | //the manipulated image on the page. 41 | context.putImageData(data,0,0); 42 | 43 | If you are using any of the separate filters instead, note that you won't have a JSManipulate 44 | object, so to use the filter you will have to create an instance of that specific filter object: 45 | 46 | //Instead of JSManipulate.lensdistortion.filter(...); 47 | new LensDistortionFilter().filter(data, {refraction: 3.0, radius: 75}); 48 | 49 | You can check out all of the filters and their parameters in index.html in the filter list folder 50 | 51 | Samples 52 | --------- 53 | ### Photo Editing 54 | http://onlinephotomashup.com/ 55 | 56 | Are you using JSManipulate in a creative way in any of your web projects? Feel free to contact me on my 57 | Github profile and I'll make sure to include a link to your project here for everyone to see. 58 | 59 | -------------------------------------------------------------------------------- /filter list/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JSManipulate - Filter List 7 | 8 | 9 |
10 |

JSManipulate Filter List

11 |
12 |

Blur

filter: blur

Default Values

amount: 3

Brightness

filter: brightness

Default Values

amount: 0

Bump

filter: bump

Default Values

N/A

Circle Smear

filter: circlesmear

Default Values

size: 4
density: 0.5
mix: 0.5

Contrast

filter: contrast

Default Values

amount: 1

Cross Smear

filter: crosssmear

Default Values

distance: 8
density: 0.5
mix: 0.5

Diffusion

filter: diffusion

Default Values

scale: 4

Dither

filter: dither

Default Values

levels: 3
color: true

Edge Detection

filter: edge

Default Values

N/A

Emboss

filter: emboss

Default Values

height: 1
angle: 135
elevation: 30

Exposure

filter: exposure

Default Values

exposure: 1

Gain/Bias

filter: gain

Default Values

gain: 0.5
bias: 0.5

Gamma

filter: gamma

Default Values

amount: 1

Grayscale

filter: grayscale

Default Values

N/A

Hue

filter: hue

Default Values

amount: 0

Invert

filter: invert

Default Values

N/A

Kaleidoscope

filter: kaleidoscope

Default Values

angle: 0
rotation: 0
sides: 3
centerX: 0.5
centerY: 0.5

Lens Distortion

filter: lensdistortion

Default Values

refraction: 1.5
radius: 50
centerX: 0.5
centerY: 0.5

Line Smear

filter: linesmear

Default Values

distance: 8
density: 0.5
angle: 0
mix: 0.5

Maximum

filter: maximum

Default Values

N/A

Median

filter: median

Default Values

N/A

Minimum

filter: minimum

Default Values

N/A

Noise

filter: noise

Default Values

amount: 25
density: 1
monochrome: true

Oil Painting

filter: oil

Default Values

range: 3

Opacity

filter: opacity

Default Values

amount: 1

Pinch/Whirl

filter: pinch

Default Values

amount: 0.5
radius: 100
angle: 0
centerX: 0.5
centerY: 0.5

Pixelation

filter: pixelate

Default Values

size: 5

Posterize

filter: posterize

Default Values

levels: 6

RGBAdjust

filter: rgbadjust

Default Values

red: 1
green: 1
blue: 1

Saturation

filter: saturation

Default Values

amount: 1

Sawtooth Ripples

filter: sawtoothripple

Default Values

xAmplitude: 5
yAmplitude: 5
xWavelength: 16
yWavelength: 16

Sepia

filter: sepia

Default Values

amount: 10

Sharpen

filter: sharpen

Default Values

N/A

Sine Ripples

filter: sineripple

Default Values

xAmplitude: 5
yAmplitude: 5
xWavelength: 16
yWavelength: 16

Solarize

filter: solarize

Default Values

N/A

Sparkle

filter: sparkle

Default Values

rays: 50
size: 25
amount: 50
randomness: 25
centerX: 0.5
centerY: 0.5

Square Smear

filter: squaresmear

Default Values

size: 4
density: 0.5
mix: 0.5

Black & White

filter: threshold

Default Values

threshold: 127

Triangle Ripples

filter: triangleripple

Default Values

xAmplitude: 5
yAmplitude: 5
xWavelength: 16
yWavelength: 16

Twirl

filter: twirl

Default Values

radius: 100
angle: 180
centerX: 0.5
centerY: 0.5

Vignette

filter: vignette

Default Values

amount: 0.3

Water Ripples

filter: waterripple

Default Values

phase: 0
radius: 50
wavelength: 16
amplitude: 10
centerX: 0.5
centerY: 0.5
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /filter list/style/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #EEEEEE; 3 | color: #232323; 4 | font: 14px Helvetica, Arial, sans-serif; 5 | line-height: 1.5em; 6 | } 7 | 8 | .filter { 9 | display:block; 10 | color: #EEEEEE; 11 | background-color: #232323; 12 | -moz-border-radius: 5px; 13 | -webkit-border-radius: 5px; 14 | border-radius: 5px; 15 | overflow: auto; 16 | padding: 10px 20px 10px 20px; 17 | } 18 | .default-values, .recommended-values { 19 | float: left; 20 | display: block; 21 | margin-right: 50px; 22 | 23 | } 24 | h2 { 25 | clear: both; 26 | } 27 | h3 { 28 | color: #FFFFFF; 29 | font-size: 14px; 30 | margin-bottom: 5px; 31 | } 32 | #wrapper { 33 | width: 580px; 34 | margin: 0 auto; 35 | } -------------------------------------------------------------------------------- /script/jsmanipulate.js: -------------------------------------------------------------------------------- 1 | /* 2 | ========================================================================= 3 | JSManipulate v1.0 (2011-08-01) 4 | 5 | Javascript image filter & effect library 6 | 7 | Developed by Joel Besada (http://www.joelb.me) 8 | Demo page: http://www.joelb.me/jsmanipulate 9 | 10 | MIT LICENSED (http://www.opensource.org/licenses/mit-license.php) 11 | Copyright (c) 2011, Joel Besada 12 | ========================================================================= 13 | */ 14 | 15 | 16 | /** 17 | * Contains common filter functions. 18 | */ 19 | function FilterUtils(){ 20 | this.HSVtoRGB = function (h, s, v){ 21 | var r, g, b; 22 | var i = Math.floor(h * 6); 23 | var f = h * 6 - i; 24 | var p = v * (1 - s); 25 | var q = v * (1 - f * s); 26 | var t = v * (1 - (1 - f) * s); 27 | switch(i % 6){ 28 | case 0: r = v; g = t; b = p; break; 29 | case 1: r = q; g = v; b = p; break; 30 | case 2: r = p; g = v; b = t; break; 31 | case 3: r = p; g = q; b = v; break; 32 | case 4: r = t; g = p; b = v; break; 33 | case 5: r = v; g = p; b = q; break; 34 | default: break; 35 | } 36 | return [r * 255, g * 255, b * 255]; 37 | }; 38 | this.RGBtoHSV = function (r, g, b){ 39 | r = r/255; g = g/255; b = b/255; 40 | var max = Math.max(r, g, b); 41 | var min = Math.min(r, g, b); 42 | var h, s, v = max; 43 | var d = max - min; 44 | s = max === 0 ? 0 : d / max; 45 | if(max === min){ 46 | h = 0; 47 | }else{ 48 | switch(max){ 49 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 50 | case g: h = (b - r) / d + 2; break; 51 | case b: h = (r - g) / d + 4; break; 52 | default: break; 53 | } 54 | h /= 6; 55 | } 56 | return [h, s, v]; 57 | }; 58 | this.getPixel = function (pixels,x,y,width,height){ 59 | var pix = (y*width + x)*4; 60 | if (x < 0 || x >= width || y < 0 || y >= height) { 61 | return [pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4], 62 | pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 1], 63 | pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 2], 64 | pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 3]]; 65 | } 66 | return [pixels[pix],pixels[pix+1],pixels[pix+2],pixels[pix+3]]; 67 | }; 68 | var haveNextGaussian = false; 69 | var nextGaussian; 70 | this.gaussianRandom = function(){ 71 | if(haveNextGaussian){ 72 | haveNextGaussian = false; 73 | return nextGaussian; 74 | } else { 75 | var v1, v2, s; 76 | do { 77 | v1 = 2 * Math.random() - 1; 78 | v2 = 2 * Math.random() - 1; 79 | s = v1 * v1 + v2 * v2; 80 | } while (s >= 1 || s === 0); 81 | var mult = Math.sqrt(-2 * Math.log(s)/s); 82 | nextGaussian = v2 * mult; 83 | haveNextGaussian = true; 84 | return v1 * mult; 85 | } 86 | }; 87 | this.clampPixel = function (x,a,b){ 88 | return (x < a) ? a : (x > b) ? b : x; 89 | }; 90 | this.triangle = function(x){ 91 | var r = this.mod(x, 1); 92 | return 2*(r < 0.5 ? r : 1-r); 93 | }; 94 | this.mod = function(a,b){ 95 | var n = parseInt(a/b,10); 96 | a -= n*b; 97 | if(a < 0){ 98 | return a + b; 99 | } 100 | return a; 101 | }; 102 | this.mixColors = function(t, rgb1, rgb2){ 103 | var r = this.linearInterpolate(t,rgb1[0],rgb2[0]); 104 | var g = this.linearInterpolate(t,rgb1[1],rgb2[1]); 105 | var b = this.linearInterpolate(t,rgb1[2],rgb2[2]); 106 | var a = this.linearInterpolate(t,rgb1[3],rgb2[3]); 107 | return [r,g,b,a]; 108 | }; 109 | 110 | this.linearInterpolate = function(t,a,b){ 111 | return a + t * (b-a); 112 | }; 113 | this.bilinearInterpolate = function (x,y,nw,ne,sw,se){ 114 | var m0, m1; 115 | var r0 = nw[0]; var g0 = nw[1]; var b0 = nw[2]; var a0 = nw[3]; 116 | var r1 = ne[0]; var g1 = ne[1]; var b1 = ne[2]; var a1 = ne[3]; 117 | var r2 = sw[0]; var g2 = sw[1]; var b2 = sw[2]; var a2 = sw[3]; 118 | var r3 = se[0]; var g3 = se[1]; var b3 = se[2]; var a3 = se[3]; 119 | var cx = 1.0 - x; var cy = 1.0 - y; 120 | 121 | m0 = cx * a0 + x * a1; 122 | m1 = cx * a2 + x * a3; 123 | var a = cy * m0 + y * m1; 124 | 125 | m0 = cx * r0 + x * r1; 126 | m1 = cx * r2 + x * r3; 127 | var r = cy * m0 + y * m1; 128 | 129 | m0 = cx * g0 + x * g1; 130 | m1 = cx * g2 + x * g3; 131 | var g = cy * m0 + y * m1; 132 | 133 | m0 = cx * b0 + x * b1; 134 | m1 = cx * b2 + x * b3; 135 | var b =cy * m0 + y * m1; 136 | return [r,g,b,a]; 137 | }; 138 | this.tableFilter = function (inputData, table, width, height){ 139 | for (var y = 0; y < height; y++) { 140 | for (var x = 0; x < width; x++) { 141 | var pixel = (y*width + x)*4; 142 | for(var i = 0; i < 3; i++){ 143 | inputData[pixel+i] = table[inputData[pixel+i]]; 144 | } 145 | } 146 | } 147 | }; 148 | this.convolveFilter = function(inputData, matrix, width, height){ 149 | var outputData = []; 150 | var rows, cols; 151 | rows = cols = Math.sqrt(matrix.length); 152 | var rows2 = parseInt(rows/2,10); 153 | var cols2 = parseInt(cols/2,10); 154 | var trace = true; 155 | for(var y = 0; y < height; y++){ 156 | for (var x = 0; x < width; x++){ 157 | var pixel = (y*width + x)*4; 158 | var r = 0, g = 0, b = 0; 159 | for(var row = -rows2; row <= rows2; row++){ 160 | var iy = y+row; 161 | var ioffset; 162 | if (0 <= iy && iy < height) { 163 | ioffset = iy*width; 164 | } else { 165 | ioffset = y*width; 166 | } 167 | var moffset = cols*(row+rows2)+cols2; 168 | for (var col = -cols2; col <= cols2; col++) { 169 | var f = matrix[moffset+col]; 170 | if (f !== 0) { 171 | var ix = x+col; 172 | if (!(0 <= ix && ix < width)) { 173 | ix = x; 174 | } 175 | var iPixel = (ioffset+ix)*4; 176 | r += f * inputData[iPixel]; 177 | g += f * inputData[iPixel+1]; 178 | b += f * inputData[iPixel+2]; 179 | } 180 | } 181 | } 182 | outputData[pixel] = parseInt(r+0.5,10); 183 | outputData[pixel+1] = parseInt(g+0.5,10); 184 | outputData[pixel+2] = parseInt(b+0.5,10); 185 | outputData[pixel+3] = inputData[pixel+3]; 186 | } 187 | } 188 | for(var k = 0; k < outputData.length; k++){ 189 | inputData[k] = outputData[k]; 190 | } 191 | }; 192 | this.transformFilter = function(inputData, transformInverse, width, height){ 193 | var out = []; 194 | var outputData = []; 195 | for(var j = 0; j < inputData.length; j++){ 196 | outputData[j] = inputData[j]; 197 | } 198 | for(var y = 0; y < height; y++){ 199 | for (var x = 0; x < width; x++){ 200 | var pixel = (y*width + x)*4; 201 | transformInverse.apply(this,[x,y,out]); 202 | var srcX = Math.floor(out[0]); 203 | var srcY = Math.floor(out[1]); 204 | var xWeight = out[0]-srcX; 205 | var yWeight = out[1]-srcY; 206 | var nw,ne,sw,se; 207 | if(srcX >= 0 && srcX < width-1 && srcY >= 0 && srcY < height-1){ 208 | var i = (width*srcY + srcX)*4; 209 | nw = [inputData[i],inputData[i+1],inputData[i+2],inputData[i+3]]; 210 | ne = [inputData[i+4],inputData[i+5],inputData[i+6],inputData[i+7]]; 211 | sw = [inputData[i+width*4],inputData[i+width*4+1],inputData[i+width*4+2],inputData[i+width*4+3]]; 212 | se = [inputData[i+(width + 1)*4],inputData[i+(width + 1)*4+1],inputData[i+(width + 1)*4+2],inputData[i+(width + 1)*4+3]]; 213 | } else { 214 | nw = this.getPixel( inputData, srcX, srcY, width, height ); 215 | ne = this.getPixel( inputData, srcX+1, srcY, width, height ); 216 | sw = this.getPixel( inputData, srcX, srcY+1, width, height ); 217 | se = this.getPixel( inputData, srcX+1, srcY+1, width, height ); 218 | } 219 | var rgba = this.bilinearInterpolate(xWeight,yWeight,nw,ne,sw,se); 220 | outputData[pixel] = rgba[0]; 221 | outputData[pixel + 1] = rgba[1]; 222 | outputData[pixel + 2] = rgba[2]; 223 | outputData[pixel + 3] = rgba[3]; 224 | } 225 | } 226 | for(var k = 0; k < outputData.length; k++){ 227 | inputData[k] = outputData[k]; 228 | } 229 | }; 230 | } 231 | /** 232 | * Blurs the image with Gaussian blur. 233 | */ 234 | function BlurFilter(){ 235 | this.name = "Blur"; 236 | this.isDirAnimatable = false; 237 | this.defaultValues = { 238 | amount : 3 239 | }; 240 | this.valueRanges = { 241 | amount : {min:0, max:10} 242 | }; 243 | this.filter = function(input,values){ 244 | var width = input.width; 245 | var width4 = width << 2; 246 | var height = input.height; 247 | var inputData = input.data; 248 | var q; 249 | var amount = values.amount; 250 | if (amount < 0.0) { 251 | amount = 0.0; 252 | } 253 | if (amount >= 2.5) { 254 | q = 0.98711 * amount - 0.96330; 255 | } else if (amount >= 0.5) { 256 | q = 3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * amount); 257 | } else { 258 | q = 2 * amount * (3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * 0.5)); 259 | } 260 | var qq = q * q; 261 | var qqq = qq * q; 262 | var b0 = 1.57825 + (2.44413 * q) + (1.4281 * qq ) + (0.422205 * qqq); 263 | var b1 = ((2.44413 * q) + (2.85619 * qq) + (1.26661 * qqq)) / b0; 264 | var b2 = (-((1.4281 * qq) + (1.26661 * qqq))) / b0; 265 | var b3 = (0.422205 * qqq) / b0; 266 | var bigB = 1.0 - (b1 + b2 + b3); 267 | var c = 0; 268 | var index; 269 | var indexLast; 270 | var pixel; 271 | var ppixel; 272 | var pppixel; 273 | var ppppixel; 274 | for (c = 0; c < 3; c++) { 275 | for (var y = 0; y < height; y++) { 276 | index = y * width4 + c; 277 | indexLast = y * width4 + ((width - 1) << 2) + c; 278 | pixel = inputData[index]; 279 | ppixel = pixel; 280 | pppixel = ppixel; 281 | ppppixel = pppixel; 282 | for (; index <= indexLast; index += 4) { 283 | pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; 284 | inputData[index] = pixel; 285 | ppppixel = pppixel; 286 | pppixel = ppixel; 287 | ppixel = pixel; 288 | } 289 | index = y * width4 + ((width - 1) << 2) + c; 290 | indexLast = y * width4 + c; 291 | pixel = inputData[index]; 292 | ppixel = pixel; 293 | pppixel = ppixel; 294 | ppppixel = pppixel; 295 | for (; index >= indexLast; index -= 4) { 296 | pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; 297 | inputData[index] = pixel; 298 | ppppixel = pppixel; 299 | pppixel = ppixel; 300 | ppixel = pixel; 301 | } 302 | } 303 | } 304 | for (c = 0; c < 3; c++) { 305 | for (var x = 0; x < width; x++) { 306 | index = (x << 2) + c; 307 | indexLast = (height - 1) * width4 + (x << 2) + c; 308 | pixel = inputData[index]; 309 | ppixel = pixel; 310 | pppixel = ppixel; 311 | ppppixel = pppixel; 312 | for (; index <= indexLast; index += width4) { 313 | pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; 314 | inputData[index] = pixel; 315 | ppppixel = pppixel; 316 | pppixel = ppixel; 317 | ppixel = pixel; 318 | } 319 | index = (height - 1) * width4 + (x << 2) + c; 320 | indexLast = (x << 2) + c; 321 | pixel = inputData[index]; 322 | ppixel = pixel; 323 | pppixel = ppixel; 324 | ppppixel = pppixel; 325 | for (; index >= indexLast; index -= width4) { 326 | pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; 327 | inputData[index] = pixel; 328 | ppppixel = pppixel; 329 | pppixel = ppixel; 330 | ppixel = pixel; 331 | } 332 | } 333 | } 334 | }; 335 | } 336 | /** 337 | * Adjusts the brightness of the image by going over to HSV values. 338 | * Negative values decrease brightness while positive values increase brightness. 339 | */ 340 | function BrightnessFilter(){ 341 | this.name = "Brightness"; 342 | this.isDirAnimatable = true; 343 | this.defaultValues = { 344 | amount : 0.0 345 | }; 346 | this.valueRanges = { 347 | amount : {min:-1.0, max:1.0} 348 | }; 349 | var filterUtils = new FilterUtils(); 350 | this.filter = function(input,values){ 351 | var width = input.width, height = input.height; 352 | var inputData = input.data; 353 | if(values === undefined){ values = this.defaultValues; } 354 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 355 | for (var y = 0; y < height; y++) { 356 | for (var x = 0; x < width; x++) { 357 | var pixel = (y*width + x)*4; 358 | var hsv = filterUtils.RGBtoHSV(inputData[pixel],inputData[pixel+1],inputData[pixel+2]); 359 | hsv[2] += amount; 360 | if(hsv[2] < 0){ 361 | hsv[2] = 0; 362 | } else if (hsv[2] > 1){ 363 | hsv[2] = 1; 364 | } 365 | var rgb = filterUtils.HSVtoRGB(hsv[0],hsv[1],hsv[2]); 366 | for(var i = 0; i < 3; i++){ 367 | inputData[pixel+i] = rgb[i]; 368 | } 369 | } 370 | } 371 | }; 372 | } 373 | /** 374 | * Embosses the edges of the image. 375 | * This filter takes no parameters but can be applied several times for 376 | * further effect. 377 | */ 378 | function BumpFilter(){ 379 | this.name = "Bump"; 380 | this.isDirAnimatable = true; 381 | this.defaultValues = { 382 | }; 383 | this.valueRanges = { 384 | }; 385 | var filterUtils = new FilterUtils(); 386 | this.filter = function(input,values){ 387 | var width = input.width, height = input.height; 388 | var inputData = input.data; 389 | var matrix = [-1,-1, 0, 390 | -1, 1, 1, 391 | 0, 1, 1]; 392 | filterUtils.convolveFilter(inputData,matrix,width,height); 393 | }; 394 | } 395 | /** 396 | * Smears out the image with circular shapes to create a painting style effect. 397 | * The mix values sets the intensity of the effect. 398 | * NOTE: This filter can be very slow, especially at higher densities/sizes. Use with caution. 399 | */ 400 | function CircleSmearFilter(){ 401 | this.name = "Circle Smear"; 402 | this.isDirAnimatable = false; 403 | this.defaultValues = { 404 | size : 4, 405 | density : 0.5, 406 | mix : 0.5 407 | }; 408 | this.valueRanges = { 409 | size : {min:1, max:10}, 410 | density : {min:0.0, max:1.0}, 411 | mix : {min:0.0, max:1.0} 412 | }; 413 | 414 | var filterUtils = new FilterUtils(); 415 | this.filter = function(input,values){ 416 | var width = input.width, height = input.height; 417 | var inputData = input.data; 418 | var outputData = []; 419 | for(var j = 0; j < inputData.length; j++){ 420 | outputData[j] = inputData[j]; 421 | } 422 | if(values === undefined){ values = this.defaultValues; } 423 | var size = (values.size === undefined) ? this.defaultValues.size : values.size; 424 | if(size < 1){ size = 1;} 425 | size = parseInt(size,10); 426 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 427 | var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; 428 | var radius = size+1; 429 | var radius2 = radius*radius; 430 | var numShapes = parseInt(2*density/30*width*height / 2,10); 431 | for(var i = 0; i < numShapes; i++){ 432 | var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; 433 | var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; 434 | var rgb2 = [inputData[(sy*width+sx)*4],inputData[(sy*width+sx)*4+1],inputData[(sy*width+sx)*4+2],inputData[(sy*width+sx)*4+3]]; 435 | for(var x = sx - radius; x < sx + radius + 1; x++){ 436 | for(var y = sy - radius; y < sy + radius + 1; y++){ 437 | var f = (x - sx) * (x - sx) + (y - sy) * (y - sy); 438 | if (x >= 0 && x < width && y >= 0 && y < height && f <= radius2) { 439 | var rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 440 | var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); 441 | for(var k = 0; k < 3; k++){ 442 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 443 | } 444 | } 445 | } 446 | } 447 | } 448 | for(var l = 0; l < outputData.length; l++){ 449 | inputData[l] = outputData[l]; 450 | } 451 | }; 452 | } 453 | /** 454 | * Adjusts the contrast of the image. 455 | */ 456 | function ContrastFilter(){ 457 | this.name = "Contrast"; 458 | this.isDirAnimatable = true; 459 | this.defaultValues = { 460 | amount : 1.0 461 | }; 462 | this.valueRanges = { 463 | amount : {min:0.0, max:2.0} 464 | }; 465 | if(!FilterUtils){ 466 | if(console){ 467 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 468 | } 469 | return; 470 | } 471 | var filterUtils = new FilterUtils(); 472 | this.filter = function(input,values){ 473 | var width = input.width, height = input.height; 474 | var inputData = input.data; 475 | if(values === undefined){ values = this.defaultValues; } 476 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 477 | if(amount < 0){ 478 | amount = 0.0; 479 | } 480 | var table = []; 481 | 482 | for(var i = 0; i < 256; i++){ 483 | table[i] = parseInt(255 * (((i/255)-0.5)*amount+0.5),10); 484 | } 485 | filterUtils.tableFilter(inputData,table,width,height); 486 | }; 487 | } 488 | /** 489 | * Smears out the image with cross shapes to create a painting style effect. 490 | * The mix values sets the intensity of the effect. 491 | */ 492 | function CrossSmearFilter(){ 493 | this.name = "Cross Smear"; 494 | this.isDirAnimatable = false; 495 | this.defaultValues = { 496 | distance : 8, 497 | density : 0.5, 498 | mix : 0.5 499 | }; 500 | this.valueRanges = { 501 | distance : {min:0, max:30}, 502 | density : {min:0.0, max:1.0}, 503 | mix : {min:0.0, max:1.0} 504 | }; 505 | 506 | var filterUtils = new FilterUtils(); 507 | this.filter = function(input,values){ 508 | var width = input.width, height = input.height; 509 | var inputData = input.data; 510 | var outputData = []; 511 | for(var j = 0; j < inputData.length; j++){ 512 | outputData[j] = inputData[j]; 513 | } 514 | if(values === undefined){ values = this.defaultValues; } 515 | var distance = (values.distance === undefined) ? this.defaultValues.distance : values.distance; 516 | if(distance < 0){ distance = 0;} 517 | distance = parseInt(distance,10); 518 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 519 | var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; 520 | var numShapes = parseInt(2*density*width * height / (distance + 1),10); 521 | for(var i = 0; i < numShapes; i++){ 522 | var x = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; 523 | var y = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; 524 | var length = (Math.random()*Math.pow(2,32)) % distance + 1; 525 | var rgb2 = [inputData[(y*width+x)*4],inputData[(y*width+x)*4+1],inputData[(y*width+x)*4+2],inputData[(y*width+x)*4+3]]; 526 | var rgb1; 527 | var mixedRGB; 528 | var k; 529 | for (var x1 = x-length; x1 < x+length+1; x1++) { 530 | if(x1 >= 0 && x1 < width){ 531 | rgb1 = [outputData[(y*width+x1)*4],outputData[(y*width+x1)*4+1],outputData[(y*width+x1)*4+2],outputData[(y*width+x1)*4+3]]; 532 | mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); 533 | for(k = 0; k < 3; k++){ 534 | outputData[(y*width+x1)*4+k] = mixedRGB[k]; 535 | } 536 | } 537 | 538 | } 539 | for (var y1 = y-length; y1 < y+length+1; y1++) { 540 | if(y1 >= 0 && y1 < height){ 541 | rgb1 = [outputData[(y1*width+x)*4],outputData[(y1*width+x)*4+1],outputData[(y1*width+x)*4+2],outputData[(y1*width+x)*4+3]]; 542 | mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); 543 | for(k = 0; k < 3; k++){ 544 | outputData[(y1*width+x)*4+k] = mixedRGB[k]; 545 | } 546 | } 547 | 548 | } 549 | } 550 | for(var l = 0; l < outputData.length; l++){ 551 | inputData[l] = outputData[l]; 552 | } 553 | }; 554 | } 555 | /** 556 | * Diffuses the image creating a frosted glass effect. 557 | */ 558 | function DiffusionFilter(){ 559 | this.name = "Diffusion"; 560 | this.isDirAnimatable = false; 561 | this.defaultValues = { 562 | scale: 4 563 | }; 564 | this.valueRanges = { 565 | scale: {min: 1, max: 100} 566 | }; 567 | 568 | var filterUtils = new FilterUtils(); 569 | this.filter = function (input, values){ 570 | var width = input.width, height = input.height; 571 | var inputData = input.data; 572 | if(values === undefined){ values = this.defaultValues; } 573 | var scale = (values.scale === undefined) ? this.defaultValues.scale : values.scale; 574 | var out = []; 575 | var outputData = []; 576 | var sinTable = []; 577 | var cosTable = []; 578 | for(var i = 0; i < 256; i++){ 579 | var angle = Math.PI*2*i/256; 580 | sinTable[i] = scale*Math.sin(angle); 581 | cosTable[i] = scale*Math.cos(angle); 582 | } 583 | transInverse = function (x,y,out){ 584 | var angle = parseInt(Math.random() * 255,10); 585 | var distance = Math.random(); 586 | out[0] = x + distance * sinTable[angle]; 587 | out[1] = y + distance * cosTable[angle]; 588 | }; 589 | filterUtils.transformFilter(inputData,transInverse,width,height); 590 | }; 591 | } 592 | /** 593 | * Dithers the image to the specified number of colors. Setting color to false 594 | * grayscales the image. 595 | */ 596 | function DitherFilter(){ 597 | this.name = "Dither"; 598 | this.isDirAnimatable = false; 599 | this.defaultValues = { 600 | levels : 3, 601 | color : true 602 | }; 603 | this.valueRanges = { 604 | levels : {min:2, max:30}, 605 | color : {min:false, max:true} 606 | }; 607 | 608 | var filterUtils = new FilterUtils(); 609 | this.filter = function(input,values){ 610 | var width = input.width, height = input.height; 611 | var inputData = input.data; 612 | var outputData = []; 613 | var i, j; 614 | for (j=0; j < inputData.length; j++) { 615 | outputData[j] = 0; 616 | } 617 | if(values === undefined){ values = this.defaultValues; } 618 | var levels = (values.levels === undefined) ? this.defaultValues.levels : values.levels; 619 | var color = (values.color === undefined) ? this.defaultValues.color : values.color; 620 | if(levels <= 1){ 621 | levels = 1; 622 | } 623 | var matrix = [0,0,0, 624 | 0,0,7, 625 | 3,5,1]; 626 | var sum = 7+3+5+1; 627 | var index = 0; 628 | var map = []; 629 | 630 | for (i=0; i < levels; i++) { 631 | map[i] = parseInt(255* i / (levels-1),10); 632 | } 633 | var div = []; 634 | for (i=0; i < 256; i++) { 635 | div[i] = parseInt(levels*i / 256,10); 636 | } 637 | for (var y = 0; y < height; y++) { 638 | var reverse = ((y & 1) == 1); 639 | var direction; 640 | if(reverse){ 641 | index = (y*width+width-1)*4; 642 | direction = -1; 643 | } else { 644 | index = y*width*4; 645 | direction = 1; 646 | } 647 | for (var x = 0; x < width; x++) { 648 | var r1 = inputData[index]; var g1 = inputData[index+1]; var b1 = inputData[index+2]; 649 | if(!color){ 650 | r1 = g1 = b1 = parseInt((r1+g1+b1) / 3,10); 651 | } 652 | var r2 = map[div[r1]];var g2 = map[div[g1]];var b2 = map[div[b1]]; 653 | 654 | outputData[index] = r2; outputData[index + 1] = g2; outputData[index+2] = b2; outputData[index+3] = inputData[index+3]; 655 | 656 | var er = r1-r2; var eg = g1-g2; var eb = b1-b2; 657 | 658 | for (i = -1; i <= 1; i++) { 659 | var iy = i+y; 660 | if (0 <= iy && iy < height) { 661 | for (j = -1; j <= 1; j++) { 662 | var jx = j+x; 663 | if (0 <= jx && jx < width) { 664 | var w; 665 | if (reverse){ 666 | w = matrix[(i+1)*3-j+1]; 667 | } else{ 668 | w = matrix[(i+1)*3+j+1]; 669 | } 670 | if (w !== 0) { 671 | var k = (reverse) ? index - j*4 : index + j*4; 672 | r1 = inputData[k]; g1 = inputData[k+1]; b1 = inputData[k+2]; 673 | var factor = w/sum; 674 | r1 += er * factor; g1 += eg * factor; b1 += eb * factor; 675 | inputData[k] = r1; inputData[k+1] = g1 ;inputData[k+2] = b1; 676 | } 677 | } 678 | } 679 | } 680 | } 681 | index += direction*4; 682 | } 683 | } 684 | for(j = 0; j < outputData.length; j++){ 685 | inputData[j] = outputData[j]; 686 | } 687 | }; 688 | } 689 | /** 690 | * Highlights the edges of the image. 691 | */ 692 | function EdgeFilter(){ 693 | this.name = "Edge Detection"; 694 | this.isDirAnimatable = true; 695 | this.defaultValues = { 696 | }; 697 | this.valueRanges = { 698 | }; 699 | var matrixH = [-1,-2,-1, 700 | 0, 0, 0, 701 | 1, 2, 1]; 702 | var matrixV = [-1, 0, 1, 703 | -2, 0, 2, 704 | -1, 0, 1]; 705 | this.filter = function(input,values){ 706 | var width = input.width, height = input.height; 707 | var inputData = input.data; 708 | var outputData = []; 709 | for (var y = 0; y < height; y++) { 710 | for (var x = 0; x < width; x++) { 711 | var pixel = (y*width + x)*4; 712 | var rh = 0; gh = 0; bh = 0; 713 | var rv = 0; gv = 0; bv = 0; 714 | for(var row = -1; row <= 1; row++){ 715 | var iy = y+row; 716 | var ioffset; 717 | if(iy >= 0 && iy < height){ 718 | ioffset = iy*width*4; 719 | } else { 720 | ioffset = y*width*4; 721 | } 722 | var moffset = 3*(row+1)+1; 723 | for(var col = -1; col <= 1; col++){ 724 | var ix = x+col; 725 | if(!(ix >= 0 && ix < width)){ 726 | ix = x; 727 | } 728 | ix *= 4; 729 | var r = inputData[ioffset+ix]; 730 | var g = inputData[ioffset+ix+1]; 731 | var b = inputData[ioffset+ix+2]; 732 | var h = matrixH[moffset+col]; 733 | var v = matrixV[moffset+col]; 734 | rh += parseInt(h*r,10); 735 | bh += parseInt(h*g,10); 736 | gh += parseInt(h*b,10); 737 | rv += parseInt(v*r,10); 738 | gv += parseInt(v*g,10); 739 | bv += parseInt(v*b,10); 740 | } 741 | } 742 | r = parseInt(Math.sqrt(rh*rh + rv*rv) / 1.8,10); 743 | g = parseInt(Math.sqrt(gh*gh + gv*gv) / 1.8,10); 744 | b = parseInt(Math.sqrt(bh*bh + bv*bv) / 1.8,10); 745 | 746 | outputData[pixel] = r; 747 | outputData[pixel+1] = g; 748 | outputData[pixel+2] = b; 749 | outputData[pixel+3] = inputData[pixel+3]; 750 | } 751 | } 752 | for(var k = 0; k < outputData.length; k++){ 753 | inputData[k] = outputData[k]; 754 | } 755 | }; 756 | } 757 | /** 758 | * Embosses the image with a simulated light source. 759 | * Angle and elevation sets the position of the light. 760 | */ 761 | function EmbossFilter(){ 762 | this.name = "Emboss"; 763 | this.isDirAnimatable = false; 764 | this.defaultValues = { 765 | height : 1, 766 | angle : 135, 767 | elevation : 30 768 | }; 769 | this.valueRanges = { 770 | height : {min:1, max:10}, 771 | angle : {min:0, max:360}, 772 | elevation : {min:0, max:180} 773 | }; 774 | this.filter = function(input,values){ 775 | var width = input.width, height = input.height; 776 | var inputData = input.data; 777 | if(values === undefined){ values = this.defaultValues; } 778 | var bumpHeight = (values.height === undefined) ? this.defaultValues.height : values.height; 779 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 780 | var elevation = (values.elevation === undefined) ? this.defaultValues.elevation : values.elevation; 781 | angle = angle / 180 * Math.PI; 782 | elevation = elevation / 180 * Math.PI; 783 | var width45 = 3 * bumpHeight; 784 | var pixelScale = 255.9; 785 | 786 | var bumpPixels = []; 787 | var bumpMapWidth = width; 788 | var bumpMapHeight = height; 789 | for(var i = 0; i < inputData.length; i+=4){ 790 | bumpPixels[i/4] = (inputData[i] + inputData[i+1] + inputData[i+2])/3; 791 | } 792 | var Nx, Ny, Nz, Lx, Ly, Lz, Nz2, NzLz, NdotL; 793 | var shade, background; 794 | 795 | Lx = parseInt(Math.cos(angle) * Math.cos(elevation) * pixelScale,10); 796 | Ly = parseInt(Math.sin(angle) * Math.cos(elevation) * pixelScale,10); 797 | Lz = parseInt(Math.sin(elevation) * pixelScale,10); 798 | 799 | Nz = parseInt(6 * 255 / width45,10); 800 | Nz2 = Nz * Nz; 801 | NzLz = Nz * Lz; 802 | background = Lz; 803 | 804 | var bumpIndex = 0; 805 | 806 | for (var y = 0; y < height; y++, bumpIndex += bumpMapWidth) { 807 | var s1 = bumpIndex; 808 | var s2 = s1 + bumpMapWidth; 809 | var s3 = s2 + bumpMapWidth; 810 | for (var x = 0; x < width; x++, s1++, s2++, s3++) { 811 | var pixel = (y*width + x)*4; 812 | if (y !== 0 && y < height-2 && x !== 0 && x < width-2) { 813 | Nx = bumpPixels[s1-1] + bumpPixels[s2-1] + bumpPixels[s3-1] - bumpPixels[s1+1] - bumpPixels[s2+1] - bumpPixels[s3+1]; 814 | Ny = bumpPixels[s3-1] + bumpPixels[s3] + bumpPixels[s3+1] - bumpPixels[s1-1] - bumpPixels[s1] - bumpPixels[s1+1]; 815 | if (Nx === 0 && Ny === 0){ 816 | shade = background; 817 | } else if ((NdotL = Nx*Lx + Ny*Ly + NzLz) < 0){ 818 | shade = 0; 819 | } else { 820 | shade = parseInt(NdotL / Math.sqrt(Nx*Nx + Ny*Ny + Nz2),10); 821 | } 822 | } else { 823 | shade = background; 824 | } 825 | inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = shade; 826 | } 827 | } 828 | }; 829 | } 830 | /** 831 | * Adjust simulated exposure values on the image. 832 | */ 833 | function ExposureFilter(){ 834 | this.name = "Exposure"; 835 | this.isDirAnimatable = true; 836 | this.defaultValues = { 837 | exposure : 1.0 838 | }; 839 | this.valueRanges = { 840 | exposure : {min:0, max:5} 841 | }; 842 | 843 | var filterUtils = new FilterUtils(); 844 | this.filter = function(input,values){ 845 | var width = input.width, height = input.height; 846 | var inputData = input.data; 847 | if(values === undefined){ values = this.defaultValues; } 848 | var exposure = (values.exposure === undefined) ? this.defaultValues.exposure : values.exposure; 849 | var table = []; 850 | for(var i = 0; i < 256; i++){ 851 | table[i] = parseInt(255 *(1-Math.exp(-(i/255) * exposure)),10); 852 | } 853 | filterUtils.tableFilter(inputData, table, width, height); 854 | }; 855 | } 856 | /** 857 | * Adjusts the gain and bias of the image. Gain alters the contrast while bias biases 858 | * colors towards lighter or darker. 859 | */ 860 | function GainFilter(){ 861 | this.name = "Gain/Bias"; 862 | this.isDirAnimatable = true; 863 | this.defaultValues = { 864 | gain: 0.5, 865 | bias: 0.5 866 | }; 867 | this.valueRanges = { 868 | gain: {min:0.0, max:1.0}, 869 | bias: {min:0.0, max:1.0} 870 | }; 871 | var table = []; 872 | 873 | var filterUtils = new FilterUtils(); 874 | this.filter = function(input,values){ 875 | var width = input.width, height = input.height; 876 | var inputData = input.data; 877 | if(values === undefined){ values = this.defaultValues; } 878 | var gain = (values.gain === undefined) ? this.defaultValues.gain : values.gain; 879 | var bias = (values.bias === undefined) ? this.defaultValues.bias : values.bias; 880 | 881 | var table = []; 882 | 883 | for(var i = 0; i < 256; i++){ 884 | var val = i/255; 885 | var k = (1/gain-2) * (1-2*val); 886 | val = (val < 0.5) ? val/(k+1) : (k-val)/(k-1); 887 | val /= (1/bias-2)*(1-val)+1; 888 | table[i] = parseInt(255 * val,10); 889 | } 890 | filterUtils.tableFilter(inputData,table,width,height); 891 | }; 892 | } 893 | /** 894 | * Adjusts the gamma values of the image. Values over 1 increase the gamma while values over 0 decrease gamma. 895 | */ 896 | function GammaFilter(){ 897 | this.name = "Gamma"; 898 | this.isDirAnimatable = true; 899 | this.defaultValues = { 900 | amount : 1.0 901 | }; 902 | this.valueRanges = { 903 | amount : {min:0.0, max:2.0} 904 | }; 905 | this.filter = function(input,values){ 906 | var width = input.width, height = input.height; 907 | var inputData = input.data; 908 | if(values === undefined){ values = this.defaultValues; } 909 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 910 | if(amount < 0){ 911 | amount = 0.0; 912 | } 913 | if(!FilterUtils){ 914 | if(console){ 915 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 916 | } 917 | return; 918 | } 919 | var filterUtils = new FilterUtils(); 920 | var table = []; 921 | for(var i = 0; i < 256; i++){ 922 | table[i] = 255 * Math.pow(i/255, 1/amount) + 0.5; 923 | } 924 | filterUtils.tableFilter(inputData,table,width,height); 925 | }; 926 | } 927 | /** 928 | * Sets the image to grayscale. 929 | */ 930 | function GrayscaleFilter(){ 931 | this.name = "Grayscale"; 932 | this.isDirAnimatable = true; 933 | this.defaultValues = { 934 | }; 935 | this.valueRanges = { 936 | }; 937 | this.filter = function(input,values){ 938 | var width = input.width, height = input.height; 939 | var inputData = input.data; 940 | for (var y = 0; y < height; y++) { 941 | for (var x = 0; x < width; x++) { 942 | var pixel = (y*width + x)*4; 943 | var luma = inputData[pixel]*0.3 + inputData[pixel+1]*0.59 + inputData[pixel+2]*0.11; 944 | inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = luma; 945 | } 946 | } 947 | }; 948 | } 949 | /** 950 | * Adjusts the hue of the image by going over to HSV values. 951 | */ 952 | function HueFilter(){ 953 | this.name = "Hue"; 954 | this.isDirAnimatable = true; 955 | this.defaultValues = { 956 | amount : 0.0 957 | }; 958 | this.valueRanges = { 959 | amount : {min:-1.0, max:1.0} 960 | }; 961 | 962 | var filterUtils = new FilterUtils(); 963 | this.filter = function(input,values){ 964 | var width = input.width, height = input.height; 965 | var inputData = input.data; 966 | if(values === undefined){ values = this.defaultValues; } 967 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 968 | for (var y = 0; y < height; y++) { 969 | for (var x = 0; x < width; x++) { 970 | var pixel = (y*width + x)*4; 971 | var hsv = filterUtils.RGBtoHSV(inputData[pixel],inputData[pixel+1],inputData[pixel+2]); 972 | hsv[0] += amount; 973 | while(hsv[0] < 0){ 974 | hsv[0] += 360; 975 | } 976 | var rgb = filterUtils.HSVtoRGB(hsv[0],hsv[1],hsv[2]); 977 | for(var i = 0; i < 3; i++){ 978 | inputData[pixel+i] = rgb[i]; 979 | } 980 | } 981 | } 982 | }; 983 | } 984 | /** 985 | * Inverts the colors of the image. 986 | */ 987 | function InvertFilter(){ 988 | this.name = "Invert"; 989 | this.isDirAnimatable = true; 990 | this.defaultValues = { 991 | }; 992 | this.valueRanges = { 993 | }; 994 | this.filter = function(input,values){ 995 | var width = input.width, height = input.height; 996 | var inputData = input.data; 997 | for (var y = 0; y < height; y++) { 998 | for (var x = 0; x < width; x++) { 999 | var pixel = (y*width + x)*4; 1000 | for(var i = 0; i < 3; i++){ 1001 | inputData[pixel+i] = 255 - inputData[pixel+i]; 1002 | } 1003 | } 1004 | } 1005 | }; 1006 | } 1007 | /** 1008 | * Creates a kaleidoscope effect on the image. CenterX and CenterY specify the 1009 | * position in terms of ratios of width and height. 1010 | */ 1011 | function KaleidoscopeFilter(){ 1012 | this.name = "Kaleidoscope"; 1013 | this.isDirAnimatable = false; 1014 | this.defaultValues = { 1015 | angle : 0, 1016 | rotation : 0, 1017 | sides : 3, 1018 | centerX : 0.5, 1019 | centerY : 0.5 1020 | }; 1021 | this.valueRanges = { 1022 | angle : {min: 0, max: 360}, 1023 | rotation : {min: 0, max: 360}, 1024 | sides : {min: 1, max: 30}, 1025 | centerX : {min: 0.0, max:1.0}, 1026 | centerY : {min: 0.0, max:1.0} 1027 | }; 1028 | 1029 | var filterUtils = new FilterUtils(); 1030 | this.filter = function (input, values){ 1031 | var width = input.width, height = input.height; 1032 | var inputData = input.data; 1033 | if(values === undefined){ values = this.defaultValues; } 1034 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 1035 | var rotation = (values.rotation === undefined) ? this.defaultValues.rotation : values.rotation; 1036 | var sides = (values.sides === undefined) ? this.defaultValues.sides : values.sides; 1037 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 1038 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 1039 | var iCenterX = width * centerX; var iCenterY = height * centerY; 1040 | angle = angle/180 * Math.PI; 1041 | rotation = rotation/180 * Math.PI; 1042 | var transInverse = function(x,y,out){ 1043 | var dx = x - iCenterX; 1044 | var dy = y - iCenterY; 1045 | var r = Math.sqrt(dx*dx + dy*dy); 1046 | var theta = Math.atan2(dy,dx) - angle - rotation; 1047 | theta = filterUtils.triangle(theta/Math.PI*sides*0.5); 1048 | theta += angle; 1049 | out[0] = iCenterX + r*Math.cos(theta); 1050 | out[1] = iCenterY + r*Math.sin(theta); 1051 | }; 1052 | filterUtils.transformFilter(inputData,transInverse,width,height); 1053 | }; 1054 | } 1055 | /** 1056 | * Applies a fisheye lens distortion effect on the image. CenterX and CenterY specify the 1057 | * position in terms of ratios of width and height. 1058 | */ 1059 | function LensDistortionFilter(){ 1060 | this.name = "Lens Distortion"; 1061 | this.isDirAnimatable = false; 1062 | this.defaultValues = { 1063 | refraction : 1.5, 1064 | radius : 50, 1065 | centerX : 0.5, 1066 | centerY : 0.5 1067 | }; 1068 | this.valueRanges = { 1069 | refraction : {min: 1, max: 10}, 1070 | radius : {min: 1, max: 200}, 1071 | centerX : {min: 0.0, max:1.0}, 1072 | centerY : {min: 0.0, max:1.0} 1073 | }; 1074 | 1075 | var filterUtils = new FilterUtils(); 1076 | 1077 | this.filter = function (input, values){ 1078 | var width = input.width, height = input.height; 1079 | var inputData = input.data; 1080 | if(values === undefined){ values = this.defaultValues; } 1081 | var refraction = (values.refraction === undefined) ? this.defaultValues.refraction : values.refraction; 1082 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 1083 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 1084 | var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; 1085 | var radius2 = radius*radius; 1086 | var iCenterX = width * centerX; var iCenterY = height * centerY; 1087 | var transInverse = function(x,y,out){ 1088 | var dx = x-iCenterX; 1089 | var dy = y-iCenterY; 1090 | var x2 = dx*dx; 1091 | var y2 = dy*dy; 1092 | if (y2 >= (radius2 - (radius2*x2)/radius2)) { 1093 | out[0] = x; 1094 | out[1] = y; 1095 | } else { 1096 | var rRefraction = 1.0 / refraction; 1097 | 1098 | var z = Math.sqrt((1.0 - x2/radius2 - y2/radius2) * radius2); 1099 | var z2 = z*z; 1100 | 1101 | var xAngle = Math.acos(dx / Math.sqrt(x2+z2)); 1102 | var angle1 = Math.PI/2 - xAngle; 1103 | var angle2 = Math.asin(Math.sin(angle1)*rRefraction); 1104 | angle2 = Math.PI/2 - xAngle - angle2; 1105 | out[0] = x - Math.tan(angle2)*z; 1106 | 1107 | var yAngle = Math.acos(dy / Math.sqrt(y2+z2)); 1108 | angle1 = Math.PI/2 - yAngle; 1109 | angle2 = Math.asin(Math.sin(angle1)*rRefraction); 1110 | angle2 = Math.PI/2 - yAngle - angle2; 1111 | out[1] = y - Math.tan(angle2)*z; 1112 | } 1113 | }; 1114 | filterUtils.transformFilter(inputData,transInverse,width,height); 1115 | }; 1116 | } 1117 | /** 1118 | * Smears out the image with line shapes to create a painting style effect. Mix specifies 1119 | * the intensity of the effect. 1120 | */ 1121 | function LineSmearFilter(){ 1122 | this.name = "Line Smear"; 1123 | this.isDirAnimatable = false; 1124 | this.defaultValues = { 1125 | distance : 8, 1126 | density : 0.5, 1127 | angle : 0, 1128 | mix : 0.5 1129 | }; 1130 | this.valueRanges = { 1131 | distance : {min:1, max:30}, 1132 | density : {min:0.0, max:1.0}, 1133 | angle : {min:0, max:360}, 1134 | mix : {min:0.0, max:1.0} 1135 | }; 1136 | 1137 | var filterUtils = new FilterUtils(); 1138 | this.filter = function(input,values){ 1139 | var width = input.width, height = input.height; 1140 | var inputData = input.data; 1141 | var outputData = []; 1142 | var k; 1143 | for(k = 0; k < inputData.length; k++){ 1144 | outputData[k] = inputData[k]; 1145 | } 1146 | if(values === undefined){ values = this.defaultValues; } 1147 | var distance = (values.distance === undefined) ? this.defaultValues.distance : values.distance; 1148 | if(distance < 1){ distance = 1;} 1149 | distance = parseInt(distance,10); 1150 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 1151 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 1152 | var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; 1153 | angle = angle/180*Math.PI; 1154 | var sinAngle = Math.sin(angle); 1155 | var cosAngle = Math.cos(angle); 1156 | var numShapes = parseInt(2*density*width*height / 2,10); 1157 | for(var i = 0; i < numShapes; i++){ 1158 | var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; 1159 | var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; 1160 | var length = (Math.random()*Math.pow(2,32) & 0x7fffffff) % distance + 1; 1161 | var rgb2 = [inputData[(sy*width+sx)*4],inputData[(sy*width+sx)*4+1],inputData[(sy*width+sx)*4+2],inputData[(sy*width+sx)*4+3]]; 1162 | var dx = parseInt(length*cosAngle,10); 1163 | var dy = parseInt(length*sinAngle,10); 1164 | 1165 | var x0 = sx-dx; 1166 | var y0 = sy-dy; 1167 | var x1 = sx+dx; 1168 | var y1 = sy+dy; 1169 | var x, y, d, incrE, incrNE, ddx, ddy; 1170 | 1171 | if (x1 < x0){ 1172 | ddx = -1; 1173 | } else { 1174 | ddx = 1; 1175 | } 1176 | if (y1 < y0){ 1177 | ddy = -1; 1178 | } else { 1179 | ddy = 1; 1180 | } 1181 | dx = x1-x0; 1182 | dy = y1-y0; 1183 | dx = Math.abs(dx); 1184 | dy = Math.abs(dy); 1185 | x = x0; 1186 | y = y0; 1187 | var rgb1; 1188 | var mixedRGB; 1189 | if (x < width && x >= 0 && y < height && y >= 0) { 1190 | rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 1191 | mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); 1192 | for(k = 0; k < 3; k++){ 1193 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 1194 | } 1195 | } 1196 | if (Math.abs(dx) > Math.abs(dy)) { 1197 | d = 2*dy-dx; 1198 | incrE = 2*dy; 1199 | incrNE = 2*(dy-dx); 1200 | 1201 | while (x != x1) { 1202 | if (d <= 0){ 1203 | d += incrE; 1204 | } else { 1205 | d += incrNE; 1206 | y += ddy; 1207 | } 1208 | x += ddx; 1209 | if (x < width && x >= 0 && y < height && y >= 0) { 1210 | rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 1211 | mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); 1212 | for(k = 0; k < 3; k++){ 1213 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 1214 | } 1215 | } 1216 | } 1217 | } else { 1218 | d = 2*dx-dy; 1219 | incrE = 2*dx; 1220 | incrNE = 2*(dx-dy); 1221 | 1222 | while (y != y1) { 1223 | if (d <= 0) { 1224 | d += incrE; 1225 | }else { 1226 | d += incrNE; 1227 | x += ddx; 1228 | } 1229 | y += ddy; 1230 | if (x < width && x >= 0 && y < height && y >= 0) { 1231 | rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 1232 | mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); 1233 | for(k = 0; k < 3; k++){ 1234 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 1235 | } 1236 | } 1237 | } 1238 | } 1239 | } 1240 | for(k = 0; k < outputData.length; k++){ 1241 | inputData[k] = outputData[k]; 1242 | } 1243 | }; 1244 | } 1245 | /** 1246 | * Replaces every pixel with the maximum RGB value of the neighboring pixels. Each color is 1247 | * considered separately. 1248 | */ 1249 | function MaximumFilter(){ 1250 | this.name = "Maximum"; 1251 | this.isDirAnimatable = true; 1252 | this.defaultValues = { 1253 | }; 1254 | this.valueRanges = { 1255 | }; 1256 | this.filter = function(input,values){ 1257 | var width = input.width, height = input.height; 1258 | var inputData = input.data; 1259 | var outputData = []; 1260 | for (var y = 0; y < height; y++) { 1261 | for (var x = 0; x < width; x++) { 1262 | var pixel = (y*width + x)*4; 1263 | var maxR = 0; 1264 | var maxG = 0; 1265 | var maxB = 0; 1266 | for (var dy = -1; dy <= 1; dy++){ 1267 | var iy = y+dy; 1268 | if(iy >= 0 && iy < height){ 1269 | for (var dx = -1; dx <= 1; dx++){ 1270 | var ix = x+dx; 1271 | if(ix >= 0 && ix < width){ 1272 | var iPixel = (iy*width + ix)*4; 1273 | maxR = Math.max(maxR,inputData[iPixel]); 1274 | maxG = Math.max(maxG,inputData[iPixel+1]); 1275 | maxB = Math.max(maxB,inputData[iPixel+2]); 1276 | } 1277 | } 1278 | } 1279 | } 1280 | outputData[pixel] = maxR; 1281 | outputData[pixel+1] = maxG; 1282 | outputData[pixel+2] = maxB; 1283 | outputData[pixel+3] = inputData[pixel+3]; 1284 | } 1285 | } 1286 | for(var k = 0; k < outputData.length; k++){ 1287 | inputData[k] = outputData[k]; 1288 | } 1289 | }; 1290 | } 1291 | /** 1292 | * Replaces every pixel with the median RGB value of the neighboring pixels. Each color is 1293 | * considered separately. 1294 | */ 1295 | function MedianFilter(){ 1296 | this.name = "Median"; 1297 | this.isDirAnimatable = false; 1298 | this.defaultValues = { 1299 | }; 1300 | this.valueRanges = { 1301 | }; 1302 | this.filter = function(input,values){ 1303 | var width = input.width, height = input.height; 1304 | var inputData = input.data; 1305 | var outputData = []; 1306 | for (var y = 0; y < height; y++) { 1307 | for (var x = 0; x < width; x++) { 1308 | var pixel = (y*width + x)*4; 1309 | var rList = []; 1310 | var gList = []; 1311 | var bList = []; 1312 | for (var dy = -1; dy <= 1; dy++){ 1313 | var iy = y+dy; 1314 | if(iy >= 0 && iy < height){ 1315 | for (var dx = -1; dx <= 1; dx++){ 1316 | var ix = x+dx; 1317 | if(ix >= 0 && ix < width){ 1318 | var iPixel = (iy*width + ix)*4; 1319 | rList.push(inputData[iPixel]); 1320 | gList.push(inputData[iPixel+1]); 1321 | bList.push(inputData[iPixel+2]); 1322 | 1323 | } 1324 | } 1325 | } 1326 | } 1327 | var sortFunc = function(a,b){ 1328 | return a-b; 1329 | }; 1330 | rList.sort(sortFunc); 1331 | gList.sort(sortFunc); 1332 | bList.sort(sortFunc); 1333 | outputData[pixel] = rList[4]; 1334 | outputData[pixel+1] = gList[4]; 1335 | outputData[pixel+2] = bList[4]; 1336 | outputData[pixel+3] = inputData[pixel+3]; 1337 | } 1338 | } 1339 | for(var k = 0; k < outputData.length; k++){ 1340 | inputData[k] = outputData[k]; 1341 | } 1342 | }; 1343 | } 1344 | /** 1345 | * Replaces every pixel with the minimum RGB value of the neighboring pixels. Each color is 1346 | * considered separately. 1347 | */ 1348 | function MinimumFilter(){ 1349 | this.name = "Minimum"; 1350 | this.isDirAnimatable = true; 1351 | this.defaultValues = { 1352 | }; 1353 | this.valueRanges = { 1354 | }; 1355 | this.filter = function(input,values){ 1356 | var width = input.width, height = input.height; 1357 | var inputData = input.data; 1358 | var outputData = []; 1359 | for (var y = 0; y < height; y++) { 1360 | for (var x = 0; x < width; x++) { 1361 | var pixel = (y*width + x)*4; 1362 | var minR = 255; 1363 | var minG = 255; 1364 | var minB = 255; 1365 | for (var dy = -1; dy <= 1; dy++){ 1366 | var iy = y+dy; 1367 | if(iy >= 0 && iy < height){ 1368 | for (var dx = -1; dx <= 1; dx++){ 1369 | var ix = x+dx; 1370 | if(ix >= 0 && ix < width){ 1371 | var iPixel = (iy*width + ix)*4; 1372 | minR = Math.min(minR,inputData[iPixel]); 1373 | minG = Math.min(minG,inputData[iPixel+1]); 1374 | minB = Math.min(minB,inputData[iPixel+2]); 1375 | } 1376 | } 1377 | } 1378 | } 1379 | outputData[pixel] = minR; 1380 | outputData[pixel+1] = minG; 1381 | outputData[pixel+2] = minB; 1382 | outputData[pixel+3] = inputData[pixel+3]; 1383 | } 1384 | } 1385 | for(var k = 0; k < outputData.length; k++){ 1386 | inputData[k] = outputData[k]; 1387 | } 1388 | }; 1389 | } 1390 | /** 1391 | * Creates random noise on the image, with or without color. 1392 | */ 1393 | function NoiseFilter(){ 1394 | this.name = "Noise"; 1395 | this.isDirAnimatable = true; 1396 | this.defaultValues = { 1397 | amount : 25, 1398 | density : 1, 1399 | monochrome : true 1400 | }; 1401 | this.valueRanges = { 1402 | amount : {min:0, max:100}, 1403 | density : {min:0, max:1.0}, 1404 | monochrome : {min:false, max:true} 1405 | }; 1406 | this.filter = function(input,values){ 1407 | var width = input.width, height = input.height; 1408 | var inputData = input.data; 1409 | if(values === undefined){ values = this.defaultValues; } 1410 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 1411 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 1412 | var monochrome = (values.monochrome === undefined) ? this.defaultValues.monochrome : values.monochrome; 1413 | for (var y = 0; y < height; y++) { 1414 | for (var x = 0; x < width; x++) { 1415 | var pixel = (y*width + x)*4; 1416 | if(Math.random() <= density){ 1417 | var n; 1418 | if(monochrome){ 1419 | n = parseInt((2*Math.random()-1) * amount,10); 1420 | inputData[pixel] += n; 1421 | inputData[pixel+1] += n; 1422 | inputData[pixel+2] += n; 1423 | } else { 1424 | for(var i = 0; i < 3; i++){ 1425 | n = parseInt((2*Math.random()-1) * amount,10); 1426 | inputData[pixel+i] += n; 1427 | } 1428 | } 1429 | } 1430 | } 1431 | } 1432 | }; 1433 | } 1434 | /** 1435 | * Produces an oil painting effect on the image. 1436 | * NOTE: This filter can be very slow, especially at higher ranges. Use with caution. 1437 | */ 1438 | function OilFilter(){ 1439 | this.name = "Oil Painting"; 1440 | this.isDirAnimatable = false; 1441 | this.defaultValues = { 1442 | range : 3 1443 | }; 1444 | this.valueRanges = { 1445 | range : {min:0, max:5} 1446 | }; 1447 | this.filter = function(input,values){ 1448 | var width = input.width, height = input.height; 1449 | var inputData = input.data; 1450 | var outputData = []; 1451 | if(values === undefined){ values = this.defaultValues; } 1452 | var range = (values.range === undefined) ? this.defaultValues.range : values.range; 1453 | range = parseInt(range,10); 1454 | var index = 0; 1455 | var rHistogram = []; 1456 | var gHistogram = []; 1457 | var bHistogram = []; 1458 | var rTotal = []; 1459 | var gTotal = []; 1460 | var bTotal = []; 1461 | var levels = 256; 1462 | for (var y = 0; y < height; y++) { 1463 | for (var x = 0; x < width; x++) { 1464 | var pixel = (y*width + x)*4; 1465 | for (var j = 0; j < levels; j++){ 1466 | rHistogram[j] = gHistogram[j] = bHistogram[j] = rTotal[j] = gTotal[j] = bTotal[j] = 0; 1467 | } 1468 | for (var row = -range; row <= range; row++) { 1469 | var iy = y+row; 1470 | var ioffset; 1471 | if (0 <= iy && iy < height) { 1472 | ioffset = iy*width; 1473 | for (var col = -range; col <= range; col++) { 1474 | var ix = x+col; 1475 | if (0 <= ix && ix < width) { 1476 | var ro = inputData[(ioffset+ix)*4]; 1477 | var go = inputData[(ioffset+ix)*4+1]; 1478 | var bo = inputData[(ioffset+ix)*4+2]; 1479 | var ri = ro*levels/256; 1480 | var gi = go*levels/256; 1481 | var bi = bo*levels/256; 1482 | rTotal[ri] += ro; 1483 | gTotal[gi] += go; 1484 | bTotal[bi] += bo; 1485 | rHistogram[ri]++; 1486 | gHistogram[gi]++; 1487 | bHistogram[bi]++; 1488 | } 1489 | } 1490 | } 1491 | } 1492 | var r = 0, g = 0, b = 0; 1493 | for (var i = 1; i < levels; i++) { 1494 | if (rHistogram[i] > rHistogram[r]){ 1495 | r = i; 1496 | } 1497 | if (gHistogram[i] > gHistogram[g]){ 1498 | g = i; 1499 | } 1500 | if (bHistogram[i] > bHistogram[b]){ 1501 | b = i; 1502 | } 1503 | } 1504 | r = rTotal[r] / rHistogram[r]; 1505 | g = gTotal[g] / gHistogram[g]; 1506 | b = bTotal[b] / bHistogram[b]; 1507 | outputData[pixel] = r; 1508 | outputData[pixel+1] = g; 1509 | outputData[pixel+2] = b; 1510 | outputData[pixel+3] = inputData[pixel+3]; 1511 | } 1512 | } 1513 | for(var k = 0; k < outputData.length; k++){ 1514 | inputData[k] = outputData[k]; 1515 | } 1516 | }; 1517 | } 1518 | /** 1519 | * Changes the opacity of the image. 1520 | */ 1521 | function OpacityFilter(){ 1522 | this.name = "Opacity"; 1523 | this.isDirAnimatable = true; 1524 | this.defaultValues = { 1525 | amount : 1.0 1526 | }; 1527 | this.valueRanges = { 1528 | amount : {min:0.0, max:1.0} 1529 | }; 1530 | this.filter = function(input,values){ 1531 | var width = input.width, height = input.height; 1532 | var inputData = input.data; 1533 | if(values === undefined){ values = this.defaultValues; } 1534 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 1535 | for (var y = 0; y < height; y++) { 1536 | for (var x = 0; x < width; x++) { 1537 | var pixel = (y*width + x)*4; 1538 | inputData[pixel+3] = 255*amount; 1539 | } 1540 | } 1541 | }; 1542 | } 1543 | /** 1544 | * Pinches and whirls the image toward the center point. CenterX and CenterY specify the 1545 | * position in terms of ratios of width and height. 1546 | */ 1547 | function PinchFilter(){ 1548 | this.name = "Pinch/Whirl"; 1549 | this.isDirAnimatable = false; 1550 | this.defaultValues = { 1551 | amount : 0.5, 1552 | radius : 100, 1553 | angle : 0, 1554 | centerX : 0.5, 1555 | centerY : 0.5 1556 | }; 1557 | this.valueRanges = { 1558 | amount : {min: -1.0, max: 1.0}, 1559 | radius : {min: 1, max: 200}, 1560 | angle : {min: 0, max: 360}, 1561 | centerX : {min: 0.0, max:1.0}, 1562 | centerY : {min: 0.0, max:1.0} 1563 | }; 1564 | 1565 | var filterUtils = new FilterUtils(); 1566 | 1567 | this.filter = function (input, values){ 1568 | var width = input.width, height = input.height; 1569 | var inputData = input.data; 1570 | if(values === undefined){ values = this.defaultValues; } 1571 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 1572 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 1573 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 1574 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 1575 | var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; 1576 | var radius2 = radius*radius; 1577 | angle = angle/180 * Math.PI; 1578 | var iCenterX = width * centerX; var iCenterY = height * centerY; 1579 | var transInverse = function(x,y,out){ 1580 | var dx = x-iCenterX; 1581 | var dy = y-iCenterY; 1582 | var distance = dx*dx + dy*dy; 1583 | if(distance > radius2 || distance === 0){ 1584 | out[0] = x; 1585 | out[1] = y; 1586 | } else { 1587 | var d = Math.sqrt( distance / radius2 ); 1588 | var t = Math.pow( Math.sin( Math.PI*0.5 * d ), -amount); 1589 | dx *= t; 1590 | dy *= t; 1591 | var e = 1 - d; 1592 | var a = angle * e * e; 1593 | var s = Math.sin(a); 1594 | var c = Math.cos(a); 1595 | out[0] = iCenterX + c*dx - s*dy; 1596 | out[1] = iCenterY + s*dx + c*dy; 1597 | } 1598 | }; 1599 | filterUtils.transformFilter(inputData,transInverse,width,height); 1600 | }; 1601 | } 1602 | /** 1603 | * Pixelates the image i.e. divides the image into blocks of color. 1604 | */ 1605 | function PixelationFilter(){ 1606 | this.name = "Pixelation"; 1607 | this.isDirAnimatable = false; 1608 | this.defaultValues = { 1609 | size : 5 1610 | }; 1611 | this.valueRanges = { 1612 | size : {min:1, max:50} 1613 | }; 1614 | this.filter = function(input,values){ 1615 | var width = input.width, height = input.height; 1616 | var inputData = input.data; 1617 | if(values === undefined){ values = this.defaultValues; } 1618 | var size = (values.size === undefined) ? this.defaultValues.size : values.size; 1619 | size = parseInt(size,10); 1620 | var pixels = []; 1621 | var by, bx, bPixel; 1622 | for (var y = 0; y < height; y+=size) { 1623 | for (var x = 0; x < width; x+=size) { 1624 | var pixel = (y*width + x)*4; 1625 | var w = Math.min(size, width-x); 1626 | var h = Math.min(size, height-y); 1627 | var t = w*h; 1628 | var r = 0, g = 0, b = 0; 1629 | for(by = y; by < y+h; by++){ 1630 | for(bx = x; bx < x+w; bx++){ 1631 | bPixel = (by*width + bx)*4; 1632 | r += inputData[bPixel]; 1633 | g += inputData[bPixel+1]; 1634 | b += inputData[bPixel+2]; 1635 | } 1636 | } 1637 | for(by = y; by < y+h; by++){ 1638 | for(bx = x; bx < x+w; bx++){ 1639 | bPixel = (by*width + bx)*4; 1640 | inputData[bPixel] = r/t; 1641 | inputData[bPixel+1] = g/t; 1642 | inputData[bPixel+2] = b/t; 1643 | } 1644 | } 1645 | } 1646 | } 1647 | }; 1648 | } 1649 | /** 1650 | * Posterizes the image, i.e. restricts the color values to a set amount of levels. 1651 | */ 1652 | function PosterizeFilter(){ 1653 | this.name = "Posterize"; 1654 | this.isDirAnimatable = false; 1655 | this.defaultValues = { 1656 | levels : 6 1657 | }; 1658 | this.valueRanges = { 1659 | levels : {min:2, max:30 } 1660 | }; 1661 | 1662 | var filterUtils = new FilterUtils(); 1663 | this.filter = function(input,values){ 1664 | var width = input.width, height = input.height; 1665 | var inputData = input.data; 1666 | if(values === undefined){ values = this.defaultValues; } 1667 | var levels = (values.levels === undefined) ? this.defaultValues.levels : parseInt(values.levels,10); 1668 | if(levels <= 1){ 1669 | return; 1670 | } 1671 | var table = []; 1672 | for(var i = 0; i < 256; i++){ 1673 | table[i] = parseInt(255 * parseInt(i*levels/256,10) / (levels-1),10); 1674 | } 1675 | filterUtils.tableFilter(inputData,table,width,height); 1676 | }; 1677 | } 1678 | /** 1679 | * Adjust the factor of each RGB color value in the image. 1680 | */ 1681 | function RGBAdjustFilter(){ 1682 | this.name = "RGBAdjust"; 1683 | this.isDirAnimatable = true; 1684 | this.defaultValues = { 1685 | red: 1.0, 1686 | green: 1.0, 1687 | blue: 1.0 1688 | }; 1689 | this.valueRanges = { 1690 | red: {min: 0.0, max: 2.0}, 1691 | green: {min: 0.0, max: 2.0}, 1692 | blue: {min: 0.0, max: 2.0} 1693 | }; 1694 | this.filter = function(input,values){ 1695 | var width = input.width, height = input.height; 1696 | var inputData = input.data; 1697 | if(values === undefined){ values = this.defaultValues; } 1698 | var red = (values.red === undefined) ? this.defaultValues.red : values.red; 1699 | var green = (values.green === undefined) ? this.defaultValues.green : values.green; 1700 | var blue = (values.blue === undefined) ? this.defaultValues.blue : values.blue; 1701 | if(red < 0){ red = 0; } 1702 | if(green < 0){ green = 0; } 1703 | if(blue < 0){ blue = 0; } 1704 | for (var y = 0; y < height; y++) { 1705 | for (var x = 0; x < width; x++) { 1706 | var pixel = (y*width + x)*4; 1707 | inputData[pixel] *= red; 1708 | inputData[pixel+1] *= green; 1709 | inputData[pixel+2] *= blue; 1710 | } 1711 | } 1712 | }; 1713 | } 1714 | /** 1715 | * Adjusts the saturation value of the image. Values over 1 increase saturation while values below decrease saturation. 1716 | * For a true grayscale effect, use the grayscale filter instead. 1717 | */ 1718 | function SaturationFilter(){ 1719 | this.name = "Saturation"; 1720 | this.isDirAnimatable = true; 1721 | this.defaultValues = { 1722 | amount : 1.0 1723 | }; 1724 | this.valueRanges = { 1725 | amount : {min:0.0, max:2.0} 1726 | }; 1727 | this.filter = function(input,values){ 1728 | var width = input.width, height = input.height; 1729 | var inputData = input.data; 1730 | if(values === undefined){ values = this.defaultValues; } 1731 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 1732 | var RW = 0.3; 1733 | var RG = 0.59; 1734 | var RB = 0.11; 1735 | var a = (1 - amount) * RW + amount; 1736 | var b = (1 - amount) * RW; 1737 | var c = (1 - amount) * RW; 1738 | var d = (1 - amount) * RG; 1739 | var e = (1 - amount) * RG + amount; 1740 | var f = (1 - amount) * RG; 1741 | var g = (1 - amount) * RB; 1742 | var h = (1 - amount) * RB; 1743 | var i = (1 - amount) * RB + amount; 1744 | for (var y = 0; y < height; y++) { 1745 | for (var x = 0; x < width; x++) { 1746 | var pixel = (y*width + x)*4; 1747 | var pR = inputData[pixel]; 1748 | var pG = inputData[pixel+1]; 1749 | var pB = inputData[pixel+2]; 1750 | inputData[pixel] = a*pR + d*pG + g*pB; 1751 | inputData[pixel+1] = b*pR + e*pG + h*pB; 1752 | inputData[pixel+2] = c*pR + f*pG + i*pB; 1753 | } 1754 | } 1755 | }; 1756 | } 1757 | /** 1758 | * Creates ripples on the image horizontally/vertically in a sawtooth pattern. 1759 | */ 1760 | function SawtoothRippleFilter(){ 1761 | this.name = "Sawtooth Ripples"; 1762 | this.isDirAnimatable = false; 1763 | this.defaultValues = { 1764 | xAmplitude : 5, 1765 | yAmplitude : 5, 1766 | xWavelength : 16, 1767 | yWavelength : 16 1768 | }; 1769 | this.valueRanges = { 1770 | xAmplitude : {min:0, max:30}, 1771 | yAmplitude : {min:0, max:30}, 1772 | xWavelength : {min:1, max:50}, 1773 | yWavelength : {min:1, max:50} 1774 | }; 1775 | 1776 | var filterUtils = new FilterUtils(); 1777 | 1778 | this.filter = function (input, values){ 1779 | var width = input.width, height = input.height; 1780 | var inputData = input.data; 1781 | if(values === undefined){ values = this.defaultValues; } 1782 | var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude; 1783 | var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude; 1784 | var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength; 1785 | var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength; 1786 | var transInverse = function(x,y,out){ 1787 | var nx = y/xWavelength; 1788 | var ny = x/yWavelength; 1789 | var fx = filterUtils.mod(nx,1); 1790 | var fy = filterUtils.mod(ny,1); 1791 | out[0] = x + xAmplitude * fx; 1792 | out[1] = y + yAmplitude * fy; 1793 | }; 1794 | filterUtils.transformFilter(inputData,transInverse,width,height); 1795 | }; 1796 | } 1797 | /** 1798 | * Creates a sepia effect on the image i.e. gives the image a yellow-brownish tone. 1799 | */ 1800 | function SepiaFilter(){ 1801 | this.name = "Sepia"; 1802 | this.isDirAnimatable = true; 1803 | this.defaultValues = { 1804 | amount : 10 1805 | }; 1806 | this.valueRanges = { 1807 | amount : {min:0, max:30} 1808 | }; 1809 | 1810 | var filterUtils = new FilterUtils(); 1811 | this.filter = function(input,values){ 1812 | var width = input.width, height = input.height; 1813 | var inputData = input.data; 1814 | if(values === undefined){ values = this.defaultValues; } 1815 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 1816 | amount *= 255/100; 1817 | for (var y = 0; y < height; y++) { 1818 | for (var x = 0; x < width; x++) { 1819 | var pixel = (y*width + x)*4; 1820 | var luma = inputData[pixel]*0.3 + inputData[pixel+1]*0.59 + inputData[pixel+2]*0.11; 1821 | var r,g,b; 1822 | r = g = b = luma; 1823 | r += 40; 1824 | g += 20; 1825 | b -= amount; 1826 | 1827 | inputData[pixel] = r; 1828 | inputData[pixel+1] = g; 1829 | inputData[pixel+2] = b; 1830 | } 1831 | } 1832 | }; 1833 | } 1834 | /** 1835 | * Sharpens the image slightly. For increased effect, apply the filter multiple times. 1836 | */ 1837 | function SharpenFilter(){ 1838 | this.name = "Sharpen"; 1839 | this.isDirAnimatable = true; 1840 | this.defaultValues = { 1841 | }; 1842 | this.valueRanges = { 1843 | }; 1844 | 1845 | var filterUtils = new FilterUtils(); 1846 | this.filter = function(input,values){ 1847 | var width = input.width, height = input.height; 1848 | var inputData = input.data; 1849 | var matrix = [ 0.0,-0.2, 0.0, 1850 | -0.2, 1.8,-0.2, 1851 | 0.0, -0.2, 0.0]; 1852 | filterUtils.convolveFilter(inputData,matrix,width,height); 1853 | }; 1854 | } 1855 | /** 1856 | * Creates ripples on the image horizontally/vertically in a sine pattern. 1857 | */ 1858 | function SineRippleFilter(){ 1859 | this.name = "Sine Ripples"; 1860 | this.isDirAnimatable = false; 1861 | this.defaultValues = { 1862 | xAmplitude : 5, 1863 | yAmplitude : 5, 1864 | xWavelength : 16, 1865 | yWavelength : 16 1866 | }; 1867 | this.valueRanges = { 1868 | xAmplitude : {min:0, max:30}, 1869 | yAmplitude : {min:0, max:30}, 1870 | xWavelength : {min:1, max:50}, 1871 | yWavelength : {min:1, max:50} 1872 | }; 1873 | 1874 | var filterUtils = new FilterUtils(); 1875 | 1876 | this.filter = function (input, values){ 1877 | var width = input.width, height = input.height; 1878 | var inputData = input.data; 1879 | if(values === undefined){ values = this.defaultValues; } 1880 | var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude; 1881 | var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude; 1882 | var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength; 1883 | var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength; 1884 | var transInverse = function(x,y,out){ 1885 | var nx = y/xWavelength; 1886 | var ny = x/yWavelength; 1887 | var fx = Math.sin(nx); 1888 | var fy = Math.sin(ny); 1889 | out[0] = x + xAmplitude * fx; 1890 | out[1] = y + yAmplitude * fy; 1891 | }; 1892 | filterUtils.transformFilter(inputData,transInverse,width,height); 1893 | }; 1894 | } 1895 | /** 1896 | * Produces a solarization effect on the image. 1897 | */ 1898 | function SolarizeFilter(){ 1899 | this.name = "Solarize"; 1900 | this.isDirAnimatable = true; 1901 | this.defaultValues = { 1902 | }; 1903 | this.valueRanges = { 1904 | }; 1905 | 1906 | var filterUtils = new FilterUtils(); 1907 | this.filter = function(input,values){ 1908 | var width = input.width, height = input.height; 1909 | var inputData = input.data; 1910 | var table = []; 1911 | for(var i = 0; i < 256; i++){ 1912 | var val = (i/255 > 0.5) ? 2*(i/255-0.5) : 2*(0.5-i/255); 1913 | table[i] = parseInt(255 * val,10); 1914 | } 1915 | filterUtils.tableFilter(inputData, table, width, height); 1916 | }; 1917 | } 1918 | /** 1919 | * Generates a sparkle/sunburst effect on the image. CenterX and CenterY specify the 1920 | * position in terms of ratios of width and height. 1921 | */ 1922 | function SparkleFilter(){ 1923 | this.name = "Sparkle"; 1924 | this.isDirAnimatable = false; 1925 | this.defaultValues = { 1926 | rays : 50, 1927 | size : 25, 1928 | amount : 50, 1929 | randomness : 25, 1930 | centerX : 0.5, 1931 | centerY : 0.5 1932 | }; 1933 | this.valueRanges = { 1934 | rays : {min:1, max:100}, 1935 | size : {min:1, max:200}, 1936 | amount : {min:0, max:100}, 1937 | randomness : {min:0, max:50}, 1938 | centerX : {min:0, max:1.0}, 1939 | centerY : {min:0, max:1.0} 1940 | }; 1941 | 1942 | var filterUtils = new FilterUtils(); 1943 | this.filter = function(input,values){ 1944 | var width = input.width, height = input.height; 1945 | var inputData = input.data; 1946 | if(values === undefined){ values = this.defaultValues; } 1947 | var rays = (values.rays === undefined) ? this.defaultValues.rays : values.rays; 1948 | rays = parseInt(rays, 10); 1949 | var size = (values.size === undefined) ? this.defaultValues.size : values.size; 1950 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 1951 | var randomness = (values.randomness === undefined) ? this.defaultValues.randomness : values.randomness; 1952 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 1953 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 1954 | var iCenterX = centerX * width; 1955 | var iCenterY = centerY * height; 1956 | var rayLengths = []; 1957 | for(var j = 0; j < rays; j++){ 1958 | rayLengths[j]= size + randomness / 100 * size * filterUtils.gaussianRandom(); 1959 | } 1960 | for (var y = 0; y < height; y++) { 1961 | for (var x = 0; x < width; x++) { 1962 | var pixel = (y*width + x)*4; 1963 | var dx = x-iCenterX; 1964 | var dy = y-iCenterY; 1965 | var distance = dx*dx + dy*dy; 1966 | var angle = Math.atan2(dy,dx); 1967 | var d = (angle+Math.PI) / (Math.PI*2) * rays; 1968 | var i = parseInt(d,10); 1969 | var f = d - i; 1970 | if(size !== 0){ 1971 | var length = filterUtils.linearInterpolate(f, rayLengths[i % rays], rayLengths[(i+1) % rays]); 1972 | var g = length*length / (distance+0.0001); 1973 | g = Math.pow(g, (100-amount) / 50); 1974 | f -= 0.5; 1975 | f = 1 - f*f; 1976 | f *= g; 1977 | } 1978 | f = filterUtils.clampPixel(f,0,1); 1979 | var mixedRGB = filterUtils.mixColors(f,[inputData[pixel],inputData[pixel+1],inputData[pixel+2],inputData[pixel+3]],[255,255,255,255]); 1980 | for(var k = 0; k < 3; k++){ 1981 | inputData[pixel+k] = mixedRGB[k]; 1982 | } 1983 | } 1984 | } 1985 | }; 1986 | } 1987 | /** 1988 | * Smears out the image with square shapes to create a painting style effect. 1989 | * The mix values sets the intensity of the effect. 1990 | * NOTE: This filter can be very slow, especially at higher densities/sizes. Use with caution. 1991 | */ 1992 | function SquareSmearFilter(){ 1993 | this.name = "Square Smear"; 1994 | this.isDirAnimatable = false; 1995 | this.defaultValues = { 1996 | size : 4, 1997 | density : 0.5, 1998 | mix : 0.5 1999 | }; 2000 | this.valueRanges = { 2001 | size : {min:1, max:10}, 2002 | density : {min:0.0, max:1.0}, 2003 | mix : {min:0.0, max:1.0} 2004 | }; 2005 | 2006 | var filterUtils = new FilterUtils(); 2007 | this.filter = function(input,values){ 2008 | var width = input.width, height = input.height; 2009 | var inputData = input.data; 2010 | var outputData = []; 2011 | var k; 2012 | for(k = 0; k < inputData.length; k++){ 2013 | outputData[k] = inputData[k]; 2014 | } 2015 | if(values === undefined){ values = this.defaultValues; } 2016 | var size = (values.size === undefined) ? this.defaultValues.size : values.size; 2017 | if(size < 1){ size = 1;} 2018 | size = parseInt(size,10); 2019 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 2020 | var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; 2021 | var radius = size+1; 2022 | var radius2 = radius*radius; 2023 | var numShapes = parseInt(2*density/30*width*height / 2,10); 2024 | for(var i = 0; i < numShapes; i++){ 2025 | var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; 2026 | var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; 2027 | var rgb2 = [inputData[(sy*width+sx)*4],inputData[(sy*width+sx)*4+1],inputData[(sy*width+sx)*4+2],inputData[(sy*width+sx)*4+3]]; 2028 | for(var x = sx - radius; x < sx + radius + 1; x++){ 2029 | 2030 | for(var y = sy - radius; y < sy + radius + 1; y++){ 2031 | if (x >= 0 && x < width && y >= 0 && y < height) { 2032 | var rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 2033 | var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); 2034 | for(k = 0; k < 3; k++){ 2035 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 2036 | } 2037 | } 2038 | } 2039 | } 2040 | } 2041 | for(k = 0; k < outputData.length; k++){ 2042 | inputData[k] = outputData[k]; 2043 | } 2044 | }; 2045 | } 2046 | /** 2047 | * Divides the colors into black and white following the treshold value. Brightnesses above the threshold 2048 | * sets the color to white while values below the threshold sets the color to black. 2049 | */ 2050 | function ThresholdFilter(){ 2051 | this.name = "Black & White"; 2052 | this.isDirAnimatable = true; 2053 | this.defaultValues = { 2054 | threshold : 127 2055 | }; 2056 | this.valueRanges = { 2057 | threshold : {min:0, max:255} 2058 | }; 2059 | this.filter = function(input,values){ 2060 | var width = input.width, height = input.height; 2061 | var inputData = input.data; 2062 | if(values === undefined){ values = this.defaultValues; } 2063 | var threshold = (values.threshold === undefined) ? this.defaultValues.threshold : values.threshold; 2064 | for (var y = 0; y < height; y++) { 2065 | for (var x = 0; x < width; x++) { 2066 | var pixel = (y*width + x)*4; 2067 | var brightness = (inputData[pixel] + inputData[pixel+1] + inputData[pixel+2])/3; 2068 | var colorVal = 0; 2069 | if(brightness > threshold){ 2070 | colorVal = 255; 2071 | } 2072 | inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = colorVal; 2073 | } 2074 | } 2075 | }; 2076 | } 2077 | /** 2078 | * Creates ripples on the image horizontally/vertically in a sine pattern. 2079 | */ 2080 | function TriangleRippleFilter(){ 2081 | this.name = "Triangle Ripples"; 2082 | this.isDirAnimatable = false; 2083 | this.defaultValues = { 2084 | xAmplitude : 5, 2085 | yAmplitude : 5, 2086 | xWavelength : 16, 2087 | yWavelength : 16 2088 | }; 2089 | this.valueRanges = { 2090 | xAmplitude : {min:0, max:30}, 2091 | yAmplitude : {min:0, max:30}, 2092 | xWavelength : {min:1, max:50}, 2093 | yWavelength : {min:1, max:50} 2094 | }; 2095 | 2096 | var filterUtils = new FilterUtils(); 2097 | 2098 | this.filter = function (input, values){ 2099 | var width = input.width, height = input.height; 2100 | var inputData = input.data; 2101 | if(values === undefined){ values = this.defaultValues; } 2102 | var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude; 2103 | var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude; 2104 | var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength; 2105 | var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength; 2106 | var transInverse = function(x,y,out){ 2107 | var nx = y/xWavelength; 2108 | var ny = x/yWavelength; 2109 | var fx = filterUtils.triangle(nx,1); 2110 | var fy = filterUtils.triangle(ny,1); 2111 | out[0] = x + xAmplitude * fx; 2112 | out[1] = y + yAmplitude * fy; 2113 | }; 2114 | filterUtils.transformFilter(inputData,transInverse,width,height); 2115 | }; 2116 | } 2117 | /** 2118 | * Twists the image around a given center point. CenterX and CenterY specify the 2119 | * position in terms of ratios of width and height. 2120 | */ 2121 | function TwirlFilter(){ 2122 | this.name = "Twirl"; 2123 | this.isDirAnimatable = false; 2124 | this.defaultValues = { 2125 | radius : 100, 2126 | angle : 180, 2127 | centerX : 0.5, 2128 | centerY : 0.5 2129 | }; 2130 | this.valueRanges = { 2131 | radius : {min: 1, max: 200}, 2132 | angle : {min: 0, max: 360}, 2133 | centerX : {min: 0.0, max:1.0}, 2134 | centerY : {min: 0.0, max:1.0} 2135 | }; 2136 | 2137 | var filterUtils = new FilterUtils(); 2138 | 2139 | this.filter = function (input, values){ 2140 | var width = input.width, height = input.height; 2141 | var inputData = input.data; 2142 | if(values === undefined){ values = this.defaultValues; } 2143 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 2144 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 2145 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 2146 | var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; 2147 | var radius2 = radius*radius; 2148 | angle = angle/180 * Math.PI; 2149 | var iCenterX = width * centerX; var iCenterY = height * centerY; 2150 | var transInverse = function(x,y,out){ 2151 | var dx = x-iCenterX; 2152 | var dy = y-iCenterY; 2153 | var distance = dx*dx + dy*dy; 2154 | if(distance > radius2){ 2155 | out[0] = x; 2156 | out[1] = y; 2157 | } else { 2158 | distance = Math.sqrt(distance); 2159 | var a = Math.atan2(dy, dx) + angle * (radius-distance) / radius; 2160 | out[0] = iCenterX + distance*Math.cos(a); 2161 | out[1] = iCenterY + distance*Math.sin(a); 2162 | } 2163 | }; 2164 | filterUtils.transformFilter(inputData,transInverse,width,height); 2165 | }; 2166 | } 2167 | /** 2168 | * Creates a classical vignette effect on the image i.e. darkens the corners. 2169 | */ 2170 | function VignetteFilter(){ 2171 | this.name = "Vignette"; 2172 | this.isDirAnimatable = false; 2173 | this.defaultValues = { 2174 | amount : 0.3 2175 | }; 2176 | this.valueRanges = { 2177 | amount : {min:0.0, max:1.0} 2178 | }; 2179 | this.filter = function(input,values){ 2180 | var width = input.width, height = input.height; 2181 | var inputData = input.data; 2182 | var outputData = []; 2183 | if(values === undefined){ values = this.defaultValues; } 2184 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 2185 | var canvas = document.createElement("canvas"); 2186 | canvas.width = width; 2187 | canvas.height = height; 2188 | var context = canvas.getContext("2d"); 2189 | var gradient; 2190 | var radius = Math.sqrt( Math.pow(width/2, 2) + Math.pow(height/2, 2) ); 2191 | context.putImageData(input,0,0); 2192 | context.globalCompositeOperation = 'source-over'; 2193 | 2194 | gradient = context.createRadialGradient(width/2, height/2, 0, width/2, height/2, radius); 2195 | gradient.addColorStop(0, 'rgba(0,0,0,0)'); 2196 | gradient.addColorStop(0.5, 'rgba(0,0,0,0)'); 2197 | gradient.addColorStop(1, 'rgba(0,0,0,' + amount + ')'); 2198 | context.fillStyle = gradient; 2199 | context.fillRect(0, 0, width, height); 2200 | outputData = context.getImageData(0,0,width,height).data; 2201 | for(var k = 0; k < outputData.length; k++){ 2202 | inputData[k] = outputData[k]; 2203 | } 2204 | }; 2205 | } 2206 | /** 2207 | * Produces a water ripple/waves on the image. CenterX and CenterY specify the 2208 | * position in terms of ratios of width and height. 2209 | */ 2210 | function WaterRippleFilter(){ 2211 | this.name = "Water Ripples"; 2212 | this.isDirAnimatable = false; 2213 | this.defaultValues = { 2214 | phase : 0, 2215 | radius : 50, 2216 | wavelength : 16, 2217 | amplitude : 10, 2218 | centerX : 0.5, 2219 | centerY : 0.5 2220 | }; 2221 | this.valueRanges = { 2222 | phase : {min: 0, max: 100}, 2223 | radius : {min: 1, max: 200}, 2224 | wavelength : {min: 1, max: 100}, 2225 | amplitude : {min: 1, max: 100}, 2226 | centerX : {min: 0.0, max:1.0}, 2227 | centerY : {min: 0.0, max:1.0} 2228 | }; 2229 | var filterUtils = new FilterUtils(); 2230 | 2231 | this.filter = function (input, values){ 2232 | var width = input.width, height = input.height; 2233 | var inputData = input.data; 2234 | if(values === undefined){ values = this.defaultValues; } 2235 | var wavelength = (values.wavelength === undefined) ? this.defaultValues.wavelength : values.wavelength; 2236 | var amplitude = (values.amplitude === undefined) ? this.defaultValues.amplitude : values.amplitude; 2237 | var phase = (values.phase === undefined) ? this.defaultValues.phase : values.phase; 2238 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 2239 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 2240 | var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; 2241 | var radius2 = radius*radius; 2242 | var iCenterX = width * centerX; var iCenterY = height * centerY; 2243 | var transInverse = function(x,y,out){ 2244 | var dx = x-iCenterX; 2245 | var dy = y-iCenterY; 2246 | var distance2 = dx*dx + dy*dy; 2247 | if(distance2 > radius2){ 2248 | out[0] = x; 2249 | out[1] = y; 2250 | } else { 2251 | var distance = Math.sqrt(distance2); 2252 | var amount = amplitude * Math.sin(distance/wavelength * Math.PI * 2 - phase); 2253 | amount *= (radius-distance)/radius; 2254 | if(distance !== 0){ 2255 | amount *= wavelength/distance; 2256 | } 2257 | out[0] = x + dx*amount; 2258 | out[1] = y + dy*amount; 2259 | } 2260 | }; 2261 | filterUtils.transformFilter(inputData,transInverse,width,height); 2262 | }; 2263 | } 2264 | /** 2265 | * A collection of all the filters. 2266 | */ 2267 | var JSManipulate = { 2268 | blur : new BlurFilter(), 2269 | brightness : new BrightnessFilter(), 2270 | bump : new BumpFilter(), 2271 | circlesmear : new CircleSmearFilter(), 2272 | contrast : new ContrastFilter(), 2273 | crosssmear : new CrossSmearFilter(), 2274 | diffusion : new DiffusionFilter(), 2275 | dither : new DitherFilter(), 2276 | edge : new EdgeFilter(), 2277 | emboss : new EmbossFilter(), 2278 | exposure : new ExposureFilter(), 2279 | gain : new GainFilter(), 2280 | gamma : new GammaFilter(), 2281 | grayscale : new GrayscaleFilter(), 2282 | hue : new HueFilter(), 2283 | invert : new InvertFilter(), 2284 | kaleidoscope : new KaleidoscopeFilter(), 2285 | lensdistortion : new LensDistortionFilter(), 2286 | linesmear : new LineSmearFilter(), 2287 | maximum : new MaximumFilter(), 2288 | median : new MedianFilter(), 2289 | minimum : new MinimumFilter(), 2290 | noise : new NoiseFilter(), 2291 | oil : new OilFilter(), 2292 | opacity : new OpacityFilter(), 2293 | pinch : new PinchFilter(), 2294 | pixelate : new PixelationFilter(), 2295 | posterize : new PosterizeFilter(), 2296 | rgbadjust : new RGBAdjustFilter(), 2297 | saturation : new SaturationFilter(), 2298 | sawtoothripple : new SawtoothRippleFilter(), 2299 | sepia : new SepiaFilter(), 2300 | sharpen : new SharpenFilter(), 2301 | sineripple : new SineRippleFilter(), 2302 | solarize : new SolarizeFilter(), 2303 | sparkle : new SparkleFilter(), 2304 | squaresmear : new SquareSmearFilter(), 2305 | threshold : new ThresholdFilter(), 2306 | triangleripple : new TriangleRippleFilter(), 2307 | twirl : new TwirlFilter(), 2308 | vignette : new VignetteFilter(), 2309 | waterripple : new WaterRippleFilter() 2310 | }; -------------------------------------------------------------------------------- /script/minified/jsmanipulate.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | ========================================================================= 3 | JSManipulate v1.0 (2011-08-01) 4 | 5 | Javascript image filter & effect library 6 | 7 | Developed by Joel Besada (http://www.joelb.me) 8 | Demo page: http://www.joelb.me/jsmanipulate 9 | 10 | MIT LICENSED (http://www.opensource.org/licenses/mit-license.php) 11 | Copyright (c) 2011, Joel Besada 12 | ========================================================================= 13 | */ 14 | function FilterUtils(){this.HSVtoRGB=function(a,b,f){var d,g,c,e=Math.floor(a*6),h=a*6-e,a=f*(1-b),k=f*(1-h*b),b=f*(1-(1-h)*b);switch(e%6){case 0:d=f;g=b;c=a;break;case 1:d=k;g=f;c=a;break;case 2:d=a;g=f;c=b;break;case 3:d=a;g=k;c=f;break;case 4:d=b;g=a;c=f;break;case 5:d=f,g=a,c=k}return[d*255,g*255,c*255]};this.RGBtoHSV=function(a,b,f){a/=255;b/=255;f/=255;var d=Math.max(a,b,f),g=Math.min(a,b,f),c,e=d-g;if(d===g)c=0;else{switch(d){case a:c=(b-f)/e+(b=d||f<0||f>=g)return[a[(this.clampPixel(f,0,g-1)*d+this.clampPixel(b,0,d-1))*4],a[(this.clampPixel(f,0,g-1)*d+this.clampPixel(b,0,d-1))*4+1],a[(this.clampPixel(f,0,g-1)*d+this.clampPixel(b,0,d-1))*4+2],a[(this.clampPixel(f,0,g-1)*d+this.clampPixel(b,0,d-1))*4+3]];return[a[c],a[c+1],a[c+2],a[c+3]]};var h=!1,e;this.gaussianRandom=function(){if(h)return h=!1,e;else{var a,b,f;do a=2*Math.random()-1,b=2* 16 | Math.random()-1,f=a*a+b*b;while(f>=1||f===0);f=Math.sqrt(-2*Math.log(f)/f);e=b*f;h=!0;return a*f}};this.clampPixel=function(a,b,f){return af?f:a};this.triangle=function(a){a=this.mod(a,1);return 2*(a<0.5?a:1-a)};this.mod=function(a,b){var f=parseInt(a/b,10);a-=f*b;if(a<0)return a+b;return a};this.mixColors=function(a,b,f){var d=this.linearInterpolate(a,b[0],f[0]),g=this.linearInterpolate(a,b[1],f[1]),c=this.linearInterpolate(a,b[2],f[2]),a=this.linearInterpolate(a,b[3],f[3]);return[d,g,c,a]}; 17 | this.linearInterpolate=function(a,b,f){return b+a*(f-b)};this.bilinearInterpolate=function(a,b,f,d,g,c){var e=f[0],h=f[1],k=f[2],i=d[0],o=d[1],p=d[2],s=g[0],n=g[1],m=g[2],q=g[3],t=c[0],r=c[1],g=c[2],u=c[3],c=1-a,v=1-b,f=c*f[3]+a*d[3],f=v*f+b*(c*q+a*u),e=v*(c*e+a*i)+b*(c*s+a*t),h=v*(c*h+a*o)+b*(c*n+a*r);return[e,h,v*(c*k+a*p)+b*(c*m+a*g),f]};this.tableFilter=function(a,b,f,d){for(var g=0;g=0&&i=0&&o=2.5?0.98711*g-0.9633:g>=0.5?3.97156-4.14554*Math.sqrt(1-0.26891*g):2*g*(3.97156-4.14554*Math.sqrt(0.865545));var c=g*g,j=c*g,l=1.57825+2.44413*g+1.4281*c+0.422205*j;g=(2.44413*g+2.85619*c+1.26661*j)/l;for(var c=-(1.4281*c+1.26661*j)/l,j=0.422205*j/l,l=1-(g+c+j),k=0,i,o,p, 22 | s,n,m,k=0;k<3;k++)for(var q=0;q=o;i-=4)p=l*d[i]+g*s+c*n+j*m,d[i]=p,m=n,n=s,s=p}for(k=0;k<3;k++)for(q=0;q=o;i-=b)p=l*d[i]+g*s+c*n+j*m,d[i]=p,m=n,n=s,s=p}}} 23 | function BrightnessFilter(){this.name="Brightness";this.isDirAnimatable=!0;this.defaultValues={amount:0};this.valueRanges={amount:{min:-1,max:1}};var h=new FilterUtils;this.filter=function(e,a){var b=e.width,f=e.height,d=e.data;if(a===void 0)a=this.defaultValues;for(var g=a.amount===void 0?this.defaultValues.amount:a.amount,c=0;c1&&(k[2]=1);for(var k=h.HSVtoRGB(k[0],k[1],k[2]),i=0;i<3;i++)d[l+i]= 24 | k[i]}}}function BumpFilter(){this.name="Bump";this.isDirAnimatable=!0;this.defaultValues={};this.valueRanges={};var h=new FilterUtils;this.filter=function(e){h.convolveFilter(e.data,[-1,-1,0,-1,1,1,0,1,1],e.width,e.height)}} 25 | function CircleSmearFilter(){this.name="Circle Smear";this.isDirAnimatable=!1;this.defaultValues={size:4,density:0.5,mix:0.5};this.valueRanges={size:{min:1,max:10},density:{min:0,max:1},mix:{min:0,max:1}};var h=new FilterUtils;this.filter=function(e,a){for(var b=e.width,f=e.height,d=e.data,g=[],c=0;c=0&&n=0&&m=0&&q=0&&q=0&&o=0&&n=j-j*e/j)c[0]=a,c[1]=b;else{var t=1/g,r=Math.sqrt((1-e/j-h/j)*j),u=r*r,d=Math.acos(d/Math.sqrt(e+u)),e=Math.PI/2-d,e=Math.asin(Math.sin(e)*t),e=Math.PI/2-d-e;c[0]=a-Math.tan(e)*r;a=Math.acos(f/Math.sqrt(h+u));e=Math.PI/2-a;e=Math.asin(Math.sin(e)*t);e=Math.PI/2-a-e;c[1]=b-Math.tan(e)*r}},b,f)}} 53 | function LineSmearFilter(){this.name="Line Smear";this.isDirAnimatable=!1;this.defaultValues={distance:8,density:0.5,angle:0,mix:0.5};this.valueRanges={distance:{min:1,max:30},density:{min:0,max:1},angle:{min:0,max:360},mix:{min:0,max:1}};var h=new FilterUtils;this.filter=function(e,a){var b=e.width,f=e.height,d=e.data,g=[],c;for(c=0;c=0&&u=0){c=[g[(u*b+r)*4],g[(u*b+r)*4+1],g[(u*b+r)*4+2],g[(u*b+r)*4+3]];z=h.mixColors(i,c,q);for(c=0;c<3;c++)g[(u*b+r)*4+c]=z[c]}if(Math.abs(t)>Math.abs(m)){v=2*m-t;y=2*m;for(t=2*(m-t);r!=s;)if(v<=0?v+=y:(v+=t,u+=x),r+=w,r=0&&u=0){c=[g[(u*b+r)*4],g[(u*b+r)*4+1],g[(u*b+r)*4+2],g[(u*b+r)*4+3]];z=h.mixColors(i,c,q);for(c=0;c<3;c++)g[(u*b+r)*4+c]=z[c]}}else{v=2*t-m;y=2*t;for(t=2*(t-m);u!=n;)if(v<=0?v+=y:(v+= 56 | t,r+=w),u+=x,r=0&&u=0){c=[g[(u*b+r)*4],g[(u*b+r)*4+1],g[(u*b+r)*4+2],g[(u*b+r)*4+3]];z=h.mixColors(i,c,q);for(c=0;c<3;c++)g[(u*b+r)*4+c]=z[c]}}}for(c=0;c=0&&i=0&&p=0&&i=0&&p=0&&i=0&&pc[m]&&(m=u),j[u]>j[q]&&(q=u),l[u]>l[t]&&(t=u);m=k[m]/c[m];q=i[q]/j[q];t=o[t]/l[t];d[n]=m;d[n+1]=q;d[n+2]=t;d[n+3]=f[n+3]}for(a=0;ai||h===0?(d[0]=a,d[1]=b):(a=Math.sqrt(h/i),b=Math.pow(Math.sin(Math.PI*0.5*a),-g),e*=b,f*=b,a=1-a,b=c*a*a,a=Math.sin(b),b=Math.cos(b),d[0]=o+b*e-a*f,d[1]=p+a*e+b*f)},b,f)}} 67 | function PixelationFilter(){this.name="Pixelation";this.isDirAnimatable=!1;this.defaultValues={size:5};this.valueRanges={size:{min:1,max:50}};this.filter=function(h,e){var a=h.width,b=h.height,f=h.data;if(e===void 0)e=this.defaultValues;for(var d=e.size===void 0?this.defaultValues.size:e.size,d=parseInt(d,10),g,c,j,l=0;l0.5?2*(d/255-0.5):2*(0.5-d/255)),10);h.tableFilter(e,f,a,b)}} 81 | function SparkleFilter(){this.name="Sparkle";this.isDirAnimatable=!1;this.defaultValues={rays:50,size:25,amount:50,randomness:25,centerX:0.5,centerY:0.5};this.valueRanges={rays:{min:1,max:100},size:{min:1,max:200},amount:{min:0,max:100},randomness:{min:0,max:50},centerX:{min:0,max:1},centerY:{min:0,max:1}};var h=new FilterUtils;this.filter=function(e,a){var b=e.width,f=e.height,d=e.data;if(a===void 0)a=this.defaultValues;for(var g=a.rays===void 0?this.defaultValues.rays:a.rays,g=parseInt(g,10),c= 82 | a.size===void 0?this.defaultValues.size:a.size,j=a.amount===void 0?this.defaultValues.amount:a.amount,l=a.randomness===void 0?this.defaultValues.randomness:a.randomness,k=(a.centerX===void 0?this.defaultValues.centerX:a.centerX)*b,i=(a.centerY===void 0?this.defaultValues.centerY:a.centerY)*f,o=[],p=0;p=0&&n=0&&md&&(l=255);f[j]=f[j+1]=f[j+2]=l}}} 87 | function TriangleRippleFilter(){this.name="Triangle Ripples";this.isDirAnimatable=!1;this.defaultValues={xAmplitude:5,yAmplitude:5,xWavelength:16,yWavelength:16};this.valueRanges={xAmplitude:{min:0,max:30},yAmplitude:{min:0,max:30},xWavelength:{min:1,max:50},yWavelength:{min:1,max:50}};var h=new FilterUtils;this.filter=function(e,a){var b=e.width,f=e.height,d=e.data;if(a===void 0)a=this.defaultValues;var g=a.xAmplitude===void 0?this.defaultValues.xAmplitude:a.xAmplitude,c=a.yAmplitude===void 0?this.defaultValues.yAmplitude: 88 | a.yAmplitude,j=a.xWavelength===void 0?this.defaultValues.xWavelength:a.xWavelength,l=a.yWavelength===void 0?this.defaultValues.yWavelength:a.yWavelength;h.transformFilter(d,function(a,b,d){var e=a/l,f=h.triangle(b/j,1),e=h.triangle(e,1);d[0]=a+g*f;d[1]=b+c*e},b,f)}} 89 | function TwirlFilter(){this.name="Twirl";this.isDirAnimatable=!1;this.defaultValues={radius:100,angle:180,centerX:0.5,centerY:0.5};this.valueRanges={radius:{min:1,max:200},angle:{min:0,max:360},centerX:{min:0,max:1},centerY:{min:0,max:1}};var h=new FilterUtils;this.filter=function(e,a){var b=e.width,f=e.height,d=e.data;if(a===void 0)a=this.defaultValues;var g=a.angle===void 0?this.defaultValues.angle:a.angle,c=a.centerX===void 0?this.defaultValues.centerX:a.centerX,j=a.centerY===void 0?this.defaultValues.centerY: 90 | a.centerY,l=a.radius===void 0?this.defaultValues.radius:a.radius,k=l*l,g=g/180*Math.PI,i=b*c,o=f*j;h.transformFilter(d,function(a,b,c){var d=a-i,e=b-o,f=d*d+e*e;f>k?(c[0]=a,c[1]=b):(f=Math.sqrt(f),a=Math.atan2(e,d)+g*(l-f)/l,c[0]=i+f*Math.cos(a),c[1]=o+f*Math.sin(a))},b,f)}} 91 | function VignetteFilter(){this.name="Vignette";this.isDirAnimatable=!1;this.defaultValues={amount:0.3};this.valueRanges={amount:{min:0,max:1}};this.filter=function(h,e){var a=h.width,b=h.height,f=h.data,d=[];if(e===void 0)e=this.defaultValues;var d=e.amount===void 0?this.defaultValues.amount:e.amount,g=document.createElement("canvas");g.width=a;g.height=b;var g=g.getContext("2d"),c;c=Math.sqrt(Math.pow(a/2,2)+Math.pow(b/2,2));g.putImageData(h,0,0);g.globalCompositeOperation="source-over";c=g.createRadialGradient(a/ 92 | 2,b/2,0,a/2,b/2,c);c.addColorStop(0,"rgba(0,0,0,0)");c.addColorStop(0.5,"rgba(0,0,0,0)");c.addColorStop(1,"rgba(0,0,0,"+d+")");g.fillStyle=c;g.fillRect(0,0,a,b);d=g.getImageData(0,0,a,b).data;for(a=0;ak)d[0]=a,d[1]=b;else{var h=Math.sqrt(h),r=c*Math.sin(h/g*Math.PI*2-j);r*=(l-h)/l;h!==0&&(r*=g/h);d[0]=a+e*r;d[1]=b+f*r}}, 95 | b,f)}} 96 | var JSManipulate={blur:new BlurFilter,brightness:new BrightnessFilter,bump:new BumpFilter,circlesmear:new CircleSmearFilter,contrast:new ContrastFilter,crosssmear:new CrossSmearFilter,diffusion:new DiffusionFilter,dither:new DitherFilter,edge:new EdgeFilter,emboss:new EmbossFilter,exposure:new ExposureFilter,gain:new GainFilter,gamma:new GammaFilter,grayscale:new GrayscaleFilter,hue:new HueFilter,invert:new InvertFilter,kaleidoscope:new KaleidoscopeFilter,lensdistortion:new LensDistortionFilter,linesmear:new LineSmearFilter, 97 | maximum:new MaximumFilter,median:new MedianFilter,minimum:new MinimumFilter,noise:new NoiseFilter,oil:new OilFilter,opacity:new OpacityFilter,pinch:new PinchFilter,pixelate:new PixelationFilter,posterize:new PosterizeFilter,rgbadjust:new RGBAdjustFilter,saturation:new SaturationFilter,sawtoothripple:new SawtoothRippleFilter,sepia:new SepiaFilter,sharpen:new SharpenFilter,sineripple:new SineRippleFilter,solarize:new SolarizeFilter,sparkle:new SparkleFilter,squaresmear:new SquareSmearFilter,threshold:new ThresholdFilter, 98 | triangleripple:new TriangleRippleFilter,twirl:new TwirlFilter,vignette:new VignetteFilter,waterripple:new WaterRippleFilter}; 99 | -------------------------------------------------------------------------------- /script/separate filters/blur.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Blurs the image with Gaussian blur. 3 | */ 4 | function BlurFilter(){ 5 | this.name = "Blur"; 6 | this.defaultValues = { 7 | amount : 3 8 | }; 9 | this.valueRanges = { 10 | amount : {min:0, max:10} 11 | }; 12 | this.filter = function(input,values){ 13 | var width = input.width; 14 | var width4 = width << 2; 15 | var height = input.height; 16 | var inputData = input.data; 17 | var q; 18 | var amount = values.amount; 19 | if (amount < 0.0) { 20 | amount = 0.0; 21 | } 22 | if (amount >= 2.5) { 23 | q = 0.98711 * amount - 0.96330; 24 | } else if (amount >= 0.5) { 25 | q = 3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * amount); 26 | } else { 27 | q = 2 * amount * (3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * 0.5)); 28 | } 29 | var qq = q * q; 30 | var qqq = qq * q; 31 | var b0 = 1.57825 + (2.44413 * q) + (1.4281 * qq ) + (0.422205 * qqq); 32 | var b1 = ((2.44413 * q) + (2.85619 * qq) + (1.26661 * qqq)) / b0; 33 | var b2 = (-((1.4281 * qq) + (1.26661 * qqq))) / b0; 34 | var b3 = (0.422205 * qqq) / b0; 35 | var bigB = 1.0 - (b1 + b2 + b3); 36 | for (var c = 0; c < 3; c++) { 37 | for (var y = 0; y < height; y++) { 38 | var index = y * width4 + c; 39 | var indexLast = y * width4 + ((width - 1) << 2) + c; 40 | var pixel = inputData[index]; 41 | var ppixel = pixel; 42 | var pppixel = ppixel; 43 | var ppppixel = pppixel; 44 | for (; index <= indexLast; index += 4) { 45 | pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; 46 | inputData[index] = pixel; 47 | ppppixel = pppixel; 48 | pppixel = ppixel; 49 | ppixel = pixel; 50 | } 51 | index = y * width4 + ((width - 1) << 2) + c; 52 | indexLast = y * width4 + c; 53 | pixel = inputData[index]; 54 | ppixel = pixel; 55 | pppixel = ppixel; 56 | ppppixel = pppixel; 57 | for (; index >= indexLast; index -= 4) { 58 | pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; 59 | inputData[index] = pixel; 60 | ppppixel = pppixel; 61 | pppixel = ppixel; 62 | ppixel = pixel; 63 | } 64 | } 65 | } 66 | for (var c = 0; c < 3; c++) { 67 | for (var x = 0; x < width; x++) { 68 | var index = (x << 2) + c; 69 | var indexLast = (height - 1) * width4 + (x << 2) + c; 70 | var pixel = inputData[index]; 71 | var ppixel = pixel; 72 | var pppixel = ppixel; 73 | var ppppixel = pppixel; 74 | for (; index <= indexLast; index += width4) { 75 | pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; 76 | inputData[index] = pixel; 77 | ppppixel = pppixel; 78 | pppixel = ppixel; 79 | ppixel = pixel; 80 | } 81 | index = (height - 1) * width4 + (x << 2) + c; 82 | indexLast = (x << 2) + c; 83 | pixel = inputData[index]; 84 | ppixel = pixel; 85 | pppixel = ppixel; 86 | ppppixel = pppixel; 87 | for (; index >= indexLast; index -= width4) { 88 | pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; 89 | inputData[index] = pixel; 90 | ppppixel = pppixel; 91 | pppixel = ppixel; 92 | ppixel = pixel; 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /script/separate filters/brightness.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adjusts the brightness of the image by going over to HSV values. 3 | * Negative values decrease brightness while positive values increase brightness. 4 | */ 5 | function BrightnessFilter(){ 6 | this.name = "Brightness"; 7 | this.defaultValues = { 8 | amount : 0.0 9 | }; 10 | this.valueRanges = { 11 | amount : {min:-1.0, max:1.0} 12 | }; 13 | if(!FilterUtils){ 14 | if(console){ 15 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 16 | } 17 | return; 18 | } 19 | var filterUtils = new FilterUtils(); 20 | this.filter = function(input,values){ 21 | var width = input.width, height = input.height; 22 | var inputData = input.data; 23 | if(values === undefined){ values = this.defaultValues; } 24 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 25 | for (var y = 0; y < height; y++) { 26 | for (var x = 0; x < width; x++) { 27 | var pixel = (y*width + x)*4; 28 | var hsv = filterUtils.RGBtoHSV(inputData[pixel],inputData[pixel+1],inputData[pixel+2]); 29 | hsv[2] += amount; 30 | if(hsv[2] < 0){ 31 | hsv[2] = 0; 32 | } else if (hsv[2] > 1){ 33 | hsv[2] = 1; 34 | } 35 | var rgb = filterUtils.HSVtoRGB(hsv[0],hsv[1],hsv[2]); 36 | for(var i = 0; i < 3; i++){ 37 | inputData[pixel+i] = rgb[i]; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /script/separate filters/bump.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Embosses the edges of the image. 3 | * This filter takes no parameters but can be applied several times for 4 | * further effect. 5 | */ 6 | function BumpFilter(){ 7 | this.name = "Bump"; 8 | this.defaultValues = { 9 | }; 10 | this.valueRanges = { 11 | }; 12 | if(!FilterUtils){ 13 | if(console){ 14 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 15 | } 16 | return; 17 | } 18 | var filterUtils = new FilterUtils(); 19 | this.filter = function(input,values){ 20 | var width = input.width, height = input.height; 21 | var inputData = input.data; 22 | var matrix = [-1,-1, 0, 23 | -1, 1, 1, 24 | 0, 1, 1]; 25 | filterUtils.convolveFilter(inputData,matrix,width,height); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /script/separate filters/circlesmear.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Smears out the image with circular shapes to create a painting style effect. 3 | * The mix values sets the intensity of the effect. 4 | * NOTE: This filter can be very slow, especially at higher densities/sizes. Use with caution. 5 | */ 6 | function CircleSmearFilter(){ 7 | this.name = "Circle Smear"; 8 | this.defaultValues = { 9 | size : 4, 10 | density : 0.5, 11 | mix : 0.5, 12 | }; 13 | this.valueRanges = { 14 | size : {min:1, max:10}, 15 | density : {min:0.0, max:1.0}, 16 | mix : {min:0.0, max:1.0}, 17 | }; 18 | if(!FilterUtils){ 19 | if(console){ 20 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 21 | } 22 | return; 23 | } 24 | var filterUtils = new FilterUtils(); 25 | this.filter = function(input,values){ 26 | var width = input.width, height = input.height; 27 | var inputData = input.data; 28 | var outputData = []; 29 | for(var k = 0; k < inputData.length; k++){ 30 | outputData[k] = inputData[k]; 31 | } 32 | if(values === undefined){ values = this.defaultValues; } 33 | var size = (values.size === undefined) ? this.defaultValues.size : values.size; 34 | if(size < 1){ size = 1;} 35 | size = parseInt(size); 36 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 37 | var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; 38 | var radius = size+1; 39 | var radius2 = radius*radius; 40 | var numShapes = parseInt(2*density/30*width*height / 2); 41 | for(var i = 0; i < numShapes; i++){ 42 | var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; 43 | var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; 44 | var rgb2 = [inputData[(sy*width+sx)*4],inputData[(sy*width+sx)*4+1],inputData[(sy*width+sx)*4+2],inputData[(sy*width+sx)*4+3]]; 45 | for(var x = sx - radius; x < sx + radius + 1; x++){ 46 | for(var y = sy - radius; y < sy + radius + 1; y++){ 47 | var f = (x - sx) * (x - sx) + (y - sy) * (y - sy); 48 | if (x >= 0 && x < width && y >= 0 && y < height && f <= radius2) { 49 | var rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 50 | var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2) 51 | for(var k = 0; k < 3; k++){ 52 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 53 | } 54 | } 55 | } 56 | } 57 | } 58 | for(var k = 0; k < outputData.length; k++){ 59 | inputData[k] = outputData[k]; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /script/separate filters/contrast.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adjusts the contrast of the image. 3 | */ 4 | function ContrastFilter(){ 5 | this.name = "Contrast"; 6 | this.defaultValues = { 7 | amount : 1.0 8 | }; 9 | this.valueRanges = { 10 | amount : {min:0.0, max:2.0} 11 | }; 12 | if(!FilterUtils){ 13 | if(console){ 14 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 15 | } 16 | return; 17 | } 18 | var filterUtils = new FilterUtils(); 19 | this.filter = function(input,values){ 20 | var width = input.width, height = input.height; 21 | var inputData = input.data; 22 | if(values === undefined){ values = this.defaultValues; } 23 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 24 | if(amount < 0){ 25 | amount = 0.0; 26 | } 27 | var table = []; 28 | 29 | for(var i = 0; i < 256; i++){ 30 | table[i] = parseInt(255 * (((i/255)-0.5)*amount+0.5)); 31 | } 32 | filterUtils.tableFilter(inputData,table,width,height); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /script/separate filters/crosssmear.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Smears out the image with cross shapes to create a painting style effect. 3 | * The mix values sets the intensity of the effect. 4 | */ 5 | function CrossSmearFilter(){ 6 | this.name = "Cross Smear"; 7 | this.defaultValues = { 8 | distance : 8, 9 | density : 0.5, 10 | mix : 0.5, 11 | }; 12 | this.valueRanges = { 13 | distance : {min:0, max:30}, 14 | density : {min:0.0, max:1.0}, 15 | mix : {min:0.0, max:1.0}, 16 | }; 17 | if(!FilterUtils){ 18 | if(console){ 19 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 20 | } 21 | return; 22 | } 23 | var filterUtils = new FilterUtils(); 24 | this.filter = function(input,values){ 25 | var width = input.width, height = input.height; 26 | var inputData = input.data; 27 | var outputData = []; 28 | for(var k = 0; k < inputData.length; k++){ 29 | outputData[k] = inputData[k]; 30 | } 31 | if(values === undefined){ values = this.defaultValues; } 32 | var distance = (values.distance === undefined) ? this.defaultValues.distance : values.distance; 33 | if(distance < 0){ distance = 0;} 34 | distance = parseInt(distance); 35 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 36 | var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; 37 | var numShapes = parseInt(2*density*width * height / (distance + 1)); 38 | for(var i = 0; i < numShapes; i++){ 39 | var x = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; 40 | var y = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; 41 | var length = (Math.random()*Math.pow(2,32)) % distance + 1; 42 | var rgb2 = [inputData[(y*width+x)*4],inputData[(y*width+x)*4+1],inputData[(y*width+x)*4+2],inputData[(y*width+x)*4+3]]; 43 | for (var x1 = x-length; x1 < x+length+1; x1++) { 44 | if(x1 >= 0 && x1 < width){ 45 | var rgb1 = [outputData[(y*width+x1)*4],outputData[(y*width+x1)*4+1],outputData[(y*width+x1)*4+2],outputData[(y*width+x1)*4+3]]; 46 | var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2) 47 | for(var k = 0; k < 3; k++){ 48 | outputData[(y*width+x1)*4+k] = mixedRGB[k]; 49 | } 50 | } 51 | 52 | } 53 | for (var y1 = y-length; y1 < y+length+1; y1++) { 54 | if(y1 >= 0 && y1 < height){ 55 | var rgb1 = [outputData[(y1*width+x)*4],outputData[(y1*width+x)*4+1],outputData[(y1*width+x)*4+2],outputData[(y1*width+x)*4+3]]; 56 | var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2) 57 | for(var k = 0; k < 3; k++){ 58 | outputData[(y1*width+x)*4+k] = mixedRGB[k]; 59 | } 60 | } 61 | 62 | } 63 | } 64 | for(var k = 0; k < outputData.length; k++){ 65 | inputData[k] = outputData[k]; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /script/separate filters/diffusion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Diffuses the image creating a frosted glass effect. 3 | */ 4 | function DiffusionFilter(){ 5 | this.name = "Diffusion"; 6 | this.defaultValues = { 7 | scale: 4 8 | }; 9 | this.valueRanges = { 10 | scale: {min: 1, max: 100} 11 | }; 12 | if(!FilterUtils){ 13 | if(console){ 14 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 15 | } 16 | return; 17 | } 18 | var filterUtils = new FilterUtils(); 19 | this.filter = function (input, values){ 20 | var width = input.width, height = input.height; 21 | var inputData = input.data; 22 | if(values === undefined){ values = this.defaultValues; } 23 | var scale = (values.scale === undefined) ? this.defaultValues.scale : values.scale; 24 | var out = []; 25 | var outputData = []; 26 | var sinTable = []; 27 | var cosTable = []; 28 | for(var i = 0; i < 256; i++){ 29 | var angle = Math.PI*2*i/256; 30 | sinTable[i] = scale*Math.sin(angle); 31 | cosTable[i] = scale*Math.cos(angle); 32 | } 33 | transInverse = function (x,y,out){ 34 | var angle = parseInt(Math.random() * 255); 35 | var distance = Math.random(); 36 | out[0] = x + distance * sinTable[angle]; 37 | out[1] = y + distance * cosTable[angle]; 38 | } 39 | filterUtils.transformFilter(inputData,transInverse,width,height); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /script/separate filters/dither.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dithers the image to the specified number of colors. Setting color to false 3 | * grayscales the image. 4 | */ 5 | function DitherFilter(){ 6 | this.name = "Dither"; 7 | this.defaultValues = { 8 | levels : 3, 9 | color : true 10 | }; 11 | this.valueRanges = { 12 | levels : {min:2, max:30}, 13 | color : {min:false, max:true} 14 | }; 15 | if(!FilterUtils){ 16 | if(console){ 17 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 18 | } 19 | return; 20 | } 21 | var filterUtils = new FilterUtils(); 22 | this.filter = function(input,values){ 23 | var width = input.width, height = input.height; 24 | var inputData = input.data; 25 | var outputData = []; 26 | for (var i=0; i < inputData.length; i++) { 27 | outputData[i] = 0; 28 | }; 29 | if(values === undefined){ values = this.defaultValues; } 30 | var levels = (values.levels === undefined) ? this.defaultValues.levels : values.levels; 31 | var color = (values.color === undefined) ? this.defaultValues.color : values.color; 32 | if(levels <= 1){ 33 | levels = 1; 34 | } 35 | var matrix = [0,0,0, 36 | 0,0,7, 37 | 3,5,1]; 38 | var sum = 7+3+5+1; 39 | var index = 0; 40 | var map = []; 41 | for (var i=0; i < levels; i++) { 42 | map[i] = parseInt(255* i / (levels-1)); 43 | }; 44 | var div = []; 45 | for (var i=0; i < 256; i++) { 46 | div[i] = parseInt(levels*i / 256); 47 | }; 48 | for (var y = 0; y < height; y++) { 49 | var reverse = ((y & 1) == 1); 50 | var direction; 51 | if(reverse){ 52 | index = (y*width+width-1)*4; 53 | direction = -1 54 | } else { 55 | index = y*width*4; 56 | direction = 1; 57 | } 58 | for (var x = 0; x < width; x++) { 59 | var r1 = inputData[index]; var g1 = inputData[index+1]; var b1 = inputData[index+2]; 60 | if(!color){ 61 | r1 = g1 = b1 = parseInt((r1+g1+b1) / 3); 62 | } 63 | var r2 = map[div[r1]];var g2 = map[div[g1]];var b2 = map[div[b1]]; 64 | 65 | outputData[index] = r2; outputData[index + 1] = g2; outputData[index+2] = b2; outputData[index+3] = inputData[index+3]; 66 | 67 | var er = r1-r2; var eg = g1-g2; var eb = b1-b2; 68 | 69 | for (var i = -1; i <= 1; i++) { 70 | var iy = i+y; 71 | if (0 <= iy && iy < height) { 72 | for (var j = -1; j <= 1; j++) { 73 | var jx = j+x; 74 | if (0 <= jx && jx < width) { 75 | var w; 76 | if (reverse){ 77 | w = matrix[(i+1)*3-j+1]; 78 | } else{ 79 | w = matrix[(i+1)*3+j+1]; 80 | } 81 | if (w != 0) { 82 | var k = (reverse) ? index - j*4 : index + j*4; 83 | r1 = inputData[k]; g1 = inputData[k+1]; b1 = inputData[k+2]; 84 | var factor = w/sum; 85 | r1 += er * factor; g1 += eg * factor; b1 += eb * factor; 86 | inputData[k] = r1; inputData[k+1] = g1 ;inputData[k+2] = b1; 87 | } 88 | } 89 | } 90 | } 91 | } 92 | index += direction*4; 93 | } 94 | } 95 | for(var k = 0; k < outputData.length; k++){ 96 | inputData[k] = outputData[k]; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /script/separate filters/edge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Highlights the edges of the image. 3 | */ 4 | function EdgeFilter(){ 5 | this.name = "Edge Detection"; 6 | var matrixH = [-1,-2,-1, 7 | 0, 0, 0, 8 | 1, 2, 1]; 9 | var matrixV = [-1, 0, 1, 10 | -2, 0, 2, 11 | -1, 0, 1]; 12 | this.filter = function(input,values){ 13 | var width = input.width, height = input.height; 14 | var inputData = input.data; 15 | var outputData = []; 16 | for (var y = 0; y < height; y++) { 17 | for (var x = 0; x < width; x++) { 18 | var pixel = (y*width + x)*4; 19 | var rh = 0; gh = 0; bh = 0; 20 | var rv = 0; gv = 0; bv = 0; 21 | for(var row = -1; row <= 1; row++){ 22 | var iy = y+row; 23 | var ioffset; 24 | if(iy >= 0 && iy < height){ 25 | ioffset = iy*width*4; 26 | } else { 27 | ioffset = y*width*4; 28 | } 29 | var moffset = 3*(row+1)+1; 30 | for(var col = -1; col <= 1; col++){ 31 | var ix = x+col; 32 | if(!(ix >= 0 && ix < width)){ 33 | ix = x; 34 | } 35 | ix *= 4; 36 | var r = inputData[ioffset+ix]; 37 | var g = inputData[ioffset+ix+1]; 38 | var b = inputData[ioffset+ix+2]; 39 | var h = matrixH[moffset+col]; 40 | var v = matrixV[moffset+col]; 41 | rh += parseInt(h*r); 42 | bh += parseInt(h*g); 43 | gh += parseInt(h*b); 44 | rv += parseInt(v*r); 45 | gv += parseInt(v*g); 46 | bv += parseInt(v*b); 47 | } 48 | } 49 | r = parseInt(Math.sqrt(rh*rh + rv*rv) / 1.8); 50 | g = parseInt(Math.sqrt(gh*gh + gv*gv) / 1.8); 51 | b = parseInt(Math.sqrt(bh*bh + bv*bv) / 1.8); 52 | 53 | outputData[pixel] = r; 54 | outputData[pixel+1] = g; 55 | outputData[pixel+2] = b; 56 | outputData[pixel+3] = inputData[pixel+3]; 57 | } 58 | } 59 | for(var k = 0; k < outputData.length; k++){ 60 | inputData[k] = outputData[k]; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /script/separate filters/emboss.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Embosses the image with a simulated light source. 3 | * Angle and elevation sets the position of the light. 4 | */ 5 | function EmbossFilter(){ 6 | this.name = "Emboss"; 7 | this.defaultValues = { 8 | height : 1, 9 | angle : 135, 10 | elevation : 30 11 | }; 12 | this.valueRanges = { 13 | height : {min:1, max:10}, 14 | angle : {min:0, max:360}, 15 | elevation : {min:0, max:180} 16 | }; 17 | this.filter = function(input,values){ 18 | var width = input.width, height = input.height; 19 | var inputData = input.data; 20 | if(values === undefined){ values = this.defaultValues; } 21 | var bumpHeight = (values.height === undefined) ? this.defaultValues.height : values.height; 22 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 23 | var elevation = (values.elevation === undefined) ? this.defaultValues.elevation : values.elevation; 24 | angle = angle / 180 * Math.PI; 25 | elevation = elevation / 180 * Math.PI; 26 | var width45 = 3 * bumpHeight; 27 | var pixelScale = 255.9; 28 | 29 | var bumpPixels = []; 30 | var bumpMapWidth = width; 31 | var bumpMapHeight = height; 32 | for(var i = 0; i < inputData.length; i+=4){ 33 | bumpPixels[i/4] = (inputData[i] + inputData[i+1] + inputData[i+2])/3 34 | } 35 | var Nx, Ny, Nz, Lx, Ly, Lz, Nz2, NzLz, NdotL; 36 | var shade, background; 37 | 38 | Lx = parseInt(Math.cos(angle) * Math.cos(elevation) * pixelScale); 39 | Ly = parseInt(Math.sin(angle) * Math.cos(elevation) * pixelScale); 40 | Lz = parseInt(Math.sin(elevation) * pixelScale); 41 | 42 | Nz = parseInt(6 * 255 / width45); 43 | Nz2 = Nz * Nz; 44 | NzLz = Nz * Lz; 45 | background = Lz; 46 | 47 | var bumpIndex = 0; 48 | 49 | for (var y = 0; y < height; y++, bumpIndex += bumpMapWidth) { 50 | var s1 = bumpIndex; 51 | var s2 = s1 + bumpMapWidth; 52 | var s3 = s2 + bumpMapWidth; 53 | for (var x = 0; x < width; x++, s1++, s2++, s3++) { 54 | var pixel = (y*width + x)*4; 55 | if (y != 0 && y < height-2 && x != 0 && x < width-2) { 56 | Nx = bumpPixels[s1-1] + bumpPixels[s2-1] + bumpPixels[s3-1] - bumpPixels[s1+1] - bumpPixels[s2+1] - bumpPixels[s3+1]; 57 | Ny = bumpPixels[s3-1] + bumpPixels[s3] + bumpPixels[s3+1] - bumpPixels[s1-1] - bumpPixels[s1] - bumpPixels[s1+1]; 58 | if (Nx == 0 && Ny == 0){ 59 | shade = background; 60 | } else if ((NdotL = Nx*Lx + Ny*Ly + NzLz) < 0){ 61 | shade = 0; 62 | } else { 63 | shade = parseInt(NdotL / Math.sqrt(Nx*Nx + Ny*Ny + Nz2)); 64 | } 65 | } else { 66 | shade = background; 67 | } 68 | inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = shade; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /script/separate filters/exposure.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adjust simulated exposure values on the image. 3 | */ 4 | function ExposureFilter(){ 5 | this.name = "Exposure"; 6 | this.defaultValues = { 7 | exposure : 1.0 8 | }; 9 | this.valueRanges = { 10 | exposure : {min:0, max:5} 11 | }; 12 | if(!FilterUtils){ 13 | if(console){ 14 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 15 | } 16 | return; 17 | } 18 | var filterUtils = new FilterUtils(); 19 | this.filter = function(input,values){ 20 | var width = input.width, height = input.height; 21 | var inputData = input.data; 22 | if(values === undefined){ values = this.defaultValues; } 23 | var exposure = (values.exposure === undefined) ? this.defaultValues.exposure : values.exposure; 24 | var table = []; 25 | for(var i = 0; i < 256; i++){ 26 | table[i] = parseInt(255 *(1-Math.exp(-(i/255) * exposure))); 27 | } 28 | filterUtils.tableFilter(inputData, table, width, height); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /script/separate filters/filterutils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains common filter functions. 3 | */ 4 | function FilterUtils(){ 5 | this.HSVtoRGB = function (h, s, v){ 6 | var r, g, b; 7 | var i = Math.floor(h * 6); 8 | var f = h * 6 - i; 9 | var p = v * (1 - s); 10 | var q = v * (1 - f * s); 11 | var t = v * (1 - (1 - f) * s); 12 | switch(i % 6){ 13 | case 0: r = v, g = t, b = p; break; 14 | case 1: r = q, g = v, b = p; break; 15 | case 2: r = p, g = v, b = t; break; 16 | case 3: r = p, g = q, b = v; break; 17 | case 4: r = t, g = p, b = v; break; 18 | case 5: r = v, g = p, b = q; break; 19 | } 20 | return [r * 255, g * 255, b * 255]; 21 | } 22 | this.RGBtoHSV = function (r, g, b){ 23 | r = r/255, g = g/255, b = b/255; 24 | var max = Math.max(r, g, b), min = Math.min(r, g, b); 25 | var h, s, v = max; 26 | var d = max - min; 27 | s = max == 0 ? 0 : d / max; 28 | if(max == min){ 29 | h = 0; 30 | }else{ 31 | switch(max){ 32 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 33 | case g: h = (b - r) / d + 2; break; 34 | case b: h = (r - g) / d + 4; break; 35 | } 36 | h /= 6; 37 | } 38 | return [h, s, v]; 39 | } 40 | this.getPixel = function (pixels,x,y,width,height){ 41 | var pix = (y*width + x)*4; 42 | if (x < 0 || x >= width || y < 0 || y >= height) { 43 | return [pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4], 44 | pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 1], 45 | pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 2], 46 | pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 3]]; 47 | } 48 | return [pixels[pix],pixels[pix+1],pixels[pix+2],pixels[pix+3]] 49 | } 50 | var haveNextGaussian = false; 51 | var nextGaussian; 52 | this.gaussianRandom = function(){ 53 | if(haveNextGaussian){ 54 | haveNextGaussian = false; 55 | return nextGaussian; 56 | } else { 57 | var v1, v2, s; 58 | do { 59 | v1 = 2 * Math.random() - 1; 60 | v2 = 2 * Math.random() - 1; 61 | s = v1 * v1 + v2 * v2; 62 | } while (s >= 1 || s == 0); 63 | var mult = Math.sqrt(-2 * Math.log(s)/s); 64 | nextGaussian = v2 * mult; 65 | haveNextGaussian = true; 66 | return v1 * mult; 67 | } 68 | } 69 | this.clampPixel = function (x,a,b){ 70 | return (x < a) ? a : (x > b) ? b : x; 71 | } 72 | this.triangle = function(x){ 73 | var r = this.mod(x, 1); 74 | return 2*(r < 0.5 ? r : 1-r); 75 | } 76 | this.mod = function(a,b){ 77 | var n = parseInt(a/b); 78 | a -= n*b; 79 | if(a < 0){ 80 | return a + b; 81 | } 82 | return a; 83 | } 84 | this.mixColors = function(t, rgb1, rgb2){ 85 | var r = this.linearInterpolate(t,rgb1[0],rgb2[0]); 86 | var g = this.linearInterpolate(t,rgb1[1],rgb2[1]); 87 | var b = this.linearInterpolate(t,rgb1[2],rgb2[2]); 88 | var a = this.linearInterpolate(t,rgb1[3],rgb2[3]); 89 | return [r,g,b,a]; 90 | } 91 | 92 | this.linearInterpolate = function(t,a,b){ 93 | return a + t * (b-a); 94 | } 95 | this.bilinearInterpolate = function (x,y,nw,ne,sw,se){ 96 | var m0, m1; 97 | var r0 = nw[0]; var g0 = nw[1]; var b0 = nw[2]; var a0 = nw[3]; 98 | var r1 = ne[0]; var g1 = ne[1]; var b1 = ne[2]; var a1 = ne[3]; 99 | var r2 = sw[0]; var g2 = sw[1]; var b2 = sw[2]; var a2 = sw[3]; 100 | var r3 = se[0]; var g3 = se[1]; var b3 = se[2]; var a3 = se[3]; 101 | var cx = 1.0 - x; var cy = 1.0 - y; 102 | 103 | m0 = cx * a0 + x * a1; 104 | m1 = cx * a2 + x * a3; 105 | var a = cy * m0 + y * m1; 106 | 107 | m0 = cx * r0 + x * r1; 108 | m1 = cx * r2 + x * r3; 109 | var r = cy * m0 + y * m1; 110 | 111 | m0 = cx * g0 + x * g1; 112 | m1 = cx * g2 + x * g3; 113 | var g = cy * m0 + y * m1; 114 | 115 | m0 = cx * b0 + x * b1; 116 | m1 = cx * b2 + x * b3; 117 | var b =cy * m0 + y * m1; 118 | return [r,g,b,a]; 119 | } 120 | this.tableFilter = function (inputData, table, width, height){ 121 | for (var y = 0; y < height; y++) { 122 | for (var x = 0; x < width; x++) { 123 | var pixel = (y*width + x)*4; 124 | for(var i = 0; i < 3; i++){ 125 | inputData[pixel+i] = table[inputData[pixel+i]]; 126 | } 127 | } 128 | } 129 | } 130 | this.convolveFilter = function(inputData, matrix, width, height){ 131 | var outputData = []; 132 | var rows, cols; 133 | rows = cols = Math.sqrt(matrix.length); 134 | var rows2 = parseInt(rows/2); 135 | var cols2 = parseInt(cols/2); 136 | var trace = true; 137 | for(var y = 0; y < height; y++){ 138 | for (var x = 0; x < width; x++){ 139 | var pixel = (y*width + x)*4; 140 | var r = 0, g = 0, b = 0; 141 | for(var row = -rows2; row <= rows2; row++){ 142 | var iy = y+row; 143 | var ioffset; 144 | if (0 <= iy && iy < height) { 145 | ioffset = iy*width; 146 | } else { 147 | ioffset = y*width; 148 | } 149 | var moffset = cols*(row+rows2)+cols2; 150 | for (var col = -cols2; col <= cols2; col++) { 151 | var f = matrix[moffset+col]; 152 | 153 | if (f != 0) { 154 | var ix = x+col; 155 | if (!(0 <= ix && ix < width)) { 156 | ix = x; 157 | } 158 | var iPixel = (ioffset+ix)*4; 159 | r += f * inputData[iPixel]; 160 | g += f * inputData[iPixel+1]; 161 | b += f * inputData[iPixel+2]; 162 | } 163 | } 164 | } 165 | outputData[pixel] = parseInt(r+0.5); 166 | outputData[pixel+1] = parseInt(g+0.5); 167 | outputData[pixel+2] = parseInt(b+0.5); 168 | outputData[pixel+3] = inputData[pixel+3]; 169 | } 170 | } 171 | for(var k = 0; k < outputData.length; k++){ 172 | inputData[k] = outputData[k]; 173 | } 174 | } 175 | this.transformFilter = function(inputData, transformInverse, width, height){ 176 | var out = []; 177 | var outputData = []; 178 | for(var j = 0; j < inputData.length; j++){ 179 | outputData[j] = inputData[j]; 180 | } 181 | for(var y = 0; y < height; y++){ 182 | for (var x = 0; x < width; x++){ 183 | var pixel = (y*width + x)*4; 184 | transformInverse.apply(this,[x,y,out]); 185 | var srcX = Math.floor(out[0]); 186 | var srcY = Math.floor(out[1]); 187 | var xWeight = out[0]-srcX; 188 | var yWeight = out[1]-srcY; 189 | var nw,ne,sw,se; 190 | if(srcX >= 0 && srcX < width-1 && srcY >= 0 && srcY < height-1){ 191 | var i = (width*srcY + srcX)*4; 192 | nw = [inputData[i],inputData[i+1],inputData[i+2],inputData[i+3]]; 193 | ne = [inputData[i+4],inputData[i+5],inputData[i+6],inputData[i+7]]; 194 | sw = [inputData[i+width*4],inputData[i+width*4+1],inputData[i+width*4+2],inputData[i+width*4+3]]; 195 | se = [inputData[i+(width + 1)*4],inputData[i+(width + 1)*4+1],inputData[i+(width + 1)*4+2],inputData[i+(width + 1)*4+3]]; 196 | 197 | } else { 198 | nw = this.getPixel( inputData, srcX, srcY, width, height ); 199 | ne = this.getPixel( inputData, srcX+1, srcY, width, height ); 200 | sw = this.getPixel( inputData, srcX, srcY+1, width, height ); 201 | se = this.getPixel( inputData, srcX+1, srcY+1, width, height ); 202 | } 203 | var rgba = this.bilinearInterpolate(xWeight,yWeight,nw,ne,sw,se); 204 | outputData[pixel] = rgba[0]; 205 | outputData[pixel + 1] = rgba[1]; 206 | outputData[pixel + 2] = rgba[2]; 207 | outputData[pixel + 3] = rgba[3]; 208 | } 209 | } 210 | for(var k = 0; k < outputData.length; k++){ 211 | inputData[k] = outputData[k]; 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /script/separate filters/gain.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adjusts the gain and bias of the image. Gain alters the contrast while bias biases 3 | * colors towards lighter or darker. 4 | */ 5 | function GainFilter(){ 6 | this.name = "Gain/Bias"; 7 | this.defaultValues = { 8 | gain: 0.5, 9 | bias: 0.5 10 | }; 11 | this.valueRanges = { 12 | gain: {min:0.0, max:1.0}, 13 | bias: {min:0.0, max:1.0} 14 | }; 15 | var table = []; 16 | if(!FilterUtils){ 17 | if(console){ 18 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 19 | } 20 | return; 21 | } 22 | var filterUtils = new FilterUtils(); 23 | this.filter = function(input,values){ 24 | var width = input.width, height = input.height; 25 | var inputData = input.data; 26 | if(values === undefined){ values = this.defaultValues; } 27 | var gain = (values.gain === undefined) ? this.defaultValues.gain : values.gain; 28 | var bias = (values.bias === undefined) ? this.defaultValues.bias : values.bias; 29 | 30 | var table = []; 31 | 32 | for(var i = 0; i < 256; i++){ 33 | var val = i/255; 34 | var k = (1/gain-2) * (1-2*val); 35 | val = (val < 0.5) ? val/(k+1) : (k-val)/(k-1); 36 | val /= (1/bias-2)*(1-val)+1; 37 | table[i] = parseInt(255 * val); 38 | } 39 | filterUtils.tableFilter(inputData,table,width,height); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /script/separate filters/gamma.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adjusts the gamma values of the image. Values over 1 increase the gamma while values over 0 decrease gamma. 3 | */ 4 | function GammaFilter(){ 5 | this.name = "Gamma"; 6 | this.defaultValues = { 7 | amount : 1.0 8 | }; 9 | this.valueRanges = { 10 | amount : {min:0.0, max:2.0} 11 | }; 12 | this.filter = function(input,values){ 13 | var width = input.width, height = input.height; 14 | var inputData = input.data; 15 | if(values === undefined){ values = this.defaultValues; } 16 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 17 | if(amount < 0){ 18 | amount = 0.0; 19 | } 20 | if(!FilterUtils){ 21 | if(console){ 22 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 23 | } 24 | return; 25 | } 26 | var filterUtils = new FilterUtils(); 27 | var table = []; 28 | for(var i = 0; i < 256; i++){ 29 | table[i] = 255 * Math.pow(i/255, 1/amount) + 0.5; 30 | } 31 | filterUtils.tableFilter(inputData,table,width,height); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /script/separate filters/grayscale.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sets the image to grayscale. 3 | */ 4 | function GrayscaleFilter(){ 5 | this.name = "Grayscale"; 6 | this.defaultValues = { 7 | }; 8 | this.valueRanges = { 9 | }; 10 | this.filter = function(input,values){ 11 | var width = input.width, height = input.height; 12 | var inputData = input.data; 13 | for (var y = 0; y < height; y++) { 14 | for (var x = 0; x < width; x++) { 15 | var pixel = (y*width + x)*4; 16 | var luma = inputData[pixel]*0.3 + inputData[pixel+1]*0.59 + inputData[pixel+2]*0.11; 17 | inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = luma; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /script/separate filters/hue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adjusts the hue of the image by going over to HSV values. 3 | */ 4 | function HueFilter(){ 5 | this.name = "Hue"; 6 | this.defaultValues = { 7 | amount : 0.0 8 | }; 9 | this.valueRanges = { 10 | amount : {min:-1.0, max:1.0} 11 | }; 12 | if(!FilterUtils){ 13 | if(console){ 14 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 15 | } 16 | return; 17 | } 18 | var filterUtils = new FilterUtils(); 19 | this.filter = function(input,values){ 20 | var width = input.width, height = input.height; 21 | var inputData = input.data; 22 | if(values === undefined){ values = this.defaultValues; } 23 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 24 | for (var y = 0; y < height; y++) { 25 | for (var x = 0; x < width; x++) { 26 | var pixel = (y*width + x)*4; 27 | var hsv = filterUtils.RGBtoHSV(inputData[pixel],inputData[pixel+1],inputData[pixel+2]); 28 | hsv[0] += amount; 29 | while(hsv[0] < 0){ 30 | hsv[0] += 360; 31 | } 32 | var rgb = filterUtils.HSVtoRGB(hsv[0],hsv[1],hsv[2]); 33 | for(var i = 0; i < 3; i++){ 34 | inputData[pixel+i] = rgb[i]; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /script/separate filters/invert.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Inverts the colors of the image. 3 | */ 4 | function InvertFilter(){ 5 | this.name = "Invert"; 6 | this.defaultValues = { 7 | }; 8 | this.valueRanges = { 9 | }; 10 | this.filter = function(input,values){ 11 | var width = input.width, height = input.height; 12 | var inputData = input.data; 13 | for (var y = 0; y < height; y++) { 14 | for (var x = 0; x < width; x++) { 15 | var pixel = (y*width + x)*4; 16 | for(var i = 0; i < 3; i++){ 17 | inputData[pixel+i] = 255 - inputData[pixel+i]; 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /script/separate filters/kaleidoscope.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a kaleidoscope effect on the image. CenterX and CenterY specify the 3 | * position in terms of ratios of width and height. 4 | */ 5 | function KaleidoscopeFilter(){ 6 | this.name = "Kaleidoscope"; 7 | this.defaultValues = { 8 | angle : 0, 9 | rotation : 0, 10 | sides : 3, 11 | centerX : 0.5, 12 | centerY : 0.5 13 | }; 14 | this.valueRanges = { 15 | angle : {min: 0, max: 360}, 16 | rotation : {min: 0, max: 360}, 17 | sides : {min: 1, max: 30}, 18 | centerX : {min: 0.0, max:1.0}, 19 | centerY : {min: 0.0, max:1.0} 20 | }; 21 | if(!FilterUtils){ 22 | if(console){ 23 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 24 | } 25 | return; 26 | } 27 | var filterUtils = new FilterUtils(); 28 | this.filter = function (input, values){ 29 | var width = input.width, height = input.height; 30 | var inputData = input.data; 31 | if(values === undefined){ values = this.defaultValues; } 32 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 33 | var rotation = (values.rotation === undefined) ? this.defaultValues.rotation : values.rotation; 34 | var sides = (values.sides === undefined) ? this.defaultValues.sides : values.sides; 35 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 36 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 37 | var iCenterX = width * centerX; var iCenterY = height * centerY; 38 | angle = angle/180 * Math.PI; 39 | rotation = rotation/180 * Math.PI; 40 | var transInverse = function(x,y,out){ 41 | var dx = x - iCenterX; 42 | var dy = y - iCenterY; 43 | var r = Math.sqrt(dx*dx + dy*dy); 44 | var theta = Math.atan2(dy,dx) - angle - rotation; 45 | theta = filterUtils.triangle(theta/Math.PI*sides*0.5); 46 | theta += angle; 47 | out[0] = iCenterX + r*Math.cos(theta); 48 | out[1] = iCenterY + r*Math.sin(theta); 49 | } 50 | filterUtils.transformFilter(inputData,transInverse,width,height); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /script/separate filters/lensdistortion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Applies a fisheye lens distortion effect on the image. CenterX and CenterY specify the 3 | * position in terms of ratios of width and height. 4 | */ 5 | function LensDistortionFilter(){ 6 | this.name = "Lens Distortion"; 7 | this.defaultValues = { 8 | refraction : 1.5, 9 | radius : 50, 10 | centerX : 0.5, 11 | centerY : 0.5 12 | }; 13 | this.valueRanges = { 14 | refraction : {min: 1, max: 10}, 15 | radius : {min: 1, max: 200}, 16 | centerX : {min: 0.0, max:1.0}, 17 | centerY : {min: 0.0, max:1.0} 18 | }; 19 | if(!FilterUtils){ 20 | if(console){ 21 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 22 | } 23 | return; 24 | } 25 | var filterUtils = new FilterUtils(); 26 | 27 | this.filter = function (input, values){ 28 | var width = input.width, height = input.height; 29 | var inputData = input.data; 30 | if(values === undefined){ values = this.defaultValues; } 31 | var refraction = (values.refraction === undefined) ? this.defaultValues.refraction : values.refraction; 32 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 33 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 34 | var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; 35 | var radius2 = radius*radius; 36 | var iCenterX = width * centerX; var iCenterY = height * centerY; 37 | var transInverse = function(x,y,out){ 38 | var dx = x-iCenterX; 39 | var dy = y-iCenterY; 40 | var x2 = dx*dx; 41 | var y2 = dy*dy; 42 | if (y2 >= (radius2 - (radius2*x2)/radius2)) { 43 | out[0] = x; 44 | out[1] = y; 45 | } else { 46 | var rRefraction = 1.0 / refraction; 47 | 48 | var z = Math.sqrt((1.0 - x2/radius2 - y2/radius2) * radius2); 49 | var z2 = z*z; 50 | 51 | var xAngle = Math.acos(dx / Math.sqrt(x2+z2)); 52 | var angle1 = Math.PI/2 - xAngle; 53 | var angle2 = Math.asin(Math.sin(angle1)*rRefraction); 54 | angle2 = Math.PI/2 - xAngle - angle2; 55 | out[0] = x - Math.tan(angle2)*z; 56 | 57 | var yAngle = Math.acos(dy / Math.sqrt(y2+z2)); 58 | angle1 = Math.PI/2 - yAngle; 59 | angle2 = Math.asin(Math.sin(angle1)*rRefraction); 60 | angle2 = Math.PI/2 - yAngle - angle2; 61 | out[1] = y - Math.tan(angle2)*z; 62 | } 63 | } 64 | filterUtils.transformFilter(inputData,transInverse,width,height); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /script/separate filters/linesmear.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Smears out the image with line shapes to create a painting style effect. Mix specifies 3 | * the intensity of the effect. 4 | */ 5 | function LineSmearFilter(){ 6 | this.name = "Line Smear"; 7 | this.defaultValues = { 8 | distance : 8, 9 | density : 0.5, 10 | angle : 0, 11 | mix : 0.5, 12 | }; 13 | this.valueRanges = { 14 | distance : {min:1, max:30}, 15 | density : {min:0.0, max:1.0}, 16 | angle : {min:0, max:360}, 17 | mix : {min:0.0, max:1.0}, 18 | }; 19 | if(!FilterUtils){ 20 | if(console){ 21 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 22 | } 23 | return; 24 | } 25 | var filterUtils = new FilterUtils(); 26 | this.filter = function(input,values){ 27 | var width = input.width, height = input.height; 28 | var inputData = input.data; 29 | var outputData = []; 30 | for(var k = 0; k < inputData.length; k++){ 31 | outputData[k] = inputData[k]; 32 | } 33 | if(values === undefined){ values = this.defaultValues; } 34 | var distance = (values.distance === undefined) ? this.defaultValues.distance : values.distance; 35 | if(distance < 1){ distance = 1;} 36 | distance = parseInt(distance); 37 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 38 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 39 | var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; 40 | angle = angle/180*Math.PI; 41 | var sinAngle = Math.sin(angle); 42 | var cosAngle = Math.cos(angle); 43 | var numShapes = parseInt(2*density*width*height / 2); 44 | for(var i = 0; i < numShapes; i++){ 45 | var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; 46 | var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; 47 | var length = (Math.random()*Math.pow(2,32) & 0x7fffffff) % distance + 1; 48 | var rgb2 = [inputData[(sy*width+sx)*4],inputData[(sy*width+sx)*4+1],inputData[(sy*width+sx)*4+2],inputData[(sy*width+sx)*4+3]]; 49 | var dx = parseInt(length*cosAngle); 50 | var dy = parseInt(length*sinAngle); 51 | 52 | var x0 = sx-dx; 53 | var y0 = sy-dy; 54 | var x1 = sx+dx; 55 | var y1 = sy+dy; 56 | var x, y, d, incrE, incrNE, ddx, ddy; 57 | 58 | if (x1 < x0){ 59 | ddx = -1; 60 | } else { 61 | ddx = 1; 62 | } 63 | if (y1 < y0){ 64 | ddy = -1; 65 | } else { 66 | ddy = 1; 67 | } 68 | dx = x1-x0; 69 | dy = y1-y0; 70 | dx = Math.abs(dx); 71 | dy = Math.abs(dy); 72 | x = x0; 73 | y = y0; 74 | 75 | if (x < width && x >= 0 && y < height && y >= 0) { 76 | var rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 77 | var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2) 78 | for(var k = 0; k < 3; k++){ 79 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 80 | } 81 | } 82 | if (Math.abs(dx) > Math.abs(dy)) { 83 | d = 2*dy-dx; 84 | incrE = 2*dy; 85 | incrNE = 2*(dy-dx); 86 | 87 | while (x != x1) { 88 | if (d <= 0) 89 | d += incrE; 90 | else { 91 | d += incrNE; 92 | y += ddy; 93 | } 94 | x += ddx; 95 | if (x < width && x >= 0 && y < height && y >= 0) { 96 | var rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 97 | var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2) 98 | for(var k = 0; k < 3; k++){ 99 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 100 | } 101 | } 102 | } 103 | } else { 104 | d = 2*dx-dy; 105 | incrE = 2*dx; 106 | incrNE = 2*(dx-dy); 107 | 108 | while (y != y1) { 109 | if (d <= 0) 110 | d += incrE; 111 | else { 112 | d += incrNE; 113 | x += ddx; 114 | } 115 | y += ddy; 116 | if (x < width && x >= 0 && y < height && y >= 0) { 117 | var rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 118 | var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2) 119 | for(var k = 0; k < 3; k++){ 120 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 121 | } 122 | } 123 | } 124 | } 125 | } 126 | for(var k = 0; k < outputData.length; k++){ 127 | inputData[k] = outputData[k]; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /script/separate filters/maximum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Replaces every pixel with the maximum RGB value of the neighboring pixels. Each color is 3 | * considered separately. 4 | */ 5 | function MaximumFilter(){ 6 | this.name = "Maximum"; 7 | this.defaultValues = { 8 | }; 9 | this.valueRanges = { 10 | }; 11 | this.filter = function(input,values){ 12 | var width = input.width, height = input.height; 13 | var inputData = input.data; 14 | var outputData = []; 15 | for (var y = 0; y < height; y++) { 16 | for (var x = 0; x < width; x++) { 17 | var pixel = (y*width + x)*4; 18 | var maxR = 0; 19 | var maxG = 0; 20 | var maxB = 0; 21 | for (var dy = -1; dy <= 1; dy++){ 22 | var iy = y+dy; 23 | if(iy >= 0 && iy < height){ 24 | for (var dx = -1; dx <= 1; dx++){ 25 | var ix = x+dx; 26 | if(ix >= 0 && ix < width){ 27 | var iPixel = (iy*width + ix)*4; 28 | maxR = Math.max(maxR,inputData[iPixel]); 29 | maxG = Math.max(maxG,inputData[iPixel+1]); 30 | maxB = Math.max(maxB,inputData[iPixel+2]); 31 | } 32 | } 33 | } 34 | } 35 | outputData[pixel] = maxR; 36 | outputData[pixel+1] = maxG; 37 | outputData[pixel+2] = maxB; 38 | outputData[pixel+3] = inputData[pixel+3]; 39 | } 40 | } 41 | for(var k = 0; k < outputData.length; k++){ 42 | inputData[k] = outputData[k]; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /script/separate filters/median.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Replaces every pixel with the median RGB value of the neighboring pixels. Each color is 3 | * considered separately. 4 | */ 5 | function MedianFilter(){ 6 | this.name = "Median"; 7 | this.defaultValues = { 8 | }; 9 | this.valueRanges = { 10 | }; 11 | this.filter = function(input,values){ 12 | var width = input.width, height = input.height; 13 | var inputData = input.data; 14 | var outputData = []; 15 | for (var y = 0; y < height; y++) { 16 | for (var x = 0; x < width; x++) { 17 | var pixel = (y*width + x)*4; 18 | var rList = []; 19 | var gList = []; 20 | var bList = []; 21 | for (var dy = -1; dy <= 1; dy++){ 22 | var iy = y+dy; 23 | if(iy >= 0 && iy < height){ 24 | for (var dx = -1; dx <= 1; dx++){ 25 | var ix = x+dx; 26 | if(ix >= 0 && ix < width){ 27 | var iPixel = (iy*width + ix)*4; 28 | rList.push(inputData[iPixel]); 29 | gList.push(inputData[iPixel+1]); 30 | bList.push(inputData[iPixel+2]); 31 | 32 | } 33 | } 34 | } 35 | } 36 | rList.sort(function(a,b){return a-b}); 37 | gList.sort(function(a,b){return a-b}); 38 | bList.sort(function(a,b){return a-b}); 39 | outputData[pixel] = rList[4]; 40 | outputData[pixel+1] = gList[4]; 41 | outputData[pixel+2] = bList[4]; 42 | outputData[pixel+3] = inputData[pixel+3]; 43 | } 44 | } 45 | for(var k = 0; k < outputData.length; k++){ 46 | inputData[k] = outputData[k]; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /script/separate filters/minimum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Replaces every pixel with the minimum RGB value of the neighboring pixels. Each color is 3 | * considered separately. 4 | */ 5 | function MinimumFilter(){ 6 | this.name = "Minimum"; 7 | this.defaultValues = { 8 | }; 9 | this.valueRanges = { 10 | }; 11 | this.filter = function(input,values){ 12 | var width = input.width, height = input.height; 13 | var inputData = input.data; 14 | var outputData = []; 15 | for (var y = 0; y < height; y++) { 16 | for (var x = 0; x < width; x++) { 17 | var pixel = (y*width + x)*4; 18 | var minR = 255; 19 | var minG = 255; 20 | var minB = 255; 21 | for (var dy = -1; dy <= 1; dy++){ 22 | var iy = y+dy; 23 | if(iy >= 0 && iy < height){ 24 | for (var dx = -1; dx <= 1; dx++){ 25 | var ix = x+dx; 26 | if(ix >= 0 && ix < width){ 27 | var iPixel = (iy*width + ix)*4; 28 | minR = Math.min(minR,inputData[iPixel]); 29 | minG = Math.min(minG,inputData[iPixel+1]); 30 | minB = Math.min(minB,inputData[iPixel+2]); 31 | } 32 | } 33 | } 34 | } 35 | outputData[pixel] = minR; 36 | outputData[pixel+1] = minG; 37 | outputData[pixel+2] = minB; 38 | outputData[pixel+3] = inputData[pixel+3]; 39 | } 40 | } 41 | for(var k = 0; k < outputData.length; k++){ 42 | inputData[k] = outputData[k]; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /script/separate filters/noise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates random noise on the image, with or without color. 3 | */ 4 | function NoiseFilter(){ 5 | this.name = "Noise"; 6 | this.defaultValues = { 7 | amount : 25, 8 | density : 1, 9 | monochrome : true 10 | }; 11 | this.valueRanges = { 12 | amount : {min:0, max:100}, 13 | density : {min:0, max:1.0}, 14 | monochrome : {min:false, max:true} 15 | }; 16 | this.filter = function(input,values){ 17 | var width = input.width, height = input.height; 18 | var inputData = input.data; 19 | if(values === undefined){ values = this.defaultValues; } 20 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 21 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 22 | var monochrome = (values.monochrome === undefined) ? this.defaultValues.monochrome : values.monochrome; 23 | for (var y = 0; y < height; y++) { 24 | for (var x = 0; x < width; x++) { 25 | var pixel = (y*width + x)*4; 26 | if(Math.random() <= density){ 27 | if(monochrome){ 28 | var n = parseInt((2*Math.random()-1) * amount); 29 | inputData[pixel] += n; 30 | inputData[pixel+1] += n; 31 | inputData[pixel+2] += n; 32 | } else { 33 | for(var i = 0; i < 3; i++){ 34 | var n = parseInt((2*Math.random()-1) * amount); 35 | inputData[pixel+i] += n; 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /script/separate filters/oil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Produces an oil painting effect on the image. 3 | * NOTE: This filter can be very slow, especially at higher ranges. Use with caution. 4 | */ 5 | function OilFilter(){ 6 | this.name = "Oil Painting"; 7 | this.defaultValues = { 8 | range : 3, 9 | }; 10 | this.valueRanges = { 11 | range : {min:0, max:5}, 12 | }; 13 | this.filter = function(input,values){ 14 | var width = input.width, height = input.height; 15 | var inputData = input.data; 16 | var outputData = []; 17 | if(values === undefined){ values = this.defaultValues; } 18 | var range = (values.range === undefined) ? this.defaultValues.range : values.range; 19 | range = parseInt(range); 20 | var index = 0; 21 | var rHistogram = []; 22 | var gHistogram = []; 23 | var bHistogram = []; 24 | var rTotal = []; 25 | var gTotal = []; 26 | var bTotal = []; 27 | var levels = 256; 28 | for (var y = 0; y < height; y++) { 29 | for (var x = 0; x < width; x++) { 30 | var pixel = (y*width + x)*4; 31 | for (var i = 0; i < levels; i++){ 32 | rHistogram[i] = gHistogram[i] = bHistogram[i] = rTotal[i] = gTotal[i] = bTotal[i] = 0; 33 | } 34 | for (var row = -range; row <= range; row++) { 35 | var iy = y+row; 36 | var ioffset; 37 | if (0 <= iy && iy < height) { 38 | ioffset = iy*width; 39 | for (var col = -range; col <= range; col++) { 40 | var ix = x+col; 41 | if (0 <= ix && ix < width) { 42 | var r = inputData[(ioffset+ix)*4] 43 | var g = inputData[(ioffset+ix)*4+1] 44 | var b = inputData[(ioffset+ix)*4+2] 45 | var ri = r*levels/256; 46 | var gi = g*levels/256; 47 | var bi = b*levels/256; 48 | rTotal[ri] += r; 49 | gTotal[gi] += g; 50 | bTotal[bi] += b; 51 | rHistogram[ri]++; 52 | gHistogram[gi]++; 53 | bHistogram[bi]++; 54 | } 55 | } 56 | } 57 | } 58 | var r = 0, g = 0, b = 0; 59 | for (var i = 1; i < levels; i++) { 60 | if (rHistogram[i] > rHistogram[r]){ 61 | r = i; 62 | } 63 | if (gHistogram[i] > gHistogram[g]){ 64 | g = i; 65 | } 66 | if (bHistogram[i] > bHistogram[b]){ 67 | b = i; 68 | } 69 | } 70 | r = rTotal[r] / rHistogram[r]; 71 | g = gTotal[g] / gHistogram[g]; 72 | b = bTotal[b] / bHistogram[b]; 73 | outputData[pixel] = r; 74 | outputData[pixel+1] = g; 75 | outputData[pixel+2] = b; 76 | outputData[pixel+3] = inputData[pixel+3]; 77 | } 78 | } 79 | for(var k = 0; k < outputData.length; k++){ 80 | inputData[k] = outputData[k]; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /script/separate filters/opacity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Changes the opacity of the image. 3 | */ 4 | function OpacityFilter(){ 5 | this.name = "Opacity"; 6 | this.defaultValues = { 7 | amount : 1.0 8 | }; 9 | this.valueRanges = { 10 | amount : {min:0.0, max:1.0} 11 | }; 12 | this.filter = function(input,values){ 13 | var width = input.width, height = input.height; 14 | var inputData = input.data; 15 | if(values === undefined){ values = this.defaultValues; } 16 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 17 | for (var y = 0; y < height; y++) { 18 | for (var x = 0; x < width; x++) { 19 | var pixel = (y*width + x)*4; 20 | inputData[pixel+3] = 255*amount; 21 | } 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /script/separate filters/pinch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pinches and whirls the image toward the center point. CenterX and CenterY specify the 3 | * position in terms of ratios of width and height. 4 | */ 5 | function PinchFilter(){ 6 | this.name = "Pinch/Whirl"; 7 | this.defaultValues = { 8 | amount : 0.5, 9 | radius : 100, 10 | angle : 0, 11 | centerX : 0.5, 12 | centerY : 0.5 13 | }; 14 | this.valueRanges = { 15 | amount : {min: -1.0, max: 1.0}, 16 | radius : {min: 1, max: 200}, 17 | angle : {min: 0, max: 360}, 18 | centerX : {min: 0.0, max:1.0}, 19 | centerY : {min: 0.0, max:1.0} 20 | }; 21 | if(!FilterUtils){ 22 | if(console){ 23 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 24 | } 25 | return; 26 | } 27 | var filterUtils = new FilterUtils(); 28 | 29 | this.filter = function (input, values){ 30 | var width = input.width, height = input.height; 31 | var inputData = input.data; 32 | if(values === undefined){ values = this.defaultValues; } 33 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 34 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 35 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 36 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 37 | var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; 38 | var radius2 = radius*radius; 39 | angle = angle/180 * Math.PI; 40 | var iCenterX = width * centerX; var iCenterY = height * centerY; 41 | var transInverse = function(x,y,out){ 42 | var dx = x-iCenterX; 43 | var dy = y-iCenterY; 44 | var distance = dx*dx + dy*dy; 45 | if(distance > radius2 || distance == 0){ 46 | out[0] = x; 47 | out[1] = y; 48 | } else { 49 | var d = Math.sqrt( distance / radius2 ); 50 | var t = Math.pow( Math.sin( Math.PI*0.5 * d ), -amount); 51 | 52 | dx *= t; 53 | dy *= t; 54 | 55 | var e = 1 - d; 56 | var a = angle * e * e; 57 | 58 | var s = Math.sin(a); 59 | var c = Math.cos(a); 60 | 61 | out[0] = iCenterX + c*dx - s*dy; 62 | out[1] = iCenterY + s*dx + c*dy; 63 | } 64 | } 65 | filterUtils.transformFilter(inputData,transInverse,width,height); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /script/separate filters/pixelation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pixelates the image i.e. divides the image into blocks of color. 3 | */ 4 | function PixelationFilter(){ 5 | this.name = "Pixelation"; 6 | this.defaultValues = { 7 | size : 5 8 | }; 9 | this.valueRanges = { 10 | size : {min:1, max:50} 11 | }; 12 | this.filter = function(input,values){ 13 | var width = input.width, height = input.height; 14 | var inputData = input.data; 15 | if(values === undefined){ values = this.defaultValues; } 16 | var size = (values.size === undefined) ? this.defaultValues.size : values.size; 17 | size = parseInt(size); 18 | var pixels = []; 19 | for (var y = 0; y < height; y+=size) { 20 | for (var x = 0; x < width; x+=size) { 21 | var pixel = (y*width + x)*4; 22 | var w = Math.min(size, width-x); 23 | var h = Math.min(size, height-y); 24 | var t = w*h; 25 | var r = 0, g = 0, b = 0; 26 | for(var by = y; by < y+h; by++){ 27 | for(var bx = x; bx < x+w; bx++){ 28 | var bPixel = (by*width + bx)*4; 29 | r += inputData[bPixel]; 30 | g += inputData[bPixel+1]; 31 | b += inputData[bPixel+2]; 32 | } 33 | } 34 | for(var by = y; by < y+h; by++){ 35 | for(var bx = x; bx < x+w; bx++){ 36 | var bPixel = (by*width + bx)*4; 37 | inputData[bPixel] = r/t; 38 | inputData[bPixel+1] = g/t; 39 | inputData[bPixel+2] = b/t; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /script/separate filters/posterize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Posterizes the image, i.e. restricts the color values to a set amount of levels. 3 | */ 4 | function PosterizeFilter(){ 5 | this.name = "Posterize"; 6 | this.defaultValues = { 7 | levels : 6 8 | }; 9 | this.valueRanges = { 10 | levels : {min:2, max:30 } 11 | }; 12 | if(!FilterUtils){ 13 | if(console){ 14 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 15 | } 16 | return; 17 | } 18 | var filterUtils = new FilterUtils(); 19 | this.filter = function(input,values){ 20 | var width = input.width, height = input.height; 21 | var inputData = input.data; 22 | if(values === undefined){ values = this.defaultValues; } 23 | var levels = (values.levels === undefined) ? this.defaultValues.levels : parseInt(values.levels); 24 | if(levels <= 1){ 25 | return; 26 | } 27 | var table = []; 28 | for(var i = 0; i < 256; i++){ 29 | table[i] = parseInt(255 * parseInt(i*levels/256) / (levels-1)); 30 | } 31 | filterUtils.tableFilter(inputData,table,width,height); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /script/separate filters/rgbadjust.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adjust the factor of each RGB color value in the image. 3 | */ 4 | function RGBAdjustFilter(){ 5 | this.name = "RGBAdjust"; 6 | this.defaultValues = { 7 | red: 1.0, 8 | green: 1.0, 9 | blue: 1.0 10 | }; 11 | this.valueRanges = { 12 | red: {min: 0.0, max: 2.0}, 13 | green: {min: 0.0, max: 2.0}, 14 | blue: {min: 0.0, max: 2.0} 15 | }; 16 | this.filter = function(input,values){ 17 | var width = input.width, height = input.height; 18 | var inputData = input.data; 19 | if(values === undefined){ values = this.defaultValues; } 20 | var red = (values.red === undefined) ? this.defaultValues.red : values.red; 21 | var green = (values.green === undefined) ? this.defaultValues.green : values.green; 22 | var blue = (values.blue === undefined) ? this.defaultValues.blue : values.blue; 23 | if(red < 0){ red = 0; } 24 | if(green < 0){ green = 0; } 25 | if(blue < 0){ blue = 0; } 26 | for (var y = 0; y < height; y++) { 27 | for (var x = 0; x < width; x++) { 28 | var pixel = (y*width + x)*4; 29 | inputData[pixel] *= red; 30 | inputData[pixel+1] *= green; 31 | inputData[pixel+2] *= blue; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /script/separate filters/saturation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adjusts the saturation value of the image. Values over 1 increase saturation while values below decrease saturation. 3 | * For a true grayscale effect, use the grayscale filter instead. 4 | */ 5 | function SaturationFilter(){ 6 | this.name = "Saturation"; 7 | this.defaultValues = { 8 | amount : 1.0 9 | }; 10 | this.valueRanges = { 11 | amount : {min:0.0, max:2.0} 12 | }; 13 | this.filter = function(input,values){ 14 | var width = input.width, height = input.height; 15 | var inputData = input.data; 16 | if(values === undefined){ values = this.defaultValues; } 17 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 18 | var RW = 0.3086; 19 | var RG = 0.6084; 20 | var RB = 0.0820; 21 | var a = (1 - amount) * RW + amount; 22 | var b = (1 - amount) * RW; 23 | var c = (1 - amount) * RW; 24 | var d = (1 - amount) * RG; 25 | var e = (1 - amount) * RG + amount; 26 | var f = (1 - amount) * RG; 27 | var g = (1 - amount) * RB; 28 | var h = (1 - amount) * RB; 29 | var i = (1 - amount) * RB + amount; 30 | for (var y = 0; y < height; y++) { 31 | for (var x = 0; x < width; x++) { 32 | var pixel = (y*width + x)*4; 33 | inputData[pixel] = a*inputData[pixel] + d*inputData[pixel+1] + g*inputData[pixel+2]; 34 | inputData[pixel+1] = b*inputData[pixel] + e*inputData[pixel+1] + h*inputData[pixel+2]; 35 | inputData[pixel+2] = c*inputData[pixel] + f*inputData[pixel+1] + i*inputData[pixel+2]; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /script/separate filters/sawtoothripple.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates ripples on the image horizontally/vertically in a sawtooth pattern. 3 | */ 4 | function SawtoothRippleFilter(){ 5 | this.name = "Sawtooth Ripples"; 6 | this.defaultValues = { 7 | xAmplitude : 5, 8 | yAmplitude : 5, 9 | xWavelength : 16, 10 | yWavelength : 16 11 | }; 12 | this.valueRanges = { 13 | xAmplitude : {min:0, max:30}, 14 | yAmplitude : {min:0, max:30}, 15 | xWavelength : {min:1, max:50}, 16 | yWavelength : {min:1, max:50} 17 | }; 18 | if(!FilterUtils){ 19 | if(console){ 20 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 21 | } 22 | return; 23 | } 24 | var filterUtils = new FilterUtils(); 25 | 26 | this.filter = function (input, values){ 27 | var width = input.width, height = input.height; 28 | var inputData = input.data; 29 | if(values === undefined){ values = this.defaultValues; } 30 | var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude; 31 | var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude; 32 | var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength; 33 | var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength; 34 | var transInverse = function(x,y,out){ 35 | var nx = y/xWavelength; 36 | var ny = x/yWavelength; 37 | var fx = filterUtils.mod(nx,1) 38 | var fy = filterUtils.mod(ny,1) 39 | out[0] = x + xAmplitude * fx; 40 | out[1] = y + yAmplitude * fy; 41 | } 42 | filterUtils.transformFilter(inputData,transInverse,width,height); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /script/separate filters/sepia.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a sepia effect on the image i.e. gives the image a yellow-brownish tone. 3 | */ 4 | function SepiaFilter(){ 5 | this.name = "Sepia"; 6 | this.defaultValues = { 7 | amount : 10 8 | }; 9 | this.valueRanges = { 10 | amount : {min:0, max:30} 11 | }; 12 | if(!FilterUtils){ 13 | if(console){ 14 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 15 | } 16 | return; 17 | } 18 | var filterUtils = new FilterUtils(); 19 | this.filter = function(input,values){ 20 | var width = input.width, height = input.height; 21 | var inputData = input.data; 22 | if(values === undefined){ values = this.defaultValues; } 23 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 24 | amount *= 255/100; 25 | for (var y = 0; y < height; y++) { 26 | for (var x = 0; x < width; x++) { 27 | var pixel = (y*width + x)*4; 28 | var luma = inputData[pixel]*0.3 + inputData[pixel+1]*0.59 + inputData[pixel+2]*0.11; 29 | var r,g,b; 30 | r = g = b = luma; 31 | r += 40; 32 | g += 20; 33 | b -= amount; 34 | 35 | inputData[pixel] = r; 36 | inputData[pixel+1] = g; 37 | inputData[pixel+2] = b; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /script/separate filters/sharpen.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sharpens the image slightly. For increased effect, apply the filter multiple times. 3 | */ 4 | function SharpenFilter(){ 5 | this.name = "Sharpen"; 6 | this.defaultValues = { 7 | }; 8 | this.valueRanges = { 9 | }; 10 | if(!FilterUtils){ 11 | if(console){ 12 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 13 | } 14 | return; 15 | } 16 | var filterUtils = new FilterUtils(); 17 | this.filter = function(input,values){ 18 | var width = input.width, height = input.height; 19 | var inputData = input.data; 20 | var matrix = [ 0,-0.2, 0, 21 | -0.2,1.8,-0.2, 22 | 0, -0.2, 0]; 23 | filterUtils.convolveFilter(inputData,matrix,width,height); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /script/separate filters/sineripple.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates ripples on the image horizontally/vertically in a sine pattern. 3 | */ 4 | function SineRippleFilter(){ 5 | this.name = "Sine Ripples"; 6 | this.defaultValues = { 7 | xAmplitude : 5, 8 | yAmplitude : 5, 9 | xWavelength : 16, 10 | yWavelength : 16 11 | }; 12 | this.valueRanges = { 13 | xAmplitude : {min:0, max:30}, 14 | yAmplitude : {min:0, max:30}, 15 | xWavelength : {min:1, max:50}, 16 | yWavelength : {min:1, max:50} 17 | }; 18 | if(!FilterUtils){ 19 | if(console){ 20 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 21 | } 22 | return; 23 | } 24 | var filterUtils = new FilterUtils(); 25 | 26 | this.filter = function (input, values){ 27 | var width = input.width, height = input.height; 28 | var inputData = input.data; 29 | if(values === undefined){ values = this.defaultValues; } 30 | var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude; 31 | var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude; 32 | var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength; 33 | var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength; 34 | var transInverse = function(x,y,out){ 35 | var nx = y/xWavelength; 36 | var ny = x/yWavelength; 37 | var fx = Math.sin(nx); 38 | var fy = Math.sin(ny); 39 | out[0] = x + xAmplitude * fx; 40 | out[1] = y + yAmplitude * fy; 41 | } 42 | filterUtils.transformFilter(inputData,transInverse,width,height); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /script/separate filters/solarize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Produces a solarization effect on the image. 3 | */ 4 | function SolarizeFilter(){ 5 | this.name = "Solarize"; 6 | this.defaultValues = { 7 | }; 8 | this.valueRanges = { 9 | }; 10 | if(!FilterUtils){ 11 | if(console){ 12 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 13 | } 14 | return; 15 | } 16 | var filterUtils = new FilterUtils(); 17 | this.filter = function(input,values){ 18 | var width = input.width, height = input.height; 19 | var inputData = input.data; 20 | var table = []; 21 | for(var i = 0; i < 256; i++){ 22 | var val = (i/255 > 0.5) ? 2*(i/255-0.5) : 2*(0.5-i/255) 23 | table[i] = parseInt(255 * val); 24 | } 25 | filterUtils.tableFilter(inputData, table, width, height); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /script/separate filters/sparkle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generates a sparkle/sunburst effect on the image. CenterX and CenterY specify the 3 | * position in terms of ratios of width and height. 4 | */ 5 | function SparkleFilter(){ 6 | this.name = "Sparkle"; 7 | this.defaultValues = { 8 | rays : 50, 9 | radius : 25, 10 | amount : 50, 11 | randomness : 25, 12 | centerX : 0.5, 13 | centerY : 0.5, 14 | }; 15 | this.valueRanges = { 16 | rays : {min:1, max:100}, 17 | radius : {min:1, max:200}, 18 | amount : {min:0, max:100}, 19 | randomness : {min:0, max:50}, 20 | centerX : {min:0, max:1.0}, 21 | centerY : {min:0, max:1.0}, 22 | }; 23 | if(!FilterUtils){ 24 | if(console){ 25 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 26 | } 27 | return; 28 | } 29 | var filterUtils = new FilterUtils(); 30 | this.filter = function(input,values){ 31 | var width = input.width, height = input.height; 32 | var inputData = input.data; 33 | if(values === undefined){ values = this.defaultValues; } 34 | var rays = (values.rays === undefined) ? this.defaultValues.rays : values.rays; 35 | var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; 36 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 37 | var randomness = (values.randomness === undefined) ? this.defaultValues.randomness : values.randomness; 38 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 39 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 40 | var iCenterX = centerX * width; 41 | var iCenterY = centerY * height; 42 | var rayLengths = []; 43 | for(var i = 0; i < rays; i++){ 44 | rayLengths[i]= radius + randomness / 100 * radius * filterUtils.gaussianRandom(); 45 | } 46 | for (var y = 0; y < height; y++) { 47 | for (var x = 0; x < width; x++) { 48 | var pixel = (y*width + x)*4; 49 | var dx = x-iCenterX; 50 | var dy = y-iCenterY; 51 | var distance = dx*dx + dy*dy; 52 | var angle = Math.atan2(dy,dx); 53 | var d = (angle+Math.PI) / (Math.PI*2) * rays; 54 | var i = parseInt(d); 55 | var f = d - i; 56 | if(radius != 0){ 57 | var length = filterUtils.linearInterpolate(f, rayLengths[i % rays], rayLengths[(i+1) % rays]); 58 | var g = length*length / (distance+0.0001); 59 | g = Math.pow(g, (100-amount) / 50); 60 | f -= 0.5; 61 | f = 1 - f*f; 62 | f *= g; 63 | } 64 | f = filterUtils.clampPixel(f,0,1); 65 | var mixedRGB = filterUtils.mixColors(f,[inputData[pixel],inputData[pixel+1],inputData[pixel+2],inputData[pixel+3]],[255,255,255,255]); 66 | for(var k = 0; k < 3; k++){ 67 | inputData[pixel+k] = mixedRGB[k]; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /script/separate filters/squaresmear.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Smears out the image with square shapes to create a painting style effect. 3 | * The mix values sets the intensity of the effect. 4 | * NOTE: This filter can be very slow, especially at higher densities/sizes. Use with caution. 5 | */ 6 | function SquareSmearFilter(){ 7 | this.name = "Square Smear"; 8 | this.defaultValues = { 9 | size : 4, 10 | density : 0.5, 11 | mix : 0.5, 12 | }; 13 | this.valueRanges = { 14 | size : {min:1, max:10}, 15 | density : {min:0.0, max:1.0}, 16 | mix : {min:0.0, max:1.0}, 17 | }; 18 | if(!FilterUtils){ 19 | if(console){ 20 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 21 | } 22 | return; 23 | } 24 | var filterUtils = new FilterUtils(); 25 | this.filter = function(input,values){ 26 | var width = input.width, height = input.height; 27 | var inputData = input.data; 28 | var outputData = []; 29 | for(var k = 0; k < inputData.length; k++){ 30 | outputData[k] = inputData[k]; 31 | } 32 | if(values === undefined){ values = this.defaultValues; } 33 | var size = (values.size === undefined) ? this.defaultValues.size : values.size; 34 | if(size < 1){ size = 1;} 35 | size = parseInt(size); 36 | var density = (values.density === undefined) ? this.defaultValues.density : values.density; 37 | var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; 38 | var radius = size+1; 39 | var radius2 = radius*radius; 40 | var numShapes = parseInt(2*density/30*width*height / 2); 41 | for(var i = 0; i < numShapes; i++){ 42 | var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; 43 | var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; 44 | var rgb2 = [inputData[(sy*width+sx)*4],inputData[(sy*width+sx)*4+1],inputData[(sy*width+sx)*4+2],inputData[(sy*width+sx)*4+3]]; 45 | for(var x = sx - radius; x < sx + radius + 1; x++){ 46 | 47 | for(var y = sy - radius; y < sy + radius + 1; y++){ 48 | if (x >= 0 && x < width && y >= 0 && y < height) { 49 | var rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; 50 | var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2) 51 | for(var k = 0; k < 3; k++){ 52 | outputData[(y*width+x)*4+k] = mixedRGB[k]; 53 | } 54 | } 55 | } 56 | } 57 | } 58 | for(var k = 0; k < outputData.length; k++){ 59 | inputData[k] = outputData[k]; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /script/separate filters/threshold.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Divides the colors into black and white following the treshold value. Brightnesses above the threshold 3 | * sets the color to white while values below the threshold sets the color to black. 4 | */ 5 | function ThresholdFilter(){ 6 | this.name = "Black & White"; 7 | this.defaultValues = { 8 | threshold : 127 9 | }; 10 | this.valueRanges = { 11 | threshold : {min:0, max:255} 12 | }; 13 | this.filter = function(input,values){ 14 | var width = input.width, height = input.height; 15 | var inputData = input.data; 16 | if(values === undefined){ values = this.defaultValues; } 17 | var threshold = (values.threshold === undefined) ? this.defaultValues.threshold : values.threshold; 18 | for (var y = 0; y < height; y++) { 19 | for (var x = 0; x < width; x++) { 20 | var pixel = (y*width + x)*4; 21 | var brightness = (inputData[pixel] + inputData[pixel+1] + inputData[pixel+2])/3 22 | var colorVal = 0; 23 | if(brightness > threshold){ 24 | colorVal = 255; 25 | } 26 | inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = colorVal; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /script/separate filters/triangleripple.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates ripples on the image horizontally/vertically in a sine pattern. 3 | */ 4 | function TriangleRippleFilter(){ 5 | this.name = "Triangle Ripples"; 6 | this.defaultValues = { 7 | xAmplitude : 5, 8 | yAmplitude : 5, 9 | xWavelength : 16, 10 | yWavelength : 16 11 | }; 12 | this.valueRanges = { 13 | xAmplitude : {min:0, max:30}, 14 | yAmplitude : {min:0, max:30}, 15 | xWavelength : {min:1, max:50}, 16 | yWavelength : {min:1, max:50} 17 | }; 18 | if(!FilterUtils){ 19 | if(console){ 20 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 21 | } 22 | return; 23 | } 24 | var filterUtils = new FilterUtils(); 25 | 26 | this.filter = function (input, values){ 27 | var width = input.width, height = input.height; 28 | var inputData = input.data; 29 | if(values === undefined){ values = this.defaultValues; } 30 | var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude; 31 | var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude; 32 | var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength; 33 | var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength; 34 | var transInverse = function(x,y,out){ 35 | var nx = y/xWavelength; 36 | var ny = x/yWavelength; 37 | var fx = filterUtils.triangle(nx,1) 38 | var fy = filterUtils.triangle(ny,1) 39 | out[0] = x + xAmplitude * fx; 40 | out[1] = y + yAmplitude * fy; 41 | } 42 | filterUtils.transformFilter(inputData,transInverse,width,height); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /script/separate filters/twirl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Twists the image around a given center point. CenterX and CenterY specify the 3 | * position in terms of ratios of width and height. 4 | */ 5 | function TwirlFilter(){ 6 | this.name = "Twirl"; 7 | this.defaultValues = { 8 | radius : 100, 9 | angle : 180, 10 | centerX : 0.5, 11 | centerY : 0.5 12 | }; 13 | this.valueRanges = { 14 | radius : {min: 1, max: 200}, 15 | angle : {min: 0, max: 360}, 16 | centerX : {min: 0.0, max:1.0}, 17 | centerY : {min: 0.0, max:1.0} 18 | }; 19 | if(!FilterUtils){ 20 | if(console){ 21 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 22 | } 23 | return; 24 | } 25 | var filterUtils = new FilterUtils(); 26 | 27 | this.filter = function (input, values){ 28 | var width = input.width, height = input.height; 29 | var inputData = input.data; 30 | if(values === undefined){ values = this.defaultValues; } 31 | var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; 32 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 33 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 34 | var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; 35 | var radius2 = radius*radius; 36 | angle = angle/180 * Math.PI; 37 | var iCenterX = width * centerX; var iCenterY = height * centerY; 38 | var transInverse = function(x,y,out){ 39 | var dx = x-iCenterX; 40 | var dy = y-iCenterY; 41 | var distance = dx*dx + dy*dy; 42 | if(distance > radius2){ 43 | out[0] = x; 44 | out[1] = y; 45 | } else { 46 | distance = Math.sqrt(distance); 47 | var a = Math.atan2(dy, dx) + angle * (radius-distance) / radius; 48 | out[0] = iCenterX + distance*Math.cos(a); 49 | out[1] = iCenterY + distance*Math.sin(a); 50 | } 51 | } 52 | filterUtils.transformFilter(inputData,transInverse,width,height); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /script/separate filters/vignette.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates a classical vignette effect on the image i.e. darkens the corners. 3 | */ 4 | function VignetteFilter(){ 5 | this.name = "Vignette"; 6 | this.defaultValues = { 7 | amount : 0.3, 8 | }; 9 | this.valueRanges = { 10 | amount : {min:0.0, max:1.0}, 11 | }; 12 | this.filter = function(input,values){ 13 | var width = input.width, height = input.height; 14 | var inputData = input.data; 15 | var outputData = []; 16 | if(values === undefined){ values = this.defaultValues; } 17 | var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; 18 | var canvas = document.createElement("canvas"); 19 | canvas.width = width; 20 | canvas.height = height; 21 | var context = canvas.getContext("2d"); 22 | var gradient; 23 | var radius = Math.sqrt( Math.pow(width/2, 2) + Math.pow(height/2, 2) ); 24 | context.putImageData(input,0,0); 25 | context.globalCompositeOperation = 'source-over'; 26 | 27 | gradient = context.createRadialGradient(width/2, height/2, 0, width/2, height/2, radius); 28 | gradient.addColorStop(0, 'rgba(0,0,0,0)'); 29 | gradient.addColorStop(0.5, 'rgba(0,0,0,0)'); 30 | gradient.addColorStop(1, 'rgba(0,0,0,' + amount + ')'); 31 | context.fillStyle = gradient; 32 | context.fillRect(0, 0, width, height); 33 | outputData = context.getImageData(0,0,width,height).data; 34 | for(var k = 0; k < outputData.length; k++){ 35 | inputData[k] = outputData[k]; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /script/separate filters/waterripple.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Produces a water ripple/waves on the image. CenterX and CenterY specify the 3 | * position in terms of ratios of width and height. 4 | */ 5 | function WaterRippleFilter(){ 6 | this.name = "Water Ripples"; 7 | this.defaultValues = { 8 | phase : 0, 9 | radius : 50, 10 | wavelength : 16, 11 | amplitude : 10, 12 | centerX : 0.5, 13 | centerY : 0.5 14 | }; 15 | this.valueRanges = { 16 | phase : {min: 0, max: 100}, 17 | radius : {min: 1, max: 200}, 18 | wavelength : {min: 1, max: 100}, 19 | amplitude : {min: 1, max: 100}, 20 | centerX : {min: 0.0, max:1.0}, 21 | centerY : {min: 0.0, max:1.0} 22 | }; 23 | if(!FilterUtils){ 24 | if(console){ 25 | console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); 26 | } 27 | return; 28 | } 29 | var filterUtils = new FilterUtils(); 30 | 31 | this.filter = function (input, values){ 32 | var width = input.width, height = input.height; 33 | var inputData = input.data; 34 | if(values === undefined){ values = this.defaultValues; } 35 | var wavelength = (values.wavelength === undefined) ? this.defaultValues.wavelength : values.wavelength; 36 | var amplitude = (values.amplitude === undefined) ? this.defaultValues.amplitude : values.amplitude; 37 | var phase = (values.phase === undefined) ? this.defaultValues.phase : values.phase; 38 | var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; 39 | var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; 40 | var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; 41 | var radius2 = radius*radius; 42 | var iCenterX = width * centerX; var iCenterY = height * centerY; 43 | var transInverse = function(x,y,out){ 44 | var dx = x-iCenterX; 45 | var dy = y-iCenterY; 46 | var distance2 = dx*dx + dy*dy; 47 | if(distance2 > radius2){ 48 | out[0] = x; 49 | out[1] = y; 50 | } else { 51 | var distance = Math.sqrt(distance2); 52 | var amount = amplitude * Math.sin(distance/wavelength * Math.PI * 2 - phase); 53 | amount *= (radius-distance)/radius; 54 | if(distance != 0){ 55 | amount *= wavelength/distance; 56 | } 57 | out[0] = x + dx*amount; 58 | out[1] = y + dy*amount; 59 | } 60 | } 61 | filterUtils.transformFilter(inputData,transInverse,width,height); 62 | } 63 | } 64 | --------------------------------------------------------------------------------