mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Restructure BlendMode code, fix issues with color-dodge and color-burn, and create BlendModes.html example.
All modes should be implemented according to specs now.
This commit is contained in:
parent
1b42822c2d
commit
b133d8fe2e
2 changed files with 273 additions and 190 deletions
65
examples/Scripts/BlendModes.html
Normal file
65
examples/Scripts/BlendModes.html
Normal file
File diff suppressed because one or more lines are too long
|
@ -10,201 +10,219 @@
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var BlendMode = {
|
var BlendMode = new function() {
|
||||||
process: function(blendMode, srcContext, dstContext, alpha, offset) {
|
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,
|
var srcCanvas = srcContext.canvas,
|
||||||
dstData = dstContext.getImageData(offset.x, offset.y,
|
dstData = dstContext.getImageData(offset.x, offset.y,
|
||||||
srcCanvas.width, srcCanvas.height),
|
srcCanvas.width, srcCanvas.height),
|
||||||
dst = dstData.data,
|
dst = dstData.data,
|
||||||
src = srcContext.getImageData(0, 0,
|
src = srcContext.getImageData(0, 0,
|
||||||
srcCanvas.width, srcCanvas.height).data,
|
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
|
|
||||||
|
|
||||||
// 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];
|
var process = modes[blendMode];
|
||||||
if (!process)
|
if (!process)
|
||||||
|
@ -228,5 +246,5 @@ var BlendMode = {
|
||||||
dst[i + 3] = sa * alpha + a2 * ba;
|
dst[i + 3] = sa * alpha + a2 * ba;
|
||||||
}
|
}
|
||||||
dstContext.putImageData(dstData, offset.x, offset.y);
|
dstContext.putImageData(dstData, offset.x, offset.y);
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue