diff --git a/Gruntfile.js b/Gruntfile.js index 6ab0d6e3..e2d19ab7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -25,7 +25,7 @@ module.exports = function(grunt) { 'src/filters/Sepia.js', 'src/filters/Solarize.js', 'src/filters/Ripple.js', - 'src/filters/Kalidescope.js', + 'src/filters/Kaleidoscope.js', //'src/filters/FilterWrapper.js', //'src/filters/Polar.js', diff --git a/src/filters/Kaleidoscope.js b/src/filters/Kaleidoscope.js new file mode 100644 index 00000000..e7d901dd --- /dev/null +++ b/src/filters/Kaleidoscope.js @@ -0,0 +1,314 @@ +(function () { + + /* + * ToPolar Filter. Converts image data to polar coordinates. Performs + * w*h*4 pixel reads and w*h pixel writes. The r axis is placed along + * what would be the y axis and the theta axis along the x axis. + * @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 y as the radius, and x 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 - + diff --git a/test/unit/Node-test.js b/test/unit/Node-test.js index f1c8b264..777ce40a 100644 --- a/test/unit/Node-test.js +++ b/test/unit/Node-test.js @@ -2948,7 +2948,7 @@ suite('Node', function() { assert.equal(circle._cache.canvas.scene.getContext().getTrace(), 'save();translate(74,74);beginPath();arc(0,0,70,0,6.283,false);closePath();fillStyle=green;fill();lineWidth=4;strokeStyle=black;stroke();restore();'); }); - test.only('show cache border', function(){ + test('show cache border', function(){ var stage = addStage(); var layer = new Kinetic.Layer(); var group = new Kinetic.Group(); diff --git a/test/unit/filters/Kaleidoscope-test.js b/test/unit/filters/Kaleidoscope-test.js new file mode 100644 index 00000000..fce58dd5 --- /dev/null +++ b/test/unit/filters/Kaleidoscope-test.js @@ -0,0 +1,137 @@ +suite('Kaleidoscope', function() { + // ====================================================== + test('basic', function(done) { + var stage = addStage(); + + var imageObj = new Image(); + imageObj.onload = function() { + + var layer = new Kinetic.Layer(); + darth = new Kinetic.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Kinetic.Filters.Kaleidoscope]); + darth.kaleidoscopeSides(9); + + assert.equal(darth.kaleidoscopeSides(), 9); + assert.equal(darth._filterUpToDate, false); + + layer.draw(); + + assert.equal(darth._filterUpToDate, true); + + darth.kaleidoscopeSides(16); + + assert.equal(darth.kaleidoscopeSides(), 16); + assert.equal(darth._filterUpToDate, false); + + layer.draw(); + + assert.equal(darth._filterUpToDate, true); + + done(); + }; + imageObj.src = 'assets/lion.png'; + + }); + + // ====================================================== + test('tween offset', function(done) { + var stage = addStage(); + + var imageObj = new Image(); + imageObj.onload = function() { + + var layer = new Kinetic.Layer(); + darth = new Kinetic.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Kinetic.Filters.Kaleidoscope]); + darth.kaleidoscopeSides(5); + darth.kaleidoscopeOffset(0); + layer.draw(); + + var tween = new Kinetic.Tween({ + node: darth, + duration: 4.0, + kaleidoscopeOffset: 64, + //rippleSize: 64, + easing: Kinetic.Easings.EaseInOut + }); + + darth.on('mouseover', function() { + tween.play(); + }); + + darth.on('mouseout', function() { + tween.reverse(); + }); + + done(); + + }; + imageObj.src = 'assets/lion.png'; + }); + + // ====================================================== + test('tween sides', function(done) { + var stage = addStage(); + + var imageObj = new Image(); + imageObj.onload = function() { + + var layer = new Kinetic.Layer(); + darth = new Kinetic.Image({ + x: 10, + y: 10, + image: imageObj, + draggable: true + }); + + layer.add(darth); + stage.add(layer); + + darth.cache(); + darth.filters([Kinetic.Filters.Kaleidoscope]); + darth.kaleidoscopeSides(1); + darth.kaleidoscopeOffset(0); + layer.draw(); + + var tween = new Kinetic.Tween({ + node: darth, + duration: 2.0, + kaleidoscopeSides: 32, + easing: Kinetic.Easings.EaseInOut + }); + + darth.on('mouseover', function() { + tween.play(); + }); + + darth.on('mouseout', function() { + tween.reverse(); + }); + + done(); + + }; + imageObj.src = 'assets/cropped-darth.jpg'; + }); + +}); \ No newline at end of file