| <!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_rgb601(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 }; |
| } |
| |
| function yuv_to_rgb709(y, u, v) { |
| let b = 1.164 * (y - 16) + 2.113 * (u - 128); |
| let g = 1.164 * (y - 16) - 0.533 * (v - 128) - 0.213 * (u - 128); |
| let r = 1.164 * (y - 16) + 1.793 * (v - 128); |
| return { r: r, g: g, b: b }; |
| } |
| |
| function compareArrayBuffers(buffer1, buffer2) { |
| const view1 = new DataView(buffer1); |
| const view2 = new DataView(buffer2); |
| |
| if (view1.byteLength !== view2.byteLength) { |
| return false; |
| } |
| |
| for (let i = 0; i < view1.byteLength; i++) { |
| if (view1.getUint8(i) !== view2.getUint8(i)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| 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: 1, height: 1 } |
| } |
| let size = frame.allocationSize(options); |
| let buffer = new ArrayBuffer(size); |
| let shared_buffer = new SharedArrayBuffer(size); |
| let layout = await frame.copyTo(buffer, options); |
| await frame.copyTo(shared_buffer, options); |
| TEST.assert(compareArrayBuffers(buffer, shared_buffer), |
| 'Readback mismatch between shared and non-shared buffers'); |
| let view = new DataView(buffer); |
| let rgb = null; |
| |
| let yuv_to_rgb = yuv_to_rgb601; |
| if (frame.colorSpace.matrix == "bt709") |
| yuv_to_rgb = yuv_to_rgb709; |
| |
| 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); |
| } |
| } |
| |
| function readWholeBuffer(buffer) { |
| let bytes = new Uint8Array(buffer); |
| return bytes.reduce((acc, byte) => { return byte ? acc + 1 : acc; }, 0); |
| } |
| |
| function makeWorker() { |
| const worker = new Worker('buffer-read-and-reply-worker.js'); |
| let resolve_promise = null; |
| worker.onmessage = function(e) { resolve_promise(e.data); } |
| let worker_promise = new Promise((resolve) => { |
| resolve_promise = resolve; |
| }); |
| return { worker , worker_promise }; |
| } |
| |
| 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(); |
| let size = frame.allocationSize(); |
| |
| // Readback a whole frame to a regular buffer detach it |
| { |
| let buf = new ArrayBuffer(size); |
| TEST.assert(readWholeBuffer(buf) == 0, "Buffer should be zero"); |
| let copy_promise = frame.copyTo(buf); |
| buf.transfer(1); |
| let layout = await copy_promise; |
| TEST.assert(layout, "layout is empty / ArrayBuffer"); |
| } |
| |
| // Readback a whole frame to a regular buffer and send it to a worker |
| { |
| let {worker, worker_promise } = makeWorker(); |
| let buf = new ArrayBuffer(size); |
| TEST.assert(readWholeBuffer(buf) == 0, "Buffer should be zero"); |
| let copy_promise = frame.copyTo(buf); |
| worker.postMessage(buf, [buf]); |
| let result_buf = await worker_promise; |
| let layout = await copy_promise; |
| TEST.assert(readWholeBuffer(result_buf) > 0, "Buffer shouldn't be zero"); |
| TEST.assert(layout, "layout is empty / ArrayBuffer"); |
| worker.terminate(); |
| } |
| |
| // Readback a whole frame to a shared buffer and send it to a worker |
| { |
| let {worker, worker_promise} = makeWorker(); |
| let buf = new SharedArrayBuffer(size); |
| TEST.assert(readWholeBuffer(buf) == 0, "Buffer should be zero"); |
| let copy_promise = frame.copyTo(buf); |
| worker.postMessage(buf); |
| await worker_promise; |
| let layout = await copy_promise; |
| TEST.assert(readWholeBuffer(buf) > 0, "SharedBuffer shouldn't be zero"); |
| TEST.assert(layout, "layout is empty / SharedArrayBuffer"); |
| worker.terminate(); |
| } |
| |
| // Validate pixels |
| if (!arg.validate_camera_frames && source_type == 'camera') { |
| TEST.log("Skip copyTo result validation"); |
| } else { |
| await validateFourColorsBytes(frame); |
| } |
| |
| frame.close(); |
| source.close(); |
| TEST.log('Test completed'); |
| } |
| addManualTestButton([{'source_type': 'offscreen'}]); |
| addManualTestButton([{'source_type': 'arraybuffer'}]); |
| </script> |