imagebitmap-replication-exif-orientation.html (6431B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Verify that image orientation is propagated when ImageBitmap objects are replicated.</title> 6 <link rel="author" title="Justin Novosad" href="mailto:junov@chromium.org"> 7 <script src="/resources/testharness.js"></script> 8 <script src="/resources/testharnessreport.js"></script> 9 </head> 10 11 <body> 12 <script> 13 // This test is most relevant for browser implementations that apply EXIF image 14 // orientation lazily. That is to say that the transform is applied at rasterization 15 // time rather than at image decode time. This implies that image orientation metadata 16 // is stored internally in the decoded image's data structure. This test ensures 17 // that the orientation metadata is correctly carried over when ImageBitmap objects 18 // are replicated (serialized/deserialized, copied or transferred). 19 20 function checkImageBitmapRotated(bitmap) { 21 assert_equals(bitmap.width, 320, 'Bitmap width'); 22 assert_equals(bitmap.height, 160, 'Bitmap height'); 23 24 const canvas = document.createElement('canvas'); 25 const ctx = canvas.getContext('2d'); 26 27 const expected_colors = [ 28 // row, col, r, g, b, a 29 [0, 0, 255, 0, 0, 255], 30 [0, 1, 0, 255, 0, 255], 31 [0, 2, 0, 0, 255, 255], 32 [0, 3, 0, 0, 0, 255], 33 [1, 0, 255, 128, 128, 255], 34 [1, 1, 128, 255, 128, 255], 35 [1, 2, 128, 128, 255, 255], 36 [1, 3, 128, 128, 128, 255], 37 ]; 38 39 canvas.width = bitmap.width; 40 canvas.height = bitmap.height; 41 ctx.drawImage(bitmap, 0, 0); 42 43 let data = ctx.getImageData(0, 0, canvas.width, canvas.height).data; 44 for (let [row, col, r, g, b, a] of expected_colors) { 45 let x = col * 80 + 40; 46 let y = row * 80 + 40; 47 let i = (x + y * canvas.width) * 4; 48 let expected = [r, g, b, a]; 49 let actual = [data[i], data[i + 1], data[i + 2], data[i + 3]]; 50 assert_array_approx_equals(actual, expected, 1, `Pixel value at (${x},${y}) ${expected} =~ ${actual}.`); 51 } 52 } 53 54 promise_test(async t => { 55 const response = await fetch("resources/squares_6.jpg"); 56 const blob = await response.blob(); 57 const image = await createImageBitmap(blob) 58 const image_copy = structuredClone(image); 59 checkImageBitmapRotated(image_copy); 60 }, "ImageBitmap from file with EXIF rotation, duplicated via structuredClone"); 61 62 promise_test(async t => { 63 const image = new Image(); 64 image.src = "resources/squares_6.jpg" 65 await new Promise(resolve => image.onload = resolve); 66 const image_copy = await createImageBitmap(image); 67 checkImageBitmapRotated(image_copy); 68 }, "ImageBitmap from file with EXIF rotation, loaded via <img>"); 69 70 promise_test(async t => { 71 const image = new Image(); 72 image.src = "resources/squares_6.jpg" 73 // The following has no effect because the image's style is not 74 // processed unless the element is connected to the DOM. 75 image.style.imageOrientation = "none"; 76 await new Promise(resolve => image.onload = resolve); 77 const image_copy = await createImageBitmap(image); 78 checkImageBitmapRotated(image_copy); 79 }, "ImageBitmap from file with EXIF rotation, loaded via <img> not in DOM, imageOrientation = none"); 80 81 promise_test(async t => { 82 const image = new Image(); 83 document.body.appendChild(image); 84 image.src = "resources/squares_6.jpg" 85 // The style is being processed in this case, but the imageOrientation 86 // CSS property must still have no effect because createImageBitmap 87 // accesses the element's underlying media directly, without being 88 // affected by the image's style (unlike drawImage). 89 image.style.imageOrientation = "none"; 90 await new Promise(resolve => image.onload = resolve); 91 const image_copy = await createImageBitmap(image); 92 checkImageBitmapRotated(image_copy); 93 }, "ImageBitmap from file with EXIF rotation, loaded via <img> in DOM, imageOrientation = none"); 94 95 96 promise_test(async t => { 97 const response = await fetch("resources/squares_6.jpg"); 98 const blob = await response.blob(); 99 const image = await createImageBitmap(blob); 100 const image_copy = await createImageBitmap(image); 101 checkImageBitmapRotated(image_copy); 102 }, "ImageBitmap from file with EXIF rotation, duplicated via createImageBitmap"); 103 104 promise_test(async t => { 105 const worker = new Worker("serialize-worker.js"); 106 const response = await fetch("resources/squares_6.jpg"); 107 const blob = await response.blob() 108 const image = await createImageBitmap(blob); 109 worker.postMessage({bitmap: image}); 110 const bitmap = (await new Promise(resolve => {worker.addEventListener("message", resolve)})).data.bitmap; 111 checkImageBitmapRotated(bitmap); 112 }, "ImageBitmap from file with EXIF rotation, duplicated via worker serialization round-trip"); 113 114 promise_test(async t => { 115 const worker = new Worker("transfer-worker.js"); 116 let response = await fetch("resources/squares_6.jpg"); 117 let blob = await response.blob(); 118 let image = await createImageBitmap(blob); 119 worker.postMessage({bitmap: image}, [image]); 120 const bitmap = (await new Promise(resolve => {worker.addEventListener("message", resolve)})).data.bitmap; 121 checkImageBitmapRotated(bitmap); 122 }, "ImageBitmap from file with EXIF rotation, duplicated via worker transfer round-trip"); 123 124 promise_test(async t => { 125 // This test variant ensures additional code coverage. 126 // By creating a canvas pattern, a reference to the ImageBitmap's 127 // underlying pixel data is held in the source realm. This forces 128 // implementations that do lazy copying to duplicate the pixel 129 // data at transfer time. This test verifies that the lazy 130 // duplication code path (if applicable) carries over the image 131 // orientation metadata. 132 const worker = new Worker("transfer-worker.js"); 133 let response = await fetch("resources/squares_6.jpg"); 134 let blob = await response.blob(); 135 let image = await createImageBitmap(blob); 136 const canvas = document.createElement('canvas'); 137 const ctx = canvas.getContext('2d'); 138 const pattern = ctx.createPattern(image, 'repeat'); 139 worker.postMessage({bitmap: image}, [image]); 140 const bitmap = (await new Promise(resolve => {worker.addEventListener("message", resolve)})).data.bitmap; 141 checkImageBitmapRotated(bitmap); 142 }, "ImageBitmap from file with EXIF rotation, duplicated via worker transfer round-trip, while referenced by a CanvasPattern"); 143 144 145 </script> 146 </body>