1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147

content / test / data / gpu / webcodecs / convert-to-rgb.js [blame]

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

function rgb2yuv(r, g, b) {
  let y = r * .299000 + g * .587000 + b * .114000
  let u = r * -.168736 + g * -.331264 + b * .500000 + 128
  let v = r * .500000 + g * -.418688 + b * -.081312 + 128

  y = Math.round(y);
  u = Math.round(u);
  v = Math.round(v);
  return {
    y, u, v
  }
}

function makeI420_frames() {
  const kYellow = {r: 0xFF, g: 0xFF, b: 0x00};
  const kRed = {r: 0xFF, g: 0x00, b: 0x00};
  const kBlue = {r: 0x00, g: 0x00, b: 0xFF};
  const kGreen = {r: 0x00, g: 0xFF, b: 0x00};
  const kPink = {r: 0xFF, g: 0x78, b: 0xFF};
  const kMagenta = {r: 0xFF, g: 0x00, b: 0xFF};
  const kBlack = {r: 0x00, g: 0x00, b: 0x00};
  const kWhite = {r: 0xFF, g: 0xFF, b: 0xFF};
  const smpte170m = {
    matrix: 'smpte170m',
    primaries: 'smpte170m',
    transfer: 'smpte170m',
    fullRange: false
  };
  const bt709 = {
    matrix: 'bt709',
    primaries: 'bt709',
    transfer: 'bt709',
    fullRange: false
  };

  const result = [];
  const init = {format: 'I420', timestamp: 0, codedWidth: 4, codedHeight: 4};
  const colors =
      [kYellow, kRed, kBlue, kGreen, kMagenta, kBlack, kWhite, kPink];
  const data = new Uint8Array(24);
  for (let colorSpace of [null, smpte170m, bt709]) {
    init.colorSpace = colorSpace;
    result.push(new VideoFrame(data, init));
    for (let color of colors) {
      color = rgb2yuv(color.r, color.g, color.b);
      data.fill(color.y, 0, 16);
      data.fill(color.u, 16, 20);
      data.fill(color.v, 20, 24);
      result.push(new VideoFrame(data, init));
    }
  }
  return result;
}

async function test_frame(frame, colorSpace) {
  const width = frame.visibleRect.width;
  const height = frame.visibleRect.height;
  const frame_description = JSON.stringify({
    format: frame.format,
    width: width,
    height: height,
    codedHeight: frame.codedHeight,
    codedWidth: frame.codedWidth,
    displayHeight: frame.displayHeight,
    displayWidth: frame.displayWidth,
    matrix: frame.colorSpace?.matrix,
    primaries: frame.colorSpace?.primaries,
    transfer: frame.colorSpace?.transfer
  });
  TEST.log(`Test color: ${colorSpace} frame:${frame_description}`);
  const cnv = new OffscreenCanvas(width, height);
  const ctx =
      cnv.getContext('2d', {colorSpace: colorSpace, willReadFrequently: true});

  // Read VideoFrame pixels via copyTo()
  let imageData = ctx.createImageData(width, height);
  let copy_to_buf = imageData.data.buffer;
  let layout = null;
  try {
    const options = {
      rect: {x: 0, y: 0, width: width, height: height},
      format: 'RGBA',
      colorSpace: colorSpace
    };
    layout = await frame.copyTo(copy_to_buf, options);
  } catch (e) {
    TEST.reportFailure(`copyTo() failure: ${e}`);
    return;
  }
  if (layout.length != 1) {
    TEST.skip('Conversion to RGB is not supported by the browser');
    return;
  }

  // Read VideoFrame pixels via drawImage()
  ctx.drawImage(frame, 0, 0, width, height, 0, 0, width, height);
  imageData = ctx.getImageData(0, 0, width, height, {colorSpace: colorSpace});
  let get_image_buf = imageData.data.buffer;

  // Compare!
  const tolerance = 1;
  for (let i = 0; i < copy_to_buf.byteLength; i += 4) {
    compareColors(
        new Uint8Array(copy_to_buf, i, 4), new Uint8Array(get_image_buf, i, 4),
        tolerance, `Mismatch at offset ${i}`);
    if (TEST.finished) {
      break;
    }
  }
}

async function check_predefined_frames() {
  // Test frames constructed from an array buffer.
  // This should be a part of the WPT tests some day.
  for (let frame of makeI420_frames()) {
    await test_frame(frame, 'srgb');
    await test_frame(frame, 'display-p3');
    frame.close();
  }
}

async function main(arg) {
  let source_type = arg.source_type;
  TEST.log('Starting test with arguments: ' + JSON.stringify(arg));
  let source = await createFrameSource(source_type, FRAME_WIDTH, FRAME_HEIGHT);
  if (!source) {
    TEST.skip('Unsupported source: ' + source_type);
    return;
  }

  let frame = await source.getNextFrame();

  await test_frame(frame, 'srgb');
  await test_frame(frame, 'display-p3');
  frame.close();

  await check_predefined_frames();

  source.close();
  TEST.log('Test completed');
}