| // 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'); |
| } |