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', 'src/plugins/Label.js',
// filters // filters
'src/filters/FilterWrapper.js',
'src/filters/Grayscale.js', 'src/filters/Grayscale.js',
'src/filters/Brighten.js', 'src/filters/Brighten.js',
'src/filters/Invert.js', 'src/filters/Invert.js',
'src/filters/Blur.js', 'src/filters/Blur.js',
'src/filters/Mask.js', 'src/filters/Mask.js',
'src/filters/ColorPack.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. // Project configuration.

View File

@@ -1,342 +1,318 @@
/* (function () {
the Gauss filter
master repo: https://github.com/pavelpower/kineticjsGaussFilter/ /**
*/ * BlurX Filter. Blurs the image in the X direction (horizontally). It
(function() { * performs w*h pixel reads, and w*h pixel writes.
/* * @function
* @author ippo615
StackBlur - a fast almost Gaussian Blur For Canvas * @memberof Kinetic.Filters
* @param {ImageData} src, the source image data (what will be transformed)
Version: 0.5 * @param {ImageData} dst, the destination image data (where it will be saved)
Author: Mario Klingemann * @param {Object} opt
Contact: mario@quasimondo.com * @param {Number} [opt.blurWidth] how many neighboring pixels to will affect the
Website: http://www.quasimondo.com/StackBlurForCanvas * blurred pixel, default: 5
Twitter: @quasimondo */
In case you find this class useful - especially in commercial projects - var BlurX = function(src,dst,opt){
I am not totally unhappy for a small donation to my PayPal account
mario@quasimondo.de var srcPixels = src.data,
dstPixels = dst.data,
Or support me on flattr: xSize = src.width,
https://flattr.com/thing/72791/StackBlur-a-fast-almost-Gaussian-Blur-Effect-for-CanvasJavascript ySize = src.height,
i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
Copyright (c) 2010 Mario Klingemann
// DONT USE: kSize = opt.blurWidth || 5;
Permission is hereby granted, free of charge, to any person // HINT: consider when (opt.blurWidth = 0)
obtaining a copy of this software and associated documentation var kSize = 5;
files (the "Software"), to deal in the Software without if( opt.hasOwnProperty('blurWidth') ){
restriction, including without limitation the rights to use, kSize = Math.round( Math.abs(opt.blurWidth) )+1;
copy, modify, merge, publish, distribute, sublicense, and/or sell }
copies of the Software, and to permit persons to whom the var kMid = Math.floor(kSize/2);
Software is furnished to do so, subject to the following
conditions: //console.info('Blur Width: '+kSize);
//console.info('Blur Middle: '+kMid);
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software. var xEnd = xSize - kMid;
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, for (y = 0; y < ySize; y += 1) {
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES r=0;g=0;b=0;a=0;
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND for (x=-kMid; x<kMid; x+=1 ){
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // Add the new
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, //if( y === 0 ){ console.info('Loading pixel at: '+((x+xSize)%xSize) ); }
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING i = (y * xSize + (x+xSize)%xSize ) * 4;
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR r += srcPixels[i+0];
OTHER DEALINGS IN THE SOFTWARE. g += srcPixels[i+1];
*/ b += srcPixels[i+2];
a += srcPixels[i+3];
function BlurStack() { }
this.r = 0;
this.g = 0; for (x = 0; x < xSize; x += 1) {
this.b = 0; //if( y === 0 ){ console.info('Added pixel at: '+(x+kMid)); }
this.a = 0; //if( y === 0 ){ console.info('Recorded pixel at: '+x); }
this.next = null; //if( y === 0 ){ console.info('Removed pixel at: '+((x-kMid+xSize)%xSize) ); }
} // Add the new
i = (y * xSize + (x+kMid)%xSize ) * 4;
var mul_table = [ r += srcPixels[i+0];
512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512, g += srcPixels[i+1];
454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512, b += srcPixels[i+2];
482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456, a += srcPixels[i+3];
437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512, // Store the result
497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328, i = (y * xSize + x) * 4;
320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456, dstPixels[i+0] = r/kSize;
446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335, dstPixels[i+1] = g/kSize;
329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512, dstPixels[i+2] = b/kSize;
505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405, dstPixels[i+3] = a/kSize;
399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328, // Subtract the old
324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271, i = (y * xSize + (x-kMid+xSize)%xSize ) * 4;
268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456, r -= srcPixels[i+0];
451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388, g -= srcPixels[i+1];
385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335, b -= srcPixels[i+2];
332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292, a -= srcPixels[i+3];
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, var _BlurX = function(src,dst,opt){
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, var srcPixels = src.data,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, dstPixels = dst.data,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, xSize = src.width,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, ySize = src.height,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
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, var kSize = opt.blurWidth || 5,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, kMid = Math.floor(kSize/2);
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, var xEnd = xSize - kMid;
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ];
for (y = 0; y < ySize; y += 1) {
function filterGaussBlurRGBA( imageData, radius) { // 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
var pixels = imageData.data, // Let's say the moving average will have 5 elements in it, that means
width = imageData.width, // we need a buffer of 5 elements but we're summing them everytime so
height = imageData.height; // we'll just store the sum.
// To start we add everything before the (5/2)=2.5, 2nd element
var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, // Then we add the 3rd element
r_out_sum, g_out_sum, b_out_sum, a_out_sum, // 12345 12345 12345 12345 12345
r_in_sum, g_in_sum, b_in_sum, a_in_sum, // 123 1234 12345 2345 345
pr, pg, pb, pa, rbs; // 6 6A 6AF 6AFE 6AFEC
r=0;g=0;b=0;a=0;
var div = radius + radius + 1, for (x=0; x<kMid; x+=1 ){
widthMinus1 = width - 1, // Add the new
heightMinus1 = height - 1, i = (y * xSize + x ) * 4;
radiusPlus1 = radius + 1, r += srcPixels[i+0];
sumFactor = radiusPlus1 * ( radiusPlus1 + 1 ) / 2, g += srcPixels[i+1];
stackStart = new BlurStack(), b += srcPixels[i+2];
stackEnd = null, a += srcPixels[i+3];
stack = stackStart, }
stackIn = null, for (x=0, tmp=kMid; x<kMid; x+=1,tmp+=1 ){
stackOut = null, // Add the new
mul_sum = mul_table[radius], i = (y * xSize + x+kMid ) * 4;
shg_sum = shg_table[radius]; r += srcPixels[i+0];
g += srcPixels[i+1];
for ( i = 1; i < div; i++ ) { b += srcPixels[i+2];
stack = stack.next = new BlurStack(); a += srcPixels[i+3];
if ( i == radiusPlus1 ) stackEnd = stack; // Store it
} i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
stack.next = stackStart; dstPixels[i+1] = g/kSize;
dstPixels[i+2] = b/kSize;
yw = yi = 0; dstPixels[i+3] = a/kSize;
}
for ( y = 0; y < height; y++ ) for (x = kMid; x < xEnd; x += 1) {
{ // Add the new
r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0; i = (y * xSize + x+kMid ) * 4;
r += srcPixels[i+0];
r_out_sum = radiusPlus1 * ( pr = pixels[yi] ); g += srcPixels[i+1];
g_out_sum = radiusPlus1 * ( pg = pixels[yi+1] ); b += srcPixels[i+2];
b_out_sum = radiusPlus1 * ( pb = pixels[yi+2] ); a += srcPixels[i+3];
a_out_sum = radiusPlus1 * ( pa = pixels[yi+3] ); // Subtract the old
i = (y * xSize + x-kMid ) * 4;
r_sum += sumFactor * pr; r -= srcPixels[i+0];
g_sum += sumFactor * pg; g -= srcPixels[i+1];
b_sum += sumFactor * pb; b -= srcPixels[i+2];
a_sum += sumFactor * pa; a -= srcPixels[i+3];
// Store the result
stack = stackStart; i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
for( i = 0; i < radiusPlus1; i++ ) dstPixels[i+1] = g/kSize;
{ dstPixels[i+2] = b/kSize;
stack.r = pr; dstPixels[i+3] = a/kSize;
stack.g = pg; }
stack.b = pb; for (x=xEnd; x<xSize; x+=1 ){
stack.a = pa; // Subtract the old
stack = stack.next; i = (y * xSize + x - kMid ) * 4;
} r -= srcPixels[i+0];
g -= srcPixels[i+1];
for( i = 1; i < radiusPlus1; i++ ) b -= srcPixels[i+2];
{ a -= srcPixels[i+3];
p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 ); // Store it
r_sum += ( stack.r = ( pr = pixels[p])) * ( rbs = radiusPlus1 - i ); i = (y * xSize + x) * 4;
g_sum += ( stack.g = ( pg = pixels[p+1])) * rbs; dstPixels[i+0] = r/kSize;
b_sum += ( stack.b = ( pb = pixels[p+2])) * rbs; dstPixels[i+1] = g/kSize;
a_sum += ( stack.a = ( pa = pixels[p+3])) * rbs; dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
r_in_sum += pr; }
g_in_sum += pg; }
b_in_sum += pb;
a_in_sum += pa; };
stack = stack.next; /**
} * BlurY Filter. Blurs the image in the Y direction (vertically). It
* performs w*h pixel reads, and w*h pixel writes.
* @function
stackIn = stackStart; * @author ippo615
stackOut = stackEnd; * @memberof Kinetic.Filters
for ( x = 0; x < width; x++ ) * @param {ImageData} src, the source image data (what will be transformed)
{ * @param {ImageData} dst, the destination image data (where it will be saved)
pixels[yi+3] = pa = (a_sum * mul_sum) >> shg_sum; * @param {Object} opt
if ( pa !== 0 ) * @param {Number} [opt.blurHeight] how many neighboring pixels to will affect the
{ * blurred pixel, default: 5
pa = 255 / pa; */
pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa;
pixels[yi+1] = ((g_sum * mul_sum) >> shg_sum) * pa; var BlurY = function(src,dst,opt){
pixels[yi+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
} else { var srcPixels = src.data,
pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0; dstPixels = dst.data,
} xSize = src.width,
ySize = src.height,
r_sum -= r_out_sum; i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
g_sum -= g_out_sum;
b_sum -= b_out_sum; var kSize = 5;
a_sum -= a_out_sum; if( opt.hasOwnProperty('blurHeight') ){
kSize = Math.round( Math.abs(opt.blurHeight) )+1;
r_out_sum -= stackIn.r; }
g_out_sum -= stackIn.g; var kMid = Math.floor(kSize/2);
b_out_sum -= stackIn.b;
a_out_sum -= stackIn.a; var yEnd = ySize - kMid;
p = ( yw + ( ( p = x + radius + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2; for (x = 0; x < xSize; x += 1) {
r=0;g=0;b=0;a=0;
r_in_sum += ( stackIn.r = pixels[p]); for (y=-kMid; y<kMid; y+=1 ){
g_in_sum += ( stackIn.g = pixels[p+1]); // Add the new
b_in_sum += ( stackIn.b = pixels[p+2]); i = ((y+ySize)%ySize * xSize + x ) * 4;
a_in_sum += ( stackIn.a = pixels[p+3]); r += srcPixels[i+0];
g += srcPixels[i+1];
r_sum += r_in_sum; b += srcPixels[i+2];
g_sum += g_in_sum; a += srcPixels[i+3];
b_sum += b_in_sum; }
a_sum += a_in_sum;
for (y = 0; y < ySize; y += 1) {
stackIn = stackIn.next; // Add the new
i = ((y+kMid+ySize)%ySize * xSize + x ) * 4;
r_out_sum += ( pr = stackOut.r ); r += srcPixels[i+0];
g_out_sum += ( pg = stackOut.g ); g += srcPixels[i+1];
b_out_sum += ( pb = stackOut.b ); b += srcPixels[i+2];
a_out_sum += ( pa = stackOut.a ); a += srcPixels[i+3];
// Store the result
r_in_sum -= pr; i = (y * xSize + x) * 4;
g_in_sum -= pg; dstPixels[i+0] = r/kSize;
b_in_sum -= pb; dstPixels[i+1] = g/kSize;
a_in_sum -= pa; dstPixels[i+2] = b/kSize;
dstPixels[i+3] = a/kSize;
stackOut = stackOut.next; // Subtract the old
i = ((y-kMid+ySize)%ySize * xSize + x ) * 4;
yi += 4; r -= srcPixels[i+0];
} g -= srcPixels[i+1];
yw += width; b -= srcPixels[i+2];
} a -= srcPixels[i+3];
}
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; var _BlurY = function(src,dst,opt){
r_out_sum = radiusPlus1 * ( pr = pixels[yi]);
g_out_sum = radiusPlus1 * ( pg = pixels[yi+1]); var srcPixels = src.data,
b_out_sum = radiusPlus1 * ( pb = pixels[yi+2]); dstPixels = dst.data,
a_out_sum = radiusPlus1 * ( pa = pixels[yi+3]); xSize = src.width,
ySize = src.height,
r_sum += sumFactor * pr; i, m, x, y, k, tmp, r=0,g=0,b=0,a=0;
g_sum += sumFactor * pg;
b_sum += sumFactor * pb; var kSize = opt.blurHeight || 5,
a_sum += sumFactor * pa; kMid = Math.floor(kSize/2);
stack = stackStart; var yEnd = ySize - kMid;
for( i = 0; i < radiusPlus1; i++ ) for (x = 0; x < xSize; x += 1) {
{ r=0;g=0;b=0;a=0;
stack.r = pr; for (y=0; y<kMid; y+=1 ){
stack.g = pg; // Add the new
stack.b = pb; i = (y * xSize + x ) * 4;
stack.a = pa; r += srcPixels[i+0];
stack = stack.next; g += srcPixels[i+1];
} b += srcPixels[i+2];
a += srcPixels[i+3];
yp = width; }
for (y=0, tmp=kMid; y<kMid; y+=1,tmp+=1 ){
for( i = 1; i <= radius; i++ ) // Add the new
{ i = ((y+kMid) * xSize + x ) * 4;
yi = ( yp + x ) << 2; r += srcPixels[i+0];
g += srcPixels[i+1];
r_sum += ( stack.r = ( pr = pixels[yi])) * ( rbs = radiusPlus1 - i ); b += srcPixels[i+2];
g_sum += ( stack.g = ( pg = pixels[yi+1])) * rbs; a += srcPixels[i+3];
b_sum += ( stack.b = ( pb = pixels[yi+2])) * rbs; // Store it
a_sum += ( stack.a = ( pa = pixels[yi+3])) * rbs; i = (y * xSize + x) * 4;
dstPixels[i+0] = r/kSize;
r_in_sum += pr; dstPixels[i+1] = g/kSize;
g_in_sum += pg; dstPixels[i+2] = b/kSize;
b_in_sum += pb; dstPixels[i+3] = a/kSize;
a_in_sum += pa; }
for (y = kMid; y < yEnd; y += 1) {
stack = stack.next; // Add the new
i = ((y+kMid) * xSize + x ) * 4;
if( i < heightMinus1 ) r += srcPixels[i+0];
{ g += srcPixels[i+1];
yp += width; b += srcPixels[i+2];
} a += srcPixels[i+3];
} // Subtract the old
i = ((y-kMid) * xSize + x ) * 4;
yi = x; r -= srcPixels[i+0];
stackIn = stackStart; g -= srcPixels[i+1];
stackOut = stackEnd; b -= srcPixels[i+2];
for ( y = 0; y < height; y++ ) a -= srcPixels[i+3];
{ // Store the result
p = yi << 2; i = (y * xSize + x) * 4;
pixels[p+3] = pa = (a_sum * mul_sum) >> shg_sum; dstPixels[i+0] = r/kSize;
if ( pa > 0 ) dstPixels[i+1] = g/kSize;
{ dstPixels[i+2] = b/kSize;
pa = 255 / pa; dstPixels[i+3] = a/kSize;
pixels[p] = ((r_sum * mul_sum) >> shg_sum ) * pa; }
pixels[p+1] = ((g_sum * mul_sum) >> shg_sum ) * pa; for (y=yEnd; y<ySize; y+=1 ){
pixels[p+2] = ((b_sum * mul_sum) >> shg_sum ) * pa; // Subtract the old
} else { i = ((y-kMid) * xSize + x ) * 4;
pixels[p] = pixels[p+1] = pixels[p+2] = 0; r -= srcPixels[i+0];
} g -= srcPixels[i+1];
b -= srcPixels[i+2];
r_sum -= r_out_sum; a -= srcPixels[i+3];
g_sum -= g_out_sum; // Store it
b_sum -= b_out_sum; i = (y * xSize + x) * 4;
a_sum -= a_out_sum; dstPixels[i+0] = r/kSize;
dstPixels[i+1] = g/kSize;
r_out_sum -= stackIn.r; dstPixels[i+2] = b/kSize;
g_out_sum -= stackIn.g; dstPixels[i+3] = a/kSize;
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])); Kinetic.Filters.BlurX = Kinetic.Util._FilterWrapSingleBuffer(BlurX);
g_sum += ( g_in_sum += ( stackIn.g = pixels[p+1])); Kinetic.Filters.BlurY = Kinetic.Util._FilterWrapSingleBuffer(BlurY);
b_sum += ( b_in_sum += ( stackIn.b = pixels[p+2]));
a_sum += ( a_in_sum += ( stackIn.a = pixels[p+3])); Kinetic.Factory.addFilterGetterSetter(Kinetic.Image, 'filterRadius', 5);
stackIn = stackIn.next; Kinetic.Filters.Blur = Kinetic.Util._FilterWrapSingleBuffer(function(src,dst,opt){
opt = opt || {
r_out_sum += ( pr = stackOut.r ); blurWidth: this.getFilterRadius(),
g_out_sum += ( pg = stackOut.g ); blurHeight: this.getFilterRadius()
b_out_sum += ( pb = stackOut.b ); };
a_out_sum += ( pa = stackOut.a ); Kinetic.Filters.BlurX(src,src,opt);
Kinetic.Filters.BlurY(src,dst,opt);
r_in_sum -= pr; // Move the destination to the source
g_in_sum -= pg; //Kinetic.Util._FilterReplaceBuffer(dst,src);
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);
})();

View File

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