photo-groove/pasta.js
Alexander Kobjolke 43971bd268 chapter 5: Implement sliders and filters
The application communicates back and forth with JavaScript using ports
and subscriptions. It also receives initial data from JavaScript by
passing 'flags' to the init function.
2023-12-14 11:22:01 +01:00

2418 lines
71 KiB
JavaScript

/*
=========================================================================
JSManipulate v1.0 (2011-08-01)
Javascript image filter & effect library
Developed by Joel Besada (http://www.joelb.me)
Demo page: http://www.joelb.me/jsmanipulate
MIT LICENSED (http://www.opensource.org/licenses/mit-license.php)
Copyright (c) 2011, Joel Besada
=========================================================================
*/
/**
* Contains common filter functions.
*/
function FilterUtils(){
this.HSVtoRGB = function (h, s, v){
var r, g, b;
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
switch(i % 6){
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
default: break;
}
return [r * 255, g * 255, b * 255];
};
this.RGBtoHSV = function (r, g, b){
r = r/255; g = g/255; b = b/255;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var h, s, v = max;
var d = max - min;
s = max === 0 ? 0 : d / max;
if(max === min){
h = 0;
}else{
switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
default: break;
}
h /= 6;
}
return [h, s, v];
};
this.getPixel = function (pixels,x,y,width,height){
var pix = (y*width + x)*4;
if (x < 0 || x >= width || y < 0 || y >= height) {
return [pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4],
pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 1],
pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 2],
pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 3]];
}
return [pixels[pix],pixels[pix+1],pixels[pix+2],pixels[pix+3]];
};
var haveNextGaussian = false;
var nextGaussian;
this.gaussianRandom = function(){
if(haveNextGaussian){
haveNextGaussian = false;
return nextGaussian;
} else {
var v1, v2, s;
do {
v1 = 2 * Math.random() - 1;
v2 = 2 * Math.random() - 1;
s = v1 * v1 + v2 * v2;
} while (s >= 1 || s === 0);
var mult = Math.sqrt(-2 * Math.log(s)/s);
nextGaussian = v2 * mult;
haveNextGaussian = true;
return v1 * mult;
}
};
this.clampPixel = function (x,a,b){
return (x < a) ? a : (x > b) ? b : x;
};
this.triangle = function(x){
var r = this.mod(x, 1);
return 2*(r < 0.5 ? r : 1-r);
};
this.mod = function(a,b){
var n = parseInt(a/b,10);
a -= n*b;
if(a < 0){
return a + b;
}
return a;
};
this.mixColors = function(t, rgb1, rgb2){
var r = this.linearInterpolate(t,rgb1[0],rgb2[0]);
var g = this.linearInterpolate(t,rgb1[1],rgb2[1]);
var b = this.linearInterpolate(t,rgb1[2],rgb2[2]);
var a = this.linearInterpolate(t,rgb1[3],rgb2[3]);
return [r,g,b,a];
};
this.linearInterpolate = function(t,a,b){
return a + t * (b-a);
};
this.bilinearInterpolate = function (x,y,nw,ne,sw,se){
var m0, m1;
var r0 = nw[0]; var g0 = nw[1]; var b0 = nw[2]; var a0 = nw[3];
var r1 = ne[0]; var g1 = ne[1]; var b1 = ne[2]; var a1 = ne[3];
var r2 = sw[0]; var g2 = sw[1]; var b2 = sw[2]; var a2 = sw[3];
var r3 = se[0]; var g3 = se[1]; var b3 = se[2]; var a3 = se[3];
var cx = 1.0 - x; var cy = 1.0 - y;
m0 = cx * a0 + x * a1;
m1 = cx * a2 + x * a3;
var a = cy * m0 + y * m1;
m0 = cx * r0 + x * r1;
m1 = cx * r2 + x * r3;
var r = cy * m0 + y * m1;
m0 = cx * g0 + x * g1;
m1 = cx * g2 + x * g3;
var g = cy * m0 + y * m1;
m0 = cx * b0 + x * b1;
m1 = cx * b2 + x * b3;
var b =cy * m0 + y * m1;
return [r,g,b,a];
};
this.tableFilter = function (inputData, table, width, height){
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
for(var i = 0; i < 3; i++){
inputData[pixel+i] = table[inputData[pixel+i]];
}
}
}
};
this.convolveFilter = function(inputData, matrix, width, height){
var outputData = [];
var rows, cols;
rows = cols = Math.sqrt(matrix.length);
var rows2 = parseInt(rows/2,10);
var cols2 = parseInt(cols/2,10);
var trace = true;
for(var y = 0; y < height; y++){
for (var x = 0; x < width; x++){
var pixel = (y*width + x)*4;
var r = 0, g = 0, b = 0;
for(var row = -rows2; row <= rows2; row++){
var iy = y+row;
var ioffset;
if (0 <= iy && iy < height) {
ioffset = iy*width;
} else {
ioffset = y*width;
}
var moffset = cols*(row+rows2)+cols2;
for (var col = -cols2; col <= cols2; col++) {
var f = matrix[moffset+col];
if (f !== 0) {
var ix = x+col;
if (!(0 <= ix && ix < width)) {
ix = x;
}
var iPixel = (ioffset+ix)*4;
r += f * inputData[iPixel];
g += f * inputData[iPixel+1];
b += f * inputData[iPixel+2];
}
}
}
outputData[pixel] = parseInt(r+0.5,10);
outputData[pixel+1] = parseInt(g+0.5,10);
outputData[pixel+2] = parseInt(b+0.5,10);
outputData[pixel+3] = inputData[pixel+3];
}
}
for(var k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
this.transformFilter = function(inputData, transformInverse, width, height){
var out = [];
var outputData = [];
for(var j = 0; j < inputData.length; j++){
outputData[j] = inputData[j];
}
for(var y = 0; y < height; y++){
for (var x = 0; x < width; x++){
var pixel = (y*width + x)*4;
transformInverse.apply(this,[x,y,out]);
var srcX = Math.floor(out[0]);
var srcY = Math.floor(out[1]);
var xWeight = out[0]-srcX;
var yWeight = out[1]-srcY;
var nw,ne,sw,se;
if(srcX >= 0 && srcX < width-1 && srcY >= 0 && srcY < height-1){
var i = (width*srcY + srcX)*4;
nw = [inputData[i],inputData[i+1],inputData[i+2],inputData[i+3]];
ne = [inputData[i+4],inputData[i+5],inputData[i+6],inputData[i+7]];
sw = [inputData[i+width*4],inputData[i+width*4+1],inputData[i+width*4+2],inputData[i+width*4+3]];
se = [inputData[i+(width + 1)*4],inputData[i+(width + 1)*4+1],inputData[i+(width + 1)*4+2],inputData[i+(width + 1)*4+3]];
} else {
nw = this.getPixel( inputData, srcX, srcY, width, height );
ne = this.getPixel( inputData, srcX+1, srcY, width, height );
sw = this.getPixel( inputData, srcX, srcY+1, width, height );
se = this.getPixel( inputData, srcX+1, srcY+1, width, height );
}
var rgba = this.bilinearInterpolate(xWeight,yWeight,nw,ne,sw,se);
outputData[pixel] = rgba[0];
outputData[pixel + 1] = rgba[1];
outputData[pixel + 2] = rgba[2];
outputData[pixel + 3] = rgba[3];
}
}
for(var k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
}
/**
* Blurs the image with Gaussian blur.
*/
function BlurFilter(){
this.name = "Blur";
this.isDirAnimatable = false;
this.defaultValues = {
amount : 3
};
this.valueRanges = {
amount : {min:0, max:10}
};
this.filter = function(input,values){
var width = input.width;
var width4 = width << 2;
var height = input.height;
var inputData = input.data;
var q;
var amount = values.amount;
if (amount < 0.0) {
amount = 0.0;
}
if (amount >= 2.5) {
q = 0.98711 * amount - 0.96330;
} else if (amount >= 0.5) {
q = 3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * amount);
} else {
q = 2 * amount * (3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * 0.5));
}
var qq = q * q;
var qqq = qq * q;
var b0 = 1.57825 + (2.44413 * q) + (1.4281 * qq ) + (0.422205 * qqq);
var b1 = ((2.44413 * q) + (2.85619 * qq) + (1.26661 * qqq)) / b0;
var b2 = (-((1.4281 * qq) + (1.26661 * qqq))) / b0;
var b3 = (0.422205 * qqq) / b0;
var bigB = 1.0 - (b1 + b2 + b3);
var c = 0;
var index;
var indexLast;
var pixel;
var ppixel;
var pppixel;
var ppppixel;
for (c = 0; c < 3; c++) {
for (var y = 0; y < height; y++) {
index = y * width4 + c;
indexLast = y * width4 + ((width - 1) << 2) + c;
pixel = inputData[index];
ppixel = pixel;
pppixel = ppixel;
ppppixel = pppixel;
for (; index <= indexLast; index += 4) {
pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel;
inputData[index] = pixel;
ppppixel = pppixel;
pppixel = ppixel;
ppixel = pixel;
}
index = y * width4 + ((width - 1) << 2) + c;
indexLast = y * width4 + c;
pixel = inputData[index];
ppixel = pixel;
pppixel = ppixel;
ppppixel = pppixel;
for (; index >= indexLast; index -= 4) {
pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel;
inputData[index] = pixel;
ppppixel = pppixel;
pppixel = ppixel;
ppixel = pixel;
}
}
}
for (c = 0; c < 3; c++) {
for (var x = 0; x < width; x++) {
index = (x << 2) + c;
indexLast = (height - 1) * width4 + (x << 2) + c;
pixel = inputData[index];
ppixel = pixel;
pppixel = ppixel;
ppppixel = pppixel;
for (; index <= indexLast; index += width4) {
pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel;
inputData[index] = pixel;
ppppixel = pppixel;
pppixel = ppixel;
ppixel = pixel;
}
index = (height - 1) * width4 + (x << 2) + c;
indexLast = (x << 2) + c;
pixel = inputData[index];
ppixel = pixel;
pppixel = ppixel;
ppppixel = pppixel;
for (; index >= indexLast; index -= width4) {
pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel;
inputData[index] = pixel;
ppppixel = pppixel;
pppixel = ppixel;
ppixel = pixel;
}
}
}
};
}
/**
* Adjusts the brightness of the image by going over to HSV values.
* Negative values decrease brightness while positive values increase brightness.
*/
function BrightnessFilter(){
this.name = "Brightness";
this.isDirAnimatable = true;
this.defaultValues = {
amount : 0.0
};
this.valueRanges = {
amount : {min:-1.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var hsv = filterUtils.RGBtoHSV(inputData[pixel],inputData[pixel+1],inputData[pixel+2]);
hsv[2] += amount;
if(hsv[2] < 0){
hsv[2] = 0;
} else if (hsv[2] > 1){
hsv[2] = 1;
}
var rgb = filterUtils.HSVtoRGB(hsv[0],hsv[1],hsv[2]);
for(var i = 0; i < 3; i++){
inputData[pixel+i] = rgb[i];
}
}
}
};
}
/**
* Embosses the edges of the image.
* This filter takes no parameters but can be applied several times for
* further effect.
*/
function BumpFilter(){
this.name = "Bump";
this.isDirAnimatable = true;
this.defaultValues = {
};
this.valueRanges = {
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var matrix = [-1,-1, 0,
-1, 1, 1,
0, 1, 1];
filterUtils.convolveFilter(inputData,matrix,width,height);
};
}
/**
* Smears out the image with circular shapes to create a painting style effect.
* The mix values sets the intensity of the effect.
* NOTE: This filter can be very slow, especially at higher densities/sizes. Use with caution.
*/
function CircleSmearFilter(){
this.name = "Circle Smear";
this.isDirAnimatable = false;
this.defaultValues = {
size : 4,
density : 0.5,
mix : 0.5
};
this.valueRanges = {
size : {min:1, max:10},
density : {min:0.0, max:1.0},
mix : {min:0.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
for(var j = 0; j < inputData.length; j++){
outputData[j] = inputData[j];
}
if(values === undefined){ values = this.defaultValues; }
var size = (values.size === undefined) ? this.defaultValues.size : values.size;
if(size < 1){ size = 1;}
size = parseInt(size,10);
var density = (values.density === undefined) ? this.defaultValues.density : values.density;
var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix;
var radius = size+1;
var radius2 = radius*radius;
var numShapes = parseInt(2*density/30*width*height / 2,10);
for(var i = 0; i < numShapes; i++){
var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width;
var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height;
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]];
for(var x = sx - radius; x < sx + radius + 1; x++){
for(var y = sy - radius; y < sy + radius + 1; y++){
var f = (x - sx) * (x - sx) + (y - sy) * (y - sy);
if (x >= 0 && x < width && y >= 0 && y < height && f <= radius2) {
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]];
var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2);
for(var k = 0; k < 3; k++){
outputData[(y*width+x)*4+k] = mixedRGB[k];
}
}
}
}
}
for(var l = 0; l < outputData.length; l++){
inputData[l] = outputData[l];
}
};
}
/**
* Adjusts the contrast of the image.
*/
function ContrastFilter(){
this.name = "Contrast";
this.isDirAnimatable = true;
this.defaultValues = {
amount : 1.0
};
this.valueRanges = {
amount : {min:0.0, max:2.0}
};
if(!FilterUtils){
if(console){
console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)");
}
return;
}
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
if(amount < 0){
amount = 0.0;
}
var table = [];
for(var i = 0; i < 256; i++){
table[i] = parseInt(255 * (((i/255)-0.5)*amount+0.5),10);
}
filterUtils.tableFilter(inputData,table,width,height);
};
}
/**
* Smears out the image with cross shapes to create a painting style effect.
* The mix values sets the intensity of the effect.
*/
function CrossSmearFilter(){
this.name = "Cross Smear";
this.isDirAnimatable = false;
this.defaultValues = {
distance : 8,
density : 0.5,
mix : 0.5
};
this.valueRanges = {
distance : {min:0, max:30},
density : {min:0.0, max:1.0},
mix : {min:0.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
for(var j = 0; j < inputData.length; j++){
outputData[j] = inputData[j];
}
if(values === undefined){ values = this.defaultValues; }
var distance = (values.distance === undefined) ? this.defaultValues.distance : values.distance;
if(distance < 0){ distance = 0;}
distance = parseInt(distance,10);
var density = (values.density === undefined) ? this.defaultValues.density : values.density;
var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix;
var numShapes = parseInt(2*density*width * height / (distance + 1),10);
for(var i = 0; i < numShapes; i++){
var x = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width;
var y = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height;
var length = (Math.random()*Math.pow(2,32)) % distance + 1;
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]];
var rgb1;
var mixedRGB;
var k;
for (var x1 = x-length; x1 < x+length+1; x1++) {
if(x1 >= 0 && x1 < width){
rgb1 = [outputData[(y*width+x1)*4],outputData[(y*width+x1)*4+1],outputData[(y*width+x1)*4+2],outputData[(y*width+x1)*4+3]];
mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2);
for(k = 0; k < 3; k++){
outputData[(y*width+x1)*4+k] = mixedRGB[k];
}
}
}
for (var y1 = y-length; y1 < y+length+1; y1++) {
if(y1 >= 0 && y1 < height){
rgb1 = [outputData[(y1*width+x)*4],outputData[(y1*width+x)*4+1],outputData[(y1*width+x)*4+2],outputData[(y1*width+x)*4+3]];
mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2);
for(k = 0; k < 3; k++){
outputData[(y1*width+x)*4+k] = mixedRGB[k];
}
}
}
}
for(var l = 0; l < outputData.length; l++){
inputData[l] = outputData[l];
}
};
}
/**
* Diffuses the image creating a frosted glass effect.
*/
function DiffusionFilter(){
this.name = "Diffusion";
this.isDirAnimatable = false;
this.defaultValues = {
scale: 4
};
this.valueRanges = {
scale: {min: 1, max: 100}
};
var filterUtils = new FilterUtils();
this.filter = function (input, values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var scale = (values.scale === undefined) ? this.defaultValues.scale : values.scale;
var out = [];
var outputData = [];
var sinTable = [];
var cosTable = [];
for(var i = 0; i < 256; i++){
var angle = Math.PI*2*i/256;
sinTable[i] = scale*Math.sin(angle);
cosTable[i] = scale*Math.cos(angle);
}
transInverse = function (x,y,out){
var angle = parseInt(Math.random() * 255,10);
var distance = Math.random();
out[0] = x + distance * sinTable[angle];
out[1] = y + distance * cosTable[angle];
};
filterUtils.transformFilter(inputData,transInverse,width,height);
};
}
/**
* Dithers the image to the specified number of colors. Setting color to false
* grayscales the image.
*/
function DitherFilter(){
this.name = "Dither";
this.isDirAnimatable = false;
this.defaultValues = {
levels : 3,
color : true
};
this.valueRanges = {
levels : {min:2, max:30},
color : {min:false, max:true}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
var i, j;
for (j=0; j < inputData.length; j++) {
outputData[j] = 0;
}
if(values === undefined){ values = this.defaultValues; }
var levels = (values.levels === undefined) ? this.defaultValues.levels : values.levels;
var color = (values.color === undefined) ? this.defaultValues.color : values.color;
if(levels <= 1){
levels = 1;
}
var matrix = [0,0,0,
0,0,7,
3,5,1];
var sum = 7+3+5+1;
var index = 0;
var map = [];
for (i=0; i < levels; i++) {
map[i] = parseInt(255* i / (levels-1),10);
}
var div = [];
for (i=0; i < 256; i++) {
div[i] = parseInt(levels*i / 256,10);
}
for (var y = 0; y < height; y++) {
var reverse = ((y & 1) == 1);
var direction;
if(reverse){
index = (y*width+width-1)*4;
direction = -1;
} else {
index = y*width*4;
direction = 1;
}
for (var x = 0; x < width; x++) {
var r1 = inputData[index]; var g1 = inputData[index+1]; var b1 = inputData[index+2];
if(!color){
r1 = g1 = b1 = parseInt((r1+g1+b1) / 3,10);
}
var r2 = map[div[r1]];var g2 = map[div[g1]];var b2 = map[div[b1]];
outputData[index] = r2; outputData[index + 1] = g2; outputData[index+2] = b2; outputData[index+3] = inputData[index+3];
var er = r1-r2; var eg = g1-g2; var eb = b1-b2;
for (i = -1; i <= 1; i++) {
var iy = i+y;
if (0 <= iy && iy < height) {
for (j = -1; j <= 1; j++) {
var jx = j+x;
if (0 <= jx && jx < width) {
var w;
if (reverse){
w = matrix[(i+1)*3-j+1];
} else{
w = matrix[(i+1)*3+j+1];
}
if (w !== 0) {
var k = (reverse) ? index - j*4 : index + j*4;
r1 = inputData[k]; g1 = inputData[k+1]; b1 = inputData[k+2];
var factor = w/sum;
r1 += er * factor; g1 += eg * factor; b1 += eb * factor;
inputData[k] = r1; inputData[k+1] = g1 ;inputData[k+2] = b1;
}
}
}
}
}
index += direction*4;
}
}
for(j = 0; j < outputData.length; j++){
inputData[j] = outputData[j];
}
};
}
/**
* Highlights the edges of the image.
*/
function EdgeFilter(){
this.name = "Edge Detection";
this.isDirAnimatable = true;
this.defaultValues = {
};
this.valueRanges = {
};
var matrixH = [-1,-2,-1,
0, 0, 0,
1, 2, 1];
var matrixV = [-1, 0, 1,
-2, 0, 2,
-1, 0, 1];
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var rh = 0; gh = 0; bh = 0;
var rv = 0; gv = 0; bv = 0;
for(var row = -1; row <= 1; row++){
var iy = y+row;
var ioffset;
if(iy >= 0 && iy < height){
ioffset = iy*width*4;
} else {
ioffset = y*width*4;
}
var moffset = 3*(row+1)+1;
for(var col = -1; col <= 1; col++){
var ix = x+col;
if(!(ix >= 0 && ix < width)){
ix = x;
}
ix *= 4;
var r = inputData[ioffset+ix];
var g = inputData[ioffset+ix+1];
var b = inputData[ioffset+ix+2];
var h = matrixH[moffset+col];
var v = matrixV[moffset+col];
rh += parseInt(h*r,10);
bh += parseInt(h*g,10);
gh += parseInt(h*b,10);
rv += parseInt(v*r,10);
gv += parseInt(v*g,10);
bv += parseInt(v*b,10);
}
}
r = parseInt(Math.sqrt(rh*rh + rv*rv) / 1.8,10);
g = parseInt(Math.sqrt(gh*gh + gv*gv) / 1.8,10);
b = parseInt(Math.sqrt(bh*bh + bv*bv) / 1.8,10);
outputData[pixel] = r;
outputData[pixel+1] = g;
outputData[pixel+2] = b;
outputData[pixel+3] = inputData[pixel+3];
}
}
for(var k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
}
/**
* Embosses the image with a simulated light source.
* Angle and elevation sets the position of the light.
*/
function EmbossFilter(){
this.name = "Emboss";
this.isDirAnimatable = false;
this.defaultValues = {
height : 1,
angle : 135,
elevation : 30
};
this.valueRanges = {
height : {min:1, max:10},
angle : {min:0, max:360},
elevation : {min:0, max:180}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var bumpHeight = (values.height === undefined) ? this.defaultValues.height : values.height;
var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle;
var elevation = (values.elevation === undefined) ? this.defaultValues.elevation : values.elevation;
angle = angle / 180 * Math.PI;
elevation = elevation / 180 * Math.PI;
var width45 = 3 * bumpHeight;
var pixelScale = 255.9;
var bumpPixels = [];
var bumpMapWidth = width;
var bumpMapHeight = height;
for(var i = 0; i < inputData.length; i+=4){
bumpPixels[i/4] = (inputData[i] + inputData[i+1] + inputData[i+2])/3;
}
var Nx, Ny, Nz, Lx, Ly, Lz, Nz2, NzLz, NdotL;
var shade, background;
Lx = parseInt(Math.cos(angle) * Math.cos(elevation) * pixelScale,10);
Ly = parseInt(Math.sin(angle) * Math.cos(elevation) * pixelScale,10);
Lz = parseInt(Math.sin(elevation) * pixelScale,10);
Nz = parseInt(6 * 255 / width45,10);
Nz2 = Nz * Nz;
NzLz = Nz * Lz;
background = Lz;
var bumpIndex = 0;
for (var y = 0; y < height; y++, bumpIndex += bumpMapWidth) {
var s1 = bumpIndex;
var s2 = s1 + bumpMapWidth;
var s3 = s2 + bumpMapWidth;
for (var x = 0; x < width; x++, s1++, s2++, s3++) {
var pixel = (y*width + x)*4;
if (y !== 0 && y < height-2 && x !== 0 && x < width-2) {
Nx = bumpPixels[s1-1] + bumpPixels[s2-1] + bumpPixels[s3-1] - bumpPixels[s1+1] - bumpPixels[s2+1] - bumpPixels[s3+1];
Ny = bumpPixels[s3-1] + bumpPixels[s3] + bumpPixels[s3+1] - bumpPixels[s1-1] - bumpPixels[s1] - bumpPixels[s1+1];
if (Nx === 0 && Ny === 0){
shade = background;
} else if ((NdotL = Nx*Lx + Ny*Ly + NzLz) < 0){
shade = 0;
} else {
shade = parseInt(NdotL / Math.sqrt(Nx*Nx + Ny*Ny + Nz2),10);
}
} else {
shade = background;
}
inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = shade;
}
}
};
}
/**
* Adjust simulated exposure values on the image.
*/
function ExposureFilter(){
this.name = "Exposure";
this.isDirAnimatable = true;
this.defaultValues = {
exposure : 1.0
};
this.valueRanges = {
exposure : {min:0, max:5}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var exposure = (values.exposure === undefined) ? this.defaultValues.exposure : values.exposure;
var table = [];
for(var i = 0; i < 256; i++){
table[i] = parseInt(255 *(1-Math.exp(-(i/255) * exposure)),10);
}
filterUtils.tableFilter(inputData, table, width, height);
};
}
/**
* Adjusts the gain and bias of the image. Gain alters the contrast while bias biases
* colors towards lighter or darker.
*/
function GainFilter(){
this.name = "Gain/Bias";
this.isDirAnimatable = true;
this.defaultValues = {
gain: 0.5,
bias: 0.5
};
this.valueRanges = {
gain: {min:0.0, max:1.0},
bias: {min:0.0, max:1.0}
};
var table = [];
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var gain = (values.gain === undefined) ? this.defaultValues.gain : values.gain;
var bias = (values.bias === undefined) ? this.defaultValues.bias : values.bias;
var table = [];
for(var i = 0; i < 256; i++){
var val = i/255;
var k = (1/gain-2) * (1-2*val);
val = (val < 0.5) ? val/(k+1) : (k-val)/(k-1);
val /= (1/bias-2)*(1-val)+1;
table[i] = parseInt(255 * val,10);
}
filterUtils.tableFilter(inputData,table,width,height);
};
}
/**
* Adjusts the gamma values of the image. Values over 1 increase the gamma while values over 0 decrease gamma.
*/
function GammaFilter(){
this.name = "Gamma";
this.isDirAnimatable = true;
this.defaultValues = {
amount : 1.0
};
this.valueRanges = {
amount : {min:0.0, max:2.0}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
if(amount < 0){
amount = 0.0;
}
if(!FilterUtils){
if(console){
console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)");
}
return;
}
var filterUtils = new FilterUtils();
var table = [];
for(var i = 0; i < 256; i++){
table[i] = 255 * Math.pow(i/255, 1/amount) + 0.5;
}
filterUtils.tableFilter(inputData,table,width,height);
};
}
/**
* Sets the image to grayscale.
*/
function GrayscaleFilter(){
this.name = "Grayscale";
this.isDirAnimatable = true;
this.defaultValues = {
};
this.valueRanges = {
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var luma = inputData[pixel]*0.3 + inputData[pixel+1]*0.59 + inputData[pixel+2]*0.11;
inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = luma;
}
}
};
}
/**
* Adjusts the hue of the image by going over to HSV values.
*/
function HueFilter(){
this.name = "Hue";
this.isDirAnimatable = true;
this.defaultValues = {
amount : 0.0
};
this.valueRanges = {
amount : {min:-1.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var hsv = filterUtils.RGBtoHSV(inputData[pixel],inputData[pixel+1],inputData[pixel+2]);
hsv[0] += amount;
while(hsv[0] < 0){
hsv[0] += 360;
}
var rgb = filterUtils.HSVtoRGB(hsv[0],hsv[1],hsv[2]);
for(var i = 0; i < 3; i++){
inputData[pixel+i] = rgb[i];
}
}
}
};
}
/**
* Inverts the colors of the image.
*/
function InvertFilter(){
this.name = "Invert";
this.isDirAnimatable = true;
this.defaultValues = {
};
this.valueRanges = {
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
for(var i = 0; i < 3; i++){
inputData[pixel+i] = 255 - inputData[pixel+i];
}
}
}
};
}
/**
* Creates a kaleidoscope effect on the image. CenterX and CenterY specify the
* position in terms of ratios of width and height.
*/
function KaleidoscopeFilter(){
this.name = "Kaleidoscope";
this.isDirAnimatable = false;
this.defaultValues = {
angle : 0,
rotation : 0,
sides : 3,
centerX : 0.5,
centerY : 0.5
};
this.valueRanges = {
angle : {min: 0, max: 360},
rotation : {min: 0, max: 360},
sides : {min: 1, max: 30},
centerX : {min: 0.0, max:1.0},
centerY : {min: 0.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function (input, values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle;
var rotation = (values.rotation === undefined) ? this.defaultValues.rotation : values.rotation;
var sides = (values.sides === undefined) ? this.defaultValues.sides : values.sides;
var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX;
var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY;
var iCenterX = width * centerX; var iCenterY = height * centerY;
angle = angle/180 * Math.PI;
rotation = rotation/180 * Math.PI;
var transInverse = function(x,y,out){
var dx = x - iCenterX;
var dy = y - iCenterY;
var r = Math.sqrt(dx*dx + dy*dy);
var theta = Math.atan2(dy,dx) - angle - rotation;
theta = filterUtils.triangle(theta/Math.PI*sides*0.5);
theta += angle;
out[0] = iCenterX + r*Math.cos(theta);
out[1] = iCenterY + r*Math.sin(theta);
};
filterUtils.transformFilter(inputData,transInverse,width,height);
};
}
/**
* Applies a fisheye lens distortion effect on the image. CenterX and CenterY specify the
* position in terms of ratios of width and height.
*/
function LensDistortionFilter(){
this.name = "Lens Distortion";
this.isDirAnimatable = false;
this.defaultValues = {
refraction : 1.5,
radius : 50,
centerX : 0.5,
centerY : 0.5
};
this.valueRanges = {
refraction : {min: 1, max: 10},
radius : {min: 1, max: 200},
centerX : {min: 0.0, max:1.0},
centerY : {min: 0.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function (input, values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var refraction = (values.refraction === undefined) ? this.defaultValues.refraction : values.refraction;
var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX;
var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY;
var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius;
var radius2 = radius*radius;
var iCenterX = width * centerX; var iCenterY = height * centerY;
var transInverse = function(x,y,out){
var dx = x-iCenterX;
var dy = y-iCenterY;
var x2 = dx*dx;
var y2 = dy*dy;
if (y2 >= (radius2 - (radius2*x2)/radius2)) {
out[0] = x;
out[1] = y;
} else {
var rRefraction = 1.0 / refraction;
var z = Math.sqrt((1.0 - x2/radius2 - y2/radius2) * radius2);
var z2 = z*z;
var xAngle = Math.acos(dx / Math.sqrt(x2+z2));
var angle1 = Math.PI/2 - xAngle;
var angle2 = Math.asin(Math.sin(angle1)*rRefraction);
angle2 = Math.PI/2 - xAngle - angle2;
out[0] = x - Math.tan(angle2)*z;
var yAngle = Math.acos(dy / Math.sqrt(y2+z2));
angle1 = Math.PI/2 - yAngle;
angle2 = Math.asin(Math.sin(angle1)*rRefraction);
angle2 = Math.PI/2 - yAngle - angle2;
out[1] = y - Math.tan(angle2)*z;
}
};
filterUtils.transformFilter(inputData,transInverse,width,height);
};
}
/**
* Smears out the image with line shapes to create a painting style effect. Mix specifies
* the intensity of the effect.
*/
function LineSmearFilter(){
this.name = "Line Smear";
this.isDirAnimatable = false;
this.defaultValues = {
distance : 8,
density : 0.5,
angle : 0,
mix : 0.5
};
this.valueRanges = {
distance : {min:1, max:30},
density : {min:0.0, max:1.0},
angle : {min:0, max:360},
mix : {min:0.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
var k;
for(k = 0; k < inputData.length; k++){
outputData[k] = inputData[k];
}
if(values === undefined){ values = this.defaultValues; }
var distance = (values.distance === undefined) ? this.defaultValues.distance : values.distance;
if(distance < 1){ distance = 1;}
distance = parseInt(distance,10);
var density = (values.density === undefined) ? this.defaultValues.density : values.density;
var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle;
var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix;
angle = angle/180*Math.PI;
var sinAngle = Math.sin(angle);
var cosAngle = Math.cos(angle);
var numShapes = parseInt(2*density*width*height / 2,10);
for(var i = 0; i < numShapes; i++){
var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width;
var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height;
var length = (Math.random()*Math.pow(2,32) & 0x7fffffff) % distance + 1;
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]];
var dx = parseInt(length*cosAngle,10);
var dy = parseInt(length*sinAngle,10);
var x0 = sx-dx;
var y0 = sy-dy;
var x1 = sx+dx;
var y1 = sy+dy;
var x, y, d, incrE, incrNE, ddx, ddy;
if (x1 < x0){
ddx = -1;
} else {
ddx = 1;
}
if (y1 < y0){
ddy = -1;
} else {
ddy = 1;
}
dx = x1-x0;
dy = y1-y0;
dx = Math.abs(dx);
dy = Math.abs(dy);
x = x0;
y = y0;
var rgb1;
var mixedRGB;
if (x < width && x >= 0 && y < height && y >= 0) {
rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]];
mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2);
for(k = 0; k < 3; k++){
outputData[(y*width+x)*4+k] = mixedRGB[k];
}
}
if (Math.abs(dx) > Math.abs(dy)) {
d = 2*dy-dx;
incrE = 2*dy;
incrNE = 2*(dy-dx);
while (x != x1) {
if (d <= 0){
d += incrE;
} else {
d += incrNE;
y += ddy;
}
x += ddx;
if (x < width && x >= 0 && y < height && y >= 0) {
rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]];
mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2);
for(k = 0; k < 3; k++){
outputData[(y*width+x)*4+k] = mixedRGB[k];
}
}
}
} else {
d = 2*dx-dy;
incrE = 2*dx;
incrNE = 2*(dx-dy);
while (y != y1) {
if (d <= 0) {
d += incrE;
}else {
d += incrNE;
x += ddx;
}
y += ddy;
if (x < width && x >= 0 && y < height && y >= 0) {
rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]];
mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2);
for(k = 0; k < 3; k++){
outputData[(y*width+x)*4+k] = mixedRGB[k];
}
}
}
}
}
for(k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
}
/**
* Replaces every pixel with the maximum RGB value of the neighboring pixels. Each color is
* considered separately.
*/
function MaximumFilter(){
this.name = "Maximum";
this.isDirAnimatable = true;
this.defaultValues = {
};
this.valueRanges = {
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var maxR = 0;
var maxG = 0;
var maxB = 0;
for (var dy = -1; dy <= 1; dy++){
var iy = y+dy;
if(iy >= 0 && iy < height){
for (var dx = -1; dx <= 1; dx++){
var ix = x+dx;
if(ix >= 0 && ix < width){
var iPixel = (iy*width + ix)*4;
maxR = Math.max(maxR,inputData[iPixel]);
maxG = Math.max(maxG,inputData[iPixel+1]);
maxB = Math.max(maxB,inputData[iPixel+2]);
}
}
}
}
outputData[pixel] = maxR;
outputData[pixel+1] = maxG;
outputData[pixel+2] = maxB;
outputData[pixel+3] = inputData[pixel+3];
}
}
for(var k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
}
/**
* Replaces every pixel with the median RGB value of the neighboring pixels. Each color is
* considered separately.
*/
function MedianFilter(){
this.name = "Median";
this.isDirAnimatable = false;
this.defaultValues = {
};
this.valueRanges = {
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var rList = [];
var gList = [];
var bList = [];
for (var dy = -1; dy <= 1; dy++){
var iy = y+dy;
if(iy >= 0 && iy < height){
for (var dx = -1; dx <= 1; dx++){
var ix = x+dx;
if(ix >= 0 && ix < width){
var iPixel = (iy*width + ix)*4;
rList.push(inputData[iPixel]);
gList.push(inputData[iPixel+1]);
bList.push(inputData[iPixel+2]);
}
}
}
}
var sortFunc = function(a,b){
return a-b;
};
rList.sort(sortFunc);
gList.sort(sortFunc);
bList.sort(sortFunc);
outputData[pixel] = rList[4];
outputData[pixel+1] = gList[4];
outputData[pixel+2] = bList[4];
outputData[pixel+3] = inputData[pixel+3];
}
}
for(var k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
}
/**
* Replaces every pixel with the minimum RGB value of the neighboring pixels. Each color is
* considered separately.
*/
function MinimumFilter(){
this.name = "Minimum";
this.isDirAnimatable = true;
this.defaultValues = {
};
this.valueRanges = {
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var minR = 255;
var minG = 255;
var minB = 255;
for (var dy = -1; dy <= 1; dy++){
var iy = y+dy;
if(iy >= 0 && iy < height){
for (var dx = -1; dx <= 1; dx++){
var ix = x+dx;
if(ix >= 0 && ix < width){
var iPixel = (iy*width + ix)*4;
minR = Math.min(minR,inputData[iPixel]);
minG = Math.min(minG,inputData[iPixel+1]);
minB = Math.min(minB,inputData[iPixel+2]);
}
}
}
}
outputData[pixel] = minR;
outputData[pixel+1] = minG;
outputData[pixel+2] = minB;
outputData[pixel+3] = inputData[pixel+3];
}
}
for(var k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
}
/**
* Creates random noise on the image, with or without color.
*/
function NoiseFilter(){
this.name = "Noise";
this.isDirAnimatable = true;
this.defaultValues = {
amount : 25,
density : 1,
monochrome : true
};
this.valueRanges = {
amount : {min:0, max:100},
density : {min:0, max:1.0},
monochrome : {min:false, max:true}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
var density = (values.density === undefined) ? this.defaultValues.density : values.density;
var monochrome = (values.monochrome === undefined) ? this.defaultValues.monochrome : values.monochrome;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
if(Math.random() <= density){
var n;
if(monochrome){
n = parseInt((2*Math.random()-1) * amount,10);
inputData[pixel] += n;
inputData[pixel+1] += n;
inputData[pixel+2] += n;
} else {
for(var i = 0; i < 3; i++){
n = parseInt((2*Math.random()-1) * amount,10);
inputData[pixel+i] += n;
}
}
}
}
}
};
}
/**
* Produces an oil painting effect on the image.
* NOTE: This filter can be very slow, especially at higher ranges. Use with caution.
*/
function OilFilter(){
this.name = "Oil Painting";
this.isDirAnimatable = false;
this.defaultValues = {
range : 3
};
this.valueRanges = {
range : {min:0, max:5}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
if(values === undefined){ values = this.defaultValues; }
var range = (values.range === undefined) ? this.defaultValues.range : values.range;
range = parseInt(range,10);
var index = 0;
var rHistogram = [];
var gHistogram = [];
var bHistogram = [];
var rTotal = [];
var gTotal = [];
var bTotal = [];
var levels = 256;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
for (var j = 0; j < levels; j++){
rHistogram[j] = gHistogram[j] = bHistogram[j] = rTotal[j] = gTotal[j] = bTotal[j] = 0;
}
for (var row = -range; row <= range; row++) {
var iy = y+row;
var ioffset;
if (0 <= iy && iy < height) {
ioffset = iy*width;
for (var col = -range; col <= range; col++) {
var ix = x+col;
if (0 <= ix && ix < width) {
var ro = inputData[(ioffset+ix)*4];
var go = inputData[(ioffset+ix)*4+1];
var bo = inputData[(ioffset+ix)*4+2];
var ri = ro*levels/256;
var gi = go*levels/256;
var bi = bo*levels/256;
rTotal[ri] += ro;
gTotal[gi] += go;
bTotal[bi] += bo;
rHistogram[ri]++;
gHistogram[gi]++;
bHistogram[bi]++;
}
}
}
}
var r = 0, g = 0, b = 0;
for (var i = 1; i < levels; i++) {
if (rHistogram[i] > rHistogram[r]){
r = i;
}
if (gHistogram[i] > gHistogram[g]){
g = i;
}
if (bHistogram[i] > bHistogram[b]){
b = i;
}
}
r = rTotal[r] / rHistogram[r];
g = gTotal[g] / gHistogram[g];
b = bTotal[b] / bHistogram[b];
outputData[pixel] = r;
outputData[pixel+1] = g;
outputData[pixel+2] = b;
outputData[pixel+3] = inputData[pixel+3];
}
}
for(var k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
}
/**
* Changes the opacity of the image.
*/
function OpacityFilter(){
this.name = "Opacity";
this.isDirAnimatable = true;
this.defaultValues = {
amount : 1.0
};
this.valueRanges = {
amount : {min:0.0, max:1.0}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
inputData[pixel+3] = 255*amount;
}
}
};
}
/**
* Pinches and whirls the image toward the center point. CenterX and CenterY specify the
* position in terms of ratios of width and height.
*/
function PinchFilter(){
this.name = "Pinch/Whirl";
this.isDirAnimatable = false;
this.defaultValues = {
amount : 0.5,
radius : 100,
angle : 0,
centerX : 0.5,
centerY : 0.5
};
this.valueRanges = {
amount : {min: -1.0, max: 1.0},
radius : {min: 1, max: 200},
angle : {min: 0, max: 360},
centerX : {min: 0.0, max:1.0},
centerY : {min: 0.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function (input, values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle;
var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX;
var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY;
var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius;
var radius2 = radius*radius;
angle = angle/180 * Math.PI;
var iCenterX = width * centerX; var iCenterY = height * centerY;
var transInverse = function(x,y,out){
var dx = x-iCenterX;
var dy = y-iCenterY;
var distance = dx*dx + dy*dy;
if(distance > radius2 || distance === 0){
out[0] = x;
out[1] = y;
} else {
var d = Math.sqrt( distance / radius2 );
var t = Math.pow( Math.sin( Math.PI*0.5 * d ), -amount);
dx *= t;
dy *= t;
var e = 1 - d;
var a = angle * e * e;
var s = Math.sin(a);
var c = Math.cos(a);
out[0] = iCenterX + c*dx - s*dy;
out[1] = iCenterY + s*dx + c*dy;
}
};
filterUtils.transformFilter(inputData,transInverse,width,height);
};
}
/**
* Pixelates the image i.e. divides the image into blocks of color.
*/
function PixelationFilter(){
this.name = "Pixelation";
this.isDirAnimatable = false;
this.defaultValues = {
size : 5
};
this.valueRanges = {
size : {min:1, max:50}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var size = (values.size === undefined) ? this.defaultValues.size : values.size;
size = parseInt(size,10);
var pixels = [];
var by, bx, bPixel;
for (var y = 0; y < height; y+=size) {
for (var x = 0; x < width; x+=size) {
var pixel = (y*width + x)*4;
var w = Math.min(size, width-x);
var h = Math.min(size, height-y);
var t = w*h;
var r = 0, g = 0, b = 0;
for(by = y; by < y+h; by++){
for(bx = x; bx < x+w; bx++){
bPixel = (by*width + bx)*4;
r += inputData[bPixel];
g += inputData[bPixel+1];
b += inputData[bPixel+2];
}
}
for(by = y; by < y+h; by++){
for(bx = x; bx < x+w; bx++){
bPixel = (by*width + bx)*4;
inputData[bPixel] = r/t;
inputData[bPixel+1] = g/t;
inputData[bPixel+2] = b/t;
}
}
}
}
};
}
/**
* Posterizes the image, i.e. restricts the color values to a set amount of levels.
*/
function PosterizeFilter(){
this.name = "Posterize";
this.isDirAnimatable = false;
this.defaultValues = {
levels : 6
};
this.valueRanges = {
levels : {min:2, max:30 }
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var levels = (values.levels === undefined) ? this.defaultValues.levels : parseInt(values.levels,10);
if(levels <= 1){
return;
}
var table = [];
for(var i = 0; i < 256; i++){
table[i] = parseInt(255 * parseInt(i*levels/256,10) / (levels-1),10);
}
filterUtils.tableFilter(inputData,table,width,height);
};
}
/**
* Adjust the factor of each RGB color value in the image.
*/
function RGBAdjustFilter(){
this.name = "RGBAdjust";
this.isDirAnimatable = true;
this.defaultValues = {
red: 1.0,
green: 1.0,
blue: 1.0
};
this.valueRanges = {
red: {min: 0.0, max: 2.0},
green: {min: 0.0, max: 2.0},
blue: {min: 0.0, max: 2.0}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var red = (values.red === undefined) ? this.defaultValues.red : values.red;
var green = (values.green === undefined) ? this.defaultValues.green : values.green;
var blue = (values.blue === undefined) ? this.defaultValues.blue : values.blue;
if(red < 0){ red = 0; }
if(green < 0){ green = 0; }
if(blue < 0){ blue = 0; }
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
inputData[pixel] *= red;
inputData[pixel+1] *= green;
inputData[pixel+2] *= blue;
}
}
};
}
/**
* Adjusts the saturation value of the image. Values over 1 increase saturation while values below decrease saturation.
* For a true grayscale effect, use the grayscale filter instead.
*/
function SaturationFilter(){
this.name = "Saturation";
this.isDirAnimatable = true;
this.defaultValues = {
amount : 1.0
};
this.valueRanges = {
amount : {min:0.0, max:2.0}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
var RW = 0.3;
var RG = 0.59;
var RB = 0.11;
var a = (1 - amount) * RW + amount;
var b = (1 - amount) * RW;
var c = (1 - amount) * RW;
var d = (1 - amount) * RG;
var e = (1 - amount) * RG + amount;
var f = (1 - amount) * RG;
var g = (1 - amount) * RB;
var h = (1 - amount) * RB;
var i = (1 - amount) * RB + amount;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var pR = inputData[pixel];
var pG = inputData[pixel+1];
var pB = inputData[pixel+2];
inputData[pixel] = a*pR + d*pG + g*pB;
inputData[pixel+1] = b*pR + e*pG + h*pB;
inputData[pixel+2] = c*pR + f*pG + i*pB;
}
}
};
}
/**
* Creates ripples on the image horizontally/vertically in a sawtooth pattern.
*/
function SawtoothRippleFilter(){
this.name = "Sawtooth Ripples";
this.isDirAnimatable = false;
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 filterUtils = new FilterUtils();
this.filter = function (input, values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude;
var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude;
var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength;
var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength;
var transInverse = function(x,y,out){
var nx = y/xWavelength;
var ny = x/yWavelength;
var fx = filterUtils.mod(nx,1);
var fy = filterUtils.mod(ny,1);
out[0] = x + xAmplitude * fx;
out[1] = y + yAmplitude * fy;
};
filterUtils.transformFilter(inputData,transInverse,width,height);
};
}
/**
* Creates a sepia effect on the image i.e. gives the image a yellow-brownish tone.
*/
function SepiaFilter(){
this.name = "Sepia";
this.isDirAnimatable = true;
this.defaultValues = {
amount : 10
};
this.valueRanges = {
amount : {min:0, max:30}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
amount *= 255/100;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var luma = inputData[pixel]*0.3 + inputData[pixel+1]*0.59 + inputData[pixel+2]*0.11;
var r,g,b;
r = g = b = luma;
r += 40;
g += 20;
b -= amount;
inputData[pixel] = r;
inputData[pixel+1] = g;
inputData[pixel+2] = b;
}
}
};
}
/**
* Sharpens the image slightly. For increased effect, apply the filter multiple times.
*/
function SharpenFilter(){
this.name = "Sharpen";
this.isDirAnimatable = true;
this.defaultValues = {
};
this.valueRanges = {
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var matrix = [ 0.0,-0.2, 0.0,
-0.2, 1.8,-0.2,
0.0, -0.2, 0.0];
filterUtils.convolveFilter(inputData,matrix,width,height);
};
}
/**
* Creates ripples on the image horizontally/vertically in a sine pattern.
*/
function SineRippleFilter(){
this.name = "Sine Ripples";
this.isDirAnimatable = false;
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 filterUtils = new FilterUtils();
this.filter = function (input, values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude;
var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude;
var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength;
var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength;
var transInverse = function(x,y,out){
var nx = y/xWavelength;
var ny = x/yWavelength;
var fx = Math.sin(nx);
var fy = Math.sin(ny);
out[0] = x + xAmplitude * fx;
out[1] = y + yAmplitude * fy;
};
filterUtils.transformFilter(inputData,transInverse,width,height);
};
}
/**
* Produces a solarization effect on the image.
*/
function SolarizeFilter(){
this.name = "Solarize";
this.isDirAnimatable = true;
this.defaultValues = {
};
this.valueRanges = {
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var table = [];
for(var i = 0; i < 256; i++){
var val = (i/255 > 0.5) ? 2*(i/255-0.5) : 2*(0.5-i/255);
table[i] = parseInt(255 * val,10);
}
filterUtils.tableFilter(inputData, table, width, height);
};
}
/**
* Generates a sparkle/sunburst effect on the image. CenterX and CenterY specify the
* position in terms of ratios of width and height.
*/
function SparkleFilter(){
this.name = "Sparkle";
this.isDirAnimatable = false;
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.0},
centerY : {min:0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var rays = (values.rays === undefined) ? this.defaultValues.rays : values.rays;
rays = parseInt(rays, 10);
var size = (values.size === undefined) ? this.defaultValues.size : values.size;
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
var randomness = (values.randomness === undefined) ? this.defaultValues.randomness : values.randomness;
var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX;
var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY;
var iCenterX = centerX * width;
var iCenterY = centerY * height;
var rayLengths = [];
for(var j = 0; j < rays; j++){
rayLengths[j]= size + randomness / 100 * size * filterUtils.gaussianRandom();
}
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var dx = x-iCenterX;
var dy = y-iCenterY;
var distance = dx*dx + dy*dy;
var angle = Math.atan2(dy,dx);
var d = (angle+Math.PI) / (Math.PI*2) * rays;
var i = parseInt(d,10);
var f = d - i;
if(size !== 0){
var length = filterUtils.linearInterpolate(f, rayLengths[i % rays], rayLengths[(i+1) % rays]);
var g = length*length / (distance+0.0001);
g = Math.pow(g, (100-amount) / 50);
f -= 0.5;
f = 1 - f*f;
f *= g;
}
f = filterUtils.clampPixel(f,0,1);
var mixedRGB = filterUtils.mixColors(f,[inputData[pixel],inputData[pixel+1],inputData[pixel+2],inputData[pixel+3]],[255,255,255,255]);
for(var k = 0; k < 3; k++){
inputData[pixel+k] = mixedRGB[k];
}
}
}
};
}
/**
* Smears out the image with square shapes to create a painting style effect.
* The mix values sets the intensity of the effect.
* NOTE: This filter can be very slow, especially at higher densities/sizes. Use with caution.
*/
function SquareSmearFilter(){
this.name = "Square Smear";
this.isDirAnimatable = false;
this.defaultValues = {
size : 4,
density : 0.5,
mix : 0.5
};
this.valueRanges = {
size : {min:1, max:10},
density : {min:0.0, max:1.0},
mix : {min:0.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
var k;
for(k = 0; k < inputData.length; k++){
outputData[k] = inputData[k];
}
if(values === undefined){ values = this.defaultValues; }
var size = (values.size === undefined) ? this.defaultValues.size : values.size;
if(size < 1){ size = 1;}
size = parseInt(size,10);
var density = (values.density === undefined) ? this.defaultValues.density : values.density;
var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix;
var radius = size+1;
var radius2 = radius*radius;
var numShapes = parseInt(2*density/30*width*height / 2,10);
for(var i = 0; i < numShapes; i++){
var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width;
var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height;
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]];
for(var x = sx - radius; x < sx + radius + 1; x++){
for(var y = sy - radius; y < sy + radius + 1; y++){
if (x >= 0 && x < width && y >= 0 && y < height) {
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]];
var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2);
for(k = 0; k < 3; k++){
outputData[(y*width+x)*4+k] = mixedRGB[k];
}
}
}
}
}
for(k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
}
/**
* Divides the colors into black and white following the treshold value. Brightnesses above the threshold
* sets the color to white while values below the threshold sets the color to black.
*/
function ThresholdFilter(){
this.name = "Black & White";
this.isDirAnimatable = true;
this.defaultValues = {
threshold : 127
};
this.valueRanges = {
threshold : {min:0, max:255}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var threshold = (values.threshold === undefined) ? this.defaultValues.threshold : values.threshold;
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var pixel = (y*width + x)*4;
var brightness = (inputData[pixel] + inputData[pixel+1] + inputData[pixel+2])/3;
var colorVal = 0;
if(brightness > threshold){
colorVal = 255;
}
inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = colorVal;
}
}
};
}
/**
* Creates ripples on the image horizontally/vertically in a sine pattern.
*/
function TriangleRippleFilter(){
this.name = "Triangle Ripples";
this.isDirAnimatable = false;
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 filterUtils = new FilterUtils();
this.filter = function (input, values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude;
var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude;
var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength;
var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength;
var transInverse = function(x,y,out){
var nx = y/xWavelength;
var ny = x/yWavelength;
var fx = filterUtils.triangle(nx,1);
var fy = filterUtils.triangle(ny,1);
out[0] = x + xAmplitude * fx;
out[1] = y + yAmplitude * fy;
};
filterUtils.transformFilter(inputData,transInverse,width,height);
};
}
/**
* Twists the image around a given center point. CenterX and CenterY specify the
* position in terms of ratios of width and height.
*/
function TwirlFilter(){
this.name = "Twirl";
this.isDirAnimatable = false;
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.0, max:1.0},
centerY : {min: 0.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function (input, values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle;
var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX;
var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY;
var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius;
var radius2 = radius*radius;
angle = angle/180 * Math.PI;
var iCenterX = width * centerX; var iCenterY = height * centerY;
var transInverse = function(x,y,out){
var dx = x-iCenterX;
var dy = y-iCenterY;
var distance = dx*dx + dy*dy;
if(distance > radius2){
out[0] = x;
out[1] = y;
} else {
distance = Math.sqrt(distance);
var a = Math.atan2(dy, dx) + angle * (radius-distance) / radius;
out[0] = iCenterX + distance*Math.cos(a);
out[1] = iCenterY + distance*Math.sin(a);
}
};
filterUtils.transformFilter(inputData,transInverse,width,height);
};
}
/**
* Creates a classical vignette effect on the image i.e. darkens the corners.
*/
function VignetteFilter(){
this.name = "Vignette";
this.isDirAnimatable = false;
this.defaultValues = {
amount : 0.3
};
this.valueRanges = {
amount : {min:0.0, max:1.0}
};
this.filter = function(input,values){
var width = input.width, height = input.height;
var inputData = input.data;
var outputData = [];
if(values === undefined){ values = this.defaultValues; }
var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount;
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var context = canvas.getContext("2d");
var gradient;
var radius = Math.sqrt( Math.pow(width/2, 2) + Math.pow(height/2, 2) );
context.putImageData(input,0,0);
context.globalCompositeOperation = 'source-over';
gradient = context.createRadialGradient(width/2, height/2, 0, width/2, height/2, radius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(0.5, 'rgba(0,0,0,0)');
gradient.addColorStop(1, 'rgba(0,0,0,' + amount + ')');
context.fillStyle = gradient;
context.fillRect(0, 0, width, height);
outputData = context.getImageData(0,0,width,height).data;
for(var k = 0; k < outputData.length; k++){
inputData[k] = outputData[k];
}
};
}
/**
* Produces a water ripple/waves on the image. CenterX and CenterY specify the
* position in terms of ratios of width and height.
*/
function WaterRippleFilter(){
this.name = "Water Ripples";
this.isDirAnimatable = false;
this.defaultValues = {
phase : 0,
radius : 50,
wavelength : 16,
amplitude : 10,
centerX : 0.5,
centerY : 0.5
};
this.valueRanges = {
phase : {min: 0, max: 100},
radius : {min: 1, max: 200},
wavelength : {min: 1, max: 100},
amplitude : {min: 1, max: 100},
centerX : {min: 0.0, max:1.0},
centerY : {min: 0.0, max:1.0}
};
var filterUtils = new FilterUtils();
this.filter = function (input, values){
var width = input.width, height = input.height;
var inputData = input.data;
if(values === undefined){ values = this.defaultValues; }
var wavelength = (values.wavelength === undefined) ? this.defaultValues.wavelength : values.wavelength;
var amplitude = (values.amplitude === undefined) ? this.defaultValues.amplitude : values.amplitude;
var phase = (values.phase === undefined) ? this.defaultValues.phase : values.phase;
var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX;
var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY;
var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius;
var radius2 = radius*radius;
var iCenterX = width * centerX; var iCenterY = height * centerY;
var transInverse = function(x,y,out){
var dx = x-iCenterX;
var dy = y-iCenterY;
var distance2 = dx*dx + dy*dy;
if(distance2 > radius2){
out[0] = x;
out[1] = y;
} else {
var distance = Math.sqrt(distance2);
var amount = amplitude * Math.sin(distance/wavelength * Math.PI * 2 - phase);
amount *= (radius-distance)/radius;
if(distance !== 0){
amount *= wavelength/distance;
}
out[0] = x + dx*amount;
out[1] = y + dy*amount;
}
};
filterUtils.transformFilter(inputData,transInverse,width,height);
};
}
/**
* A collection of all the filters.
*/
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(),
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(),
triangleripple : new TriangleRippleFilter(),
twirl : new TwirlFilter(),
vignette : new VignetteFilter(),
waterripple : new WaterRippleFilter()
};
/*
=========================================================================
Pasta
The code below was added by Richard Feldman
MIT LICENSED (http://www.opensource.org/licenses/mit-license.php)
Copyright (c) 2016, Richard Feldman
=========================================================================
*/
var statusListeners = [];
var addActivityListener = function(callback) {
statusListeners.push(callback);
};
var updatePastaActivity = function(status) {
statusListeners.forEach(function(callback) { callback(status); });
};
var activatePasta = function(canvas, opts) {
if (!canvas) {
return;
}
var url = opts.url;
var filterName = opts.filter;
var imageObj = new Image();
imageObj.setAttribute("crossorigin", true);
imageObj.onload = function() {
updatePastaActivity("Rendering a " + imageObj.width + "x" + imageObj.height + " image...");
var startTimeMs = new Date().getTime();
canvas.width = imageObj.width;
canvas.height = imageObj.height;
var context = canvas.getContext("2d");
context.drawImage(imageObj, 0, 0);
var data = context.getImageData(0, 0, canvas.width, canvas.height);
opts.filters.forEach(function(filter) {
var filterName = (typeof filter === "string" ? filter : filter.name).toLowerCase();
var filterOpts;
if (filterName === "ripple") {
filterName = "sineripple";
}
if (typeof filter === "object") {
if (filter.amount === 0) {
return;
}
var multiplier = Math.max(0.0, Math.min(1.0, filter.amount));
switch (filterName) {
case "sepia":
filterOpts = {amount: Math.round(multiplier * 30)};
break;
case "circlesmear":
filterOpts = {density: multiplier};
break;
case "noise":
filterOpts = {amount: Math.round(multiplier * 100)};
break;
case "oil":
filterOpts = {range: Math.round(multiplier * 5)};
break;
case "sineripple":
filterOpts = {xAmplitude: Math.round(multiplier * 30)};
break;
case "hue":
filterOpts = {amount: 1.0 - (multiplier * 2.0)};
break;
case "vignette":
filterOpts = {amount: multiplier};
break;
}
}
if (typeof JSManipulate[filterName] === "object") {
JSManipulate[filterName].filter(data, filterOpts);
} else {
console.warn("Unregoznied filter:", filterName);
}
});
context.putImageData(data, 0, 0);
updatePastaActivity("Applied some tasty filters in " + (new Date().getTime() - startTimeMs) + " ms.");
};
imageObj.src = url;
}
Pasta = { apply: activatePasta, addActivityListener: addActivityListener, version: 4.2 };