image-decoder-utils.js (7900B)
1 const kYellow = 0xFFFF00FF; 2 const kRed = 0xFF0000FF; 3 const kBlue = 0x0000FFFF; 4 const kGreen = 0x00FF00FF; 5 6 function getColorName(color) { 7 switch (color) { 8 case kYellow: 9 return "Yellow"; 10 case kRed: 11 return "Red"; 12 case kBlue: 13 return "Blue"; 14 case kGreen: 15 return "Green"; 16 } 17 return "#" + color.toString(16); 18 } 19 20 function toUInt32(pixelArray, roundForYuv) { 21 let p = pixelArray.data; 22 23 // YUV to RGB conversion introduces some loss, so provide some leeway. 24 if (roundForYuv) { 25 const tolerance = 3; 26 for (var i = 0; i < p.length; ++i) { 27 if (p[i] >= 0xFF - tolerance) 28 p[i] = 0xFF; 29 if (p[i] <= 0x00 + tolerance) 30 p[i] = 0x00; 31 } 32 } 33 34 return ((p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]) >>> 0; 35 } 36 37 function flipMatrix(m) { 38 return m.map(row => row.reverse()); 39 } 40 41 function rotateMatrix(m, count) { 42 for (var i = 0; i < count; ++i) 43 m = m[0].map((val, index) => m.map(row => row[index]).reverse()); 44 return m; 45 } 46 47 function testFourColorsDecodeBuffer(buffer, mimeType, options = {}) { 48 var decoder = new ImageDecoder( 49 {data: buffer, type: mimeType, preferAnimation: options.preferAnimation}); 50 return decoder.decode().then(result => { 51 assert_equals(result.image.displayWidth, 320); 52 assert_equals(result.image.displayHeight, 240); 53 if (options.preferAnimation !== undefined) { 54 assert_greater_than(decoder.tracks.length, 1); 55 assert_equals( 56 options.preferAnimation, decoder.tracks.selectedTrack.animated); 57 } 58 if (options.yuvFormat !== undefined) 59 assert_equals(result.image.format, options.yuvFormat); 60 if (options.tolerance === undefined) 61 options.tolerance = 0; 62 63 if (options.colorSpace !== undefined) { 64 assert_equals( 65 result.image.colorSpace.primaries, options.colorSpace.primaries); 66 assert_equals( 67 result.image.colorSpace.transfer, options.colorSpace.transfer); 68 assert_equals(result.image.colorSpace.matrix, options.colorSpace.matrix); 69 assert_equals( 70 result.image.colorSpace.fullRange, options.colorSpace.fullRange); 71 } 72 73 let canvas = new OffscreenCanvas( 74 result.image.displayWidth, result.image.displayHeight); 75 let ctx = canvas.getContext('2d'); 76 if (ctx.globalHDRHeadroom !== undefined) { 77 ctx.globalHDRHeadroom = Infinity; 78 } 79 ctx.drawImage(result.image, 0, 0); 80 81 let top_left = ctx.getImageData(0, 0, 1, 1); 82 let top_right = ctx.getImageData(result.image.displayWidth - 1, 0, 1, 1); 83 let bottom_left = ctx.getImageData(0, result.image.displayHeight - 1, 1, 1); 84 let left_corner = ctx.getImageData( 85 result.image.displayWidth - 1, result.image.displayHeight - 1, 1, 1); 86 87 assert_array_approx_equals( 88 top_left.data, [0xFF, 0xFF, 0x00, 0xFF], options.tolerance, 89 'top left corner is yellow'); 90 assert_array_approx_equals( 91 top_right.data, [0xFF, 0x00, 0x00, 0xFF], options.tolerance, 92 'top right corner is red'); 93 assert_array_approx_equals( 94 bottom_left.data, [0x00, 0x00, 0xFF, 0xFF], options.tolerance, 95 'bottom left corner is blue'); 96 assert_array_approx_equals( 97 left_corner.data, [0x00, 0xFF, 0x00, 0xFF], options.tolerance, 98 'bottom right corner is green'); 99 }); 100 } 101 102 function testFourColorDecodeWithExifOrientation(orientation, canvas, useYuv) { 103 return ImageDecoder.isTypeSupported('image/jpeg').then(support => { 104 assert_implements_optional( 105 support, 'Optional codec image/jpeg not supported.'); 106 const testFile = 107 useYuv ? 'four-colors-limited-range-420-8bpc.jpg' : 'four-colors.jpg'; 108 return fetch(testFile) 109 .then(response => { 110 return response.arrayBuffer(); 111 }) 112 .then(buffer => { 113 let u8buffer = new Uint8Array(buffer); 114 u8buffer[useYuv ? 0x31 : 0x1F] = 115 orientation; // Location derived via diff. 116 let decoder = new ImageDecoder({data: u8buffer, type: 'image/jpeg'}); 117 return decoder.decode(); 118 }) 119 .then(result => { 120 let respectOrientation = true; 121 if (canvas) 122 respectOrientation = canvas.style.imageOrientation != 'none'; 123 124 let expectedWidth = 320; 125 let expectedHeight = 240; 126 if (orientation > 4 && respectOrientation) 127 [expectedWidth, expectedHeight] = [expectedHeight, expectedWidth]; 128 129 if (respectOrientation) { 130 assert_equals(result.image.displayWidth, expectedWidth); 131 assert_equals(result.image.displayHeight, expectedHeight); 132 } else if (orientation > 4) { 133 assert_equals(result.image.displayHeight, expectedWidth); 134 assert_equals(result.image.displayWidth, expectedHeight); 135 } 136 137 if (!canvas) { 138 canvas = new OffscreenCanvas( 139 result.image.displayWidth, result.image.displayHeight); 140 } else { 141 canvas.width = expectedWidth; 142 canvas.height = expectedHeight; 143 } 144 145 let ctx = canvas.getContext('2d'); 146 ctx.drawImage(result.image, 0, 0); 147 148 let matrix = [ 149 [kYellow, kRed], 150 [kBlue, kGreen], 151 ]; 152 if (respectOrientation) { 153 switch (orientation) { 154 case 1: // kOriginTopLeft, default 155 break; 156 case 2: // kOriginTopRight, mirror along y-axis 157 matrix = flipMatrix(matrix); 158 break; 159 case 3: // kOriginBottomRight, 180 degree rotation 160 matrix = rotateMatrix(matrix, 2); 161 break; 162 case 4: // kOriginBottomLeft, mirror along the x-axis 163 matrix = flipMatrix(rotateMatrix(matrix, 2)); 164 break; 165 case 5: // kOriginLeftTop, mirror along x-axis + 270 degree CW 166 // rotation 167 matrix = flipMatrix(rotateMatrix(matrix, 1)); 168 break; 169 case 6: // kOriginRightTop, 90 degree CW rotation 170 matrix = rotateMatrix(matrix, 1); 171 break; 172 case 7: // kOriginRightBottom, mirror along x-axis + 90 degree CW 173 // rotation 174 matrix = flipMatrix(rotateMatrix(matrix, 3)); 175 break; 176 case 8: // kOriginLeftBottom, 270 degree CW rotation 177 matrix = rotateMatrix(matrix, 3); 178 break; 179 default: 180 assert_between_inclusive( 181 orientation, 1, 8, 'unknown image orientation'); 182 break; 183 }; 184 } 185 186 verifyFourColorsImage( 187 expectedWidth, expectedHeight, ctx, matrix, useYuv); 188 }); 189 }); 190 } 191 192 function verifyFourColorsImage(width, height, ctx, matrix, isYuv) { 193 if (!matrix) { 194 matrix = [ 195 [kYellow, kRed], 196 [kBlue, kGreen], 197 ]; 198 } 199 200 let expectedTopLeft = matrix[0][0]; 201 let expectedTopRight = matrix[0][1]; 202 let expectedBottomLeft = matrix[1][0]; 203 let expectedBottomRight = matrix[1][1]; 204 205 let topLeft = toUInt32(ctx.getImageData(0, 0, 1, 1), isYuv); 206 let topRight = toUInt32(ctx.getImageData(width - 1, 0, 1, 1), isYuv); 207 let bottomLeft = toUInt32(ctx.getImageData(0, height - 1, 1, 1), isYuv); 208 let bottomRight = 209 toUInt32(ctx.getImageData(width - 1, height - 1, 1, 1), isYuv); 210 211 assert_equals(getColorName(topLeft), getColorName(expectedTopLeft), 212 'top left corner'); 213 assert_equals(getColorName(topRight), getColorName(expectedTopRight), 214 'top right corner'); 215 assert_equals(getColorName(bottomLeft), getColorName(expectedBottomLeft), 216 'bottom left corner'); 217 assert_equals(getColorName(bottomRight), getColorName(expectedBottomRight), 218 'bottom right corner'); 219 }