| <!DOCTYPE html> |
| <!-- |
| Take frames coming from various sources and read them using copyTo(). |
| --> |
| <title>copyTo() test</title> |
| <script src="webcodecs_common.js"></script> |
| <script type="text/javascript"> |
| 'use strict'; |
| |
| function yuv_to_rgb(y, u, v) { |
| let b = 1.164 * (y - 16) + 2.018 * (u - 128); |
| let g = 1.164 * (y - 16) - 0.813 * (v - 128) - 0.391 * (u - 128); |
| let r = 1.164 * (y - 16) + 1.596 * (v - 128); |
| return { r: r, g: g, b: b }; |
| } |
| |
| async function validateFourColorsBytes(frame) { |
| const m = 4; |
| const tolerance = 8; |
| let expected_xy_color = [ |
| // Left-top yellow |
| { x: m, y: m, r: 255, g: 255, b: 0 }, |
| // Right-top red |
| { x: frame.displayWidth - m, y: m, r: 255, g: 0, b: 0 }, |
| // Left-bottom blue |
| { x: m, y: frame.displayHeight - m, r: 0, g: 0, b: 255 }, |
| // Right-bottom green |
| { x: frame.displayWidth - m, y: frame.displayHeight - m, |
| r: 0, g: 255, b: 0 }, |
| ] |
| |
| for (let test of expected_xy_color) { |
| const options = { |
| rect: { x: test.x, y: test.y, width: 2, height: 2 } |
| } |
| let size = frame.allocationSize(options); |
| let buffer = new ArrayBuffer(size); |
| let layout = await frame.copyTo(buffer, options); |
| let view = new DataView(buffer); |
| let rgb = null; |
| |
| switch (frame.format) { |
| case "I420": |
| case "I420A": |
| rgb = yuv_to_rgb(view.getUint8(layout[0].offset), // Y |
| view.getUint8(layout[1].offset), // U |
| view.getUint8(layout[2].offset)); // V |
| break; |
| case "NV12": |
| rgb = yuv_to_rgb(view.getUint8(layout[0].offset), // Y |
| view.getUint8(layout[1].offset), // U |
| view.getUint8(layout[1].offset + 1)); // V |
| break; |
| case "RGBX": |
| case "RGBA": |
| rgb = { |
| r: view.getUint8(layout[0].offset), |
| g: view.getUint8(layout[0].offset + 1), |
| b: view.getUint8(layout[0].offset + 2) |
| }; |
| break; |
| case "BGRX": |
| case "BGRA": |
| rgb = { |
| r: view.getUint8(layout[0].offset + 2), |
| g: view.getUint8(layout[0].offset + 1), |
| b: view.getUint8(layout[0].offset) |
| }; |
| break; |
| default: |
| TEST.reportFailure("Unexpected frame format: " + frame.format); |
| return; |
| } |
| |
| let message = |
| `Test ${frame.format} ${JSON.stringify(test)} ${JSON.stringify(rgb)}`; |
| TEST.assert(Math.abs(rgb.r - test.r) < tolerance, message); |
| TEST.assert(Math.abs(rgb.g - test.g) < tolerance, message); |
| TEST.assert(Math.abs(rgb.b - test.b) < tolerance, message); |
| } |
| } |
| |
| async function main(arg) { |
| let source_type = arg.source_type; |
| let source = await createFrameSource(source_type, 320, 240); |
| if (!source) { |
| TEST.skip('Unsupported source: ' + source_type); |
| return; |
| } |
| |
| let frame = await source.getNextFrame(); |
| if (!arg.validate_camera_frames && source_type == 'camera') { |
| TEST.log("Skip copyTo result validation"); |
| let size = frame.allocationSize(); |
| let buf = new ArrayBuffer(size); |
| let layout = await frame.copyTo(buf); |
| TEST.assert(layout, "layout is empty"); |
| } else { |
| await validateFourColorsBytes(frame); |
| } |
| |
| frame.close(); |
| source.close(); |
| } |
| </script> |