From 29967153c376561bda73f6361ea59724bdd84629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 10:49:43 +0100 Subject: [PATCH 01/16] Clean up TODO comments... --- src/color/Gradient.js | 4 ++-- src/item/Item.js | 8 +++----- src/item/Raster.js | 14 +++++++------- src/path/CompoundPath.js | 2 +- src/project/Project.js | 10 +++++----- src/project/Symbol.js | 2 +- src/text/PointText.js | 4 ++-- src/tool/ToolEvent.js | 2 +- src/ui/Key.js | 2 +- src/util/CanvasProvider.js | 4 ++-- 10 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/color/Gradient.js b/src/color/Gradient.js index 639cdc3c..1231e961 100644 --- a/src/color/Gradient.js +++ b/src/color/Gradient.js @@ -19,8 +19,8 @@ var Gradient = this.Gradient = Base.extend({ beans: true, - // TODO: Should type here be called 'radial' and have it - // receive a boolean value? + // TODO: Should type here be called 'radial' and have it receive a + // boolean value? /** * Creates a gradient object * diff --git a/src/item/Item.js b/src/item/Item.js index 7c54e1a7..9a43c996 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -1043,8 +1043,8 @@ var Item = this.Item = Base.extend({ */ transform: function(matrix, flags) { // TODO: Handle flags, add TransformFlag class and convert to bit mask - // for quicker checking - // TODO: Call transform on chidren only if 'children' flag is provided + // for quicker checking. + // TODO: Call transform on chidren only if 'children' flag is provided. if (this._transform) this._transform(matrix, flags); // Transform position as well. Do not modify _position directly, @@ -1085,7 +1085,7 @@ var Item = this.Item = Base.extend({ } }, - // TODO: Implement View into the drawing + // TODO: Implement View into the drawing. // TODO: Optimize temporary canvas drawing to ignore parts that are // outside of the visible view. draw: function(item, ctx, param) { @@ -1241,8 +1241,6 @@ var Item = this.Item = Base.extend({ moveBelow: move(false) }; }, new function() { - //DOCS: document removeOn(param) - /** * {@grouptitle Remove On Event} * Removes the item when the next {@link Tool#onMouseMove} event is fired. diff --git a/src/item/Raster.js b/src/item/Raster.js index 51020109..bb8cc9f7 100644 --- a/src/item/Raster.js +++ b/src/item/Raster.js @@ -19,9 +19,9 @@ var Raster = this.Raster = Item.extend({ beans: true, - // TODO: implement url / type, width, height - // TODO: have PlacedSymbol & Raster inherit from a shared class? - // DOCS: document Raster constructor + // TODO: Implement url / type, width, height. + // TODO: Have PlacedSymbol & Raster inherit from a shared class? + // DOCS: Document Raster constructor. /** * Creates a new raster item and places it in the active layer. * @@ -163,12 +163,12 @@ var Raster = this.Raster = Item.extend({ return this._image || this.getCanvas(); }, - // TODO: support string id of image element + // TODO: Support string id of image element. setImage: function(image) { if (this._canvas) CanvasProvider.returnCanvas(this._canvas); this._image = image; - // TODO: cross browser compatible? + // TODO: Cross browser compatible? this._size = new Size(image.naturalWidth, image.naturalHeight); this._canvas = null; this._context = null; @@ -216,8 +216,8 @@ var Raster = this.Raster = Item.extend({ object = this.getBounds(); var bounds, path; if (object instanceof PathItem) { - // TODO: what if the path is smaller than 1 px? - // TODO: how about rounding of bounds.size? + // TODO: What if the path is smaller than 1 px? + // TODO: How about rounding of bounds.size? path = object; bounds = object.getBounds(); } else if (object.width) { diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js index bca3b713..4b03128c 100644 --- a/src/path/CompoundPath.js +++ b/src/path/CompoundPath.js @@ -54,7 +54,7 @@ var CompoundPath = this.CompoundPath = PathItem.extend({ // clockwise orientation when creating a compound path, so that they // appear as holes, but only if their orientation was not already // specified before (= _clockwise is defined). - // TODO: This should really be handled in appendTop / Bottom, right? + // TODO: Should this be handled in appendTop / Bottom instead? if (path._clockwise === undefined) path.setClockwise(i < l - 1); this.appendTop(path); diff --git a/src/project/Project.js b/src/project/Project.js index 589451eb..89e4bffd 100644 --- a/src/project/Project.js +++ b/src/project/Project.js @@ -20,7 +20,7 @@ var Project = this.Project = Base.extend({ beans: true, // TODO: Add arguments to define pages - // DOCS: document Project constructor and class + // DOCS: Document Project constructor and class /** * Creates a Paper.js project * @@ -90,7 +90,7 @@ var Project = this.Project = Base.extend({ }, setCurrentStyle: function(style) { - // TODO: style selected items with the style: + // TODO: Style selected items with the style: this._currentStyle.initialize(style); }, @@ -129,9 +129,9 @@ var Project = this.Project = Base.extend({ * @bean */ getSelectedItems: function() { - // TODO: return groups if their children are all selected, + // TODO: Return groups if their children are all selected, // and filter out their children from the list. - // TODO: the order of these items should be that of their + // TODO: The order of these items should be that of their // drawing order. var items = []; Base.each(this._selectedItems, function(item) { @@ -140,7 +140,7 @@ var Project = this.Project = Base.extend({ return items; }, - // TODO: implement setSelectedItems? + // TODO: Implement setSelectedItems? _selectItem: function(item, select) { if (select) { diff --git a/src/project/Symbol.js b/src/project/Symbol.js index 5ed4f5a4..f35de100 100644 --- a/src/project/Symbol.js +++ b/src/project/Symbol.js @@ -57,7 +57,7 @@ var Symbol = this.Symbol = Base.extend({ }, // TODO: Symbol#remove() - // TODO: Size#name (accessible by name through project#symbols) + // TODO: Symbol#name (accessible by name through project#symbols) /** * The project that this symbol belongs to. diff --git a/src/text/PointText.js b/src/text/PointText.js index 535cb2e3..1f341f6e 100644 --- a/src/text/PointText.js +++ b/src/text/PointText.js @@ -69,8 +69,8 @@ var PointText = this.PointText = TextItem.extend({ Point.read(arguments).subtract(this._point))); }, - // TODO: position should be the center point of the bounds - // but we currently don't support bounds for PointText. + // TODO: Position should be the center point of the bounds but we currently + // don't support bounds for PointText. getPosition: function() { return this._point; }, diff --git a/src/tool/ToolEvent.js b/src/tool/ToolEvent.js index f49a5ec6..b3d76363 100644 --- a/src/tool/ToolEvent.js +++ b/src/tool/ToolEvent.js @@ -172,7 +172,7 @@ var ToolEvent = this.ToolEvent = Base.extend({ return Key.modifiers; }, - // TODO: implement hitTest first + // TODO: Implement hitTest first // getItem: function() { // if (this.item == null) { // var result = Project.getActiveProject().hitTest(this.getPoint()); diff --git a/src/ui/Key.js b/src/ui/Key.js index fa0a645b..019e7c61 100644 --- a/src/ui/Key.js +++ b/src/ui/Key.js @@ -19,7 +19,7 @@ * @name Key */ var Key = this.Key = new function() { - // TODO: make sure the keys are called the same as in Scriptographer + // TODO: Make sure the keys are called the same as in Scriptographer // Missing: tab, cancel, clear, page-down, page-up, comma, minus, period, // slash, etc etc etc. diff --git a/src/util/CanvasProvider.js b/src/util/CanvasProvider.js index 8bbdca93..49b352f8 100644 --- a/src/util/CanvasProvider.js +++ b/src/util/CanvasProvider.js @@ -14,8 +14,8 @@ * All rights reserved. */ -// TODO: it might be better to make a ContextProvider class, since you -// can always find the canvas through context.canvas. This saves code and +// TODO: It might be better to make a ContextProvider class, since you +// can always find the canvas through context.canvas. This saves code and // speed by not having to do canvas.getContext('2d') // TODO: Run through the canvas array to find a canvas with the requested // width / height, so we don't need to resize it? From c47d281308484180957099e4cf9005a76ffc38e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 13:37:36 +0100 Subject: [PATCH 02/16] Speed up blend-modes by using a lookup table for process functions rather than a switch() statement for each pixel. --- src/util/BlendMode.js | 186 ++++++++++++++++++++++-------------------- 1 file changed, 98 insertions(+), 88 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index 7d462103..c620a14a 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -51,56 +51,95 @@ var BlendMode = { sourceCanvas.width, sourceCanvas.height), src = srcD.data, dst = dstD.data, - sA, dA, len = dst.length, - sRA, sGA, sBA, dRA, dGA, dBA, dA2, - demultiply, - min = Math.min; + min = Math.min, + sA, dA, dA2, sRA, sGA, sBA, dRA, dGA, dBA, demultiply; - for (var i = 0; i < len; i += 4) { - sA = src[i + 3] / 255 * opacity; - dA = dst[i + 3] / 255; - dA2 = sA + dA - sA * dA; - dst[i + 3] = dA2 * 255; + // TODO: Some blend modes seem broken at the moment, e.g. + // dodge, burn + // TODO: This could be optimised by not diving everything by 255 and + // keeping it integer instead, with one divide at the end. + var modes = { + unsupported: function(i) { + // Render checker pattern + dst[i] = dst[i + 3] = 255; + dst[i + 1] = i % 8 == 0 ? 255 : 0; + dst[i + 2] = i % 8 == 0 ? 0 : 255; + }, - sRA = src[i] / 255 * sA; - dRA = dst[i] / 255 * dA; - sGA = src[i + 1] / 255 * sA; - dGA = dst[i + 1] / 255 * dA; - sBA = src[i + 2] / 255 * sA; - dBA = dst[i + 2] / 255 * dA; - - demultiply = 255 / dA2; - - // TODO: Some blend modes seem broken at the moment, e.g. - // dodge, burn - // TODO: This could be optimised by not diving everything by 255 and - // keeping it integer instead, with one divide at the end. - - switch (blendMode) { - // Very close match to Photoshop - case 'normal': - case 'src-over': + normal: function(i) { dst[i] = (sRA + dRA - dRA * sA) * demultiply; dst[i + 1] = (sGA + dGA - dGA * sA) * demultiply; dst[i + 2] = (sBA + dBA - dBA * sA) * demultiply; - break; - case 'screen': - dst[i] = (sRA + dRA - sRA * dRA) * demultiply; - dst[i + 1] = (sGA + dGA - sGA * dGA) * demultiply; - dst[i + 2] = (sBA + dBA - sBA * dBA) * demultiply; - break; - case 'multiply': + }, + + multiply: function(i) { dst[i] = (sRA * dRA + sRA * (1 - dA) + dRA * (1 - sA)) * demultiply; dst[i + 1] = (sGA * dGA + sGA * (1 - dA) + dGA * (1 - sA)) * demultiply; dst[i + 2] = (sBA * dBA + sBA * (1 - dA) + dBA * (1 - sA)) * demultiply; - break; - case 'difference': + }, + + screen: function(i) { + dst[i] = (sRA + dRA - sRA * dRA) * demultiply; + dst[i + 1] = (sGA + dGA - sGA * dGA) * demultiply; + dst[i + 2] = (sBA + dBA - sBA * dBA) * demultiply; + }, + + overlay: function(i) { + // Correct for 100% opacity case; colors get clipped as opacity falls + dst[i] = dRA <= 0.5 ? (2 * src[i] * dRA / dA) : 255 - (2 - 2 * dRA / dA) * (255 - src[i]); + dst[i + 1] = dGA <= 0.5 ? (2 * src[i + 1] * dGA / dA) : 255 - (2 - 2 * dGA / dA) * (255 - src[i + 1]); + dst[i + 2] = dBA <= 0.5 ? (2 * src[i + 2] * dBA / dA) : 255 - (2 - 2 * dBA / dA) * (255 - src[i + 2]); + }, + + // TODO: Missing: soft-light + + 'hard-light': function(i) { + dst[i] = sRA <= 0.5 ? (2 * dst[i] * sRA / dA) : 255 - (2 - 2 * sRA / sA) * (255 - dst[i]); + dst[i + 1] = sGA <= 0.5 ? (2 * dst[i + 1] * sGA / dA) : 255 - (2 - 2 * sGA / sA) * (255 - dst[i + 1]); + dst[i + 2] = sBA <= 0.5 ? (2 * dst[i + 2] * sBA / dA) : 255 - (2 - 2 * sBA / sA) * (255 - dst[i + 2]); + }, + + 'color-dodge': function(i) { + dst[i] = src[i] == 255 && dRA == 0 ? 255 : min(255, dst[i] / (255 - src[i] )) * demultiply; + dst[i + 1] = src[i + 1] == 255 && dGA == 0 ? 255 : min(255, dst[i + 1] / (255 - src[i + 1])) * demultiply; + dst[i + 2] = src[i + 2] == 255 && dBA == 0 ? 255 : min(255, dst[i + 2] / (255 - src[i + 2])) * demultiply; + }, + + 'color-burn': function(i) { + dst[i] = src[i] == 0 && dRA == 0 ? 0 : (1 - min(1, (1 - dRA) / sRA)) * demultiply; + dst[i + 1] = src[i + 1] == 0 && dGA == 0 ? 0 : (1 - min(1, (1 - dGA) / sGA)) * demultiply; + dst[i + 2] = src[i + 2] == 0 && dBA == 0 ? 0 : (1 - min(1, (1 - dBA) / sBA)) * demultiply; + }, + + darken: function(i) { + dst[i] = (sRA > dRA ? dRA : sRA) * demultiply; + dst[i + 1] = (sGA > dGA ? dGA : sGA) * demultiply; + dst[i + 2] = (sBA > dBA ? dBA : sBA) * demultiply; + }, + + lighten: function(i) { + dst[i] = (sRA < dRA ? dRA : sRA) * demultiply; + dst[i + 1] = (sGA < dGA ? dGA : sGA) * demultiply; + dst[i + 2] = (sBA < dBA ? dBA : sBA) * demultiply; + }, + + difference: function(i) { dst[i] = (sRA + dRA - 2 * min(sRA * dA, dRA * sA)) * demultiply; dst[i + 1] = (sGA + dGA - 2 * min(sGA * dA, dGA * sA)) * demultiply; dst[i + 2] = (sBA + dBA - 2 * min(sBA * dA, dBA * sA)) * demultiply; - break; - // Slightly different from Photoshop, where alpha is concerned - case 'src-in': + }, + + exclusion: function(i) { + dst[i] = (dRA + sRA - 2 * dRA * sRA) * demultiply; + dst[i + 1] = (dGA + sGA - 2 * dGA * sGA) * demultiply; + dst[i + 2] = (dBA + sBA - 2 * dBA * sBA) * demultiply; + }, + + // TODO: Missing: hue, saturation, color, luminosity + + // Not in Illustrator: + + 'src-in': function(i) { // Only differs from Photoshop in low - opacity areas dA2 = sA * dA; demultiply = 255 / dA2; @@ -108,9 +147,9 @@ var BlendMode = { dst[i] = sRA * dA * demultiply; dst[i + 1] = sGA * dA * demultiply; dst[i + 2] = sBA * dA * demultiply; - break; - case 'plus': - case 'add': + }, + + add: function(i) { // Photoshop doesn't simply add the alpha channels; this might be correct wrt SVG 1.2 dA2 = min(1, sA + dA); dst[i + 3] = dA2 * 255; @@ -118,54 +157,25 @@ var BlendMode = { dst[i] = min(sRA + dRA, 1) * demultiply; dst[i + 1] = min(sGA + dGA, 1) * demultiply; dst[i + 2] = min(sBA + dBA, 1) * demultiply; - break; - case 'overlay': - // Correct for 100% opacity case; colors get clipped as opacity falls - dst[i] = dRA <= 0.5 ? (2 * src[i] * dRA / dA) : 255 - (2 - 2 * dRA / dA) * (255 - src[i]); - dst[i + 1] = dGA <= 0.5 ? (2 * src[i + 1] * dGA / dA) : 255 - (2 - 2 * dGA / dA) * (255 - src[i + 1]); - dst[i + 2] = dBA <= 0.5 ? (2 * src[i + 2] * dBA / dA) : 255 - (2 - 2 * dBA / dA) * (255 - src[i + 2]); - break; - case 'hard-light': - dst[i] = sRA <= 0.5 ? (2 * dst[i] * sRA / dA) : 255 - (2 - 2 * sRA / sA) * (255 - dst[i]); - dst[i + 1] = sGA <= 0.5 ? (2 * dst[i + 1] * sGA / dA) : 255 - (2 - 2 * sGA / sA) * (255 - dst[i + 1]); - dst[i + 2] = sBA <= 0.5 ? (2 * dst[i + 2] * sBA / dA) : 255 - (2 - 2 * sBA / sA) * (255 - dst[i + 2]); - break; - case 'color-dodge': - case 'dodge': - dst[i] = src[i] == 255 && dRA == 0 ? 255 : min(255, dst[i] / (255 - src[i] )) * demultiply; - dst[i + 1] = src[i + 1] == 255 && dGA == 0 ? 255 : min(255, dst[i + 1] / (255 - src[i + 1])) * demultiply; - dst[i + 2] = src[i + 2] == 255 && dBA == 0 ? 255 : min(255, dst[i + 2] / (255 - src[i + 2])) * demultiply; - break; - case 'color-burn': - case 'burn': - dst[i] = src[i] == 0 && dRA == 0 ? 0 : (1 - min(1, (1 - dRA) / sRA)) * demultiply; - dst[i + 1] = src[i + 1] == 0 && dGA == 0 ? 0 : (1 - min(1, (1 - dGA) / sGA)) * demultiply; - dst[i + 2] = src[i + 2] == 0 && dBA == 0 ? 0 : (1 - min(1, (1 - dBA) / sBA)) * demultiply; - break; - case 'darken': - case 'darker': - dst[i] = (sRA > dRA ? dRA : sRA) * demultiply; - dst[i + 1] = (sGA > dGA ? dGA : sGA) * demultiply; - dst[i + 2] = (sBA > dBA ? dBA : sBA) * demultiply; - break; - case 'lighten': - case 'lighter': - dst[i] = (sRA < dRA ? dRA : sRA) * demultiply; - dst[i + 1] = (sGA < dGA ? dGA : sGA) * demultiply; - dst[i + 2] = (sBA < dBA ? dBA : sBA) * demultiply; - break; - case 'exclusion': - dst[i] = (dRA + sRA - 2 * dRA * sRA) * demultiply; - dst[i + 1] = (dGA + sGA - 2 * dGA * sGA) * demultiply; - dst[i + 2] = (dBA + sBA - 2 * dBA * sBA) * demultiply; - break; - // Unsupported - default: - dst[i] = dst[i + 3] = 255; - dst[i + 1] = i % 8 == 0 ? 255 : 0; - dst[i + 2] = i % 8 == 0 ? 0 : 255; } } + + var process = modes[blendMode] || modes.unsupported; + + for (var i = 0, l = dst.length; i < l; i += 4) { + sA = src[i + 3] / 255 * opacity; + dA = dst[i + 3] / 255; + dA2 = sA + dA - sA * dA; + sRA = src[i] / 255 * sA; + dRA = dst[i] / 255 * dA; + sGA = src[i + 1] / 255 * sA; + dGA = dst[i + 1] / 255 * dA; + sBA = src[i + 2] / 255 * sA; + dBA = dst[i + 2] / 255 * dA; + demultiply = 255 / dA2; + process(i); + dst[i + 3] = dA2 * 255; + } destContext.putImageData(dstD, offset.x, offset.y); } }; From 69e989f8343811f4edb0457d49c20d8af88191ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 13:42:22 +0100 Subject: [PATCH 03/16] No need to set dst[i + 3] again after changing dA2, since we are now only setting it after calling process() from the main loop. --- src/util/BlendMode.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index c620a14a..bf5e5d70 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -143,22 +143,21 @@ var BlendMode = { // Only differs from Photoshop in low - opacity areas dA2 = sA * dA; demultiply = 255 / dA2; - dst[i + 3] = dA2 * 255; dst[i] = sRA * dA * demultiply; dst[i + 1] = sGA * dA * demultiply; dst[i + 2] = sBA * dA * demultiply; }, add: function(i) { - // Photoshop doesn't simply add the alpha channels; this might be correct wrt SVG 1.2 + // Photoshop doesn't simply add the alpha channels, + // this might be correct wrt SVG 1.2 dA2 = min(1, sA + dA); - dst[i + 3] = dA2 * 255; demultiply = 255 / dA2; dst[i] = min(sRA + dRA, 1) * demultiply; dst[i + 1] = min(sGA + dGA, 1) * demultiply; dst[i + 2] = min(sBA + dBA, 1) * demultiply; } - } + }; var process = modes[blendMode] || modes.unsupported; From a1d996df05e41d469633e5515b7ec6f5dac1ad7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 13:42:34 +0100 Subject: [PATCH 04/16] Clean up formating. --- src/util/BlendMode.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index bf5e5d70..8138bb31 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -94,31 +94,31 @@ var BlendMode = { // TODO: Missing: soft-light 'hard-light': function(i) { - dst[i] = sRA <= 0.5 ? (2 * dst[i] * sRA / dA) : 255 - (2 - 2 * sRA / sA) * (255 - dst[i]); + dst[i] = sRA <= 0.5 ? (2 * dst[i] * sRA / dA) : 255 - (2 - 2 * sRA / sA) * (255 - dst[i]); dst[i + 1] = sGA <= 0.5 ? (2 * dst[i + 1] * sGA / dA) : 255 - (2 - 2 * sGA / sA) * (255 - dst[i + 1]); dst[i + 2] = sBA <= 0.5 ? (2 * dst[i + 2] * sBA / dA) : 255 - (2 - 2 * sBA / sA) * (255 - dst[i + 2]); }, 'color-dodge': function(i) { - dst[i] = src[i] == 255 && dRA == 0 ? 255 : min(255, dst[i] / (255 - src[i] )) * demultiply; + dst[i] = src[i] == 255 && dRA == 0 ? 255 : min(255, dst[i] / (255 - src[i] )) * demultiply; dst[i + 1] = src[i + 1] == 255 && dGA == 0 ? 255 : min(255, dst[i + 1] / (255 - src[i + 1])) * demultiply; dst[i + 2] = src[i + 2] == 255 && dBA == 0 ? 255 : min(255, dst[i + 2] / (255 - src[i + 2])) * demultiply; }, 'color-burn': function(i) { - dst[i] = src[i] == 0 && dRA == 0 ? 0 : (1 - min(1, (1 - dRA) / sRA)) * demultiply; + dst[i] = src[i] == 0 && dRA == 0 ? 0 : (1 - min(1, (1 - dRA) / sRA)) * demultiply; dst[i + 1] = src[i + 1] == 0 && dGA == 0 ? 0 : (1 - min(1, (1 - dGA) / sGA)) * demultiply; dst[i + 2] = src[i + 2] == 0 && dBA == 0 ? 0 : (1 - min(1, (1 - dBA) / sBA)) * demultiply; }, darken: function(i) { - dst[i] = (sRA > dRA ? dRA : sRA) * demultiply; + dst[i] = (sRA > dRA ? dRA : sRA) * demultiply; dst[i + 1] = (sGA > dGA ? dGA : sGA) * demultiply; dst[i + 2] = (sBA > dBA ? dBA : sBA) * demultiply; }, lighten: function(i) { - dst[i] = (sRA < dRA ? dRA : sRA) * demultiply; + dst[i] = (sRA < dRA ? dRA : sRA) * demultiply; dst[i + 1] = (sGA < dGA ? dGA : sGA) * demultiply; dst[i + 2] = (sBA < dBA ? dBA : sBA) * demultiply; }, @@ -130,7 +130,7 @@ var BlendMode = { }, exclusion: function(i) { - dst[i] = (dRA + sRA - 2 * dRA * sRA) * demultiply; + dst[i] = (dRA + sRA - 2 * dRA * sRA) * demultiply; dst[i + 1] = (dGA + sGA - 2 * dGA * sGA) * demultiply; dst[i + 2] = (dBA + sBA - 2 * dBA * sBA) * demultiply; }, From 4a9f0d726f1e48b230033b5a2532f9a9618c389d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 14:00:02 +0100 Subject: [PATCH 05/16] Pre-calculate opacity value. --- src/util/BlendMode.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index 8138bb31..4f0300c3 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -53,6 +53,7 @@ var BlendMode = { dst = dstD.data, min = Math.min, sA, dA, dA2, sRA, sGA, sBA, dRA, dGA, dBA, demultiply; + opacity = opacity / 255, // TODO: Some blend modes seem broken at the moment, e.g. // dodge, burn @@ -162,7 +163,7 @@ var BlendMode = { var process = modes[blendMode] || modes.unsupported; for (var i = 0, l = dst.length; i < l; i += 4) { - sA = src[i + 3] / 255 * opacity; + sA = src[i + 3] * opacity; dA = dst[i + 3] / 255; dA2 = sA + dA - sA * dA; sRA = src[i] / 255 * sA; From b9739aa26a5052c1c076ae61761914e903cfddad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 14:00:50 +0100 Subject: [PATCH 06/16] Optimise blend mode loop by pre-calculating divisions. --- src/util/BlendMode.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index 4f0300c3..d81b81be 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -52,8 +52,8 @@ var BlendMode = { src = srcD.data, dst = dstD.data, min = Math.min, - sA, dA, dA2, sRA, sGA, sBA, dRA, dGA, dBA, demultiply; opacity = opacity / 255, + sA, dA, sAM, dAM, dA2, sRA, sGA, sBA, dRA, dGA, dBA, demultiply; // TODO: Some blend modes seem broken at the moment, e.g. // dodge, burn @@ -166,12 +166,14 @@ var BlendMode = { sA = src[i + 3] * opacity; dA = dst[i + 3] / 255; dA2 = sA + dA - sA * dA; - sRA = src[i] / 255 * sA; - dRA = dst[i] / 255 * dA; - sGA = src[i + 1] / 255 * sA; - dGA = dst[i + 1] / 255 * dA; - sBA = src[i + 2] / 255 * sA; - dBA = dst[i + 2] / 255 * dA; + sAM = sA / 255; + dAM = dA / 255; + sRA = src[i] * sAM; + dRA = dst[i] * dAM; + sGA = src[i + 1] * sAM; + dGA = dst[i + 1] * dAM; + sBA = src[i + 2] * sAM; + dBA = dst[i + 2] * dAM; demultiply = 255 / dA2; process(i); dst[i + 3] = dA2 * 255; From acecb1a2c1c278afdf4eab92c2346e5cf183925e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 14:01:22 +0100 Subject: [PATCH 07/16] Clean up variable names. --- src/util/BlendMode.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index d81b81be..55b719f2 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -45,12 +45,11 @@ var BlendMode = { // we can easily encorporate changes? process: function(blendMode, sourceContext, destContext, opacity, offset) { var sourceCanvas = sourceContext.canvas, - dstD = destContext.getImageData(offset.x, offset.y, + dstData = destContext.getImageData(offset.x, offset.y, sourceCanvas.width, sourceCanvas.height), - srcD = sourceContext.getImageData(0, 0, - sourceCanvas.width, sourceCanvas.height), - src = srcD.data, - dst = dstD.data, + dst = dstData.data, + src = sourceContext.getImageData(0, 0, + sourceCanvas.width, sourceCanvas.height).data, min = Math.min, opacity = opacity / 255, sA, dA, sAM, dAM, dA2, sRA, sGA, sBA, dRA, dGA, dBA, demultiply; @@ -178,6 +177,6 @@ var BlendMode = { process(i); dst[i + 3] = dA2 * 255; } - destContext.putImageData(dstD, offset.x, offset.y); + destContext.putImageData(dstData, offset.x, offset.y); } }; From 117a828b2ba347636e94d8159daccd5398f33ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 14:01:46 +0100 Subject: [PATCH 08/16] Don't set dst[i + 3] directly, set dA2 instead. --- src/util/BlendMode.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index 55b719f2..f6991095 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -61,7 +61,8 @@ var BlendMode = { var modes = { unsupported: function(i) { // Render checker pattern - dst[i] = dst[i + 3] = 255; + dA2 = 1; + dst[i] = 255; dst[i + 1] = i % 8 == 0 ? 255 : 0; dst[i + 2] = i % 8 == 0 ? 0 : 255; }, From 9fa193d26f372b647c7638b4db8e254586855ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 15:31:02 +0100 Subject: [PATCH 09/16] Move opacity precalculation out of variable definition. --- src/util/BlendMode.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index f6991095..6e7b4db8 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -51,7 +51,6 @@ var BlendMode = { src = sourceContext.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height).data, min = Math.min, - opacity = opacity / 255, sA, dA, sAM, dAM, dA2, sRA, sGA, sBA, dRA, dGA, dBA, demultiply; // TODO: Some blend modes seem broken at the moment, e.g. @@ -161,7 +160,7 @@ var BlendMode = { }; var process = modes[blendMode] || modes.unsupported; - + opacity /= 255; for (var i = 0, l = dst.length; i < l; i += 4) { sA = src[i + 3] * opacity; dA = dst[i + 3] / 255; From 44605433d14c0051f8ab204477eaa7ffbf07c2cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 16:34:57 +0100 Subject: [PATCH 10/16] Further shorten and simplify BlendMode cide by having process() only process one pixel value at a time. --- src/util/BlendMode.js | 123 ++++++++++++++++++------------------------ 1 file changed, 51 insertions(+), 72 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index 6e7b4db8..79e650d4 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -39,10 +39,6 @@ */ var BlendMode = { - // TODO: Should we remove the blend modes that are not in Scriptographer? - // TODO: Add missing blendmodes like hue / saturation / color / luminosity - // TODO: Clean up codespacing of original code, or keep it as is, so - // we can easily encorporate changes? process: function(blendMode, sourceContext, destContext, opacity, offset) { var sourceCanvas = sourceContext.canvas, dstData = destContext.getImageData(offset.x, offset.y, @@ -51,131 +47,114 @@ var BlendMode = { src = sourceContext.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height).data, min = Math.min, - sA, dA, sAM, dAM, dA2, sRA, sGA, sBA, dRA, dGA, dBA, demultiply; + sA, dA, rA, sM, dM, demultiply; // TODO: Some blend modes seem broken at the moment, e.g. // dodge, burn - // TODO: This could be optimised by not diving everything by 255 and - // keeping it integer instead, with one divide at the end. var modes = { - unsupported: function(i) { - // Render checker pattern - dA2 = 1; - dst[i] = 255; - dst[i + 1] = i % 8 == 0 ? 255 : 0; - dst[i + 2] = i % 8 == 0 ? 0 : 255; - }, - normal: function(i) { - dst[i] = (sRA + dRA - dRA * sA) * demultiply; - dst[i + 1] = (sGA + dGA - dGA * sA) * demultiply; - dst[i + 2] = (sBA + dBA - dBA * sA) * demultiply; + var s = src[i] * sM, d = dst[i] * dM; + dst[i] = (s + d * (1 - sA)) * demultiply; }, multiply: function(i) { - dst[i] = (sRA * dRA + sRA * (1 - dA) + dRA * (1 - sA)) * demultiply; - dst[i + 1] = (sGA * dGA + sGA * (1 - dA) + dGA * (1 - sA)) * demultiply; - dst[i + 2] = (sBA * dBA + sBA * (1 - dA) + dBA * (1 - sA)) * demultiply; + var s = src[i] * sM, d = dst[i] * dM; + dst[i] = (s * d + s * (1 - dA) + d * (1 - sA)) * demultiply; }, screen: function(i) { - dst[i] = (sRA + dRA - sRA * dRA) * demultiply; - dst[i + 1] = (sGA + dGA - sGA * dGA) * demultiply; - dst[i + 2] = (sBA + dBA - sBA * dBA) * demultiply; + var s = src[i] * sM, d = dst[i] * dM; + dst[i] = (s * (1 - d) + d) * demultiply; }, overlay: function(i) { // Correct for 100% opacity case; colors get clipped as opacity falls - dst[i] = dRA <= 0.5 ? (2 * src[i] * dRA / dA) : 255 - (2 - 2 * dRA / dA) * (255 - src[i]); - dst[i + 1] = dGA <= 0.5 ? (2 * src[i + 1] * dGA / dA) : 255 - (2 - 2 * dGA / dA) * (255 - src[i + 1]); - dst[i + 2] = dBA <= 0.5 ? (2 * src[i + 2] * dBA / dA) : 255 - (2 - 2 * dBA / dA) * (255 - src[i + 2]); + var s = src[i], d = dst[i] * dM; // src is unmultiplied + dst[i] = d <= 0.5 ? (2 * s * d / dA) : 255 - (2 - 2 * d / dA) * (255 - s); }, // TODO: Missing: soft-light 'hard-light': function(i) { - dst[i] = sRA <= 0.5 ? (2 * dst[i] * sRA / dA) : 255 - (2 - 2 * sRA / sA) * (255 - dst[i]); - dst[i + 1] = sGA <= 0.5 ? (2 * dst[i + 1] * sGA / dA) : 255 - (2 - 2 * sGA / sA) * (255 - dst[i + 1]); - dst[i + 2] = sBA <= 0.5 ? (2 * dst[i + 2] * sBA / dA) : 255 - (2 - 2 * sBA / sA) * (255 - dst[i + 2]); + var s = src[i] * sM, d = dst[i]; // dst is unmultiplied + dst[i] = s <= 0.5 ? (2 * d * s / dA) : 255 - (2 - 2 * s / sA) * (255 - d); }, 'color-dodge': function(i) { - dst[i] = src[i] == 255 && dRA == 0 ? 255 : min(255, dst[i] / (255 - src[i] )) * demultiply; - dst[i + 1] = src[i + 1] == 255 && dGA == 0 ? 255 : min(255, dst[i + 1] / (255 - src[i + 1])) * demultiply; - dst[i + 2] = src[i + 2] == 255 && dBA == 0 ? 255 : min(255, dst[i + 2] / (255 - src[i + 2])) * demultiply; + var s = src[i], d = dst[i]; // both unmultiplied + dst[i] = s == 255 && d * dM == 0 ? 255 : min(255, d / (255 - s)) * demultiply; }, 'color-burn': function(i) { - dst[i] = src[i] == 0 && dRA == 0 ? 0 : (1 - min(1, (1 - dRA) / sRA)) * demultiply; - dst[i + 1] = src[i + 1] == 0 && dGA == 0 ? 0 : (1 - min(1, (1 - dGA) / sGA)) * demultiply; - dst[i + 2] = src[i + 2] == 0 && dBA == 0 ? 0 : (1 - min(1, (1 - dBA) / sBA)) * demultiply; + var s = src[i] * sM, d = dst[i] * dM; + dst[i] = src[i] == 0 && d == 0 ? 0 : (1 - min(1, (1 - d) / s)) * demultiply; }, darken: function(i) { - dst[i] = (sRA > dRA ? dRA : sRA) * demultiply; - dst[i + 1] = (sGA > dGA ? dGA : sGA) * demultiply; - dst[i + 2] = (sBA > dBA ? dBA : sBA) * demultiply; + var s = src[i] * sM, d = dst[i] * dM; + dst[i] = (s > d ? d : s) * demultiply; }, lighten: function(i) { - dst[i] = (sRA < dRA ? dRA : sRA) * demultiply; - dst[i + 1] = (sGA < dGA ? dGA : sGA) * demultiply; - dst[i + 2] = (sBA < dBA ? dBA : sBA) * demultiply; + var s = src[i] * sM, d = dst[i] * dM; + dst[i] = (s < d ? d : s) * demultiply; }, difference: function(i) { - dst[i] = (sRA + dRA - 2 * min(sRA * dA, dRA * sA)) * demultiply; - dst[i + 1] = (sGA + dGA - 2 * min(sGA * dA, dGA * sA)) * demultiply; - dst[i + 2] = (sBA + dBA - 2 * min(sBA * dA, dBA * sA)) * demultiply; + var s = src[i] * sM, d = dst[i] * dM; + dst[i] = (s + d - 2 * min(s * dA, d * sA)) * demultiply; }, exclusion: function(i) { - dst[i] = (dRA + sRA - 2 * dRA * sRA) * demultiply; - dst[i + 1] = (dGA + sGA - 2 * dGA * sGA) * demultiply; - dst[i + 2] = (dBA + sBA - 2 * dBA * sBA) * demultiply; + var s = src[i] * sM, d = dst[i] * dM; + dst[i] = (d + s - 2 * d * s) * demultiply; }, // TODO: Missing: hue, saturation, color, luminosity - // Not in Illustrator: - + // TODO: Not in Illustrator. Remove these? 'src-in': function(i) { // Only differs from Photoshop in low - opacity areas - dA2 = sA * dA; - demultiply = 255 / dA2; - dst[i] = sRA * dA * demultiply; - dst[i + 1] = sGA * dA * demultiply; - dst[i + 2] = sBA * dA * demultiply; + dst[i] = sRA * dA * demultiply; }, add: function(i) { // Photoshop doesn't simply add the alpha channels, // this might be correct wrt SVG 1.2 - dA2 = min(1, sA + dA); - demultiply = 255 / dA2; - dst[i] = min(sRA + dRA, 1) * demultiply; - dst[i + 1] = min(sGA + dGA, 1) * demultiply; - dst[i + 2] = min(sBA + dBA, 1) * demultiply; + dst[i] = min(sRA + dRA, 1) * demultiply; } }; - var process = modes[blendMode] || modes.unsupported; + var alphas = { + 'src-in': function(sA, dA) { + return sA * dA; + }, + + add: function(sA, dA) { + return min(1, sA + dA); + } + }; + + var process = modes[blendMode]; + var alpha = alphas[blendMode]; + if (!process) + return; + // Divide opacity by 255 so it can be multiplied straight into pixel + // values to get 0 .. 1 range. opacity /= 255; for (var i = 0, l = dst.length; i < l; i += 4) { sA = src[i + 3] * opacity; dA = dst[i + 3] / 255; - dA2 = sA + dA - sA * dA; - sAM = sA / 255; - dAM = dA / 255; - sRA = src[i] * sAM; - dRA = dst[i] * dAM; - sGA = src[i + 1] * sAM; - dGA = dst[i + 1] * dAM; - sBA = src[i + 2] * sAM; - dBA = dst[i + 2] * dAM; - demultiply = 255 / dA2; + // Result alpha: + rA = alpha ? alpha(sA, dA) : sA + dA - sA * dA; + demultiply = 255 / rA; + // Multipliers: + sM = sA / 255; + dM = dA / 255; process(i); - dst[i + 3] = dA2 * 255; + process(i + 1); + process(i + 2); + dst[i + 3] = rA * 255; } destContext.putImageData(dstData, offset.x, offset.y); } From 62f5204810d1aae143260301c827260df5f1c0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 16:45:42 +0100 Subject: [PATCH 11/16] Shorten variable names. --- src/util/BlendMode.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index 79e650d4..1a18eb43 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -47,24 +47,24 @@ var BlendMode = { src = sourceContext.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height).data, min = Math.min, - sA, dA, rA, sM, dM, demultiply; + sA, dA, rA, sM, dM, rM; // TODO: Some blend modes seem broken at the moment, e.g. // dodge, burn var modes = { normal: function(i) { var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s + d * (1 - sA)) * demultiply; + dst[i] = (s + d * (1 - sA)) * rM; }, multiply: function(i) { var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s * d + s * (1 - dA) + d * (1 - sA)) * demultiply; + dst[i] = (s * d + s * (1 - dA) + d * (1 - sA)) * rM; }, screen: function(i) { var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s * (1 - d) + d) * demultiply; + dst[i] = (s * (1 - d) + d) * rM; }, overlay: function(i) { @@ -82,32 +82,32 @@ var BlendMode = { 'color-dodge': function(i) { var s = src[i], d = dst[i]; // both unmultiplied - dst[i] = s == 255 && d * dM == 0 ? 255 : min(255, d / (255 - s)) * demultiply; + dst[i] = s == 255 && d * dM == 0 ? 255 : min(255, d / (255 - s)) * rM; }, 'color-burn': function(i) { var s = src[i] * sM, d = dst[i] * dM; - dst[i] = src[i] == 0 && d == 0 ? 0 : (1 - min(1, (1 - d) / s)) * demultiply; + dst[i] = src[i] == 0 && d == 0 ? 0 : (1 - min(1, (1 - d) / s)) * rM; }, darken: function(i) { var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s > d ? d : s) * demultiply; + dst[i] = (s > d ? d : s) * rM; }, lighten: function(i) { var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s < d ? d : s) * demultiply; + dst[i] = (s < d ? d : s) * rM; }, difference: function(i) { var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s + d - 2 * min(s * dA, d * sA)) * demultiply; + dst[i] = (s + d - 2 * min(s * dA, d * sA)) * rM; }, exclusion: function(i) { var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (d + s - 2 * d * s) * demultiply; + dst[i] = (d + s - 2 * d * s) * rM; }, // TODO: Missing: hue, saturation, color, luminosity @@ -115,13 +115,13 @@ var BlendMode = { // TODO: Not in Illustrator. Remove these? 'src-in': function(i) { // Only differs from Photoshop in low - opacity areas - dst[i] = sRA * dA * demultiply; + dst[i] = sRA * dA * rM; }, add: function(i) { // Photoshop doesn't simply add the alpha channels, // this might be correct wrt SVG 1.2 - dst[i] = min(sRA + dRA, 1) * demultiply; + dst[i] = min(sRA + dRA, 1) * rM; } }; @@ -147,7 +147,7 @@ var BlendMode = { dA = dst[i + 3] / 255; // Result alpha: rA = alpha ? alpha(sA, dA) : sA + dA - sA * dA; - demultiply = 255 / rA; + rM = 255 / rA; // Multipliers: sM = sA / 255; dM = dA / 255; From 9142c974c35140d1d9a0e248a53b7ec6a338f0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 16:49:48 +0100 Subject: [PATCH 12/16] Revert to longer version of blend mode code as it is faster. --- src/util/BlendMode.js | 114 ++++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 50 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index 1a18eb43..96c6cc27 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -47,113 +47,127 @@ var BlendMode = { src = sourceContext.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height).data, min = Math.min, - sA, dA, rA, sM, dM, rM; + sA, dA, rA, sM, dM, rM, sRA, sGA, sBA, dRA, dGA, dBA; - // TODO: Some blend modes seem broken at the moment, e.g. - // dodge, burn + // TODO: Some blend modes seem broken at the moment, e.g. dodge, burn var modes = { + unsupported: function(i) { + // Render checker pattern + rA = 1; + dst[i] = 255; + dst[i + 1] = i % 8 == 0 ? 255 : 0; + dst[i + 2] = i % 8 == 0 ? 0 : 255; + }, + normal: function(i) { - var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s + d * (1 - sA)) * rM; + dst[i] = (sRA + dRA - dRA * sA) * rM; + dst[i + 1] = (sGA + dGA - dGA * sA) * rM; + dst[i + 2] = (sBA + dBA - dBA * sA) * rM; }, multiply: function(i) { - var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s * d + s * (1 - dA) + d * (1 - sA)) * rM; + dst[i] = (sRA * dRA + sRA * (1 - dA) + dRA * (1 - sA)) * rM; + dst[i + 1] = (sGA * dGA + sGA * (1 - dA) + dGA * (1 - sA)) * rM; + dst[i + 2] = (sBA * dBA + sBA * (1 - dA) + dBA * (1 - sA)) * rM; }, screen: function(i) { - var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s * (1 - d) + d) * rM; + dst[i] = (sRA + dRA - sRA * dRA) * rM; + dst[i + 1] = (sGA + dGA - sGA * dGA) * rM; + dst[i + 2] = (sBA + dBA - sBA * dBA) * rM; }, overlay: function(i) { // Correct for 100% opacity case; colors get clipped as opacity falls - var s = src[i], d = dst[i] * dM; // src is unmultiplied - dst[i] = d <= 0.5 ? (2 * s * d / dA) : 255 - (2 - 2 * d / dA) * (255 - s); + dst[i] = dRA <= 0.5 ? (2 * src[i] * dRA / dA) : 255 - (2 - 2 * dRA / dA) * (255 - src[i]); + dst[i + 1] = dGA <= 0.5 ? (2 * src[i + 1] * dGA / dA) : 255 - (2 - 2 * dGA / dA) * (255 - src[i + 1]); + dst[i + 2] = dBA <= 0.5 ? (2 * src[i + 2] * dBA / dA) : 255 - (2 - 2 * dBA / dA) * (255 - src[i + 2]); }, // TODO: Missing: soft-light 'hard-light': function(i) { - var s = src[i] * sM, d = dst[i]; // dst is unmultiplied - dst[i] = s <= 0.5 ? (2 * d * s / dA) : 255 - (2 - 2 * s / sA) * (255 - d); + dst[i] = sRA <= 0.5 ? (2 * dst[i] * sRA / dA) : 255 - (2 - 2 * sRA / sA) * (255 - dst[i]); + dst[i + 1] = sGA <= 0.5 ? (2 * dst[i + 1] * sGA / dA) : 255 - (2 - 2 * sGA / sA) * (255 - dst[i + 1]); + dst[i + 2] = sBA <= 0.5 ? (2 * dst[i + 2] * sBA / dA) : 255 - (2 - 2 * sBA / sA) * (255 - dst[i + 2]); }, 'color-dodge': function(i) { - var s = src[i], d = dst[i]; // both unmultiplied - dst[i] = s == 255 && d * dM == 0 ? 255 : min(255, d / (255 - s)) * rM; + dst[i] = src[i] == 255 && dRA == 0 ? 255 : min(255, dst[i] / (255 - src[i] )) * rM; + dst[i + 1] = src[i + 1] == 255 && dGA == 0 ? 255 : min(255, dst[i + 1] / (255 - src[i + 1])) * rM; + dst[i + 2] = src[i + 2] == 255 && dBA == 0 ? 255 : min(255, dst[i + 2] / (255 - src[i + 2])) * rM; }, 'color-burn': function(i) { - var s = src[i] * sM, d = dst[i] * dM; - dst[i] = src[i] == 0 && d == 0 ? 0 : (1 - min(1, (1 - d) / s)) * rM; + dst[i] = src[i] == 0 && dRA == 0 ? 0 : (1 - min(1, (1 - dRA) / sRA)) * rM; + dst[i + 1] = src[i + 1] == 0 && dGA == 0 ? 0 : (1 - min(1, (1 - dGA) / sGA)) * rM; + dst[i + 2] = src[i + 2] == 0 && dBA == 0 ? 0 : (1 - min(1, (1 - dBA) / sBA)) * rM; }, darken: function(i) { - var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s > d ? d : s) * rM; + dst[i] = (sRA > dRA ? dRA : sRA) * rM; + dst[i + 1] = (sGA > dGA ? dGA : sGA) * rM; + dst[i + 2] = (sBA > dBA ? dBA : sBA) * rM; }, lighten: function(i) { - var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s < d ? d : s) * rM; + dst[i] = (sRA < dRA ? dRA : sRA) * rM; + dst[i + 1] = (sGA < dGA ? dGA : sGA) * rM; + dst[i + 2] = (sBA < dBA ? dBA : sBA) * rM; }, difference: function(i) { - var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (s + d - 2 * min(s * dA, d * sA)) * rM; + dst[i] = (sRA + dRA - 2 * min(sRA * dA, dRA * sA)) * rM; + dst[i + 1] = (sGA + dGA - 2 * min(sGA * dA, dGA * sA)) * rM; + dst[i + 2] = (sBA + dBA - 2 * min(sBA * dA, dBA * sA)) * rM; }, exclusion: function(i) { - var s = src[i] * sM, d = dst[i] * dM; - dst[i] = (d + s - 2 * d * s) * rM; + dst[i] = (dRA + sRA - 2 * dRA * sRA) * rM; + dst[i + 1] = (dGA + sGA - 2 * dGA * sGA) * rM; + dst[i + 2] = (dBA + sBA - 2 * dBA * sBA) * rM; }, // TODO: Missing: hue, saturation, color, luminosity - // TODO: Not in Illustrator. Remove these? + // Not in Illustrator: + 'src-in': function(i) { // Only differs from Photoshop in low - opacity areas - dst[i] = sRA * dA * rM; + rA = sA * dA; + rM = 255 / rA; + dst[i] = sRA * dA * rM; + dst[i + 1] = sGA * dA * rM; + dst[i + 2] = sBA * dA * rM; }, add: function(i) { // Photoshop doesn't simply add the alpha channels, // this might be correct wrt SVG 1.2 - dst[i] = min(sRA + dRA, 1) * rM; + rA = min(1, sA + dA); + rM = 255 / rA; + dst[i] = min(sRA + dRA, 1) * rM; + dst[i + 1] = min(sGA + dGA, 1) * rM; + dst[i + 2] = min(sBA + dBA, 1) * rM; } }; - var alphas = { - 'src-in': function(sA, dA) { - return sA * dA; - }, - - add: function(sA, dA) { - return min(1, sA + dA); - } - }; - - var process = modes[blendMode]; - var alpha = alphas[blendMode]; - if (!process) - return; - // Divide opacity by 255 so it can be multiplied straight into pixel - // values to get 0 .. 1 range. + var process = modes[blendMode] || modes.unsupported; opacity /= 255; for (var i = 0, l = dst.length; i < l; i += 4) { sA = src[i + 3] * opacity; dA = dst[i + 3] / 255; - // Result alpha: - rA = alpha ? alpha(sA, dA) : sA + dA - sA * dA; - rM = 255 / rA; - // Multipliers: + rA = sA + dA - sA * dA; sM = sA / 255; dM = dA / 255; + sRA = src[i] * sM; + dRA = dst[i] * dM; + sGA = src[i + 1] * sM; + dGA = dst[i + 1] * dM; + sBA = src[i + 2] * sM; + dBA = dst[i + 2] * dM; + rM = 255 / rA; process(i); - process(i + 1); - process(i + 2); dst[i + 3] = rA * 255; } destContext.putImageData(dstData, offset.x, offset.y); From b08abcdd6933f802d7d9f55e9467845344aee9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 16:50:14 +0100 Subject: [PATCH 13/16] Remove 'unsupported' blend mode. --- src/util/BlendMode.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index 96c6cc27..ded19181 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -51,14 +51,6 @@ var BlendMode = { // TODO: Some blend modes seem broken at the moment, e.g. dodge, burn var modes = { - unsupported: function(i) { - // Render checker pattern - rA = 1; - dst[i] = 255; - dst[i + 1] = i % 8 == 0 ? 255 : 0; - dst[i + 2] = i % 8 == 0 ? 0 : 255; - }, - normal: function(i) { dst[i] = (sRA + dRA - dRA * sA) * rM; dst[i + 1] = (sGA + dGA - dGA * sA) * rM; @@ -152,7 +144,9 @@ var BlendMode = { } }; - var process = modes[blendMode] || modes.unsupported; + var process = modes[blendMode]; + if (!process) + return; opacity /= 255; for (var i = 0, l = dst.length; i < l; i += 4) { sA = src[i + 3] * opacity; From 8bbb3744fe33a299329a85c79fc8428aa57a3b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 17:09:32 +0100 Subject: [PATCH 14/16] Use min() / max() in darken / lighten for shorter code. --- src/util/BlendMode.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index ded19181..bf0f153d 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -47,6 +47,7 @@ var BlendMode = { src = sourceContext.getImageData(0, 0, sourceCanvas.width, sourceCanvas.height).data, min = Math.min, + max = Math.max, sA, dA, rA, sM, dM, rM, sRA, sGA, sBA, dRA, dGA, dBA; // TODO: Some blend modes seem broken at the moment, e.g. dodge, burn @@ -97,15 +98,15 @@ var BlendMode = { }, darken: function(i) { - dst[i] = (sRA > dRA ? dRA : sRA) * rM; - dst[i + 1] = (sGA > dGA ? dGA : sGA) * rM; - dst[i + 2] = (sBA > dBA ? dBA : sBA) * rM; + dst[i] = min(sRA, dRA) * rM; + dst[i + 1] = min(sGA, dGA) * rM; + dst[i + 2] = min(sBA, dBA) * rM; }, lighten: function(i) { - dst[i] = (sRA < dRA ? dRA : sRA) * rM; - dst[i + 1] = (sGA < dGA ? dGA : sGA) * rM; - dst[i + 2] = (sBA < dBA ? dBA : sBA) * rM; + dst[i] = max(sRA, dRA) * rM; + dst[i + 1] = max(sGA, dGA) * rM; + dst[i + 2] = max(sBA, dBA) * rM; }, difference: function(i) { From 3850e4a53b9e3ee6296455b5bd79a07d9944f789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 17:10:40 +0100 Subject: [PATCH 15/16] Optimise code for normal and multiply blend modes by precalculating values. --- src/util/BlendMode.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index bf0f153d..b1b20a02 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -53,15 +53,17 @@ var BlendMode = { // TODO: Some blend modes seem broken at the moment, e.g. dodge, burn var modes = { normal: function(i) { - dst[i] = (sRA + dRA - dRA * sA) * rM; - dst[i + 1] = (sGA + dGA - dGA * sA) * rM; - dst[i + 2] = (sBA + dBA - dBA * sA) * rM; + var sA1 = 1 - sA; + dst[i] = (sRA + dRA * sA1) * rM; + dst[i + 1] = (sGA + dGA * sA1) * rM; + dst[i + 2] = (sBA + dRA * sA1) * rM; }, multiply: function(i) { - dst[i] = (sRA * dRA + sRA * (1 - dA) + dRA * (1 - sA)) * rM; - dst[i + 1] = (sGA * dGA + sGA * (1 - dA) + dGA * (1 - sA)) * rM; - dst[i + 2] = (sBA * dBA + sBA * (1 - dA) + dBA * (1 - sA)) * rM; + var sA1 = 1 - sA, dA1 = 1 - dA; + dst[i] = (sRA * dRA + sRA * dA1 + dRA * sA1) * rM; + dst[i + 1] = (sGA * dGA + sGA * dA1 + dGA * sA1) * rM; + dst[i + 2] = (sBA * dBA + sBA * dA1 + dBA * sA1) * rM; }, screen: function(i) { From f58aa2a23be3a571be879c97b8d7e4bb214ca051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 1 Jun 2011 17:11:06 +0100 Subject: [PATCH 16/16] Add comment about overlay mode being the reverse of hard-light. --- src/util/BlendMode.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/BlendMode.js b/src/util/BlendMode.js index b1b20a02..b5fc7d11 100644 --- a/src/util/BlendMode.js +++ b/src/util/BlendMode.js @@ -73,6 +73,7 @@ var BlendMode = { }, overlay: function(i) { + // = Reverse of hard-light // Correct for 100% opacity case; colors get clipped as opacity falls dst[i] = dRA <= 0.5 ? (2 * src[i] * dRA / dA) : 255 - (2 - 2 * dRA / dA) * (255 - src[i]); dst[i + 1] = dGA <= 0.5 ? (2 * src[i + 1] * dGA / dA) : 255 - (2 - 2 * dGA / dA) * (255 - src[i + 1]);