Added new filters from filter branch.

This commit is contained in:
ippo615
2013-11-26 16:26:03 -05:00
parent 31ea1f8905
commit 8e3b9580d3
15 changed files with 1405 additions and 628 deletions

View File

@@ -37,13 +37,23 @@ module.exports = function(grunt) {
'src/plugins/Label.js',
// filters
'src/filters/FilterWrapper.js',
'src/filters/Grayscale.js',
'src/filters/Brighten.js',
'src/filters/Invert.js',
'src/filters/Blur.js',
'src/filters/Mask.js',
'src/filters/ColorPack.js',
'src/filters/ConvolvePack.js'
'src/filters/ConvolvePack.js',
'src/filters/ColorStretch.js',
'src/filters/Flip.js',
'src/filters/Levels.js',
'src/filters/Mirror.js',
'src/filters/Noise.js',
'src/filters/Pixelate.js',
'src/filters/Polar.js',
'src/filters/Threshold.js'
];
// Project configuration.

View File

@@ -1,342 +1,318 @@
/*
the Gauss filter
master repo: https://github.com/pavelpower/kineticjsGaussFilter/
*/
(function() {
/*
StackBlur - a fast almost Gaussian Blur For Canvas
Version: 0.5
Author: Mario Klingemann
Contact: mario@quasimondo.com
Website: http://www.quasimondo.com/StackBlurForCanvas
Twitter: @quasimondo
In case you find this class useful - especially in commercial projects -
I am not totally unhappy for a small donation to my PayPal account
mario@quasimondo.de
Or support me on flattr:
https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript
Copyright (c) 2010 Mario Klingemann
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:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
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.
*/
function BlurStack() {
this.r = 0;
this.g = 0;
this.b = 0;
this.a = 0;
this.next = null;
}
var mul_table = [
512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
289,287,285,282,280,278,275,273,271,269,267,265,263,261,259];
var shg_table = [
9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ];
function filterGaussBlurRGBA( imageData, radius) {
var pixels = imageData.data,
width = imageData.width,
height = imageData.height;
var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum,
r_out_sum, g_out_sum, b_out_sum, a_out_sum,
r_in_sum, g_in_sum, b_in_sum, a_in_sum,
pr, pg, pb, pa, rbs;
var div = radius + radius + 1,
widthMinus1 = width - 1,
heightMinus1 = height - 1,
radiusPlus1 = radius + 1,
sumFactor = radiusPlus1 * ( radiusPlus1 + 1 ) / 2,
stackStart = new BlurStack(),
stackEnd = null,
stack = stackStart,
stackIn = null,
stackOut = null,
mul_sum = mul_table[radius],
shg_sum = shg_table[radius];
for ( i = 1; i < div; i++ ) {
stack = stack.next = new BlurStack();
if ( i == radiusPlus1 ) stackEnd = stack;
}
stack.next = stackStart;
yw = yi = 0;
for ( y = 0; y < height; y++ )
{
r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0;
r_out_sum = radiusPlus1 * ( pr = pixels[yi] );
g_out_sum = radiusPlus1 * ( pg = pixels[yi+1] );
b_out_sum = radiusPlus1 * ( pb = pixels[yi+2] );
a_out_sum = radiusPlus1 * ( pa = pixels[yi+3] );
r_sum += sumFactor * pr;
g_sum += sumFactor * pg;
b_sum += sumFactor * pb;
a_sum += sumFactor * pa;
stack = stackStart;
for( i = 0; i < radiusPlus1; i++ )
{
stack.r = pr;
stack.g = pg;
stack.b = pb;
stack.a = pa;
stack = stack.next;
}
for( i = 1; i < radiusPlus1; i++ )
{
p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 );
r_sum += ( stack.r = ( pr = pixels[p])) * ( rbs = radiusPlus1 - i );
g_sum += ( stack.g = ( pg = pixels[p+1])) * rbs;
b_sum += ( stack.b = ( pb = pixels[p+2])) * rbs;
a_sum += ( stack.a = ( pa = pixels[p+3])) * rbs;
r_in_sum += pr;
g_in_sum += pg;
b_in_sum += pb;
a_in_sum += pa;
stack = stack.next;
}
stackIn = stackStart;
stackOut = stackEnd;
for ( x = 0; x < width; x++ )
{
pixels[yi+3] = pa = (a_sum * mul_sum) >> shg_sum;
if ( pa !== 0 )
{
pa = 255 / pa;
pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa;
pixels[yi+1] = ((g_sum * mul_sum) >> shg_sum) * pa;
pixels[yi+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
} else {
pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0;
}
r_sum -= r_out_sum;
g_sum -= g_out_sum;
b_sum -= b_out_sum;
a_sum -= a_out_sum;
r_out_sum -= stackIn.r;
g_out_sum -= stackIn.g;
b_out_sum -= stackIn.b;
a_out_sum -= stackIn.a;
p = ( yw + ( ( p = x + radius + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2;
r_in_sum += ( stackIn.r = pixels[p]);
g_in_sum += ( stackIn.g = pixels[p+1]);
b_in_sum += ( stackIn.b = pixels[p+2]);
a_in_sum += ( stackIn.a = pixels[p+3]);
r_sum += r_in_sum;
g_sum += g_in_sum;
b_sum += b_in_sum;
a_sum += a_in_sum;
stackIn = stackIn.next;
r_out_sum += ( pr = stackOut.r );
g_out_sum += ( pg = stackOut.g );
b_out_sum += ( pb = stackOut.b );
a_out_sum += ( pa = stackOut.a );
r_in_sum -= pr;
g_in_sum -= pg;
b_in_sum -= pb;
a_in_sum -= pa;
stackOut = stackOut.next;
yi += 4;
}
yw += width;
}
for ( x = 0; x < width; x++ )
{
g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0;
yi = x << 2;
r_out_sum = radiusPlus1 * ( pr = pixels[yi]);
g_out_sum = radiusPlus1 * ( pg = pixels[yi+1]);
b_out_sum = radiusPlus1 * ( pb = pixels[yi+2]);
a_out_sum = radiusPlus1 * ( pa = pixels[yi+3]);
r_sum += sumFactor * pr;
g_sum += sumFactor * pg;
b_sum += sumFactor * pb;
a_sum += sumFactor * pa;
stack = stackStart;
for( i = 0; i < radiusPlus1; i++ )
{
stack.r = pr;
stack.g = pg;
stack.b = pb;
stack.a = pa;
stack = stack.next;
}
yp = width;
for( i = 1; i <= radius; i++ )
{
yi = ( yp + x ) << 2;
r_sum += ( stack.r = ( pr = pixels[yi])) * ( rbs = radiusPlus1 - i );
g_sum += ( stack.g = ( pg = pixels[yi+1])) * rbs;
b_sum += ( stack.b = ( pb = pixels[yi+2])) * rbs;
a_sum += ( stack.a = ( pa = pixels[yi+3])) * rbs;
r_in_sum += pr;
g_in_sum += pg;
b_in_sum += pb;
a_in_sum += pa;
stack = stack.next;
if( i < heightMinus1 )
{
yp += width;
}
}
yi = x;
stackIn = stackStart;
stackOut = stackEnd;
for ( y = 0; y < height; y++ )
{
p = yi << 2;
pixels[p+3] = pa = (a_sum * mul_sum) >> shg_sum;
if ( pa > 0 )
{
pa = 255 / pa;
pixels[p] = ((r_sum * mul_sum) >> shg_sum ) * pa;
pixels[p+1] = ((g_sum * mul_sum) >> shg_sum ) * pa;
pixels[p+2] = ((b_sum * mul_sum) >> shg_sum ) * pa;
} else {
pixels[p] = pixels[p+1] = pixels[p+2] = 0;
}
r_sum -= r_out_sum;
g_sum -= g_out_sum;
b_sum -= b_out_sum;
a_sum -= a_out_sum;
r_out_sum -= stackIn.r;
g_out_sum -= stackIn.g;
b_out_sum -= stackIn.b;
a_out_sum -= stackIn.a;
p = ( x + (( ( p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2;
r_sum += ( r_in_sum += ( stackIn.r = pixels[p]));
g_sum += ( g_in_sum += ( stackIn.g = pixels[p+1]));
b_sum += ( b_in_sum += ( stackIn.b = pixels[p+2]));
a_sum += ( a_in_sum += ( stackIn.a = pixels[p+3]));
stackIn = stackIn.next;
r_out_sum += ( pr = stackOut.r );
g_out_sum += ( pg = stackOut.g );
b_out_sum += ( pb = stackOut.b );
a_out_sum += ( pa = stackOut.a );
r_in_sum -= pr;
g_in_sum -= pg;
b_in_sum -= pb;
a_in_sum -= pa;
stackOut = stackOut.next;
yi += width;
}
}
}
/**
* Blur Filter
* @function
* @memberof Kinetic.Filters
* @param {Object} imageData
*/
Kinetic.Filters.Blur = function(imageData) {
var radius = this.getFilterRadius() | 0;
if (radius > 0) {
filterGaussBlurRGBA(imageData, radius);
}
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterRadius', 0);
})();
(function () {
/**
* BlurX Filter. Blurs the image in the X direction (horizontally). It
* performs w*h pixel reads, and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.blurWidth] how many neighboring pixels to will affect the
* blurred pixel, default: 5
*/
var BlurX = function(src,dst,opt){
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
// DONT USE: kSize = opt.blurWidth || 5;
// HINT: consider when (opt.blurWidth = 0)
var kSize = 5;
if( opt.hasOwnProperty('blurWidth') ){
kSize = Math.round( Math.abs(opt.blurWidth) )+1;
}
var kMid = Math.floor(kSize/2);
//console.info('Blur Width: '+kSize);
//console.info('Blur Middle: '+kMid);
var xEnd = xSize - kMid;
for (y = 0; y < ySize; y += 1) {
r=0;g=0;b=0;a=0;
for (x=-kMid; x<kMid; x+=1 ){
// Add the new
//if( y === 0 ){ console.info('Loading pixel at: '+((x+xSize)%xSize) ); }
i = (y * xSize + (x+xSize)%xSize ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
}
for (x = 0; x < xSize; x += 1) {
//if( y === 0 ){ console.info('Added pixel at: '+(x+kMid)); }
//if( y === 0 ){ console.info('Recorded pixel at: '+x); }
//if( y === 0 ){ console.info('Removed pixel at: '+((x-kMid+xSize)%xSize) ); }
// Add the new
i = (y * xSize + (x+kMid)%xSize ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
// Store the result
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
// Subtract the old
i = (y * xSize + (x-kMid+xSize)%xSize ) * 4;
r -= srcPixels[i+0];
g -= srcPixels[i+1];
b -= srcPixels[i+2];
a -= srcPixels[i+3];
}
}
};
var _BlurX = function(src,dst,opt){
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
var kSize = opt.blurWidth || 5,
kMid = Math.floor(kSize/2);
var xEnd = xSize - kMid;
for (y = 0; y < ySize; y += 1) {
// Let's pretend we have a row of pixels with values: [1,2,3,4,5,6,7,8]
// To blur them, we need to compute a moving average as we move across it
// Let's say the moving average will have 5 elements in it, that means
// we need a buffer of 5 elements but we're summing them everytime so
// we'll just store the sum.
// To start we add everything before the (5/2)=2.5, 2nd element
// Then we add the 3rd element
// 12345 12345 12345 12345 12345
// 123 1234 12345 2345 345
// 6 6A 6AF 6AFE 6AFEC
r=0;g=0;b=0;a=0;
for (x=0; x<kMid; x+=1 ){
// Add the new
i = (y * xSize + x ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
}
for (x=0, tmp=kMid; x<kMid; x+=1,tmp+=1 ){
// Add the new
i = (y * xSize + x+kMid ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
// Store it
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
}
for (x = kMid; x < xEnd; x += 1) {
// Add the new
i = (y * xSize + x+kMid ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
// Subtract the old
i = (y * xSize + x-kMid ) * 4;
r -= srcPixels[i+0];
g -= srcPixels[i+1];
b -= srcPixels[i+2];
a -= srcPixels[i+3];
// Store the result
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
}
for (x=xEnd; x<xSize; x+=1 ){
// Subtract the old
i = (y * xSize + x - kMid ) * 4;
r -= srcPixels[i+0];
g -= srcPixels[i+1];
b -= srcPixels[i+2];
a -= srcPixels[i+3];
// Store it
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
}
}
};
/**
* BlurY Filter. Blurs the image in the Y direction (vertically). It
* performs w*h pixel reads, and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.blurHeight] how many neighboring pixels to will affect the
* blurred pixel, default: 5
*/
var BlurY = function(src,dst,opt){
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
var kSize = 5;
if( opt.hasOwnProperty('blurHeight') ){
kSize = Math.round( Math.abs(opt.blurHeight) )+1;
}
var kMid = Math.floor(kSize/2);
var yEnd = ySize - kMid;
for (x = 0; x < xSize; x += 1) {
r=0;g=0;b=0;a=0;
for (y=-kMid; y<kMid; y+=1 ){
// Add the new
i = ((y+ySize)%ySize * xSize + x ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
}
for (y = 0; y < ySize; y += 1) {
// Add the new
i = ((y+kMid+ySize)%ySize * xSize + x ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
// Store the result
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
// Subtract the old
i = ((y-kMid+ySize)%ySize * xSize + x ) * 4;
r -= srcPixels[i+0];
g -= srcPixels[i+1];
b -= srcPixels[i+2];
a -= srcPixels[i+3];
}
}
};
var _BlurY = function(src,dst,opt){
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
var kSize = opt.blurHeight || 5,
kMid = Math.floor(kSize/2);
var yEnd = ySize - kMid;
for (x = 0; x < xSize; x += 1) {
r=0;g=0;b=0;a=0;
for (y=0; y<kMid; y+=1 ){
// Add the new
i = (y * xSize + x ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
}
for (y=0, tmp=kMid; y<kMid; y+=1,tmp+=1 ){
// Add the new
i = ((y+kMid) * xSize + x ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
// Store it
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
}
for (y = kMid; y < yEnd; y += 1) {
// Add the new
i = ((y+kMid) * xSize + x ) * 4;
r += srcPixels[i+0];
g += srcPixels[i+1];
b += srcPixels[i+2];
a += srcPixels[i+3];
// Subtract the old
i = ((y-kMid) * xSize + x ) * 4;
r -= srcPixels[i+0];
g -= srcPixels[i+1];
b -= srcPixels[i+2];
a -= srcPixels[i+3];
// Store the result
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
}
for (y=yEnd; y<ySize; y+=1 ){
// Subtract the old
i = ((y-kMid) * xSize + x ) * 4;
r -= srcPixels[i+0];
g -= srcPixels[i+1];
b -= srcPixels[i+2];
a -= srcPixels[i+3];
// Store it
i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
}
}
};
Kinetic.Filters.BlurX = Kinetic.Util._FilterWrapSingleBuffer(BlurX);
Kinetic.Filters.BlurY = Kinetic.Util._FilterWrapSingleBuffer(BlurY);
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterRadius', 5);
Kinetic.Filters.Blur = Kinetic.Util._FilterWrapSingleBuffer(function(src,dst,opt){
opt = opt || {
blurWidth: this.getFilterRadius(),
blurHeight: this.getFilterRadius()
};
Kinetic.Filters.BlurX(src,src,opt);
Kinetic.Filters.BlurY(src,dst,opt);
// Move the destination to the source
//Kinetic.Util._FilterReplaceBuffer(dst,src);
});
})();

View File

@@ -1,152 +1,228 @@
(function() {
var rgb_to_hsl = function(r,g,b){
// Input colors are in 0-255, calculations are between 0-1
r /= 255; g /= 255; b /= 255;
// http://en.wikipedia.org/wiki/HSL_and_HSV
// Convert to HSL
var max = Math.max(r,g,b),
min = Math.min(r,g,b),
chroma = max - min,
luminance = chroma / 2,
saturation = chroma / (1 - Math.abs(2*luminance-1)),
hue = 0;
if( max == r ){ hue = ((g-b)/chroma) % 6; }else
if( max == g ){ hue = (b-r)/chroma + 2; }else
if( max == b ){ hue = (r-g)/chroma + 4; }
return [(hue*60+360) % 360, saturation, luminance];
};
var pixelShiftHue = function(r,g,b,deg){
// Input colors are in 0-255, calculations are between 0-1
r /= 255; g /= 255; b /= 255;
// http://en.wikipedia.org/wiki/HSL_and_HSV
// Convert to HSL
var max = Math.max(r,g,b),
min = Math.min(r,g,b),
chroma = max - min,
luminance = chroma / 2,
saturation = chroma / (1 - Math.abs(2*luminance-1)),
hue = 0;
if( max == r ){ hue = ((g-b)/chroma) % 6; }else
if( max == g ){ hue = (b-r)/chroma + 2; }else
if( max == b ){ hue = (r-g)/chroma + 4; }
hue *= 60;
hue %= 360;
// Shift hue
hue += deg;
hue %= 360;
//hue /= 360;
// hsl to rgb:
hue /= 60;
var rR = 0, rG = 0, rB = 0,
//chroma = saturation*(1 - Math.abs(2*luminance - 1)),
tmp = chroma * (1-Math.abs(hue % 2 - 1)),
m = luminance - chroma/2;
if( 0 <= hue && hue < 1 ){ rR = chroma; rG = tmp; }else
if( 1 <= hue && hue < 2 ){ rG = chroma; rR = tmp; }else
if( 2 <= hue && hue < 3 ){ rG = chroma; rB = tmp; }else
if( 3 <= hue && hue < 4 ){ rB = chroma; rG = tmp; }else
if( 4 <= hue && hue < 5 ){ rB = chroma; rR = tmp; }else
if( 5 <= hue && hue < 6 ){ rR = chroma; rB = tmp; }
rR += m; rG += m; rB += m;
rR = (255*rR);
rG = (255*rG);
rB = (255*rB);
return [rR,rG,rB];
};
var shift_hue = function(imageData,deg){
var data = imageData.data,
pixel;
for(var i = 0; i < data.length; i += 4) {
pixel = pixelShiftHue(data[i+0],data[i+1],data[i+2],deg);
data[i+0] = pixel[0];
data[i+1] = pixel[1];
data[i+2] = pixel[2];
}
};
/**
* Shift Hue Filter.
* @function
* @memberof Kinetic.Filters
* @param {Object} imageData
* @author ippo615
*/
Kinetic.Filters.ShiftHue = function(imageData) {
shift_hue(imageData, this.getFilterHueShiftDeg() % 360 );
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterHueShiftDeg', 0);
/**
* get hue shift amount. The shift amount is a number between 0 and 360.
* @name getFilterBrightness
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* set hue shift amount
* @name setFilterBrightness
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* Colorize Filter.
* colorizes the image so that it is just varying shades of the specified color
* @function
* @memberof Kinetic.Filters
* @param {Object} imageData
* @author ippo615
*/
Kinetic.Filters.Colorize = function(imageData) {
var data = imageData.data;
// First we'll colorize it red, then shift by the hue specified
var color = this.getFilterColorizeColor(),
hsl = rgb_to_hsl(color[0],color[1],color[2]),
hue = hsl[0];
// Color it red, by removing green and blue
for(var i = 0; i < data.length; i += 4) {
data[i + 1] = 0;
data[i + 2] = 0;
}
// Shift by the hue
shift_hue(imageData,hue);
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterColorizeColor', [255,0,0] );
/**
* Gets the colorizing color.
* @name getFilterColorizeColor
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* Gets the colorizing color. Should be an array [r,g,b] ie [255,0,128].
* note that white [255,255,255] black [0,0,0] and greys [r,r,r] get treated as red.
* @name setFilterColorizeColor
* @method
* @memberof Kinetic.Image.prototype
*/
})();
(function () {
/**
* HSV Filter. Adjusts the hue, saturation and value of an image.
* Performs w*h pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.hue] amount to shift to the hue (in degrees)
* 0 represents no shift, while 360 is the maximum. Default: 0
* @param {Number} [opt.saturation] amount to scale the saturation.
* 1 means no change, 0.5 halves (more gray), 2.0 doubles
* (more color), etc... Default is 1.
* @param {Number} [opt.value] amount to scale the value.
* 1 means no change, 0.5 halves (darker), 2.0 doubles (lighter), etc..
* Default is 1.
*/
var HSV = function (src, dst, opt) {
var srcPixels = src.data,
dstPixels = dst.data,
nPixels = srcPixels.length,
i;
var v = opt.value || 1,
s = opt.saturation || 1,
h = Math.abs((opt.hue || 0) + 360) % 360;
// Basis for the technique used:
// http://beesbuzz.biz/code/hsv_color_transforms.php
// V is the value multiplier (1 for none, 2 for double, 0.5 for half)
// S is the saturation multiplier (1 for none, 2 for double, 0.5 for half)
// H is the hue shift in degrees (0 to 360)
// vsu = V*S*cos(H*PI/180);
// vsw = V*S*sin(H*PI/180);
//[ .299V+.701vsu+.168vsw .587V-.587vsu+.330vsw .114V-.114vsu-.497vsw ] [R]
//[ .299V-.299vsu-.328vsw .587V+.413vsu+.035vsw .114V-.114vsu+.292vsw ]*[G]
//[ .299V-.300vsu+1.25vsw .587V-.588vsu-1.05vsw .114V+.886vsu-.203vsw ] [B]
// Precompute the values in the matrix:
var vsu = v*s*Math.cos(h*Math.PI/180),
vsw = v*s*Math.sin(h*Math.PI/180);
// (result spot)(source spot)
var rr = 0.299*v+0.701*vsu+0.167*vsw,
rg = 0.587*v-0.587*vsu+0.330*vsw,
rb = 0.114*v-0.114*vsu-0.497*vsw;
var gr = 0.299*v-0.299*vsu-0.328*vsw,
gg = 0.587*v+0.413*vsu+0.035*vsw,
gb = 0.114*v-0.114*vsu+0.293*vsw;
var br = 0.299*v-0.300*vsu+1.250*vsw,
bg = 0.587*v-0.586*vsu-1.050*vsw,
bb = 0.114*v+0.886*vsu-0.200*vsw;
var r,g,b,a;
for (i = 0; i < nPixels; i += 4) {
r = srcPixels[i+0];
g = srcPixels[i+1];
b = srcPixels[i+2];
a = srcPixels[i+3];
dstPixels[i+0] = rr*r + rg*g + rb*b;
dstPixels[i+1] = gr*r + gg*g + gb*b;
dstPixels[i+2] = br*r + bg*g + bb*b;
dstPixels[i+3] = a; // alpha
}
};
Kinetic.Filters.HSV = Kinetic.Util._FilterWrapSingleBuffer(HSV);
})();
(function() {
var rgb_to_hsl = function(r,g,b){
// Input colors are in 0-255, calculations are between 0-1
r /= 255; g /= 255; b /= 255;
// http://en.wikipedia.org/wiki/HSL_and_HSV
// Convert to HSL
var max = Math.max(r,g,b),
min = Math.min(r,g,b),
chroma = max - min,
luminance = chroma / 2,
saturation = chroma / (1 - Math.abs(2*luminance-1)),
hue = 0;
if( max == r ){ hue = ((g-b)/chroma) % 6; }else
if( max == g ){ hue = (b-r)/chroma + 2; }else
if( max == b ){ hue = (r-g)/chroma + 4; }
return [(hue*60+360) % 360, saturation, luminance];
};
var pixelShiftHue = function(r,g,b,deg){
// Input colors are in 0-255, calculations are between 0-1
r /= 255; g /= 255; b /= 255;
// http://en.wikipedia.org/wiki/HSL_and_HSV
// Convert to HSL
var max = Math.max(r,g,b),
min = Math.min(r,g,b),
chroma = max - min,
luminance = chroma / 2,
saturation = chroma / (1 - Math.abs(2*luminance-1)),
hue = 0;
if( max == r ){ hue = ((g-b)/chroma) % 6; }else
if( max == g ){ hue = (b-r)/chroma + 2; }else
if( max == b ){ hue = (r-g)/chroma + 4; }
hue *= 60;
hue %= 360;
// Shift hue
hue += deg;
hue %= 360;
//hue /= 360;
// hsl to rgb:
hue /= 60;
var rR = 0, rG = 0, rB = 0,
//chroma = saturation*(1 - Math.abs(2*luminance - 1)),
tmp = chroma * (1-Math.abs(hue % 2 - 1)),
m = luminance - chroma/2;
if( 0 <= hue && hue < 1 ){ rR = chroma; rG = tmp; }else
if( 1 <= hue && hue < 2 ){ rG = chroma; rR = tmp; }else
if( 2 <= hue && hue < 3 ){ rG = chroma; rB = tmp; }else
if( 3 <= hue && hue < 4 ){ rB = chroma; rG = tmp; }else
if( 4 <= hue && hue < 5 ){ rB = chroma; rR = tmp; }else
if( 5 <= hue && hue < 6 ){ rR = chroma; rB = tmp; }
rR += m; rG += m; rB += m;
rR = (255*rR);
rG = (255*rG);
rB = (255*rB);
return [rR,rG,rB];
};
var shift_hue = function(imageData,deg){
var data = imageData.data,
pixel;
for(var i = 0; i < data.length; i += 4) {
pixel = pixelShiftHue(data[i+0],data[i+1],data[i+2],deg);
data[i+0] = pixel[0];
data[i+1] = pixel[1];
data[i+2] = pixel[2];
}
};
/**
* Shift Hue Filter.
* @function
* @memberof Kinetic.Filters
* @param {Object} imageData
* @author ippo615
*/
Kinetic.Filters.ShiftHue = function(imageData) {
shift_hue(imageData, this.getFilterHueShiftDeg() % 360 );
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterHueShiftDeg', 0);
/**
* get hue shift amount. The shift amount is a number between 0 and 360.
* @name getFilterBrightness
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* set hue shift amount
* @name setFilterBrightness
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* Colorize Filter.
* colorizes the image so that it is just varying shades of the specified color
* @function
* @memberof Kinetic.Filters
* @param {Object} imageData
* @author ippo615
*/
Kinetic.Filters.Colorize = function(imageData) {
var data = imageData.data;
// First we'll colorize it red, then shift by the hue specified
var color = this.getFilterColorizeColor(),
hsl = rgb_to_hsl(color[0],color[1],color[2]),
hue = hsl[0];
// Color it red, by removing green and blue
for(var i = 0; i < data.length; i += 4) {
data[i + 1] = 0;
data[i + 2] = 0;
}
// Shift by the hue
shift_hue(imageData,hue);
};
Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterColorizeColor', [255,0,0] );
/**
* Gets the colorizing color.
* @name getFilterColorizeColor
* @method
* @memberof Kinetic.Image.prototype
*/
/**
* Gets the colorizing color. Should be an array [r,g,b] ie [255,0,128].
* note that white [255,255,255] black [0,0,0] and greys [r,r,r] get treated as red.
* @name setFilterColorizeColor
* @method
* @memberof Kinetic.Image.prototype
*/
})();

View File

@@ -0,0 +1,95 @@
(function () {
function remap(fromValue, fromMin, fromMax, toMin, toMax) {
// Make sure min is less than max (covered outside)
/*
var swap;
if (fromMin > fromMax) {
swap = fromMax;
fromMin = fromMax;
fromMin = swap;
}
if (toMin > toMax) {
swap = toMax;
toMin = toMax;
toMin = swap;
}
*/
// Compute the range of the data
var fromRange = fromMax - fromMin;
var toRange = toMax - toMin;
// If either range is 0, then the value can only be mapped to 1 value
if (fromRange === 0) {
return toMin + toRange / 2;
}
if (toRange === 0) {
return toMin;
}
// (1) untranslate, (2) unscale, (3) rescale, (4) retranslate
var toValue = (fromValue - fromMin) / fromRange;
toValue = (toRange * toValue) + toMin;
return toValue;
}
/**
* ColorStretch Filter. Adjusts the colors so that they span the widest
* possible range (ie 0-255). Performs w*h pixel reads and w*h pixel
* writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt, There are no options for this filter
*/
var ColorStretch = function (src, dst, opt) {
var srcPixels = src.data,
dstPixels = dst.data,
nPixels = srcPixels.length,
i;
// 1st Pass - find the min and max for each channel:
var rMin = srcPixels[0], rMax = rMin, r,
gMin = srcPixels[1], gMax = gMin, g,
bMin = srcPixels[3], bMax = bMin, b,
aMin = srcPixels[4], aMax = aMin, a;
for (i = 0; i < nPixels; i += 4) {
r = srcPixels[i + 0];
if (r < rMin) { rMin = r; } else
if (r > rMax) { rMax = r; }
g = srcPixels[i + 1];
if (g < gMin) { gMin = g; } else
if (g > gMax) { gMax = g; }
b = srcPixels[i + 2];
if (b < bMin) { bMin = b; } else
if (b > bMax) { bMax = b; }
a = srcPixels[i + 3];
if (a < aMin) { aMin = a; } else
if (a > aMax) { aMax = a; }
}
// If there is only 1 level - don't remap
if( rMax === rMin ){ rMax = 255; rMin = 0; }
if( gMax === gMin ){ gMax = 255; gMin = 0; }
if( bMax === bMin ){ bMax = 255; bMin = 0; }
if( aMax === aMin ){ aMax = 255; aMin = 0; }
// Pass 2 - remap everything to fill the full range
for (i = 0; i < nPixels; i += 1) {
dstPixels[i + 0] = remap(srcPixels[i + 0], rMin, rMax, 0, 255);
dstPixels[i + 1] = remap(srcPixels[i + 1], gMin, gMax, 0, 255);
dstPixels[i + 2] = remap(srcPixels[i + 2], bMin, bMax, 0, 255);
dstPixels[i + 3] = remap(srcPixels[i + 3], aMin, aMax, 0, 255);
}
};
Kinetic.Filters.ColorStretch = Kinetic.Util._FilterWrapSingleBuffer(ColorStretch);
})();

View File

@@ -1,71 +1,5 @@
(function() {
var convolve_internal = function(imageData,matrix){
// Input data
var pixels = imageData.data,
imageSizeX = imageData.width,
imageSizeY = imageData.height,
nPixels = imageSizeX*imageSizeY,
pixel;
// An array for storing the result
var result = [];
result.length = imageSizeX*imageSizeY*4;
// Determine the size and demsionality of the matrix
// Note: it should be square and odd (3,5,7,9 etc...)
var matrixSizeX = matrix.length,
matrixSizeY = matrix[0].length,
matrixMidX = Math.floor(matrix.length/2),
matrixMidY = Math.floor(matrix[0].length/2);
// Accumlators and positions for iterating
var r,g,b,a, x,y, px,py, pos, i,j;
// Handle the 2D matrix
for( y=0; y<imageSizeY; y+=1){
for( x=0; x<imageSizeX; x+=1){
// Perform the convolution
r = 0; g = 0; b = 0; a = 0;
for( i=0; i<matrixSizeX; i+=1){
for( j=0; j<matrixSizeY; j+=1){
// tile the image to account for pixels past the
// edge (and make sure they are positive)
px = (x+i-matrixMidX) % imageSizeX;
px = (px>0)?px:-px;
py = (y+i-matrixMidY) % imageSizeY;
py = (py>0)?py:-py;
// get the pixel and convolve
pos = (py*imageSizeX + px)*4;
r += matrix[j][i]*pixels[pos+0];
g += matrix[j][i]*pixels[pos+1];
b += matrix[j][i]*pixels[pos+2];
//a += matrix[j][i]*pixels[pos+3];
}
}
// Store the result
pos = (y*imageSizeX+x)*4;
result[pos+0] = r;
result[pos+1] = g;
result[pos+2] = b;
//result[pos+3] = a;
}
}
// copy the result to the original canvas
var lastPos = nPixels*4;
for( pos=0; pos<lastPos; pos+=4 ){
pixels[pos+0] = result[pos+0];
pixels[pos+1] = result[pos+1];
pixels[pos+2] = result[pos+2];
//pixels[pos+3] = result[pos+3];
}
};
// Definition of a gaussian function
var gaussian = function(x,mean,sigma){
var dx = x - mean;
@@ -156,11 +90,7 @@
* @param {Object} imageData
* @author ippo615
*/
Kinetic.Filters.UnsharpMask = function(imageData) {
convolve_internal(imageData,
make_unsharp_kernel(5,this.getFilterAmount()/100)
);
};
/**
* Soft Blur Filter.
@@ -169,11 +99,6 @@
* @param {Object} imageData
* @author ippo615
*/
Kinetic.Filters.SoftBlur = function(imageData) {
convolve_internal(imageData,
make_soft_blur_kernel(5,this.getFilterAmount()/100)
);
};
/**
@@ -184,15 +109,6 @@
* @param {Object} imageData
* @author ippo615
*/
Kinetic.Filters.Edge = function(imageData) {
var s = this.getFilterAmount()/100;
if( s === 0 ){ return; }
convolve_internal(imageData,[
[ 0, -1*s, 0],
[-1*s,(1-s)+4*s,-1*s],
[ 0, -1*s, 0]
]);
};
/**
* Emboss Filter.
@@ -202,14 +118,84 @@
* @param {Object} imageData
* @author ippo615
*/
Kinetic.Filters.Emboss = function(imageData) {
var s = this.getFilterAmount()/100;
if( s === 0 ){ return; }
convolve_internal(imageData,[
[-1*s,-0.5*s, 0],
[-0.5*s,1+0.5*s, 0.5*s],
[ 0, 0.5*s, 1*s]
]);
};
var convolve = function (src, dst, opt) {
var xSize = src.width,
ySize = src.height,
srcPixels = src.data,
dstPixels = dst.data;
// Determine the size and demsionality of the matrix
// Note: it should be square and odd (3,5,7,9 etc...)
var matrix = opt.kernel;
var matrixSizeX = matrix.length,
matrixSizeY = matrix[0].length,
matrixMidX = Math.floor(matrix.length / 2),
matrixMidY = Math.floor(matrix[0].length / 2);
// Accumlators and positions for iterating
var r, g, b, a, x, y, px, py, pos, i, j;
for (y = 0; y < ySize; y += 1) {
for (x = 0; x < xSize; x += 1) {
// Perform the convolution
r = 0; g = 0; b = 0; a = 0;
for (i = 0; i < matrixSizeX; i += 1) {
for (j = 0; j < matrixSizeY; j += 1) {
// tile the image to account for pixels past the
// edge (and make sure they are positive)
px = (x + i - matrixMidX) % xSize;
px = (px > 0) ? px : -px;
py = (y + i - matrixMidY) % ySize;
py = (py > 0) ? py : -py;
// get the pixel and convolve
pos = (py * xSize + px) * 4;
r += matrix[j][i] * srcPixels[pos + 0];
g += matrix[j][i] * srcPixels[pos + 1];
b += matrix[j][i] * srcPixels[pos + 2];
//a += matrix[j][i]*srcPixels[pos+3];
}
}
// Store the result
pos = (y * xSize + x) * 4;
dstPixels[pos + 0] = r;
dstPixels[pos + 1] = g;
dstPixels[pos + 2] = b;
dstPixels[pos + 3] = srcPixels[pos + 3];
}
}
};
Kinetic.Filters.Emboss = Kinetic.Util._FilterWrapDoubleBuffer(function(src,dst,opt){
var s = this.getFilterAmount()/100;
convolve(src,dst,{kernel:[
[-1*s, -0.5*s, 0],
[-0.5*s,1+0.5*s, 0.5*s],
[ 0, 0.5*s, 1*s]
]});
});
Kinetic.Filters.Edge = Kinetic.Util._FilterWrapDoubleBuffer(function(src,dst,opt){
var s = this.getFilterAmount()/100;
convolve(src,dst,{kernel:[
[ 0, -1*s, 0],
[-1*s,(1-s)+4*s,-1*s],
[ 0, -1*s, 0]
]});
});
Kinetic.Filters.SoftBlur = Kinetic.Util._FilterWrapDoubleBuffer(function(src,dst,opt){
var s = this.getFilterAmount()/100;
convolve(src,dst,{kernel:make_soft_blur_kernel(5,s)});
});
Kinetic.Filters.UnsharpMask = Kinetic.Util._FilterWrapDoubleBuffer(function(src,dst,opt){
var s = this.getFilterAmount()/100;
convolve(src,dst,{kernel:make_unsharp_kernel(5,s)});
});
})();

View File

@@ -0,0 +1,50 @@
(function () {
Kinetic.Util._FilterWrapDoubleBuffer = function(filter,defaultOpt){
return function(src,dst,opt) {
// If no dst imageData is provided: make an imitation
// blank one, the same size as the src image data
var isOnlySrc = ! dst;
var data = [],
srcData = src.data,
l = srcData.length, i;
if( isOnlySrc ){
dst = {
width: src.width,
height: src.height
};
for( i=0; i<l; i+=1 ){
data.push(0);
}
dst.data = data;
}
filter.call(this, src, dst, opt || defaultOpt);
// Copy the dst to the src if this was called the old way
if( isOnlySrc ){
var dstData = dst.data;
for( i=0; i<l; i+=1 ){
srcData[i] = dstData[i];
}
}
};
};
Kinetic.Util._FilterWrapSingleBuffer = function(filter,defaultOpt){
return function(src,dst,opt) {
// If no dst imageData is provided: use the src imageData
filter.call(this, src, dst||src, opt || defaultOpt);
};
};
Kinetic.Util._FilterReplaceBuffer = function(src,dst){
var i, l = src.length;
for( i=0; i<l; ){
dst[i] = src[i]; i++;
dst[i] = src[i]; i++;
dst[i] = src[i]; i++;
dst[i] = src[i]; i++;
}
};
})();

65
src/filters/Flip.js Normal file
View File

@@ -0,0 +1,65 @@
(function () {
/**
* FlipX Filter. Flips the image horizontally so that the
* left-most pixels become the right-most pixels and vice-versa.
* Performs w*h pixel reads, 0 computations, and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt, There are no options for this filter
*/
var FlipX = function (src, dst, opt) {
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
i, m, x, y;
for (x = 0; x < xSize; x += 1) {
for (y = 0; y < ySize; y += 1) {
i = (y * xSize + x) * 4; // original
m = (y * xSize + (xSize-1) - x) * 4; // flipped
dstPixels[m + 0] = srcPixels[i + 0];
dstPixels[m + 1] = srcPixels[i + 1];
dstPixels[m + 2] = srcPixels[i + 2];
dstPixels[m + 3] = srcPixels[i + 3];
}
}
};
/**
* FlipY Filter. Flips the image vertically so that the top-most
* pixels become the bottom-most pixels and vice-versa.
* Performs w*h pixel reads, 0 computations, and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt, There are no options for this filter
*/
var FlipY = function (src, dst, opt) {
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
i, m, x, y;
for (x = 0; x < xSize; x += 1) {
for (y = 0; y < ySize; y += 1) {
i = (y * xSize + x) * 4; // original
m = ((ySize-1 - y) * xSize + x) * 4; // flipped
dstPixels[m + 0] = srcPixels[i + 0];
dstPixels[m + 1] = srcPixels[i + 1];
dstPixels[m + 2] = srcPixels[i + 2];
dstPixels[m + 3] = srcPixels[i + 3];
}
}
};
Kinetic.Filters.FlipX = Kinetic.Util._FilterWrapSingleBuffer(FlipX);
Kinetic.Filters.FlipY = Kinetic.Util._FilterWrapSingleBuffer(FlipY);
})();

View File

@@ -1,20 +1,30 @@
(function() {
/**
* Grayscale Filter
* @function
* @memberof Kinetic.Filters
* @param {Object} imageData
*/
Kinetic.Filters.Grayscale = function(imageData) {
var data = imageData.data;
for(var i = 0; i < data.length; i += 4) {
var brightness = 0.34 * data[i] + 0.5 * data[i + 1] + 0.16 * data[i + 2];
// red
data[i] = brightness;
// green
data[i + 1] = brightness;
// blue
data[i + 2] = brightness;
}
};
})();
(function () {
/**
* Grayscale Filter. Converts the image to shades of gray.
* Performs w*h pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt, There are no options for this filter
*/
var Grayscale = function (src, dst, opt) {
var srcPixels = src.data,
dstPixels = dst.data,
nPixels = srcPixels.length,
i, brightness;
for (i = 0; i < nPixels; i += 4) {
brightness = 0.34 * srcPixels[i] + 0.5 * srcPixels[i + 1] + 0.16 * srcPixels[i + 2];
dstPixels[i] = brightness; // r
dstPixels[i + 1] = brightness; // g
dstPixels[i + 2] = brightness; // b
dstPixels[i + 3] = srcPixels[i + 3]; // alpha
}
};
Kinetic.Filters.Grayscale = Kinetic.Util._FilterWrapSingleBuffer(Grayscale);
})();

View File

@@ -1,19 +1,30 @@
(function() {
/**
* Invert Filter
* @function
* @memberof Kinetic.Filters
* @param {Object} imageData
*/
Kinetic.Filters.Invert = function(imageData) {
var data = imageData.data;
for(var i = 0; i < data.length; i += 4) {
// red
data[i] = 255 - data[i];
// green
data[i + 1] = 255 - data[i + 1];
// blue
data[i + 2] = 255 - data[i + 2];
}
};
})();
(function () {
/**
* Invert Filter. Moves all color channels toward the opposite extreme
* ie 0 becomes 255, 10 becomes 245 etc... It does not alter the
* alpha channel.
* Performs w*h pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt, There are no options for this filter
*/
var Invert = function (src, dst, opt) {
var srcPixels = src.data,
dstPixels = dst.data,
nPixels = srcPixels.length,
i;
for (i = 0; i < nPixels; i += 4) {
dstPixels[i+0] = 255 - srcPixels[i+0]; // r
dstPixels[i+1] = 255 - srcPixels[i+1]; // g
dstPixels[i+2] = 255 - srcPixels[i+2]; // b
dstPixels[i+3] = srcPixels[i+3]; // copy alpha
}
};
Kinetic.Filters.Invert = Kinetic.Util._FilterWrapSingleBuffer(Invert);
})();

31
src/filters/Levels.js Normal file
View File

@@ -0,0 +1,31 @@
(function () {
/**
* Levels Filter. Adjusts the channels so that there are no more
* than n different values for that channel. This is also applied
* to the alpha channel.
* Performs w*h pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.quantizationLevels], the number of values allowed for each
* channel. Between 2 and 255. Default is 2.
*/
var Levels = function (src, dst, opt) {
var nLevels = opt.quantizationLevels || 2;
var srcPixels = src.data,
dstPixels = dst.data,
nPixels = srcPixels.length,
scale = (255 / nLevels),
i;
for (i = 0; i < nPixels; i += 1) {
dstPixels[i] = Math.floor(srcPixels[i] / scale) * scale;
}
};
Kinetic.Filters.Levels = Kinetic.Util._FilterWrapSingleBuffer(Levels);
})();

79
src/filters/Mirror.js Normal file
View File

@@ -0,0 +1,79 @@
(function () {
/**
* MirrorX Filter. Copies and flips the left half of the image
* to the right side of the image
* Performs w*h pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt, There are no options for this filter
*/
var MirrorX = function (src, dst, opt) {
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
xMid = Math.ceil(xSize / 2),
i, m, x, y;
for (x = 0; x <= xMid; x += 1) {
for (y = 0; y < ySize; y += 1) {
// copy the original
i = (y * xSize + x) * 4;
dstPixels[i + 0] = srcPixels[i + 0];
dstPixels[i + 1] = srcPixels[i + 1];
dstPixels[i + 2] = srcPixels[i + 2];
dstPixels[i + 3] = srcPixels[i + 3];
// copy the mirrored
m = (y * xSize + xSize - x) * 4;
dstPixels[m + 0] = srcPixels[i + 0];
dstPixels[m + 1] = srcPixels[i + 1];
dstPixels[m + 2] = srcPixels[i + 2];
dstPixels[m + 3] = srcPixels[i + 3];
}
}
};
/**
* MirrorY Filter. Copies and flips the top half of the image
* to the bottom of the image
* Performs w*h pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt, There are no options for this filter
*/
var MirrorY = function (src, dst, opt) {
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
yMid = Math.ceil(ySize / 2),
i, m, x, y;
for (x = 0; x < xSize; x += 1) {
for (y = 0; y <= yMid; y += 1) {
// copy the original
i = (y * xSize + x) * 4;
dstPixels[i + 0] = srcPixels[i + 0];
dstPixels[i + 1] = srcPixels[i + 1];
dstPixels[i + 2] = srcPixels[i + 2];
dstPixels[i + 3] = srcPixels[i + 3];
// copy the mirrored
m = ( (ySize-y) * xSize + x) * 4;
dstPixels[m + 0] = srcPixels[i + 0];
dstPixels[m + 1] = srcPixels[i + 1];
dstPixels[m + 2] = srcPixels[i + 2];
dstPixels[m + 3] = srcPixels[i + 3];
}
}
};
Kinetic.Filters.MirrorX = Kinetic.Util._FilterWrapSingleBuffer(MirrorX);
Kinetic.Filters.MirrorY = Kinetic.Util._FilterWrapSingleBuffer(MirrorY);
})();

44
src/filters/Noise.js Normal file
View File

@@ -0,0 +1,44 @@
(function () {
/**
* Noise Filter. Randomly adds or substracts to the color channels.
* Performs w*h pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.noiseAmount] The amount of noise to add. Between 0 and 255.
* Each channel of each pixel will change by a random amount
* between +- amount/2. Default is 32.
* @param {Number} [opt.affectAlpha] 1 to add noise to the alpha channel.
* Default is 0.
*/
var Noise = function (src, dst, opt) {
var amount = opt.noiseAmount || 32,
affectAlpha = opt.affectAlpha || 0;
var srcPixels = src.data,
dstPixels = dst.data,
nPixels = srcPixels.length,
half = amount / 2,
i;
if (affectAlpha) {
for (i = 0; i < nPixels; i += 4) {
dstPixels[i + 0] = srcPixels[i + 0] + half - 2 * half * Math.random();
dstPixels[i + 1] = srcPixels[i + 1] + half - 2 * half * Math.random();
dstPixels[i + 2] = srcPixels[i + 2] + half - 2 * half * Math.random();
dstPixels[i + 3] = srcPixels[i + 3] + half - 2 * half * Math.random();
}
} else {
for (i = 0; i < nPixels; i += 4) {
dstPixels[i + 0] = srcPixels[i + 0] + half - 2 * half * Math.random();
dstPixels[i + 1] = srcPixels[i + 1] + half - 2 * half * Math.random();
dstPixels[i + 2] = srcPixels[i + 2] + half - 2 * half * Math.random();
dstPixels[i + 3] = srcPixels[i + 3];
}
}
};
Kinetic.Filters.Noise = Kinetic.Util._FilterWrapSingleBuffer(Noise);
})();

90
src/filters/Pixelate.js Normal file
View File

@@ -0,0 +1,90 @@
(function () {
/**
* Pixelate Filter. Averages groups of pixels and redraws
* them as larger "pixels".
* Performs w*h pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.pixelWidth], The width (in pixels) of the
* new larger pixels, default is 8.
* @param {Number} [opt.pixelHeight], The height (in pixels) of the
* new larger pixels, default is 8.
*/
var Pixelate = function (src, dst, opt) {
var xBinSize = opt.pixelWidth || 8,
yBinSize = opt.pixelHeight || 8;
var xSize = src.width,
ySize = src.height,
srcPixels = src.data,
dstPixels = dst.data,
x, y, i;
var pixelsPerBin = xBinSize * yBinSize,
red, green, blue, alpha,
nBinsX = Math.ceil(xSize / xBinSize),
nBinsY = Math.ceil(ySize / yBinSize),
xBinStart, xBinEnd, yBinStart, yBinEnd,
xBin, yBin, pixelsInBin;
for (xBin = 0; xBin < nBinsX; xBin += 1) {
for (yBin = 0; yBin < nBinsY; yBin += 1) {
// Initialize the color accumlators to 0
red = 0;
green = 0;
blue = 0;
alpha = 0;
// Determine which pixels are included in this bin
xBinStart = xBin * xBinSize;
xBinEnd = xBinStart + xBinSize;
yBinStart = yBin * yBinSize;
yBinEnd = yBinStart + yBinSize;
// Add all of the pixels to this bin!
pixelsInBin = 0;
for (x = xBinStart; x < xBinEnd; x += 1) {
if( x >= xSize ){ continue; }
for (y = yBinStart; y < yBinEnd; y += 1) {
if( y >= ySize ){ continue; }
i = (xSize * y + x) * 4;
red += srcPixels[i + 0];
green += srcPixels[i + 1];
blue += srcPixels[i + 2];
alpha += srcPixels[i + 3];
pixelsInBin += 1;
}
}
// Make sure the channels are between 0-255
red = red / pixelsInBin;
green = green / pixelsInBin;
blue = blue / pixelsInBin;
alphas = alpha / pixelsInBin;
// Draw this bin
for (x = xBinStart; x < xBinEnd; x += 1) {
if( x >= xSize ){ continue; }
for (y = yBinStart; y < yBinEnd; y += 1) {
if( y >= ySize ){ continue; }
i = (xSize * y + x) * 4;
dstPixels[i + 0] = red;
dstPixels[i + 1] = green;
dstPixels[i + 2] = blue;
dstPixels[i + 3] = alpha;
}
}
}
}
};
Kinetic.Filters.Pixelate = Kinetic.Util._FilterWrapSingleBuffer(Pixelate);
})();

220
src/filters/Polar.js Normal file
View File

@@ -0,0 +1,220 @@
(function () {
/**
* ToPolar Filter. Converts image data to polar coordinates. Performs
* w*h*4 pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.polarCenterX] horizontal location for the center of the circle,
* default is in the middle
* @param {Number} [opt.polarCenterY] vertical location for the center of the circle,
* default is in the middle
*/
var ToPolar = function(src,dst,opt){
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
xMid = opt.polarCenterX || xSize/2,
yMid = opt.polarCenterY || ySize/2,
i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
// Find the largest radius
var rad, rMax = Math.sqrt( xMid*xMid + yMid*yMid );
x = xSize - xMid;
y = ySize - yMid;
rad = Math.sqrt( x*x + y*y );
rMax = (rad > rMax)?rad:rMax;
// We'll be uisng x as the radius, and y as the angle (theta=t)
var rSize = ySize,
tSize = xSize,
radius, theta;
// We want to cover all angles (0-360) and we need to convert to
// radians (*PI/180)
var conversion = 360/tSize*Math.PI/180, sin, cos;
var x1, x2, x1i, x2i, y1, y2, y1i, y2i, scale;
for( theta=0; theta<tSize; theta+=1 ){
sin = Math.sin(theta*conversion);
cos = Math.cos(theta*conversion);
for( radius=0; radius<rSize; radius+=1 ){
x = xMid+rMax*radius/rSize*cos;
y = yMid+rMax*radius/rSize*sin;
if( x <= 1 ){ x = 1; }
if( x >= xSize-0.5 ){ x = xSize-1; }
if( y <= 1 ){ y = 1; }
if( y >= ySize-0.5 ){ y = ySize-1; }
// Interpolate x and y by going +-0.5 around the pixel's central point
// this gives us the 4 nearest pixels to our 1x1 non-aligned pixel.
// We average the vaules of those pixels based on how much of our
// non-aligned pixel overlaps each of them.
x1 = x - 0.5;
x2 = x + 0.5;
x1i = Math.floor(x1);
x2i = Math.floor(x2);
y1 = y - 0.5;
y2 = y + 0.5;
y1i = Math.floor(y1);
y2i = Math.floor(y2);
scale = (1-(x1-x1i))*(1-(y1-y1i));
i = (y1i*xSize + x1i)*4;
r = srcPixels[i+0]*scale;
g = srcPixels[i+1]*scale;
b = srcPixels[i+2]*scale;
a = srcPixels[i+3]*scale;
scale = (1-(x1-x1i))*(y2-y2i);
i = (y2i*xSize + x1i)*4;
r += srcPixels[i+0]*scale;
g += srcPixels[i+1]*scale;
b += srcPixels[i+2]*scale;
a += srcPixels[i+3]*scale;
scale = (x2-x2i)*(y2-y2i);
i = (y2i*xSize + x2i)*4;
r += srcPixels[i+0]*scale;
g += srcPixels[i+1]*scale;
b += srcPixels[i+2]*scale;
a += srcPixels[i+3]*scale;
scale = (x2-x2i)*(1-(y1-y1i));
i = (y1i*xSize + x2i)*4;
r += srcPixels[i+0]*scale;
g += srcPixels[i+1]*scale;
b += srcPixels[i+2]*scale;
a += srcPixels[i+3]*scale;
// Store it
//i = (theta * xSize + radius) * 4;
i = (theta + radius*xSize) * 4;
dstPixels[i+0] = r;
dstPixels[i+1] = g;
dstPixels[i+2] = b;
dstPixels[i+3] = a;
}
}
};
/**
* FromPolar Filter. Converts image data from polar coordinates back to rectangular.
* Performs w*h*4 pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.polarCenterX] horizontal location for the center of the circle,
* default is in the middle
* @param {Number} [opt.polarCenterY] vertical location for the center of the circle,
* default is in the middle
* @param {Number} [opt.polarRotation] amount to rotate the image counterclockwis,
* 0 is no rotation, 360 degrees is a full rotation
*/
var FromPolar = function(src,dst,opt){
var srcPixels = src.data,
dstPixels = dst.data,
xSize = src.width,
ySize = src.height,
xMid = opt.polarCenterX || xSize/2,
yMid = opt.polarCenterY || ySize/2,
i, m, x, y, dx, dy, k, tmp, r=0,g=0,b=0,a=0;
// Find the largest radius
var rad, rMax = Math.sqrt( xMid*xMid + yMid*yMid );
x = xSize - xMid;
y = ySize - yMid;
rad = Math.sqrt( x*x + y*y );
rMax = (rad > rMax)?rad:rMax;
// We'll be uisng x as the radius, and y as the angle (theta=t)
var rSize = ySize,
tSize = xSize,
radius, theta,
phaseShift = opt.polarRotation || 0;
// We need to convert to degrees and we need to make sure
// it's between (0-360)
// var conversion = tSize/360*180/Math.PI;
var conversion = tSize/360*180/Math.PI;
var x1, x2, x1i, x2i, y1, y2, y1i, y2i, scale;
for( x=0; x<xSize; x+=1 ){
for( y=0; y<ySize; y+=1 ){
dx = x - xMid;
dy = y - yMid;
radius = Math.sqrt(dx*dx + dy*dy)*rSize/rMax;
theta = (Math.atan2(dy,dx)*180/Math.PI + 360 + phaseShift)%360;
theta = theta*tSize/360;
// Interpolate x and y by going +-0.5 around the pixel's central point
// this gives us the 4 nearest pixels to our 1x1 non-aligned pixel.
// We average the vaules of those pixels based on how much of our
// non-aligned pixel overlaps each of them.
x1 = theta - 0.5;
x2 = theta + 0.5;
x1i = Math.floor(x1);
x2i = Math.floor(x2);
y1 = radius - 0.5;
y2 = radius + 0.5;
y1i = Math.floor(y1);
y2i = Math.floor(y2);
scale = (1-(x1-x1i))*(1-(y1-y1i));
i = (y1i*xSize + x1i)*4;
r = srcPixels[i+0]*scale;
g = srcPixels[i+1]*scale;
b = srcPixels[i+2]*scale;
a = srcPixels[i+3]*scale;
scale = (1-(x1-x1i))*(y2-y2i);
i = (y2i*xSize + x1i)*4;
r += srcPixels[i+0]*scale;
g += srcPixels[i+1]*scale;
b += srcPixels[i+2]*scale;
a += srcPixels[i+3]*scale;
scale = (x2-x2i)*(y2-y2i);
i = (y2i*xSize + x2i)*4;
r += srcPixels[i+0]*scale;
g += srcPixels[i+1]*scale;
b += srcPixels[i+2]*scale;
a += srcPixels[i+3]*scale;
scale = (x2-x2i)*(1-(y1-y1i));
i = (y1i*xSize + x2i)*4;
r += srcPixels[i+0]*scale;
g += srcPixels[i+1]*scale;
b += srcPixels[i+2]*scale;
a += srcPixels[i+3]*scale;
// Store it
i = (y*xSize + x)*4;
dstPixels[i+0] = r;
dstPixels[i+1] = g;
dstPixels[i+2] = b;
dstPixels[i+3] = a;
}
}
};
Kinetic.Filters.ToPolar = Kinetic.Util._FilterWrapDoubleBuffer(ToPolar);
Kinetic.Filters.FromPolar = Kinetic.Util._FilterWrapDoubleBuffer(FromPolar);
})();

34
src/filters/Threshold.js Normal file
View File

@@ -0,0 +1,34 @@
(function () {
/**
* Threshold Filter. Pushes any value above the mid point to
* the max and any value below the mid point to the min.
* This affects the alpha channel.
* Performs w*h pixel reads and w*h pixel writes.
* @function
* @author ippo615
* @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
* @param {ImageData} dst, the destination image data (where it will be saved)
* @param {Object} opt
* @param {Number} [opt.thresholdLevel=128] the value which divides
* the channel value into 2 groups (between 0 and 255)
*/
var Threshold = function (src, dst, opt) {
var level = opt.thresholdLevel || 128;
var srcPixels = src.data,
dstPixels = dst.data,
nPixels = srcPixels.length,
i;
for (i = 0; i < nPixels; i += 1) {
if (srcPixels[i] < level) {
dstPixels[i] = 0;
} else {
dstPixels[i] = 255;
}
}
};
Kinetic.Filters.Threshold = Kinetic.Util._FilterWrapSingleBuffer(Threshold);
})();