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); - } + }; };