mirror of
https://github.com/konvajs/konva.git
synced 2025-12-29 18:34:36 +08:00
Added new filters from filter branch.
This commit is contained in:
12
Gruntfile.js
12
Gruntfile.js
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
})();
|
||||
|
||||
|
||||
95
src/filters/ColorStretch.js
Normal file
95
src/filters/ColorStretch.js
Normal 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);
|
||||
|
||||
})();
|
||||
@@ -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)});
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
50
src/filters/FilterWrapper.js
Normal file
50
src/filters/FilterWrapper.js
Normal 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
65
src/filters/Flip.js
Normal 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);
|
||||
})();
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
|
||||
@@ -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
31
src/filters/Levels.js
Normal 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
79
src/filters/Mirror.js
Normal 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
44
src/filters/Noise.js
Normal 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
90
src/filters/Pixelate.js
Normal 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
220
src/filters/Polar.js
Normal 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
34
src/filters/Threshold.js
Normal 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);
|
||||
})();
|
||||
Reference in New Issue
Block a user