<!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 };
}
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 layout = await frame.copyTo(buffer, options);
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>