tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>