diff options
Diffstat (limited to 'dom/canvas/test/captureStream_common.js')
-rw-r--r-- | dom/canvas/test/captureStream_common.js | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/dom/canvas/test/captureStream_common.js b/dom/canvas/test/captureStream_common.js new file mode 100644 index 000000000..3fca8df4b --- /dev/null +++ b/dom/canvas/test/captureStream_common.js @@ -0,0 +1,250 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* + * Util base class to help test a captured canvas element. Initializes the + * output canvas (used for testing the color of video elements), and optionally + * overrides the default element |width| and |height|. + */ +function CaptureStreamTestHelper(width, height) { + this.cout = document.createElement('canvas'); + if (width) { + this.elemWidth = width; + } + if (height) { + this.elemHeight = height; + } + this.cout.width = this.elemWidth; + this.cout.height = this.elemHeight; + document.body.appendChild(this.cout); +} + +CaptureStreamTestHelper.prototype = { + /* Predefined colors for use in the methods below. */ + black: { data: [0, 0, 0, 255], name: "black" }, + blackTransparent: { data: [0, 0, 0, 0], name: "blackTransparent" }, + green: { data: [0, 255, 0, 255], name: "green" }, + red: { data: [255, 0, 0, 255], name: "red" }, + blue: { data: [0, 0, 255, 255], name: "blue"}, + grey: { data: [128, 128, 128, 255], name: "grey" }, + + /* Default element size for createAndAppendElement() */ + elemWidth: 100, + elemHeight: 100, + + /* + * Perform the drawing operation on each animation frame until stop is called + * on the returned object. + */ + startDrawing: function (f) { + var stop = false; + var draw = () => { + f(); + if (!stop) { window.requestAnimationFrame(draw); } + }; + draw(); + return { stop: () => stop = true }; + }, + + /* Request a frame from the stream played by |video|. */ + requestFrame: function (video) { + info("Requesting frame from " + video.id); + video.srcObject.requestFrame(); + }, + + /* + * Returns the pixel at (|offsetX|, |offsetY|) (from top left corner) of + * |video| as an array of the pixel's color channels: [R,G,B,A]. Allows + * optional scaling of the drawImage() call (so that a 1x1 black image + * won't just draw 1 pixel in the corner) + */ + getPixel: function (video, offsetX, offsetY, width, height) { + offsetX = offsetX || 0; // Set to 0 if not passed in. + offsetY = offsetY || 0; // Set to 0 if not passed in. + width = width || 0; // Set to 0 if not passed in. + height = height || 0; // Set to 0 if not passed in. + + // Avoids old values in case of a transparent image. + CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout); + + var ctxout = this.cout.getContext('2d'); + if (width != 0 || height != 0) { + ctxout.drawImage(video, 0, 0, width, height); + } else { + ctxout.drawImage(video, 0, 0); + } + return ctxout.getImageData(offsetX, offsetY, 1, 1).data; + }, + + /* + * Returns true if px lies within the per-channel |threshold| of the + * referenced color for all channels. px is on the form of an array of color + * channels, [R,G,B,A]. Each channel is in the range [0, 255]. + */ + isPixel: function (px, refColor, threshold) { + threshold = threshold || 0; // Default to 0 (exact match) if not passed in. + return px.every((ch, i) => Math.abs(ch - refColor.data[i]) <= threshold); + }, + + /* + * Returns true if px lies further away than |threshold| of the + * referenced color for any channel. px is on the form of an array of color + * channels, [R,G,B,A]. Each channel is in the range [0, 255]. + */ + isPixelNot: function (px, refColor, threshold) { + if (threshold === undefined) { + // Default to 127 (should be sufficiently far away) if not passed in. + threshold = 127; + } + return px.some((ch, i) => Math.abs(ch - refColor.data[i]) > threshold); + }, + + /* + * Behaves like isPixelNot but ignores the alpha channel. + */ + isOpaquePixelNot: function(px, refColor, threshold) { + px[3] = refColor.data[3]; + return h.isPixelNot(px, refColor, threshold); + }, + + /* + * Returns a promise that resolves when the provided function |test| + * returns true. + */ + waitForPixel: function (video, offsetX, offsetY, test, timeout, width, height) { + return new Promise(resolve => { + const startTime = video.currentTime; + var ontimeupdate = () => { + var pixelMatch = false; + try { + pixelMatch = test(this.getPixel(video, offsetX, offsetY, width, height)); + } catch (e) { + info("Waiting for pixel but no video available: " + e + "\n" + e.stack); + } + if (!pixelMatch && + (!timeout || video.currentTime < startTime + (timeout / 1000.0))) { + // No match yet and, + // No timeout (waiting indefinitely) or |timeout| has not passed yet. + return; + } + video.removeEventListener("timeupdate", ontimeupdate); + resolve(pixelMatch); + }; + video.addEventListener("timeupdate", ontimeupdate); + }); + }, + + /* + * Returns a promise that resolves when the top left pixel of |video| matches + * on all channels. Use |threshold| for fuzzy matching the color on each + * channel, in the range [0,255]. + */ + waitForPixelColor: function (video, refColor, threshold, infoString) { + info("Waiting for video " + video.id + " to match [" + + refColor.data.join(',') + "] - " + refColor.name + + " (" + infoString + ")"); + return this.waitForPixel(video, 0, 0, + px => this.isPixel(px, refColor, threshold)) + .then(() => ok(true, video.id + " " + infoString)); + }, + + /* + * Returns a promise that resolves after |timeout| ms of playback or when the + * top left pixel of |video| becomes |refColor|. The test is failed if the + * timeout is not reached. + */ + waitForPixelColorTimeout: function (video, refColor, threshold, timeout, infoString) { + info("Waiting for " + video.id + " to time out after " + timeout + + "ms against [" + refColor.data.join(',') + "] - " + refColor.name); + return this.waitForPixel(video, 0, 0, + px => this.isPixel(px, refColor, threshold), + timeout) + .then(result => ok(!result, video.id + " " + infoString)); + }, + + /* Create an element of type |type| with id |id| and append it to the body. */ + createAndAppendElement: function (type, id) { + var e = document.createElement(type); + e.id = id; + e.width = this.elemWidth; + e.height = this.elemHeight; + if (type === 'video') { + e.autoplay = true; + } + document.body.appendChild(e); + return e; + }, +} + +/* Sub class holding 2D-Canvas specific helpers. */ +function CaptureStreamTestHelper2D(width, height) { + CaptureStreamTestHelper.call(this, width, height); +} + +CaptureStreamTestHelper2D.prototype = Object.create(CaptureStreamTestHelper.prototype); +CaptureStreamTestHelper2D.prototype.constructor = CaptureStreamTestHelper2D; + +/* Clear all drawn content on |canvas|. */ +CaptureStreamTestHelper2D.prototype.clear = function(canvas) { + var ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); +}; + +/* Draw the color |color| to the source canvas |canvas|. Format [R,G,B,A]. */ +CaptureStreamTestHelper2D.prototype.drawColor = function(canvas, color) { + var ctx = canvas.getContext('2d'); + var rgba = color.data.slice(); // Copy to not overwrite the original array + rgba[3] = rgba[3] / 255.0; // Convert opacity to double in range [0,1] + info("Drawing color " + rgba.join(',')); + ctx.fillStyle = "rgba(" + rgba.join(',') + ")"; + + // Only fill top left corner to test that output is not flipped or rotated. + ctx.fillRect(0, 0, canvas.width / 2, canvas.height / 2); +}; + +/* Test that the given 2d canvas is NOT origin-clean. */ +CaptureStreamTestHelper2D.prototype.testNotClean = function(canvas) { + var ctx = canvas.getContext('2d'); + var error = "OK"; + try { + var data = ctx.getImageData(0, 0, 1, 1); + } catch(e) { + error = e.name; + } + is(error, "SecurityError", + "Canvas '" + canvas.id + "' should not be origin-clean"); +}; + +/* Sub class holding WebGL specific helpers. */ +function CaptureStreamTestHelperWebGL(width, height) { + CaptureStreamTestHelper.call(this, width, height); +} + +CaptureStreamTestHelperWebGL.prototype = Object.create(CaptureStreamTestHelper.prototype); +CaptureStreamTestHelperWebGL.prototype.constructor = CaptureStreamTestHelperWebGL; + +/* Set the (uniform) color location for future draw calls. */ +CaptureStreamTestHelperWebGL.prototype.setFragmentColorLocation = function(colorLocation) { + this.colorLocation = colorLocation; +}; + +/* Clear the given WebGL context with |color|. */ +CaptureStreamTestHelperWebGL.prototype.clearColor = function(canvas, color) { + info("WebGL: clearColor(" + color.name + ")"); + var gl = canvas.getContext('webgl'); + var conv = color.data.map(i => i / 255.0); + gl.clearColor(conv[0], conv[1], conv[2], conv[3]); + gl.clear(gl.COLOR_BUFFER_BIT); +}; + +/* Set an already setFragmentColorLocation() to |color| and drawArrays() */ +CaptureStreamTestHelperWebGL.prototype.drawColor = function(canvas, color) { + info("WebGL: drawArrays(" + color.name + ")"); + var gl = canvas.getContext('webgl'); + var conv = color.data.map(i => i / 255.0); + gl.uniform4f(this.colorLocation, conv[0], conv[1], conv[2], conv[3]); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); +}; |