var blendModes = ["source-over", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn",
"hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"];
// Helper functions for separate blend mode
var separateBlendmodes = ["normal", "multiply", "screen", "overlay",
"darken", "lighten", "colorDodge","colorBurn",
"hardLight", "softLight", "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);
},
softLight: function(b, s) {
var c = 0;
if(b <= 0.25)
c = ((16 * b - 12) * b + 4) * b;
else
c = Math.sqrt(b);
if(s <= 0.5)
return b - (1 - 2 * s) * b * (1 - b);
return b + (2 * s - 1) * (c - b);
},
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];
}
function checkBlendModeResult(i, context, sigma, x, y) {
if (x === undefined)
x = 0;
if (y === undefined)
y = 0;
var actualColor = context.getImageData(x, y, 1, 1).data;
var expectedColor = blendColors([129 / 255, 1, 129 / 255, 1], [1, 129 / 255, 129 / 255, 1], i);
assert_approx_equals(actualColor[0], expectedColor[0], sigma);
assert_approx_equals(actualColor[1], expectedColor[1], sigma);
assert_approx_equals(actualColor[2], expectedColor[2], sigma);
assert_approx_equals(actualColor[3], expectedColor[3], sigma);
}