forked from mirrors/gecko-dev
		
	 f421080879
			
		
	
	
		f421080879
		
	
	
	
	
		
			
			It was supporting a simpler case of only drawing in the upper left corner of the input canvas. This supports that by default still, but also allows the caller to exactly specify coordinates and size of the rectangle to draw. MozReview-Commit-ID: GVQh0HqejqU --HG-- extra : rebase_source : fb48fd1681f0545c53b5cb49b2791f42270ca83c
		
			
				
	
	
		
			280 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			280 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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 `createAndAppendElement` element |width| and |height|.
 | |
|  */
 | |
| function CaptureStreamTestHelper(width, height) {
 | |
|   if (width) {
 | |
|     this.elemWidth = width;
 | |
|   }
 | |
|   if (height) {
 | |
|     this.elemHeight = height;
 | |
|   }
 | |
| 
 | |
|   /* cout is used for `getPixel`; only needs to be big enough for one pixel */
 | |
|   this.cout = document.createElement('canvas');
 | |
|   this.cout.width = 1;
 | |
|   this.cout.height = 1;
 | |
| }
 | |
| 
 | |
| 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 = () => {
 | |
|       if (stop) { return; }
 | |
|       f();
 | |
|       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].
 | |
|    */
 | |
|   getPixel: function (video, offsetX = 0, offsetY = 0) {
 | |
|     // Avoids old values in case of a transparent image.
 | |
|     CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout);
 | |
| 
 | |
|     var ctxout = this.cout.getContext('2d');
 | |
|     ctxout.drawImage(video,
 | |
|       offsetX, // source x coordinate
 | |
|       offsetY, // source y coordinate
 | |
|       1,       // source width
 | |
|       1,       // source height
 | |
|       0,       // destination x coordinate
 | |
|       0,       // destination y coordinate
 | |
|       1,       // destination width
 | |
|       1);      // destination height
 | |
|     return ctxout.getImageData(0, 0, 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].
 | |
|    *
 | |
|    * Threshold defaults to 0 which is an exact match.
 | |
|    */
 | |
|   isPixel: function (px, refColor, threshold = 0) {
 | |
|     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].
 | |
|    *
 | |
|    * Threshold defaults to 127 which should be far enough for most cases.
 | |
|    */
 | |
|   isPixelNot: function (px, refColor, 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 this.isPixelNot(px, refColor, threshold);
 | |
|   },
 | |
| 
 | |
|   /*
 | |
|    * Returns a promise that resolves when the provided function |test|
 | |
|    * returns true, or rejects when the optional `cancel` promise resolves.
 | |
|    */
 | |
|   waitForPixel: async function (video, test, {
 | |
|                                   offsetX = 0, offsetY = 0,
 | |
|                                   width = 0, height = 0,
 | |
|                                   cancel = new Promise(() => {}),
 | |
|                                 } = {}) {
 | |
|     let aborted = false;
 | |
|     cancel.then(e => aborted = true);
 | |
| 
 | |
|     while (true) {
 | |
|       await Promise.race([
 | |
|         new Promise(resolve => video.addEventListener("timeupdate", resolve, { once: true })),
 | |
|         cancel,
 | |
|       ]);
 | |
|       if (aborted) {
 | |
|         throw await cancel;
 | |
|       }
 | |
|       try {
 | |
|         if (test(this.getPixel(video, offsetX, offsetY, width, height))) {
 | |
|           return;
 | |
|         }
 | |
|       } catch (e) {
 | |
|         info("Waiting for pixel but no video available: " + e + "\n" + e.stack);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /*
 | |
|    * 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]. 0 means exact match, 255 accepts anything.
 | |
|    */
 | |
|   pixelMustBecome: async function (video, refColor, {
 | |
|                                      threshold = 0, infoString = "n/a",
 | |
|                                      cancel = new Promise(() => {}),
 | |
|                                    } = {}) {
 | |
|     info("Waiting for video " + video.id + " to match [" +
 | |
|          refColor.data.join(',') + "] - " + refColor.name +
 | |
|          " (" + infoString + ")");
 | |
|     var paintedFrames = video.mozPaintedFrames-1;
 | |
|     await this.waitForPixel(video, px => {
 | |
|         if (paintedFrames != video.mozPaintedFrames) {
 | |
|          info("Frame: " + video.mozPaintedFrames +
 | |
|              " IsPixel ref=" + refColor.data +
 | |
|              " threshold=" + threshold +
 | |
|              " value=" + px);
 | |
|          paintedFrames = video.mozPaintedFrames;
 | |
|         }
 | |
|         return this.isPixel(px, refColor, threshold);
 | |
|       }, {
 | |
|         offsetX: 0, offsetY: 0,
 | |
|         width: 0, height: 0,
 | |
|         cancel,
 | |
|       });
 | |
|     ok(true, video.id + " " + infoString);
 | |
|   },
 | |
| 
 | |
|   /*
 | |
|    * Returns a promise that resolves after |time| ms of playback or when the
 | |
|    * top left pixel of |video| becomes |refColor|. The test is failed if the
 | |
|    * time is not reached, or if the cancel promise resolves.
 | |
|    */
 | |
|   pixelMustNotBecome: async function (video, refColor, {
 | |
|                                         threshold = 0, time = 5000,
 | |
|                                         infoString = "n/a",
 | |
|                                       } = {}) {
 | |
|     info("Waiting for " + video.id + " to time out after " + time +
 | |
|          "ms against [" + refColor.data.join(',') + "] - " + refColor.name);
 | |
|     let timeout = new Promise(resolve => setTimeout(resolve, time));
 | |
|     let analysis = async () => {
 | |
|       await this.waitForPixel(video, px => this.isPixel(px, refColor, threshold), {
 | |
|           offsetX: 0, offsetY: 0, width: 0, height: 0,
 | |
|         });
 | |
|       throw new Error("Got color " + refColor.name + ". " + infoString);
 | |
|     };
 | |
|     await Promise.race([timeout, analysis()]);
 | |
|     ok(true, 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,
 | |
|     { offsetX = 0,
 | |
|       offsetY = 0,
 | |
|       width = canvas.width / 2,
 | |
|       height = canvas.height / 2,
 | |
|     } = {}) {
 | |
|   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(offsetX, offsetY, width, height);
 | |
| };
 | |
| 
 | |
| /* 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);
 | |
| };
 |