const kYellow = 0xFFFF00FF; const kRed = 0xFF0000FF; const kBlue = 0x0000FFFF; const kGreen = 0x00FF00FF; function getColorName(color) { switch (color) { case kYellow: return "Yellow"; case kRed: return "Red"; case kBlue: return "Blue"; case kGreen: return "Green"; } return "#" + color.toString(16); } function toUInt32(pixelArray, roundForYuv) { let p = pixelArray.data; // YUV to RGB conversion introduces some loss, so provide some leeway. if (roundForYuv) { const tolerance = 3; for (var i = 0; i < p.length; ++i) { if (p[i] >= 0xFF - tolerance) p[i] = 0xFF; if (p[i] <= 0x00 + tolerance) p[i] = 0x00; } } return ((p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]) >>> 0; } function flipMatrix(m) { return m.map(row => row.reverse()); } function rotateMatrix(m, count) { for (var i = 0; i < count; ++i) m = m[0].map((val, index) => m.map(row => row[index]).reverse()); return m; } function testFourColorsDecodeBuffer(buffer, mimeType, options = {}) { var decoder = new ImageDecoder( {data: buffer, type: mimeType, preferAnimation: options.preferAnimation}); return decoder.decode().then(result => { assert_equals(result.image.displayWidth, 320); assert_equals(result.image.displayHeight, 240); if (options.preferAnimation !== undefined) { assert_greater_than(decoder.tracks.length, 1); assert_equals( options.preferAnimation, decoder.tracks.selectedTrack.animated); } if (options.yuvFormat !== undefined) assert_equals(result.image.format, options.yuvFormat); if (options.tolerance === undefined) options.tolerance = 0; let canvas = new OffscreenCanvas( result.image.displayWidth, result.image.displayHeight); let ctx = canvas.getContext('2d'); ctx.drawImage(result.image, 0, 0); let top_left = ctx.getImageData(0, 0, 1, 1); let top_right = ctx.getImageData(result.image.displayWidth - 1, 0, 1, 1); let bottom_left = ctx.getImageData(0, result.image.displayHeight - 1, 1, 1); let left_corner = ctx.getImageData( result.image.displayWidth - 1, result.image.displayHeight - 1, 1, 1); assert_array_approx_equals( top_left.data, [0xFF, 0xFF, 0x00, 0xFF], options.tolerance, 'top left corner is yellow'); assert_array_approx_equals( top_right.data, [0xFF, 0x00, 0x00, 0xFF], options.tolerance, 'top right corner is red'); assert_array_approx_equals( bottom_left.data, [0x00, 0x00, 0xFF, 0xFF], options.tolerance, 'bottom left corner is blue'); assert_array_approx_equals( left_corner.data, [0x00, 0xFF, 0x00, 0xFF], options.tolerance, 'bottom right corner is green'); }); } function testFourColorDecodeWithExifOrientation(orientation, canvas, useYuv) { return ImageDecoder.isTypeSupported('image/jpeg').then(support => { assert_implements_optional( support, 'Optional codec image/jpeg not supported.'); const testFile = useYuv ? 'four-colors-limited-range-420-8bpc.jpg' : 'four-colors.jpg'; return fetch(testFile) .then(response => { return response.arrayBuffer(); }) .then(buffer => { let u8buffer = new Uint8Array(buffer); u8buffer[useYuv ? 0x31 : 0x1F] = orientation; // Location derived via diff. let decoder = new ImageDecoder({data: u8buffer, type: 'image/jpeg'}); return decoder.decode(); }) .then(result => { let respectOrientation = true; if (canvas) respectOrientation = canvas.style.imageOrientation != 'none'; let expectedWidth = 320; let expectedHeight = 240; if (orientation > 4 && respectOrientation) [expectedWidth, expectedHeight] = [expectedHeight, expectedWidth]; if (respectOrientation) { assert_equals(result.image.displayWidth, expectedWidth); assert_equals(result.image.displayHeight, expectedHeight); } else if (orientation > 4) { assert_equals(result.image.displayHeight, expectedWidth); assert_equals(result.image.displayWidth, expectedHeight); } if (!canvas) { canvas = new OffscreenCanvas( result.image.displayWidth, result.image.displayHeight); } else { canvas.width = expectedWidth; canvas.height = expectedHeight; } let ctx = canvas.getContext('2d'); ctx.drawImage(result.image, 0, 0); let matrix = [ [kYellow, kRed], [kBlue, kGreen], ]; if (respectOrientation) { switch (orientation) { case 1: // kOriginTopLeft, default break; case 2: // kOriginTopRight, mirror along y-axis matrix = flipMatrix(matrix); break; case 3: // kOriginBottomRight, 180 degree rotation matrix = rotateMatrix(matrix, 2); break; case 4: // kOriginBottomLeft, mirror along the x-axis matrix = flipMatrix(rotateMatrix(matrix, 2)); break; case 5: // kOriginLeftTop, mirror along x-axis + 270 degree CW // rotation matrix = flipMatrix(rotateMatrix(matrix, 1)); break; case 6: // kOriginRightTop, 90 degree CW rotation matrix = rotateMatrix(matrix, 1); break; case 7: // kOriginRightBottom, mirror along x-axis + 90 degree CW // rotation matrix = flipMatrix(rotateMatrix(matrix, 3)); break; case 8: // kOriginLeftBottom, 270 degree CW rotation matrix = rotateMatrix(matrix, 3); break; default: assert_between_inclusive( orientation, 1, 8, 'unknown image orientation'); break; }; } verifyFourColorsImage( expectedWidth, expectedHeight, ctx, matrix, useYuv); }); }); } function verifyFourColorsImage(width, height, ctx, matrix, isYuv) { if (!matrix) { matrix = [ [kYellow, kRed], [kBlue, kGreen], ]; } let expectedTopLeft = matrix[0][0]; let expectedTopRight = matrix[0][1]; let expectedBottomLeft = matrix[1][0]; let expectedBottomRight = matrix[1][1]; let topLeft = toUInt32(ctx.getImageData(0, 0, 1, 1), isYuv); let topRight = toUInt32(ctx.getImageData(width - 1, 0, 1, 1), isYuv); let bottomLeft = toUInt32(ctx.getImageData(0, height - 1, 1, 1), isYuv); let bottomRight = toUInt32(ctx.getImageData(width - 1, height - 1, 1, 1), isYuv); assert_equals(getColorName(topLeft), getColorName(expectedTopLeft), 'top left corner'); assert_equals(getColorName(topRight), getColorName(expectedTopRight), 'top right corner'); assert_equals(getColorName(bottomLeft), getColorName(expectedBottomLeft), 'bottom left corner'); assert_equals(getColorName(bottomRight), getColorName(expectedBottomRight), 'bottom right corner'); }