diff --git a/examples/Scripts/BlendModes.html b/examples/Scripts/BlendModes.html
new file mode 100644
index 00000000..4d226970
--- /dev/null
+++ b/examples/Scripts/BlendModes.html
@@ -0,0 +1,65 @@
+
+
+
+
+ BlendModes
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/canvas/BlendMode.js b/src/canvas/BlendMode.js
index 1d078906..e2454307 100644
--- a/src/canvas/BlendMode.js
+++ b/src/canvas/BlendMode.js
@@ -10,201 +10,219 @@
* All rights reserved.
*/
-var BlendMode = {
- process: function(blendMode, srcContext, dstContext, alpha, offset) {
+var BlendMode = new function() {
+ var min = Math.min,
+ max = Math.max,
+ abs = Math.abs,
+ sr, sg, sb, sa, // source
+ br, bg, bb, ba, // backdrop
+ dr, dg, db; // destination
+
+ // Conversion methods for HSL modes, as described by
+ // http://www.aiim.org/documents/standards/pdf/blend_modes.pdf
+ // The setters modify the variables dr, dg, db directly.
+
+ function getLum(r, g, b) {
+ return 0.2989 * r + 0.587 * g + 0.114 * b;
+ }
+
+ function setLum(r, g, b, l) {
+ var d = l - getLum(r, g, b);
+ dr = r + d;
+ dg = g + d;
+ db = b + d;
+ var l = getLum(dr, dg, db),
+ mn = min(dr, dg, db),
+ mx = max(dr, dg, db);
+ if (mn < 0) {
+ var lmn = l - mn;
+ dr = l + (dr - l) * l / lmn;
+ dg = l + (dg - l) * l / lmn;
+ db = l + (db - l) * l / lmn;
+ }
+ if (mx > 255) {
+ var ln = 255 - l,
+ mxl = mx - l;
+ dr = l + (dr - l) * ln / mxl;
+ dg = l + (dg - l) * ln / mxl;
+ db = l + (db - l) * ln / mxl;
+ }
+ }
+
+ function getSat(r, g, b) {
+ return max(r, g, b) - min(r, g, b);
+ }
+
+ function setSat(r, g, b, s) {
+ var col = [r, g, b],
+ mx = max(r, g, b), // max
+ mn = min(r, g, b), // min
+ md; // mid
+ // Determine indices for min and max in col:
+ mn = mn === r ? 0 : mn === g ? 1 : 2;
+ mx = mx === r ? 0 : mx === g ? 1 : 2;
+ // Determine the index in col that is not used yet by min and max,
+ // and assign it to mid:
+ md = min(mn, mx) === 0 ? max(mn, mx) === 1 ? 2 : 1 : 0;
+ // Now perform the actual algorithm
+ if (col[mx] > col[mn]) {
+ col[md] = (col[md] - col[mn]) * s / (col[mx] - col[mn]);
+ col[mx] = s;
+ } else {
+ col[md] = col[mx] = 0;
+ }
+ col[mn] = 0;
+ // Finally write out the values
+ dr = col[0];
+ dg = col[1];
+ db = col[2];
+ }
+
+ var modes = {
+ // B(Cb, Cs) = Cb x Cs
+ multiply: function() {
+ dr = br * sr / 255;
+ dg = bg * sg / 255;
+ db = bb * sb / 255;
+ },
+
+ // B(Cb, Cs) = 1 - [(1 - Cb) x (1 - Cs)] = Cb + Cs -(Cb x Cs)
+ screen: function() {
+ dr = br + sr - (br * sr / 255);
+ dg = bg + sg - (bg * sg / 255);
+ db = bb + sb - (bb * sb / 255);
+ },
+
+ // B(Cb, Cs) = HardLight(Cs, Cb)
+ overlay: function() {
+ // = Reverse of hard-light
+ dr = br < 128 ? 2 * br * sr / 255 : 255 - 2 * (255 - br) * (255 - sr) / 255;
+ dg = bg < 128 ? 2 * bg * sg / 255 : 255 - 2 * (255 - bg) * (255 - sg) / 255;
+ db = bb < 128 ? 2 * bb * sb / 255 : 255 - 2 * (255 - bb) * (255 - sb) / 255;
+ },
+
+ 'soft-light': function() {
+ var t = sr * br / 255;
+ dr = t + br * (255 - (255 - br) * (255 - sr) / 255 - t) / 255;
+ t = sg * bg / 255;
+ dg = t + bg * (255 - (255 - bg) * (255 - sg) / 255 - t) / 255;
+ t = sb * bb / 255;
+ db = t + bb * (255 - (255 - bb) * (255 - sb) / 255 - t) / 255;
+ },
+
+ // if (Cs <= 0.5) B(Cb, Cs) = Multiply(Cb, 2 x Cs)
+ // else B(Cb, Cs) = Screen(Cb, 2 x Cs -1)
+ 'hard-light': function() {
+ dr = sr < 128 ? 2 * sr * br / 255 : 255 - 2 * (255 - sr) * (255 - br) / 255;
+ dg = sg < 128 ? 2 * sg * bg / 255 : 255 - 2 * (255 - sg) * (255 - bg) / 255;
+ db = sb < 128 ? 2 * sb * bb / 255 : 255 - 2 * (255 - sb) * (255 - bb) / 255;
+ },
+
+ // if (Cb == 0) B(Cb, Cs) = 0
+ // else if (Cs == 1) B(Cb, Cs) = 1
+ // else B(Cb, Cs) = min(1, Cb / (1 - Cs))
+ 'color-dodge': function() {
+ dr = br === 0 ? 0 : sr === 255 ? 255 : min(255, 255 * br / (255 - sr));
+ dg = bg === 0 ? 0 : sg === 255 ? 255 : min(255, 255 * bg / (255 - sg));
+ db = bb === 0 ? 0 : sb === 255 ? 255 : min(255, 255 * bb / (255 - sb));
+ },
+
+ // if (Cb == 1) B(Cb, Cs) = 1
+ // else if(Cs == 0) B(Cb, Cs) = 0
+ // else B(Cb, Cs) = 1 - min(1, (1 - Cb) / Cs)
+ 'color-burn': function() {
+ dr = br === 255 ? 255 : sr === 0 ? 0 : max(0, 255 - (255 - br) * 255 / sr);
+ dg = bg === 255 ? 255 : sg === 0 ? 0 : max(0, 255 - (255 - bg) * 255 / sg);
+ db = bb === 255 ? 255 : sb === 0 ? 0 : max(0, 255 - (255 - bb) * 255 / sb);
+ },
+
+ // B(Cb, Cs) = min(Cb, Cs)
+ darken: function() {
+ dr = br < sr ? br : sr;
+ dg = bg < sg ? bg : sg;
+ db = bb < sb ? bb : sb;
+ },
+
+ // B(Cb, Cs) = max(Cb, Cs)
+ lighten: function() {
+ dr = br > sr ? br : sr;
+ dg = bg > sg ? bg : sg;
+ db = bb > sb ? bb : sb;
+ },
+
+ // B(Cb, Cs) = | Cb - Cs |
+ difference: function() {
+ dr = br - sr;
+ if (dr < 0)
+ dr = -dr;
+ dg = bg - sg;
+ if (dg < 0)
+ dg = -dg;
+ db = bb - sb;
+ if (db < 0)
+ db = -db;
+ },
+
+ // B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs
+ exclusion: function() {
+ dr = br + sr * (255 - br - br) / 255;
+ dg = bg + sg * (255 - bg - bg) / 255;
+ db = bb + sb * (255 - bb - bb) / 255;
+ },
+
+ // HSL Modes:
+ hue: function() {
+ setSat(sr, sg, sb, getSat(br, bg, bb));
+ setLum(dr, dg, db, getLum(br, bg, bb));
+ },
+
+ saturation: function() {
+ setSat(br, bg, bb, getSat(sr, sg, sb));
+ setLum(dr, dg, db, getLum(br, bg, bb));
+ },
+
+ luminosity: function() {
+ setLum(br, bg, bb, getLum(sr, sg, sb));
+ },
+
+ color: function() {
+ setLum(sr, sg, sb, getLum(br, bg, bb));
+ },
+
+ // TODO: Not in Illustrator:
+ add: function() {
+ dr = min(br + sr, 255);
+ dg = min(bg + sg, 255);
+ db = min(bb + sb, 255);
+ },
+
+ subtract: function() {
+ dr = max(br - sr, 0);
+ dg = max(bg - sg, 0);
+ db = max(bb - sb, 0);
+ },
+
+ average: function() {
+ dr = (br + sr) / 2;
+ dg = (bg + sg) / 2;
+ db = (bb + sb) / 2;
+ },
+
+ negation: function() {
+ dr = 255 - abs(255 - sr - br);
+ dg = 255 - abs(255 - sg - bg);
+ db = 255 - abs(255 - sb - bb);
+ }
+ };
+
+ this.process = function(blendMode, srcContext, dstContext, alpha, offset) {
var srcCanvas = srcContext.canvas,
dstData = dstContext.getImageData(offset.x, offset.y,
srcCanvas.width, srcCanvas.height),
dst = dstData.data,
src = srcContext.getImageData(0, 0,
- srcCanvas.width, srcCanvas.height).data,
- min = Math.min,
- max = Math.max,
- abs = Math.abs,
- sr, sg, sb, sa, // source
- br, bg, bb, ba, // backdrop
- dr, dg, db; // destination
+ srcCanvas.width, srcCanvas.height).data;
- // Conversion methods for HSL modes, as described by
- // http://www.aiim.org/documents/standards/pdf/blend_modes.pdf
- // The setters modify the variables dr, dg, db directly.
-
- function getLum(r, g, b) {
- return 0.2989 * r + 0.587 * g + 0.114 * b;
- }
-
- function setLum(r, g, b, l) {
- var d = l - getLum(r, g, b);
- dr = r + d;
- dg = g + d;
- db = b + d;
- var l = getLum(dr, dg, db),
- mn = min(dr, dg, db),
- mx = max(dr, dg, db);
- if (mn < 0) {
- var lmn = l - mn;
- dr = l + (dr - l) * l / lmn;
- dg = l + (dg - l) * l / lmn;
- db = l + (db - l) * l / lmn;
- }
- if (mx > 255) {
- var ln = 255 - l, mxl = mx - l;
- dr = l + (dr - l) * ln / mxl;
- dg = l + (dg - l) * ln / mxl;
- db = l + (db - l) * ln / mxl;
- }
- }
-
- function getSat(r, g, b) {
- return max(r, g, b) - min(r, g, b);
- }
-
- function setSat(r, g, b, s) {
- var col = [r, g, b],
- mx = max(r, g, b), // max
- mn = min(r, g, b), // min
- md; // mid
- // Determine indices for min and max in col:
- mn = mn == r ? 0 : mn == g ? 1 : 2;
- mx = mx == r ? 0 : mx == g ? 1 : 2;
- // Determine the index in col that is not used yet by min and max,
- // and assign it to mid:
- md = min(mn, mx) == 0 ? max(mn, mx) == 1 ? 2 : 1 : 0;
- // Now perform the actual algorithm
- if (col[mx] > col[mn]) {
- col[md] = (col[md] - col[mn]) * s / (col[mx] - col[mn]);
- col[mx] = s;
- } else {
- col[md] = col[mx] = 0;
- }
- col[mn] = 0;
- // Finally write out the values
- dr = col[0];
- dg = col[1];
- db = col[2];
- }
-
- var modes = {
- multiply: function() {
- dr = br * sr / 255;
- dg = bg * sg / 255;
- db = bb * sb / 255;
- },
-
- screen: function() {
- dr = 255 - (255 - br) * (255 - sr) / 255;
- dg = 255 - (255 - bg) * (255 - sg) / 255;
- db = 255 - (255 - bb) * (255 - sb) / 255;
- },
-
- overlay: function() {
- dr = br < 128 ? 2 * br * sr / 255 : 255 - 2 * (255 - br) * (255 - sr) / 255;
- dg = bg < 128 ? 2 * bg * sg / 255 : 255 - 2 * (255 - bg) * (255 - sg) / 255;
- db = bb < 128 ? 2 * bb * sb / 255 : 255 - 2 * (255 - bb) * (255 - sb) / 255;
- },
-
- 'soft-light': function() {
- var t = sr * br / 255;
- dr = t + br * (255 - (255 - br) * (255 - sr) / 255 - t) / 255;
- t = sg * bg / 255;
- dg = t + bg * (255 - (255 - bg) * (255 - sg) / 255 - t) / 255;
- t = sb * bb / 255;
- db = t + bb * (255 - (255 - bb) * (255 - sb) / 255 - t) / 255;
- },
-
- 'hard-light': function() {
- // = Reverse of overlay
- dr = sr < 128 ? 2 * sr * br / 255 : 255 - 2 * (255 - sr) * (255 - br) / 255;
- dg = sg < 128 ? 2 * sg * bg / 255 : 255 - 2 * (255 - sg) * (255 - bg) / 255;
- db = sb < 128 ? 2 * sb * bb / 255 : 255 - 2 * (255 - sb) * (255 - bb) / 255;
- },
-
- 'color-dodge': function() {
- dr = sr == 255 ? sr : min(255, br * 255 / (255 - sr));
- dg = sg == 255 ? sg : min(255, bg * 255 / (255 - sg));
- db = sb == 255 ? sb : min(255, bb * 255 / (255 - sb));
- },
-
- 'color-burn': function() {
- dr = sr == 0 ? 0 : max(255 - ((255 - br) * 255) / sr, 0);
- dg = sg == 0 ? 0 : max(255 - ((255 - bg) * 255) / sg, 0);
- db = sb == 0 ? 0 : max(255 - ((255 - bb) * 255) / sb, 0);
- },
-
- darken: function() {
- dr = br < sr ? br : sr;
- dg = bg < sg ? bg : sg;
- db = bb < sb ? bb : sb;
- },
-
- lighten: function() {
- dr = br > sr ? br : sr;
- dg = bg > sg ? bg : sg;
- db = bb > sb ? bb : sb;
- },
-
- difference: function() {
- dr = br - sr;
- if (dr < 0)
- dr = -dr;
- dg = bg - sg;
- if (dg < 0)
- dg = -dg;
- db = bb - sb;
- if (db < 0)
- db = -db;
- },
-
- exclusion: function() {
- dr = br + sr * (255 - br - br) / 255;
- dg = bg + sg * (255 - bg - bg) / 255;
- db = bb + sb * (255 - bb - bb) / 255;
- },
-
- // HSL Modes:
- hue: function() {
- setSat(sr, sg, sb, getSat(br, bg, bb));
- setLum(dr, dg, db, getLum(br, bg, bb));
- },
-
- saturation: function() {
- setSat(br, bg, bb, getSat(sr, sg, sb));
- setLum(dr, dg, db, getLum(br, bg, bb));
- },
-
- luminosity: function() {
- setLum(br, bg, bb, getLum(sr, sg, sb));
- },
-
- color: function() {
- setLum(sr, sg, sb, getLum(br, bg, bb));
- },
-
- // TODO: Not in Illustrator:
- add: function() {
- dr = min(br + sr, 255);
- dg = min(bg + sg, 255);
- db = min(bb + sb, 255);
- },
-
- subtract: function() {
- dr = max(br - sr, 0);
- dg = max(bg - sg, 0);
- db = max(bb - sb, 0);
- },
-
- average: function() {
- dr = (br + sr) / 2;
- dg = (bg + sg) / 2;
- db = (bb + sb) / 2;
- },
-
- negation: function() {
- dr = 255 - abs(255 - sr - br);
- dg = 255 - abs(255 - sg - bg);
- db = 255 - abs(255 - sb - bb);
- }
- };
var process = modes[blendMode];
if (!process)
@@ -228,5 +246,5 @@ var BlendMode = {
dst[i + 3] = sa * alpha + a2 * ba;
}
dstContext.putImageData(dstData, offset.x, offset.y);
- }
+ };
};