| // META: script=/webcodecs/videoFrame-utils.js | |
| promise_test(async t => { | |
| const W = 5; | |
| const H = 4; | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = W; | |
| canvas.height = H; | |
| const ctx = canvas.getContext('2d'); | |
| ctx.fillStyle = '#00ff00'; | |
| ctx.fillRect(0, 0, W, H); | |
| const frame = new VideoFrame(canvas, {timestamp: 0}); | |
| t.add_cleanup(() => frame.close()); | |
| const format = frame.format; | |
| assert_true( | |
| format === 'RGBA' || format === 'RGBX' || | |
| format === 'BGRA' || format === 'BGRX', | |
| `Expected an RGB(A/X) format, got ${format}`); | |
| const size = frame.allocationSize(); | |
| const buf = new Uint8Array(size); | |
| await frame.copyTo(buf); | |
| const bpp = 4; | |
| for (let row = 0; row < H; row++) { | |
| for (let x = 0; x < W; x++) { | |
| const i = (row * W + x) * bpp; | |
| const pixel = [buf[i], buf[i + 1], buf[i + 2], buf[i + 3]]; | |
| // Green channel must be 255; red and blue must be 0. | |
| // Alpha may be 255 for RGBA/BGRA or implementation-defined for X formats. | |
| const rIdx = (format === 'BGRA' || format === 'BGRX') ? 2 : 0; | |
| const gIdx = 1; | |
| const bIdx = (format === 'BGRA' || format === 'BGRX') ? 0 : 2; | |
| const aIdx = 3; | |
| assert_equals(pixel[rIdx], 0, | |
| `row ${row} col ${x}: red channel should be 0, got ${pixel[rIdx]}`); | |
| assert_equals(pixel[gIdx], 255, | |
| `row ${row} col ${x}: green channel should be 255, got ${pixel[gIdx]}`); | |
| assert_equals(pixel[bIdx], 0, | |
| `row ${row} col ${x}: blue channel should be 0, got ${pixel[bIdx]}`); | |
| if (format === 'RGBA' || format === 'BGRA') { | |
| assert_equals(pixel[aIdx], 255, | |
| `row ${row} col ${x}: alpha should be 255, got ${pixel[aIdx]}`); | |
| } | |
| } | |
| } | |
| }, 'copyTo from canvas-backed VideoFrame returns correct pixels for all rows'); | |
| promise_test(async t => { | |
| // Use a different non-aligned width to ensure this isn't width-specific. | |
| const W = 3; | |
| const H = 8; | |
| const canvas = document.createElement('canvas'); | |
| canvas.width = W; | |
| canvas.height = H; | |
| const ctx = canvas.getContext('2d'); | |
| // Draw a distinct color per row so stride errors are easily detected. | |
| for (let row = 0; row < H; row++) { | |
| ctx.fillStyle = `rgb(${row * 30}, ${100 + row * 10}, ${200 - row * 20})`; | |
| ctx.fillRect(0, row, W, 1); | |
| } | |
| const frame = new VideoFrame(canvas, {timestamp: 0}); | |
| t.add_cleanup(() => frame.close()); | |
| const format = frame.format; | |
| const size = frame.allocationSize(); | |
| const buf = new Uint8Array(size); | |
| await frame.copyTo(buf); | |
| const bpp = 4; | |
| // Read back canvas pixels for ground truth. | |
| const imageData = ctx.getImageData(0, 0, W, H); | |
| const canvasPixels = imageData.data; | |
| for (let row = 0; row < H; row++) { | |
| for (let x = 0; x < W; x++) { | |
| const frameIdx = (row * W + x) * bpp; | |
| const canvasIdx = (row * W + x) * 4; | |
| const cR = canvasPixels[canvasIdx]; | |
| const cG = canvasPixels[canvasIdx + 1]; | |
| const cB = canvasPixels[canvasIdx + 2]; | |
| let fR, fG, fB; | |
| if (format === 'BGRA' || format === 'BGRX') { | |
| fB = buf[frameIdx]; | |
| fG = buf[frameIdx + 1]; | |
| fR = buf[frameIdx + 2]; | |
| } else { | |
| fR = buf[frameIdx]; | |
| fG = buf[frameIdx + 1]; | |
| fB = buf[frameIdx + 2]; | |
| } | |
| assert_approx_equals(fR, cR, 1, | |
| `row ${row} col ${x}: R mismatch`); | |
| assert_approx_equals(fG, cG, 1, | |
| `row ${row} col ${x}: G mismatch`); | |
| assert_approx_equals(fB, cB, 1, | |
| `row ${row} col ${x}: B mismatch`); | |
| } | |
| } | |
| }, 'copyTo from canvas-backed VideoFrame matches canvas pixel data per row'); |