diff --git a/Gruntfile.js b/Gruntfile.js index debba9c7..3e776080 100644 --- a/Gruntfile.js +++ b/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. diff --git a/src/filters/Blur.js b/src/filters/Blur.js index d0deb3a3..4933bd33 100644 --- a/src/filters/Blur.js +++ b/src/filters/Blur.js @@ -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 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); + +})(); diff --git a/src/filters/ConvolvePack.js b/src/filters/ConvolvePack.js index 1fe2a462..b160d679 100644 --- a/src/filters/ConvolvePack.js +++ b/src/filters/ConvolvePack.js @@ -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; y0)?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 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)}); + }); })(); diff --git a/src/filters/FilterWrapper.js b/src/filters/FilterWrapper.js new file mode 100644 index 00000000..af6aa1d5 --- /dev/null +++ b/src/filters/FilterWrapper.js @@ -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= 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); +})(); diff --git a/src/filters/Polar.js b/src/filters/Polar.js new file mode 100644 index 00000000..9a49cc38 --- /dev/null +++ b/src/filters/Polar.js @@ -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= 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