| <title>Test Encode VideoFrame from CanvasImageSource.</title> | |
| <img src="four-colors.png"/> | |
| <svg width="320" height="240" xmlns="http://www.w3.org/2000/svg"> | |
| <image href="four-colors.png" height="320" width="240"/> | |
| </svg> | |
| <canvas id=""></canvas> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="/webcodecs/utils.js"></script> | |
| <script> | |
| promise_test(() => { | |
| return new Promise(resolve => onload = resolve); | |
| }, "Wait for onload event to get access to image data"); | |
| promise_test(async t => { | |
| const img = document.querySelector('img'); | |
| const frame = new VideoFrame(img, {timestamp: 0}); | |
| assert_equals(frame.codedWidth, img.width); | |
| assert_equals(frame.codedHeight, img.height); | |
| let outputs = 0; | |
| const codecInit = getDefaultCodecInit(t); | |
| codecInit.output = (chunk, metadata) => { | |
| outputs += 1; | |
| } | |
| const encoder = new VideoEncoder(codecInit); | |
| encoder.configure({ | |
| codec: 'vp8', | |
| width: frame.codedWidth, | |
| height: frame.codedHeight, | |
| }); | |
| encoder.encode(frame); | |
| frame.close(); | |
| await encoder.flush(); | |
| encoder.close(); | |
| assert_equals(outputs, 1, 'outputs'); | |
| }, 'Test encode ImageElement-constructed VideoFrame'); | |
| promise_test(async t => { | |
| const svg = document.querySelector('svg'); | |
| const svgImage = document.querySelector('image'); | |
| const frame = new VideoFrame(svgImage, {timestamp: 0}); | |
| assert_equals(frame.codedWidth, svg.width.baseVal.value); | |
| assert_equals(frame.codedHeight, svg.height.baseVal.value); | |
| let outputs = 0; | |
| const codecInit = getDefaultCodecInit(t); | |
| codecInit.output = (chunk, metadata) => { | |
| outputs += 1; | |
| } | |
| const encoder = new VideoEncoder(codecInit); | |
| encoder.configure({ | |
| codec: 'vp8', | |
| width: frame.codedWidth, | |
| height: frame.codedHeight, | |
| }); | |
| encoder.encode(frame); | |
| frame.close(); | |
| await encoder.flush(); | |
| encoder.close(); | |
| assert_equals(outputs, 1, 'outputs'); | |
| }, 'Test encode SVGImageElement-constructed VideoFrame'); | |
| function drawFourColors(canvas) { | |
| let ctx = canvas.getContext('2d'); | |
| ctx.fillStyle = '#FFFF00'; // yellow | |
| ctx.fillRect(0, 0, canvas.width / 2, canvas.height / 2); | |
| ctx.fillStyle = '#FF0000'; // red | |
| ctx.fillRect(canvas.width / 2, 0, canvas.width / 2, canvas.height / 2); | |
| ctx.fillStyle = '#0000FF'; // blue | |
| ctx.fillRect(0, canvas.height / 2, canvas.width / 2, canvas.height / 2); | |
| ctx.fillStyle = '#00FF00'; // green | |
| ctx.fillRect(canvas.width / 2, canvas.height / 2, canvas.width / 2, | |
| canvas.height / 2); | |
| } | |
| promise_test(async t => { | |
| const canvas = document.querySelector('canvas'); | |
| canvas.width = 320; | |
| canvas.height = 240; | |
| drawFourColors(canvas); | |
| const frame = new VideoFrame(canvas, {timestamp: 0}); | |
| assert_equals(frame.codedWidth, canvas.width); | |
| assert_equals(frame.codedHeight, canvas.height); | |
| let outputs = 0; | |
| const codecInit = getDefaultCodecInit(t); | |
| codecInit.output = (chunk, metadata) => { | |
| outputs += 1; | |
| } | |
| const encoder = new VideoEncoder(codecInit); | |
| encoder.configure({ | |
| codec: 'vp8', | |
| width: canvas.width, | |
| height: canvas.height, | |
| }); | |
| encoder.encode(frame); | |
| frame.close(); | |
| await encoder.flush(); | |
| encoder.close(); | |
| assert_equals(outputs, 1, 'outputs'); | |
| }, 'Test encode canvas-element-constructed VideoFrame'); | |
| promise_test(async t => { | |
| const canvas = document.querySelector('canvas'); | |
| canvas.width = 320; | |
| canvas.height = 240; | |
| drawFourColors(canvas); | |
| // Crop to top-left quadrant (yellow: #FFFF00). | |
| const visibleRect = {x: 0, y: 0, width: 160, height: 120}; | |
| const frame = new VideoFrame(canvas, {timestamp: 0, visibleRect: visibleRect}); | |
| assert_equals(frame.codedWidth, canvas.width); | |
| assert_equals(frame.codedHeight, canvas.height); | |
| assert_equals(frame.visibleRect.x, 0); | |
| assert_equals(frame.visibleRect.y, 0); | |
| assert_equals(frame.visibleRect.width, 160); | |
| assert_equals(frame.visibleRect.height, 120); | |
| let outputChunk = null; | |
| let outputMetadata = null; | |
| const codecInit = getDefaultCodecInit(t); | |
| codecInit.output = (chunk, metadata) => { | |
| outputChunk = chunk; | |
| if (metadata && metadata.decoderConfig) { | |
| outputMetadata = metadata; | |
| } | |
| }; | |
| const encoder = new VideoEncoder(codecInit); | |
| encoder.configure({ | |
| codec: 'vp8', | |
| width: visibleRect.width, | |
| height: visibleRect.height, | |
| }); | |
| encoder.encode(frame); | |
| frame.close(); | |
| await encoder.flush(); | |
| encoder.close(); | |
| assert_not_equals(outputChunk, null, 'output chunk'); | |
| let decodedFrame = null; | |
| const decoder = new VideoDecoder({ | |
| output(f) { | |
| decodedFrame = f; | |
| }, | |
| error(e) { | |
| assert_unreached(e.message); | |
| }, | |
| }); | |
| decoder.configure(outputMetadata.decoderConfig); | |
| decoder.decode(outputChunk); | |
| await decoder.flush(); | |
| decoder.close(); | |
| assert_not_equals(decodedFrame, null, 'decoded frame'); | |
| assert_equals(decodedFrame.displayWidth, 160); | |
| assert_equals(decodedFrame.displayHeight, 120); | |
| const testCanvas = document.createElement('canvas'); | |
| testCanvas.width = 160; | |
| testCanvas.height = 120; | |
| const testCtx = testCanvas.getContext('2d'); | |
| testCtx.drawImage(decodedFrame, 0, 0); | |
| decodedFrame.close(); | |
| // All 4 quadrants in the decoded frame should be yellow (#FFFF00). | |
| const samplePoints = [ | |
| {x: 40, y: 30}, | |
| {x: 120, y: 30}, | |
| {x: 40, y: 90}, | |
| {x: 120, y: 90}, | |
| ]; | |
| for (const pt of samplePoints) { | |
| const pixel = testCtx.getImageData(pt.x, pt.y, 1, 1).data; | |
| assert_greater_than(pixel[0], 200, `Red channel at (${pt.x}, ${pt.y})`); | |
| assert_greater_than(pixel[1], 200, `Green channel at (${pt.x}, ${pt.y})`); | |
| assert_less_than(pixel[2], 50, `Blue channel at (${pt.x}, ${pt.y})`); | |
| } | |
| }, 'Test encode canvas-element-constructed VideoFrame with visibleRect'); | |
| promise_test(async t => { | |
| const canvas = document.querySelector('canvas'); | |
| canvas.width = 320; | |
| canvas.height = 240; | |
| drawFourColors(canvas); | |
| // Crop to bottom-right quadrant (green: #00FF00). | |
| const visibleRect = {x: 160, y: 120, width: 160, height: 120}; | |
| const frame = new VideoFrame(canvas, {timestamp: 0, visibleRect: visibleRect}); | |
| assert_equals(frame.codedWidth, canvas.width); | |
| assert_equals(frame.codedHeight, canvas.height); | |
| assert_equals(frame.visibleRect.x, 160); | |
| assert_equals(frame.visibleRect.y, 120); | |
| assert_equals(frame.visibleRect.width, 160); | |
| assert_equals(frame.visibleRect.height, 120); | |
| let outputChunk = null; | |
| let outputMetadata = null; | |
| const codecInit = getDefaultCodecInit(t); | |
| codecInit.output = (chunk, metadata) => { | |
| outputChunk = chunk; | |
| if (metadata && metadata.decoderConfig) { | |
| outputMetadata = metadata; | |
| } | |
| }; | |
| const encoder = new VideoEncoder(codecInit); | |
| encoder.configure({ | |
| codec: 'vp8', | |
| width: visibleRect.width, | |
| height: visibleRect.height, | |
| }); | |
| encoder.encode(frame); | |
| frame.close(); | |
| await encoder.flush(); | |
| encoder.close(); | |
| assert_not_equals(outputChunk, null, 'output chunk'); | |
| let decodedFrame = null; | |
| const decoder = new VideoDecoder({ | |
| output(f) { | |
| decodedFrame = f; | |
| }, | |
| error(e) { | |
| assert_unreached(e.message); | |
| }, | |
| }); | |
| decoder.configure(outputMetadata.decoderConfig); | |
| decoder.decode(outputChunk); | |
| await decoder.flush(); | |
| decoder.close(); | |
| assert_not_equals(decodedFrame, null, 'decoded frame'); | |
| assert_equals(decodedFrame.displayWidth, 160); | |
| assert_equals(decodedFrame.displayHeight, 120); | |
| const testCanvas = document.createElement('canvas'); | |
| testCanvas.width = 160; | |
| testCanvas.height = 120; | |
| const testCtx = testCanvas.getContext('2d'); | |
| testCtx.drawImage(decodedFrame, 0, 0); | |
| decodedFrame.close(); | |
| // All 4 quadrants in the decoded frame should be green (#00FF00). | |
| const samplePoints = [ | |
| {x: 40, y: 30}, | |
| {x: 120, y: 30}, | |
| {x: 40, y: 90}, | |
| {x: 120, y: 90}, | |
| ]; | |
| for (const pt of samplePoints) { | |
| const pixel = testCtx.getImageData(pt.x, pt.y, 1, 1).data; | |
| assert_less_than(pixel[0], 50, `Red channel at (${pt.x}, ${pt.y})`); | |
| assert_greater_than(pixel[1], 200, `Green channel at (${pt.x}, ${pt.y})`); | |
| assert_less_than(pixel[2], 50, `Blue channel at (${pt.x}, ${pt.y})`); | |
| } | |
| }, 'Test encode canvas-element-constructed VideoFrame with non-zero offset visibleRect'); | |
| </script> |