| <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> |