tor-browser

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

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 }