294 lines
8.5 KiB
JavaScript
294 lines
8.5 KiB
JavaScript
// Test only these blend modes for now, since soft light as well as the non separate blend modes seem to be broken,
|
|
// as stated in // https://bugs.webkit.org/show_bug.cgi?id=105609.
|
|
// Radar 12922671.
|
|
var blendModes = ["source-over", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn",
|
|
"hard-light", "difference", "exclusion"];
|
|
|
|
// Helper functions for separate blend mode
|
|
|
|
var separateBlendmodes = ["normal", "multiply", "screen", "overlay",
|
|
"darken", "lighten", "colorDodge","colorBurn",
|
|
"hardLight", "difference", "exclusion"];
|
|
|
|
var separateBlendFunctions = {
|
|
normal: function(b, s) {
|
|
return s;
|
|
},
|
|
multiply: function(b, s) {
|
|
return b * s;
|
|
},
|
|
screen: function(b, s) {
|
|
return b + s - b * s;
|
|
},
|
|
overlay: function(b, s) {
|
|
return separateBlendFunctions.hardLight(b, s);
|
|
},
|
|
darken: function(b, s) {
|
|
return Math.min(b, s);
|
|
},
|
|
lighten: function(b, s) {
|
|
return Math.max(b, s);
|
|
},
|
|
colorDodge: function(b, s) {
|
|
if(b == 1)
|
|
return 1;
|
|
return Math.min(1, s / (1 - b));
|
|
},
|
|
colorBurn: function(b, s) {
|
|
if(s == 0)
|
|
return 0;
|
|
return 1 - Math.min(1, (1 - b) / s);
|
|
},
|
|
hardLight: function(b, s) {
|
|
if(s <= 0.5)
|
|
return separateBlendFunctions.multiply(s, 2 * b);
|
|
|
|
return separateBlendFunctions.screen(s, 2 * b - 1);
|
|
},
|
|
difference: function(b, s) {
|
|
return Math.abs(b - s);
|
|
},
|
|
exclusion: function(b, s) {
|
|
return s + b - 2 * b * s;
|
|
}
|
|
};
|
|
|
|
function applyBlendMode(b, s, blendFunc) {
|
|
var resultedColor = [0, 0, 0, 255];
|
|
for (var i = 0; i < 3; ++i)
|
|
resultedColor[i] = 255 * (s[3] * (1 - b[3]) * s[i] + b[3] * s[3] * blendFunc(b[i], s[i]) + (1 - s[3]) * b[3] * b[i]);
|
|
return resultedColor;
|
|
}
|
|
|
|
|
|
// Helper functions for nonseparate blend modes
|
|
|
|
var nonSeparateBlendModes = ["hue", "saturation", "color", "luminosity"];
|
|
|
|
function luminosity(c) {
|
|
return 0.3 * c[0] + 0.59 * c[1] + 0.11 * c[2];
|
|
}
|
|
|
|
function clipColor(c) {
|
|
var l = luminosity(c);
|
|
var n = Math.min(c[0], c[1], c[2]);
|
|
var x = Math.max(c[0], c[1], c[2]);
|
|
|
|
if (n < 0) {
|
|
c[0] = l + (((c[0] - l) * l) / (l - n));
|
|
c[1] = l + (((c[1] - l) * l) / (l - n));
|
|
c[2] = l + (((c[1] - l) * l) / (l - n));
|
|
}
|
|
|
|
if (x > 1) {
|
|
c[0] = l + (((c[0] - l) * (1 - l)) / (x - l));
|
|
c[1] = l + (((c[1] - l) * (1 - l)) / (x - l));
|
|
c[2] = l + (((c[2] - l) * (1 - l)) / (x - l));
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
function setLuminosity(c, l) {
|
|
var d = l - luminosity(c);
|
|
c[0] += d;
|
|
c[1] += d;
|
|
c[2] += d;
|
|
return clipColor(c);
|
|
}
|
|
|
|
function saturation(c) {
|
|
return Math.max(c[0], c[1], c[2]) - Math.min(c[0], c[1], c[2]);
|
|
}
|
|
|
|
function setSaturation(c, s) {
|
|
var max = Math.max(c[0], c[1], c[2]);
|
|
var min = Math.min(c[0], c[1], c[2]);
|
|
var index_max = -1;
|
|
var index_min = -1;
|
|
|
|
for (var i = 0; i < 3; ++i) {
|
|
if (c[i] == min && index_min == -1) {
|
|
index_min = i;
|
|
continue;
|
|
}
|
|
if (c[i] == max && index_max == -1)
|
|
index_max = i;
|
|
}
|
|
var index_mid = 3 - index_max - index_min;
|
|
var mid = c[index_mid];
|
|
|
|
|
|
if (max > min) {
|
|
mid = (((mid - min) * s) / (max - min));
|
|
max = s;
|
|
} else {
|
|
mid = 0;
|
|
max = 0;
|
|
}
|
|
min = 0;
|
|
|
|
var newColor = [0, 0, 0];
|
|
|
|
newColor[index_min] = min;
|
|
newColor[index_mid] = mid;
|
|
newColor[index_max] = max;
|
|
|
|
return newColor;
|
|
}
|
|
|
|
var nonSeparateBlendFunctions = {
|
|
hue: function(b, s) {
|
|
var bCopy = [b[0], b[1], b[2]];
|
|
var sCopy = [s[0], s[1], s[2]];
|
|
return setLuminosity(setSaturation(sCopy, saturation(bCopy)), luminosity(bCopy));
|
|
},
|
|
saturation: function(b, s) {
|
|
var bCopy = [b[0], b[1], b[2]];
|
|
var sCopy = [s[0], s[1], s[2]];
|
|
return setLuminosity(setSaturation(bCopy, saturation(sCopy)), luminosity(bCopy));
|
|
},
|
|
color: function(b, s) {
|
|
var bCopy = [b[0], b[1], b[2]];
|
|
var sCopy = [s[0], s[1], s[2]];
|
|
return setLuminosity(sCopy, luminosity(bCopy));
|
|
},
|
|
luminosity: function(b, s) {
|
|
var bCopy = [b[0], b[1], b[2]];
|
|
var sCopy = [s[0], s[1], s[2]];
|
|
return setLuminosity(bCopy, luminosity(sCopy));
|
|
}
|
|
};
|
|
|
|
// Helper functions for drawing in canvas tests
|
|
|
|
function drawColorInContext(color, context) {
|
|
context.fillStyle = color;
|
|
context.fillRect(0, 0, 10, 10);
|
|
}
|
|
|
|
function drawBackdropColorInContext(context) {
|
|
drawColorInContext("rgba(129, 255, 129, 1)", context);
|
|
}
|
|
|
|
function drawSourceColorInContext(context) {
|
|
drawColorInContext("rgba(255, 129, 129, 1)", context);
|
|
}
|
|
|
|
function fillPathWithColorInContext(color, context) {
|
|
context.fillStyle = color;
|
|
context.lineTo(0, 10);
|
|
context.lineTo(10, 10);
|
|
context.lineTo(10, 0);
|
|
context.lineTo(0, 0);
|
|
context.fill();
|
|
}
|
|
|
|
function fillPathWithBackdropInContext(context) {
|
|
fillPathWithColorInContext("rgba(129, 255, 129, 1)", context);
|
|
}
|
|
|
|
function fillPathWithSourceInContext(context) {
|
|
fillPathWithColorInContext("rgba(255, 129, 129, 1)", context);
|
|
}
|
|
|
|
function applyTransformsToContext(context) {
|
|
context.translate(1, 1);
|
|
context.rotate(Math.PI / 2);
|
|
context.scale(2, 2);
|
|
}
|
|
|
|
function drawBackdropColorWithShadowInContext(context) {
|
|
context.save();
|
|
context.shadowOffsetX = 2;
|
|
context.shadowOffsetY = 2;
|
|
context.shadowColor = 'rgba(192, 192, 192, 1)';
|
|
drawBackdropColorInContext(context);
|
|
context.restore();
|
|
}
|
|
|
|
function drawSourceColorRectOverShadow(context) {
|
|
context.fillStyle = "rgba(255, 129, 129, 1)";
|
|
context.fillRect(0, 0, 12, 12);
|
|
}
|
|
|
|
function drawColorImageInContext(color, context, callback) {
|
|
var cvs = document.createElement("canvas");
|
|
var ctx = cvs.getContext("2d");
|
|
drawColorInContext(color, ctx);
|
|
var imageURL = cvs.toDataURL();
|
|
|
|
var backdropImage = new Image();
|
|
backdropImage.onload = function() {
|
|
context.drawImage(this, 0, 0);
|
|
callback();
|
|
}
|
|
backdropImage.src = imageURL;
|
|
}
|
|
|
|
function drawBackdropColorImageInContext(context, callback) {
|
|
drawColorImageInContext("rgba(129, 255, 129, 1)", context, callback);
|
|
}
|
|
|
|
function drawSourceColorImageInContext(context, callback) {
|
|
drawColorImageInContext("rgba(255, 129, 129, 1)", context, callback);
|
|
}
|
|
|
|
function drawColorPatternInContext(color, context, callback) {
|
|
var cvs = document.createElement("canvas");
|
|
var ctx = cvs.getContext("2d");
|
|
drawColorInContext(color, ctx);
|
|
var imageURL = cvs.toDataURL();
|
|
|
|
var backdropImage = new Image();
|
|
backdropImage.onload = function() {
|
|
var pattern = context.createPattern(backdropImage, 'repeat');
|
|
context.rect(0, 0, 10, 10);
|
|
context.fillStyle = pattern;
|
|
context.fill();
|
|
callback();
|
|
}
|
|
backdropImage.src = imageURL;
|
|
}
|
|
|
|
function drawBackdropColorPatternInContext(context, callback) {
|
|
drawColorPatternInContext("rgba(129, 255, 129, 1)", context, callback);
|
|
}
|
|
|
|
function drawSourceColorPatternInContext(context, callback) {
|
|
drawColorPatternInContext("rgba(255, 129, 129, 1)", context, callback);
|
|
}
|
|
|
|
function drawGradientInContext(color1, context) {
|
|
var grad = context.createLinearGradient(0, 0, 10, 10);
|
|
grad.addColorStop(0, color1);
|
|
grad.addColorStop(1, color1);
|
|
context.fillStyle = grad;
|
|
context.fillRect(0, 0, 10, 10);
|
|
}
|
|
|
|
function drawBackdropColorGradientInContext(context) {
|
|
drawGradientInContext("rgba(129, 255, 129, 1)", context);
|
|
}
|
|
|
|
function drawSourceColorGradientInContext(context) {
|
|
drawGradientInContext("rgba(255, 129, 129, 1)", context);
|
|
}
|
|
|
|
function blendColors(backdrop, source, blendModeIndex) {
|
|
if (blendModeIndex < separateBlendmodes.length)
|
|
return separateBlendColors(backdrop, source, blendModeIndex);
|
|
return nonSeparateBlendColors(backdrop, source, blendModeIndex - separateBlendmodes.length);
|
|
}
|
|
|
|
function separateBlendColors(backdrop, source, blendModeIndex) {
|
|
return applyBlendMode(backdrop, source, separateBlendFunctions[separateBlendmodes[blendModeIndex]]);
|
|
}
|
|
|
|
function nonSeparateBlendColors(backdrop, source, blendModeIndex) {
|
|
var expectedColor = nonSeparateBlendFunctions[nonSeparateBlendModes[blendModeIndex]](backdrop, source);
|
|
for (var i = 0; i < 3; ++i)
|
|
expectedColor[i] = source[3] * (1 - backdrop[3]) * source[i] + source[3] * backdrop[3] * expectedColor[i] + (1 - source[3]) * backdrop[3] * backdrop[i];
|
|
return [Math.round(255 * expectedColor[0]), Math.round(255 * expectedColor[1]), Math.round(255 * expectedColor[2]), 255];
|
|
}
|