diff --git a/Gruntfile.js b/Gruntfile.js index 25396196..5560cab2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,6 +16,7 @@ module.exports = function(grunt) { 'src/filters/Mask.js', 'src/filters/RGB.js', 'src/filters/HSV.js', + 'src/filters/HSL.js', 'src/filters/Emboss.js', 'src/filters/Enhance.js', 'src/filters/Posterize.js', diff --git a/src/filters/HSL.js b/src/filters/HSL.js new file mode 100644 index 00000000..9d70057a --- /dev/null +++ b/src/filters/HSL.js @@ -0,0 +1,118 @@ +(function () { + + HSV = function (imageData) { + var data = imageData.data, + nPixels = data.length, + v = Math.pow(2,this.value()), + s = Math.pow(2,this.saturation()), + h = Math.abs((this.hue()) + 360) % 360, + i; + + // 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 = data[i+0]; + g = data[i+1]; + b = data[i+2]; + a = data[i+3]; + + data[i+0] = rr*r + rg*g + rb*b; + data[i+1] = gr*r + gg*g + gb*b; + data[i+2] = br*r + bg*g + bb*b; + data[i+3] = a; // alpha + } + + }; + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'hue', 0, null, Kinetic.Factory.afterSetFilter); + /** + * get/set hsv hue in degrees + * @name hue + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} hue value between 0 and 359 + * @returns {Number} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'saturation', 0, null, Kinetic.Factory.afterSetFilter); + /** + * get/set hsv saturation + * @name saturation + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. + * @returns {Number} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'value', 0, null, Kinetic.Factory.afterSetFilter); + /** + * get/set hsv value + * @name value + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc.. + * @returns {Number} + */ + + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'luminance', 0, null, Kinetic.Factory.afterSetFilter); + /** + * get/set hsl luminance + * @name value + * @method + * @memberof Kinetic.Node.prototype + * @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc.. + * @returns {Number} + */ + + /** + * HSL Filter. Adjusts the hue, saturation and luminance (or lightness) + * @function + * @memberof Kinetic.Filters + * @param {Object} imageData + * @author ippo615 + */ + + Kinetic.Filters.HSL = function (imageData) { + // Hue stays the same but saturation, value and brightness will be + // adjusted to match common photo-editing software's extreme values + var oldSaturation = this.saturation(), + oldBrightness = this.brightness(), + oldValue = this.value(); + + this.saturation(oldSaturation); + this.brightness(0.5*this.luminance()); + this.value(0.0); + + HSV.call(this,imageData); + Kinetic.Filters.Brighten.call(this,imageData); + + this.saturation(oldSaturation); + this.brightness(oldBrightness); + this.value(oldValue); + }; +})(); diff --git a/src/filters/HSV.js b/src/filters/HSV.js index 8669d214..372eaf5c 100644 --- a/src/filters/HSV.js +++ b/src/filters/HSV.js @@ -11,8 +11,8 @@ Kinetic.Filters.HSV = function (imageData) { var data = imageData.data, nPixels = data.length, - v = this.value(), - s = this.saturation(), + v = Math.pow(2,this.value()), + s = Math.pow(2,this.saturation()), h = Math.abs((this.hue()) + 360) % 360, i; @@ -67,60 +67,24 @@ * @returns {Number} */ - Kinetic.Factory.addGetterSetter(Kinetic.Node, 'saturation', 1, null, Kinetic.Factory.afterSetFilter); + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'saturation', 0, null, Kinetic.Factory.afterSetFilter); /** * get/set hsv saturation * @name saturation * @method * @memberof Kinetic.Node.prototype - * @param {Number} saturation 1 is no change, 0.5 halves the saturation, 2.0 doubles, etc.. + * @param {Number} saturation 0 is no change, -1.0 halves the saturation, 1.0 doubles, etc.. * @returns {Number} */ - Kinetic.Factory.addGetterSetter(Kinetic.Node, 'value', 1, null, Kinetic.Factory.afterSetFilter); + Kinetic.Factory.addGetterSetter(Kinetic.Node, 'value', 0, null, Kinetic.Factory.afterSetFilter); /** * get/set hsv value * @name value * @method * @memberof Kinetic.Node.prototype - * @param {Number} value 1 is no change, 0.5 halves the value, 2.0 doubles, etc.. + * @param {Number} value 0 is no change, -1.0 halves the value, 1.0 doubles, etc.. * @returns {Number} */ - Kinetic.Factory.addGetterSetter(Kinetic.Node, 'luminance', 1, null, Kinetic.Factory.afterSetFilter); - /** - * get/set hsl luminance - * @name value - * @method - * @memberof Kinetic.Node.prototype - * @param {Number} value 1 is no change, 0.5 halves the value, 2.0 doubles, etc.. - * @returns {Number} - */ - - /** - * HSL Filter. Adjusts the hue, saturation and luminance (or lightness) - * @function - * @memberof Kinetic.Filters - * @param {Object} imageData - * @author ippo615 - */ - - Kinetic.Filters.HSL = function (imageData) { - // Hue stays the same but saturation, value and brightness will be - // adjusted to match common photo-editing software's extreme values - var oldSaturation = this.saturation(), - oldBrightness = this.brightness(), - oldValue = this.value(); - - this.saturation(oldSaturation*oldSaturation); - this.brightness(0.5*(this.luminance()-1)); - this.value(1.0); - - Kinetic.Filters.HSV.call(this,imageData); - Kinetic.Filters.Brighten.call(this,imageData); - - this.saturation(oldSaturation); - this.brightness(oldBrightness); - this.value(oldValue); - }; })(); diff --git a/test/runner.html b/test/runner.html index 645885c3..e429dfd5 100644 --- a/test/runner.html +++ b/test/runner.html @@ -84,6 +84,7 @@ + diff --git a/test/unit/filters/HSL-test.js b/test/unit/filters/HSL-test.js new file mode 100644 index 00000000..51a422b4 --- /dev/null +++ b/test/unit/filters/HSL-test.js @@ -0,0 +1,135 @@ +suite('HSL', function() { + + + // ====================================================== + test('hue shift tween transparancy', 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.HSL]); + darth.hue(360); + layer.draw(); + + var tween = new Kinetic.Tween({ + node: darth, + duration: 1.0, + hue: 0, + easing: Kinetic.Easings.EaseInOut + }); + + darth.on('mouseover', function() { + tween.play(); + }); + + darth.on('mouseout', function() { + tween.reverse(); + }); + + done(); + }; + imageObj.src = 'assets/lion.png'; + + }); + + // ====================================================== + test('HSL luminance tween transparancy', 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.HSL]); + darth.luminance(1.0); + layer.draw(); + + var tween = new Kinetic.Tween({ + node: darth, + duration: 1.0, + luminance: -1.0, + easing: Kinetic.Easings.EaseInOut + }); + + darth.on('mouseover', function() { + tween.play(); + }); + + darth.on('mouseout', function() { + tween.reverse(); + }); + + done(); + }; + imageObj.src = 'assets/lion.png'; + + }); + + // ====================================================== + test('HSL saturation tween transparancy', 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.HSL]); + darth.saturation(1.0); + layer.draw(); + + var tween = new Kinetic.Tween({ + node: darth, + duration: 1.0, + saturation: -1.0, + easing: Kinetic.Easings.EaseInOut + }); + + darth.on('mouseover', function() { + tween.play(); + }); + + darth.on('mouseout', function() { + tween.reverse(); + }); + + done(); + }; + imageObj.src = 'assets/lion.png'; + + }); +}); diff --git a/test/unit/filters/HSV-test.js b/test/unit/filters/HSV-test.js index 2e488804..33fa8355 100644 --- a/test/unit/filters/HSV-test.js +++ b/test/unit/filters/HSV-test.js @@ -66,13 +66,13 @@ suite('HSV', function() { darth.cache(); darth.filters([Kinetic.Filters.HSV]); - darth.saturation(2); + darth.saturation(1.0); layer.draw(); var tween = new Kinetic.Tween({ node: darth, duration: 1.0, - saturation: 0, + saturation: -1.0, easing: Kinetic.Easings.EaseInOut }); @@ -110,13 +110,13 @@ suite('HSV', function() { darth.cache(); darth.filters([Kinetic.Filters.HSV]); - darth.saturation(2.0); + darth.saturation(1.0); layer.draw(); var tween = new Kinetic.Tween({ node: darth, duration: 1.0, - saturation: 0.001, + saturation: -1, easing: Kinetic.Easings.EaseInOut }); @@ -154,13 +154,13 @@ suite('HSV', function() { darth.cache(); darth.filters([Kinetic.Filters.HSV]); - darth.value(2.0); + darth.value(1.0); layer.draw(); var tween = new Kinetic.Tween({ node: darth, duration: 1.0, - value: 0.001, + value: -1.0, easing: Kinetic.Easings.EaseInOut }); @@ -178,91 +178,4 @@ suite('HSV', function() { }); - // ====================================================== - test('HSL luminance tween transparancy', 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.HSL]); - darth.luminance(2.0); - layer.draw(); - - var tween = new Kinetic.Tween({ - node: darth, - duration: 1.0, - luminance: 0.001, - easing: Kinetic.Easings.EaseInOut - }); - - darth.on('mouseover', function() { - tween.play(); - }); - - darth.on('mouseout', function() { - tween.reverse(); - }); - - done(); - }; - imageObj.src = 'assets/lion.png'; - - }); - - // ====================================================== - test('HSL saturation tween transparancy', 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.HSL]); - darth.saturation(2.0); - layer.draw(); - - var tween = new Kinetic.Tween({ - node: darth, - duration: 1.0, - saturation: 0.001, - easing: Kinetic.Easings.EaseInOut - }); - - darth.on('mouseover', function() { - tween.play(); - }); - - darth.on('mouseout', function() { - tween.reverse(); - }); - - done(); - }; - imageObj.src = 'assets/lion.png'; - - }); });