| 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; | |
| if (options.colorSpace !== undefined) { | |
| assert_equals( | |
| result.image.colorSpace.primaries, options.colorSpace.primaries); | |
| assert_equals( | |
| result.image.colorSpace.transfer, options.colorSpace.transfer); | |
| assert_equals(result.image.colorSpace.matrix, options.colorSpace.matrix); | |
| assert_equals( | |
| result.image.colorSpace.fullRange, options.colorSpace.fullRange); | |
| } | |
| let canvas = new OffscreenCanvas( | |
| result.image.displayWidth, result.image.displayHeight); | |
| let ctx = canvas.getContext('2d'); | |
| if (ctx.globalHDRHeadroom !== undefined) { | |
| ctx.globalHDRHeadroom = Infinity; | |
| } | |
| 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'); | |
| } |