| // 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]; |
| } |