<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
var canvas = document.createElement('canvas');
canvas.width = 200;
canvas.height = 200;
ctx = canvas.getContext('2d', {willReadFrequently: true});
// Create the image for blending test with images.
var img = document.createElement('canvas');
img.width = 200;
img.height = 200;
var imgCtx = img.getContext('2d');
imgCtx.fillStyle = "red";
imgCtx.fillRect(0, 0, 100, 100);
imgCtx.fillStyle = "yellow";
imgCtx.fillRect(100, 0, 100, 100);
imgCtx.fillStyle = "green";
imgCtx.fillRect(100, 100, 100, 100);
imgCtx.fillStyle = "blue";
imgCtx.fillRect(0, 100, 100, 100);
TEST_POINTS = [{x: 50, y: 50}, {x: 150, y: 50}, {x: 150, y: 150}, {x: 50, y: 150}];
// Expected pixel values for various globalCompositeOperations at each testPoint
var BLEND_MODES = [
{
"globalCompositeOperation": "source-over",
"expectedColors": {
"solidOnSolid": [[255, 0, 0, 255], [255, 255, 0, 255], [0, 128, 0, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[128, 0, 127, 255], [128, 128, 127, 255], [0, 64, 127, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[255, 0, 0, 255], [255, 255, 0, 255], [0, 128, 0, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[171, 0, 84, 191], [171, 171, 84, 191], [0, 85, 84, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "multiply",
"expectedColors": {
"solidOnSolid": [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[0, 0, 127, 255], [0, 0, 127, 255], [0, 0, 127, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[128, 0, 0, 255], [128, 128, 0, 255], [0, 64, 0, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[85, 0, 84, 191], [85, 85, 84, 191], [0, 43, 84, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "screen",
"expectedColors": {
"solidOnSolid": [[255, 0, 255, 255], [255, 255, 255, 255], [0, 128, 255, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[128, 0, 255, 255], [128, 128, 255, 255], [0, 64, 255, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[255, 0, 127, 255], [255, 255, 127, 255], [0, 128, 127, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[171, 0, 170, 191], [171, 171, 170, 191], [0, 85, 170, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "overlay",
"expectedColors": {
"solidOnSolid": [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[128, 0, 127, 255], [128, 128, 127, 255], [0, 64, 127, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[85, 0, 170, 191], [85, 85, 170, 191], [0, 43, 170, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "darken",
"expectedColors": {
"solidOnSolid": [[0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 0, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[0, 0, 127, 255], [0, 0, 127, 255], [0, 0, 127, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[128, 0, 0, 255], [128, 128, 0, 255], [0, 64, 0, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[85, 0, 84, 191], [85, 85, 84, 191], [0, 43, 84, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "lighten",
"expectedColors": {
"solidOnSolid": [[255, 0, 255, 255], [255, 255, 255, 255], [0, 128, 255, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[128, 0, 255, 255], [128, 128, 255, 255], [0, 64, 255, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[255, 0, 127, 255], [255, 255, 127, 255], [0, 128, 127, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[171, 0, 170, 191], [171, 171, 170, 191], [0, 85, 170, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "color-dodge",
"expectedColors": {
"solidOnSolid": [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[128, 0, 127, 255], [128, 128, 127, 255], [0, 64, 127, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[85, 0, 170, 191], [85, 85, 170, 191], [0, 43, 170, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "color-burn",
"expectedColors": {
"solidOnSolid": [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[128, 0, 127, 255], [128, 128, 127, 255], [0, 64, 127, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[85, 0, 170, 191], [85, 85, 170, 191], [0, 42, 170, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "hard-light",
"expectedColors": {
"solidOnSolid": [[255, 0, 0, 255], [255, 255, 0, 255], [0, 1, 0, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[128, 0, 127, 255], [128, 128, 127, 255], [0, 0, 127, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[255, 0, 0, 255], [255, 255, 0, 255], [0, 65, 0, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[171, 0, 84, 191], [171, 171, 84, 191], [0, 43, 84, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "soft-light",
"expectedColors": {
"solidOnSolid": [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[128, 0, 127, 255], [128, 128, 127, 255], [0, 64, 127, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[85, 0, 170, 191], [85, 85, 170, 191], [0, 43, 170, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "difference",
"expectedColors": {
"solidOnSolid": [[255, 0, 255, 255], [255, 255, 255, 255], [0, 128, 255, 255], [0, 0, 0, 255]],
"solidOnAlpha": [[128, 0, 255, 255], [128, 128, 255, 255], [0, 64, 255, 255], [0, 0, 127, 255]],
"alphaOnSolid": [[255, 0, 127, 255], [255, 255, 127, 255], [0, 128, 127, 255], [0, 0, 128, 255]],
"alphaOnAlpha": [[171, 0, 170, 191], [171, 171, 170, 191], [0, 85, 170, 191], [0, 0, 171, 191]]
}
}, {
"globalCompositeOperation": "exclusion",
"expectedColors": {
"solidOnSolid": [[255, 0, 255, 255], [255, 255, 255, 255], [0, 128, 255, 255], [0, 0, 0, 255]],
"solidOnAlpha": [[128, 0, 255, 255], [128, 128, 255, 255], [0, 64, 255, 255], [0, 0, 127, 255]],
"alphaOnSolid": [[255, 0, 127, 255], [255, 255, 127, 255], [0, 128, 127, 255], [0, 0, 128, 255]],
"alphaOnAlpha": [[171, 0, 170, 191], [171, 171, 170, 191], [0, 85, 170, 191], [0, 0, 171, 191]]
}
}, {
"globalCompositeOperation": "hue",
"expectedColors": {
"solidOnSolid": [[93, 0, 0, 255], [31, 31, 0, 255], [0, 46, 0, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[49, 0, 127, 255], [16, 16, 127, 255], [0, 25, 127, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[175, 0, 0, 255], [144, 144, 0, 255], [0, 88, 0, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[116, 0, 84, 191], [96, 96, 84, 191], [0, 58, 84, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "saturation",
"expectedColors": {
"solidOnSolid": [[0, 0, 255, 255], [0, 0, 255, 255], [14, 14, 142, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[0, 0, 255, 255], [0, 0, 255, 255], [7, 7, 198, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[128, 0, 127, 255], [128, 128, 127, 255], [7, 71, 70, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[85, 0, 167, 191], [85, 85, 167, 191], [4, 48, 130, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "color",
"expectedColors": {
"solidOnSolid": [[93, 0, 0, 255], [31, 31, 0, 255], [0, 47, 0, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[49, 0, 127, 255], [16, 16, 127, 255], [0, 24, 127, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[175, 0, 0, 255], [144, 144, 0, 255], [0, 88, 0, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[116, 0, 84, 191], [96, 96, 84, 191], [0, 58, 84, 191], [0, 0, 255, 191]]
}
}, {
"globalCompositeOperation": "luminosity",
"expectedColors": {
"solidOnSolid": [[55, 55, 255, 255], [224, 224, 255, 255], [54, 54, 255, 255], [0, 0, 255, 255]],
"solidOnAlpha": [[28, 28, 255, 255], [112, 112, 255, 255], [27, 27, 255, 255], [0, 0, 255, 255]],
"alphaOnSolid": [[155, 27, 127, 255], [239, 239, 127, 255], [26, 90, 127, 255], [0, 0, 255, 255]],
"alphaOnAlpha": [[104, 19, 167, 191], [158, 158, 167, 191], [16, 58, 167, 191], [0, 0, 255, 191]]
}
}
];
TEST_SCENARIOS = [
{"name": 'solidOnSolid', "alpha": 1, "globalAlpha": 1,},
{"name": 'solidOnAlpha', "alpha": 1, "globalAlpha": 0.5,},
{"name": 'alphaOnSolid', "alpha": 0.5, "globalAlpha": 1,},
{"name": 'alphaOnAlpha', "alpha": 0.5, "globalAlpha": 0.5,}
];
// Combine every alpha combination with every globalCompositeOperation
const testsToRun = [];
BLEND_MODES.forEach((blendMode) => {
TEST_SCENARIOS.forEach((testScenario) => {
const parameters = Object.assign({}, blendMode, testScenario);
const name = `${parameters.name} with globalCompositeOperation: ${parameters.globalCompositeOperation}`;
testsToRun.push([name, parameters]);
});
});
function pixelDataAtPoint(i) {
return ctx.getImageData(TEST_POINTS[i].x, TEST_POINTS[i].y, 1, 1).data;
}
function checkBlendModeResult(expectedColors, sigma) {
for (var i = 0; i < TEST_POINTS.length; i++) {
var resultColor = pixelDataAtPoint(i);
assert_approx_equals(resultColor[0], expectedColors[i][0], sigma);
assert_approx_equals(resultColor[1], expectedColors[i][1], sigma);
assert_approx_equals(resultColor[2], expectedColors[i][2], sigma);
assert_approx_equals(resultColor[3], expectedColors[i][3], sigma);
}
}
function testBlending(parameters) {
ctx.clearRect(0, 0, 200, 200);
ctx.save();
// Draw backdrop.
ctx.fillStyle = 'rgba(0, 0, 255, ' + parameters.alpha + ')';
ctx.fillRect(0, 0, 200, 200);
// Apply blend mode.
ctx.globalCompositeOperation = parameters.globalCompositeOperation;
ctx.globalAlpha = parameters.globalAlpha;
ctx.drawImage(img, 0, 0);
checkBlendModeResult(parameters.expectedColors[parameters.name], 5);
ctx.restore();
}
test(function(t) {
generate_tests(testBlending, testsToRun);
}, 'Series of tests to ensure correct results on applying different blend modes.');
</script>