Bug 1935611 - Fix libyuv/libpng link failed for loongarch64. r=glandium,tnikkel,ng
[gecko.git] / dom / canvas / test / captureStream_common.js
bloba3c66133e339bc94af4c6b87b5983bf16d967b8b
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 /*
8  * Util base class to help test a captured canvas element. Initializes the
9  * output canvas (used for testing the color of video elements), and optionally
10  * overrides the default `createAndAppendElement` element |width| and |height|.
11  */
12 function CaptureStreamTestHelper(width, height) {
13   if (width) {
14     this.elemWidth = width;
15   }
16   if (height) {
17     this.elemHeight = height;
18   }
20   /* cout is used for `getPixel`; only needs to be big enough for one pixel */
21   this.cout = document.createElement("canvas");
22   this.cout.width = 1;
23   this.cout.height = 1;
26 CaptureStreamTestHelper.prototype = {
27   /* Predefined colors for use in the methods below. */
28   black: { data: [0, 0, 0, 255], name: "black" },
29   blackTransparent: { data: [0, 0, 0, 0], name: "blackTransparent" },
30   white: { data: [255, 255, 255, 255], name: "white" },
31   green: { data: [0, 255, 0, 255], name: "green" },
32   red: { data: [255, 0, 0, 255], name: "red" },
33   blue: { data: [0, 0, 255, 255], name: "blue" },
34   grey: { data: [128, 128, 128, 255], name: "grey" },
36   /* Default element size for createAndAppendElement() */
37   elemWidth: 100,
38   elemHeight: 100,
40   /*
41    * Perform the drawing operation on each animation frame until stop is called
42    * on the returned object.
43    */
44   startDrawing(f) {
45     var stop = false;
46     var draw = () => {
47       if (stop) {
48         return;
49       }
50       f();
51       window.requestAnimationFrame(draw);
52     };
53     draw();
54     return { stop: () => (stop = true) };
55   },
57   /* Request a frame from the stream played by |video|. */
58   requestFrame(video) {
59     info("Requesting frame from " + video.id);
60     video.srcObject.requestFrame();
61   },
63   /*
64    * Returns the pixel at (|offsetX|, |offsetY|) (from top left corner) of
65    * |video| as an array of the pixel's color channels: [R,G,B,A].
66    */
67   getPixel(video, offsetX = 0, offsetY = 0) {
68     // Avoids old values in case of a transparent image.
69     CaptureStreamTestHelper2D.prototype.clear.call(this, this.cout);
71     var ctxout = this.cout.getContext("2d");
72     ctxout.drawImage(
73       video,
74       offsetX, // source x coordinate
75       offsetY, // source y coordinate
76       1, // source width
77       1, // source height
78       0, // destination x coordinate
79       0, // destination y coordinate
80       1, // destination width
81       1
82     ); // destination height
83     return ctxout.getImageData(0, 0, 1, 1).data;
84   },
86   /*
87    * Returns true if px lies within the per-channel |threshold| of the
88    * referenced color for all channels. px is on the form of an array of color
89    * channels, [R,G,B,A]. Each channel is in the range [0, 255].
90    *
91    * Threshold defaults to 0 which is an exact match.
92    */
93   isPixel(px, refColor, threshold = 0) {
94     return px.every((ch, i) => Math.abs(ch - refColor.data[i]) <= threshold);
95   },
97   /*
98    * Returns true if px lies further away than |threshold| of the
99    * referenced color for any channel. px is on the form of an array of color
100    * channels, [R,G,B,A]. Each channel is in the range [0, 255].
101    *
102    * Threshold defaults to 127 which should be far enough for most cases.
103    */
104   isPixelNot(px, refColor, threshold = 127) {
105     return px.some((ch, i) => Math.abs(ch - refColor.data[i]) > threshold);
106   },
108   /*
109    * Behaves like isPixelNot but ignores the alpha channel.
110    */
111   isOpaquePixelNot(px, refColor, threshold) {
112     px[3] = refColor.data[3];
113     return this.isPixelNot(px, refColor, threshold);
114   },
116   /*
117    * Returns a promise that resolves when the provided function |test|
118    * returns true, or rejects when the optional `cancel` promise resolves.
119    */
120   async waitForPixel(
121     video,
122     test,
123     {
124       offsetX = 0,
125       offsetY = 0,
126       width = 0,
127       height = 0,
128       cancel = new Promise(() => {}),
129     } = {}
130   ) {
131     let aborted = false;
132     cancel.then(e => (aborted = true));
134     while (true) {
135       await Promise.race([
136         new Promise(resolve =>
137           video.addEventListener("timeupdate", resolve, { once: true })
138         ),
139         cancel,
140       ]);
141       if (aborted) {
142         throw await cancel;
143       }
144       if (test(this.getPixel(video, offsetX, offsetY, width, height))) {
145         return;
146       }
147     }
148   },
150   /*
151    * Returns a promise that resolves when the top left pixel of |video| matches
152    * on all channels. Use |threshold| for fuzzy matching the color on each
153    * channel, in the range [0,255]. 0 means exact match, 255 accepts anything.
154    */
155   async pixelMustBecome(
156     video,
157     refColor,
158     { threshold = 0, infoString = "n/a", cancel = new Promise(() => {}) } = {}
159   ) {
160     info(
161       "Waiting for video " +
162         video.id +
163         " to match [" +
164         refColor.data.join(",") +
165         "] - " +
166         refColor.name +
167         " (" +
168         infoString +
169         ")"
170     );
171     var paintedFrames = video.mozPaintedFrames - 1;
172     await this.waitForPixel(
173       video,
174       px => {
175         if (paintedFrames != video.mozPaintedFrames) {
176           info(
177             "Frame: " +
178               video.mozPaintedFrames +
179               " IsPixel ref=" +
180               refColor.data +
181               " threshold=" +
182               threshold +
183               " value=" +
184               px
185           );
186           paintedFrames = video.mozPaintedFrames;
187         }
188         return this.isPixel(px, refColor, threshold);
189       },
190       {
191         offsetX: 0,
192         offsetY: 0,
193         width: 0,
194         height: 0,
195         cancel,
196       }
197     );
198     ok(true, video.id + " " + infoString);
199   },
201   /*
202    * Returns a promise that resolves after |time| ms of playback or when the
203    * top left pixel of |video| becomes |refColor|. The test is failed if the
204    * time is not reached, or if the cancel promise resolves.
205    */
206   async pixelMustNotBecome(
207     video,
208     refColor,
209     { threshold = 0, time = 5000, infoString = "n/a" } = {}
210   ) {
211     info(
212       "Waiting for " +
213         video.id +
214         " to time out after " +
215         time +
216         "ms against [" +
217         refColor.data.join(",") +
218         "] - " +
219         refColor.name
220     );
221     let timeout = new Promise(resolve => setTimeout(resolve, time));
222     let analysis = async () => {
223       await this.waitForPixel(
224         video,
225         px => this.isPixel(px, refColor, threshold),
226         {
227           offsetX: 0,
228           offsetY: 0,
229           width: 0,
230           height: 0,
231         }
232       );
233       throw new Error("Got color " + refColor.name + ". " + infoString);
234     };
235     await Promise.race([timeout, analysis()]);
236     ok(true, video.id + " " + infoString);
237   },
239   /* Create an element of type |type| with id |id| and append it to the body. */
240   createAndAppendElement(type, id) {
241     var e = document.createElement(type);
242     e.id = id;
243     e.width = this.elemWidth;
244     e.height = this.elemHeight;
245     if (type === "video") {
246       e.autoplay = true;
247     }
248     document.body.appendChild(e);
249     return e;
250   },
253 /* Sub class holding 2D-Canvas specific helpers. */
254 function CaptureStreamTestHelper2D(width, height) {
255   CaptureStreamTestHelper.call(this, width, height);
258 CaptureStreamTestHelper2D.prototype = Object.create(
259   CaptureStreamTestHelper.prototype
261 CaptureStreamTestHelper2D.prototype.constructor = CaptureStreamTestHelper2D;
263 /* Clear all drawn content on |canvas|. */
264 CaptureStreamTestHelper2D.prototype.clear = function (canvas) {
265   var ctx = canvas.getContext("2d");
266   ctx.clearRect(0, 0, canvas.width, canvas.height);
269 /* Draw the color |color| to the source canvas |canvas|. Format [R,G,B,A]. */
270 CaptureStreamTestHelper2D.prototype.drawColor = function (
271   canvas,
272   color,
273   {
274     offsetX = 0,
275     offsetY = 0,
276     width = canvas.width / 2,
277     height = canvas.height / 2,
278   } = {}
279 ) {
280   var ctx = canvas.getContext("2d");
281   var rgba = color.data.slice(); // Copy to not overwrite the original array
282   rgba[3] = rgba[3] / 255.0; // Convert opacity to double in range [0,1]
283   info("Drawing color " + rgba.join(","));
284   ctx.fillStyle = "rgba(" + rgba.join(",") + ")";
286   // Only fill top left corner to test that output is not flipped or rotated.
287   ctx.fillRect(offsetX, offsetY, width, height);
290 /* Test that the given 2d canvas is NOT origin-clean. */
291 CaptureStreamTestHelper2D.prototype.testNotClean = function (canvas) {
292   var ctx = canvas.getContext("2d");
293   var error = "OK";
294   try {
295     var data = ctx.getImageData(0, 0, 1, 1);
296   } catch (e) {
297     error = e.name;
298   }
299   is(
300     error,
301     "SecurityError",
302     "Canvas '" + canvas.id + "' should not be origin-clean"
303   );
306 /* Sub class holding WebGL specific helpers. */
307 function CaptureStreamTestHelperWebGL(width, height) {
308   CaptureStreamTestHelper.call(this, width, height);
311 CaptureStreamTestHelperWebGL.prototype = Object.create(
312   CaptureStreamTestHelper.prototype
314 CaptureStreamTestHelperWebGL.prototype.constructor =
315   CaptureStreamTestHelperWebGL;
317 /* Set the (uniform) color location for future draw calls. */
318 CaptureStreamTestHelperWebGL.prototype.setFragmentColorLocation = function (
319   colorLocation
320 ) {
321   this.colorLocation = colorLocation;
324 /* Clear the given WebGL context with |color|. */
325 CaptureStreamTestHelperWebGL.prototype.clearColor = function (canvas, color) {
326   info("WebGL: clearColor(" + color.name + ")");
327   var gl = canvas.getContext("webgl");
328   var conv = color.data.map(i => i / 255.0);
329   gl.clearColor(conv[0], conv[1], conv[2], conv[3]);
330   gl.clear(gl.COLOR_BUFFER_BIT);
333 /* Set an already setFragmentColorLocation() to |color| and drawArrays() */
334 CaptureStreamTestHelperWebGL.prototype.drawColor = function (canvas, color) {
335   info("WebGL: drawArrays(" + color.name + ")");
336   var gl = canvas.getContext("webgl");
337   var conv = color.data.map(i => i / 255.0);
338   gl.uniform4f(this.colorLocation, conv[0], conv[1], conv[2], conv[3]);
339   gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);