| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Media Capture from DOM Elements (canvas) Browser Test</title> |
| </head> |
| <body> |
| <div>Canvas capture Color Test.</div> |
| <canvas id='canvas-2d' width=10 height=10></canvas> |
| <video id='canvas-2d-local-view' autoplay></video> |
| <canvas id='canvas-webgl' width=10 height=10></canvas> |
| <video id='canvas-webgl-local-view' autoplay></video> |
| <canvas id='local-view-canvas' width=10 height=10></canvas> |
| <script type="text/javascript" src="mediarecorder_test_utils.js"></script> |
| <script> |
| |
| 'use strict'; |
| |
| const RED = [255, 0, 0, 1]; |
| const GREEN = [0, 255, 0, 1]; |
| const BLUE = [0, 0, 255, 1]; |
| const RED_WITH_ALPHA = [255, 0, 0, 0.2]; |
| const GREEN_WITH_ALPHA = [0, 255, 0, 0.5]; |
| const BLUE_WITH_ALPHA = [0, 0, 255, 0.9]; |
| const MAX_ALPHA = 255; |
| |
| // The RGBA to YUV conversion is not perfectly reversible, so it is expected |
| // that there will be some color info lost when converting RGBA to YUV and then |
| // later YUV to RGBA. |
| const TOLERANCE = 10; |
| |
| // This function draws a colored rectangle on the canvas. |
| function draw(canvasId, contextType, colorRgba, alphaContext) { |
| return new Promise(function(resolve, reject) { |
| // Wrapping the update in requestAnimationFrame is required for this to be |
| // a regression test for crbug.com/702446. requestAnimationFrame exposes |
| // this use case to a potential race between the frame capture and the |
| // frame clear that is caused by the {preserveDrawingBuffer: false} option |
| // on webgl contexts. |
| requestAnimationFrame(function() { |
| var context = canvasId.getContext(contextType, {alpha : alphaContext}); |
| if (contextType == '2d') { |
| context.clearRect(0, 0, canvasId.clientWidth, canvasId.clientHeight); |
| context.fillStyle = 'rgba(' + colorRgba.join() + ')'; |
| context.fillRect(0, 0, canvasId.clientWidth, canvasId.clientHeight); |
| } else { |
| context.clearColor(colorRgba[0] / 255, colorRgba[1] / 255, colorRgba[2] / 255, colorRgba[3]); |
| context.clear(context.COLOR_BUFFER_BIT); |
| } |
| resolve(); |
| }); |
| }); |
| } |
| |
| // This function gets all the pixels from the snapshot canvas. |
| // The snapshot canvas represents a snapshot of the video frame. |
| function getPixels(videoId, canvasId) { |
| var context = canvasId.getContext('2d'); |
| context.clearRect(0, 0, canvasId.clientWidth, canvasId.clientHeight); |
| context.drawImage(videoId, 0, 0); |
| return context.getImageData(0, 0 , canvasId.clientWidth, |
| canvasId.clientHeight).data; |
| } |
| |
| // This generator yields one pixel [R, G, B, A] from the supplied data. |
| function* getPixelGenerator(pixelData) { |
| for (var i = 0; i < pixelData.length; i += 4) { |
| yield pixelData.slice(i, i + 4); |
| } |
| } |
| |
| // This function checks the color correctness of the whole video frame. |
| function isVideoColor(videoId, canvasId, colorRgba, alphaContext) { |
| var pixelIterator = getPixelGenerator(getPixels(videoId, canvasId)); |
| for (var pixelRgba of pixelIterator) { |
| if (!isPixelColor(pixelRgba, colorRgba, alphaContext)) |
| return false; |
| } |
| return true; |
| } |
| |
| // This function checks the rgba color of a single pixel in the video. |
| function isPixelColor(pixel, color, alphaContext) { |
| var expectedColor = color.slice(0); |
| // The css rgba() function takes an alpha channel (a) such as 0 <= a <= 1, |
| // but context.getImageData() returns array of rgba data with alpha |
| // channel (a') such as 0 <= a' <= 255. |
| var checkLength = alphaContext ? color.length : color.length - 1; |
| expectedColor[expectedColor.length - 1] *= MAX_ALPHA; |
| for (var i = 0; i < checkLength; i++) { |
| if (Math.abs(pixel[i] - expectedColor[i]) > TOLERANCE) { |
| console.log('Expected ' + expectedColor + ', got ' + pixel); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| function connectStream(contextType) { |
| var canvas = document.getElementById('canvas-' + contextType); |
| var video = document.getElementById('canvas-' + contextType + '-local-view'); |
| var stream = canvas.captureStream(); |
| video.src = URL.createObjectURL(stream); |
| video.load(); |
| } |
| |
| // This function runs the canvas capture rgba color checks. |
| function testCanvas2DCaptureColors(alphaContext) { |
| connectStream('2d'); |
| |
| doCanvasCaptureAndCheckRgba('2d', RED, alphaContext) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('2d', GREEN, alphaContext); |
| }) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('2d', BLUE, alphaContext); |
| }) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('2d', RED_WITH_ALPHA, alphaContext); |
| }) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('2d', GREEN_WITH_ALPHA, |
| alphaContext); |
| }) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('2d', BLUE_WITH_ALPHA, alphaContext); |
| }) |
| .catch(function(err) { window.domAutomationController.send(err); }) |
| .then(function() { window.domAutomationController.send('OK'); }); |
| } |
| |
| function testCanvasWebGLCaptureColors(alphaContext) { |
| connectStream('webgl'); |
| |
| doCanvasCaptureAndCheckRgba('webgl', RED, alphaContext) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('webgl', GREEN, alphaContext); |
| }) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('webgl', BLUE, alphaContext); |
| }) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('webgl', RED_WITH_ALPHA, |
| alphaContext); |
| }) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('webgl', GREEN_WITH_ALPHA, |
| alphaContext); |
| }) |
| .then(function() { |
| return doCanvasCaptureAndCheckRgba('webgl', BLUE_WITH_ALPHA, |
| alphaContext); |
| }) |
| .catch(function(err) { window.domAutomationController.send(err); }) |
| .then(function() { window.domAutomationController.send('OK'); }); |
| } |
| |
| // Forces a context loss between captured frames and checks if capture continues |
| // as expected. |
| function testCanvas2DContextLoss(alphaContext) { |
| var contextType = '2d'; |
| connectStream(contextType); |
| var canvas = document.getElementById('canvas-' + contextType); |
| |
| doCanvasCaptureAndCheckRgba(contextType, RED, alphaContext) |
| .then(function() { |
| window.internals.loseSharedGraphicsContext3D(); |
| // For the canvas to realize its Graphics context was lost we must try |
| // to use the contents of the canvas. |
| var imageData = canvas.getContext(contextType).getImageData(0, 0, 1, 1); |
| return doCanvasCaptureAndCheckRgba(contextType, BLUE, alphaContext); |
| }) |
| .then(function() { |
| window.domAutomationController.send('OK'); |
| }, function(err) { |
| window.domAutomationController.send(err); |
| }); |
| } |
| |
| // This function fills a canvas with one rgba color and canvas-captures it |
| // to a video element. We then snapshot the video element to |
| // another canvas and checks the color is what we expect. |
| function doCanvasCaptureAndCheckRgba(contextType, colorRgba, alphaContext) { |
| return new Promise(function(resolve, reject) { |
| var canvas = document.getElementById('canvas-' + contextType); |
| var video = document.getElementById('canvas-' + contextType + '-local-view'); |
| var snapshotCanvas = document.getElementById('local-view-canvas'); |
| |
| draw(canvas, contextType, colorRgba, alphaContext) |
| .then(function() { |
| return waitFor('Verify the canvas color is as expected', |
| function() { |
| return isVideoColor(video, snapshotCanvas, colorRgba, |
| alphaContext); |
| }); |
| }) |
| .catch(function(err) { |
| reject(err); |
| }) |
| .then(function() { |
| resolve(); |
| }); |
| }); |
| } |
| |
| </script> |
| </body> |
| </html> |