summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/reftest/color_quads.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/test/reftest/color_quads.html')
-rw-r--r--dom/canvas/test/reftest/color_quads.html327
1 files changed, 327 insertions, 0 deletions
diff --git a/dom/canvas/test/reftest/color_quads.html b/dom/canvas/test/reftest/color_quads.html
new file mode 100644
index 0000000000..944d1f1370
--- /dev/null
+++ b/dom/canvas/test/reftest/color_quads.html
@@ -0,0 +1,327 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <!--
+# color_quads.html
+
+* The default is a 400x400 2d canvas, with 0, 16, 235, and 255 "gray" outer
+ quads, and 50%-red, -green, -blue, and -gray inner quads.
+
+* We default to showing the settings pane when loaded without a query string.
+ This way, someone naively opens this in a browser, they can immediately see
+ all available options.
+
+* The "Publish" button updates the url, and so causes the settings pane to
+ hide.
+
+* Clicking on the canvas toggles the settings pane for further editing.
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>color_quads.html (2022-07-15)</title>
+ </head>
+ <body>
+ <div id="e_settings">
+ Image override: <input id="e_img" type="text">
+
+ <br>
+ <br>Canvas Width: <input id="e_width" type="text" value="400">
+ <br>Canvas Height: <input id="e_height" type="text" value="400">
+ <br>Canvas Colorspace: <input id="e_cspace" type="text">
+ <br>Canvas Context Type: <select id="e_context">
+ <option value="2d" selected="selected">Canvas2D</option>
+ <option value="webgl">WebGL</option>
+ </select>
+ <br>Canvas Context Options: <input id="e_options" type="text" value="{}">
+
+ <br>
+ <br>OuterTopLeft: <input id="e_color_o1" type="text" value="rgb(0,0,0)">
+ <br>OuterTopRight: <input id="e_color_o2" type="text" value="rgb(16,16,16)">
+ <br>OuterBottomLeft: <input id="e_color_o3" type="text" value="rgb(235,235,235)">
+ <br>OuterBottomRight: <input id="e_color_o4" type="text" value="rgb(255,255,255)">
+ <br>
+ <br>InnerTopLeft: <input id="e_color_i1" type="text" value="rgb(127,0,0)">
+ <br>InnerTopRight: <input id="e_color_i2" type="text" value="rgb(0,127,0)">
+ <br>InnerBottomLeft: <input id="e_color_i3" type="text" value="rgb(0,0,127)">
+ <br>InnerBottomRight: <input id="e_color_i4" type="text" value="rgb(127,127,127)">
+ <br><input id="e_publish" type="button" value="Publish">
+ <hr>
+ </div>
+ <div id="e_canvas_holder">
+ <canvas></canvas>
+ </div>
+ <script>
+"use strict";
+
+// document.body.style.backgroundColor = '#fdf';
+
+// -
+
+// Click the canvas to toggle the settings pane.
+e_canvas_holder.addEventListener("click", () => {
+ // Toggle display:none to hide/unhide.
+ e_settings.style.display = e_settings.style.display ? "" : "none";
+});
+
+// Hide settings initially if there's a query string in the url.
+if (window.location.search.startsWith("?")) {
+ e_settings.style.display = "none";
+}
+
+// -
+
+function map(obj, fn) {
+ fn = fn || (x => x);
+ const ret = {};
+ for (const [k,v] of Object.entries(obj)) {
+ ret[k] = fn(v, k);
+ }
+ return ret;
+}
+
+function map_keys_required(obj, keys, fn) {
+ fn = fn || (x => x);
+
+ const ret = {};
+ for (const k of keys) {
+ const v = obj[k];
+ if (v === undefined) throw {k, obj};
+ ret[k] = fn(v, k);
+ }
+ return ret;
+}
+
+function set_device_pixel_size(e, device_size) {
+ const DPR = window.devicePixelRatio;
+ map_keys_required(device_size, ['width', 'height'], (device, k) => {
+ const css = device / DPR;
+ e.style[k] = css + 'px';
+ });
+}
+
+function pad_top_left_to_device_pixels(e) {
+ const DPR = window.devicePixelRatio;
+
+ e.style.padding = '';
+ let css_rect = e.getBoundingClientRect();
+ css_rect = map_keys_required(css_rect, ['left', 'top']);
+
+ const orig_device_rect = {};
+ const snapped_padding = map(css_rect, (css, k) => {
+ const device = orig_device_rect[k] = css * DPR;
+ const device_snapped = Math.round(device);
+ let device_padding = device_snapped - device;
+ // Negative padding is treated as 0.
+ // We want to pad:
+ // * 3.9 -> 4.0
+ // * 3.1 -> 4.0
+ // * 3.00000001 -> 3.0
+ if (device_padding < 0.01) {
+ device_padding += 1;
+ }
+ const css_padding = device_padding / DPR;
+ // console.log({css, k, device, device_snapped, device_padding, css_padding});
+ return css_padding;
+ });
+
+ e.style.paddingLeft = snapped_padding.left + 'px';
+ e.style.paddingTop = snapped_padding.top + 'px';
+ console.log(`[info] At dpr=${DPR}, padding`, css_rect, '(', orig_device_rect, 'device) by', snapped_padding);
+}
+
+// -
+
+const SETTING_NODES = {};
+e_settings.childNodes.forEach(n => {
+ if (!n.id) return;
+ SETTING_NODES[n.id] = n;
+ n._default = n.value;
+});
+
+const URL_PARAMS = new URLSearchParams(window.location.search);
+URL_PARAMS.forEach((v,k) => {
+ const n = SETTING_NODES[k];
+ if (!n) {
+ if (k && !k.startsWith('__')) {
+ console.warn(`Unrecognized setting: ${k} = ${v}`);
+ }
+ return;
+ }
+ n.value = v;
+});
+
+// -
+
+function UNITTEST_STR_EQ(was, expected) {
+ function to_result(src) {
+ let result = src;
+ if (typeof(result) == 'string') {
+ result = eval(result);
+ }
+ let result_str = result.toString();
+ if (result instanceof Array) {
+ result_str = '[' + result_str + ']';
+ }
+ return {src, result, result_str};
+ }
+ was = to_result(was);
+ expected = to_result(expected);
+
+ if (false) {
+ if (was.result_str != expected.result_str) {
+ throw {was, expected};
+ }
+ console.log(`[unittest] OK `, was.src, ` -> ${was.result_str} (`, expected.src, `)`);
+ }
+ console.assert(was.result_str == expected.result_str,
+ was.src, ` -> ${was.result_str} (`, expected.src, `)`);
+}
+
+// -
+
+/// Non-Premult-Alpha, e.g. [1.0, 1.0, 1.0, 0.5]
+function parse_css_color_npa(str) {
+ const m = /(rgba?)\((.*)\)/.exec(str);
+ if (!m) throw str;
+
+ let vals = m[2];
+ vals = vals.split(',').map(s => parseFloat(s));
+ if (vals.length == 3) {
+ vals.push(1.0);
+ }
+ for (let i = 0; i < 3; i++) {
+ vals[i] /= 255;
+ }
+ return vals;
+}
+UNITTEST_STR_EQ(`parse_css_color_npa('rgb(255,255,255)');`, [1,1,1,1]);
+UNITTEST_STR_EQ(`parse_css_color_npa('rgba(255,255,255)');`, [1,1,1,1]);
+UNITTEST_STR_EQ(`parse_css_color_npa('rgb(20,40,60)');`, '[20/255, 40/255, 60/255, 1]');
+UNITTEST_STR_EQ(`parse_css_color_npa('rgb(20,40,60,0.5)');`, '[20/255, 40/255, 60/255, 0.5]');
+UNITTEST_STR_EQ(`parse_css_color_npa('rgb(20,40,60,0)');`, '[20/255, 40/255, 60/255, 0]');
+
+// -
+
+let e_canvas;
+
+async function draw() {
+ while (e_canvas_holder.firstChild) {
+ e_canvas_holder.removeChild(e_canvas_holder.firstChild);
+ }
+
+ if (e_img.value) {
+ const img = document.createElement("img");
+ img.src = e_img.value;
+ console.log('img.src =', img.src);
+ await img.decode();
+ e_canvas_holder.appendChild(img);
+ set_device_pixel_size(img, {width: img.naturalWidth, height: img.naturalHeight});
+ pad_top_left_to_device_pixels(img);
+ return;
+ }
+
+ e_canvas = document.createElement("canvas");
+
+ let options = eval(`Object.assign(${e_options.value})`);
+ options.colorSpace = e_cspace.value || undefined;
+
+ const context = e_canvas.getContext(e_context.value, options);
+ if (context.drawingBufferColorSpace && options.colorSpace) {
+ context.drawingBufferColorSpace = options.colorSpace;
+ }
+ if (context.getContextAttributes) {
+ options = context.getContextAttributes();
+ }
+ console.log({options});
+
+ // -
+
+ const W = parseInt(e_width.value);
+ const H = parseInt(e_height.value);
+ context.canvas.width = W;
+ context.canvas.height = H;
+ e_canvas_holder.appendChild(e_canvas);
+
+ // If we don't snap to the device pixel grid, borders between color blocks
+ // will be filtered, and this causes a lot of fuzzy() annotations.
+ set_device_pixel_size(e_canvas, e_canvas);
+ pad_top_left_to_device_pixels(e_canvas);
+
+ // -
+
+ let fillFromElem;
+ if (context.fillRect) {
+ const c2d = context;
+ fillFromElem = (e, left, top, w, h) => {
+ if (!e.value) return;
+ c2d.fillStyle = e.value;
+ c2d.fillRect(left, top, w, h);
+ };
+
+ } else if (context.drawArrays) {
+ const gl = context;
+ gl.enable(gl.SCISSOR_TEST);
+ gl.disable(gl.DEPTH_TEST);
+ fillFromElem = (e, left, top, w, h) => {
+ if (!e.value) return;
+ const rgba = parse_css_color_npa(e.value.trim());
+ if (false && options.premultipliedAlpha) {
+ for (let i = 0; i < 3; i++) {
+ rgba[i] *= rgba[3];
+ }
+ }
+
+ const bottom = top+h; // in y-down c2d coords
+ gl.scissor(left, gl.drawingBufferHeight - bottom, w, h);
+ gl.clearColor(...rgba);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ };
+ }
+
+ // -
+
+ const LEFT_HALF = W/2 | 0; // Round
+ const TOP_HALF = H/2 | 0;
+
+ fillFromElem(e_color_o1, 0 , 0 , LEFT_HALF, TOP_HALF);
+ fillFromElem(e_color_o2, LEFT_HALF, 0 , W-LEFT_HALF, TOP_HALF);
+ fillFromElem(e_color_o3, 0 , TOP_HALF, LEFT_HALF, H-TOP_HALF);
+ fillFromElem(e_color_o4, LEFT_HALF, TOP_HALF, W-LEFT_HALF, H-TOP_HALF);
+
+ // -
+
+ const INNER_SCALE = 1/4;
+ const W_INNER = W*INNER_SCALE | 0;
+ const H_INNER = H*INNER_SCALE | 0;
+
+ fillFromElem(e_color_i1, LEFT_HALF-W_INNER, TOP_HALF-H_INNER, W_INNER, H_INNER);
+ fillFromElem(e_color_i2, LEFT_HALF , TOP_HALF-H_INNER, W_INNER, H_INNER);
+ fillFromElem(e_color_i3, LEFT_HALF-W_INNER, TOP_HALF , W_INNER, H_INNER);
+ fillFromElem(e_color_i4, LEFT_HALF , TOP_HALF , W_INNER, H_INNER);
+}
+
+(async () => {
+ await draw();
+ document.documentElement.removeAttribute("class");
+})();
+
+// -
+
+Object.values(SETTING_NODES).forEach(x => {
+ x.addEventListener("change", draw);
+});
+
+e_publish.addEventListener("click", () => {
+ let settings = [];
+ for (const n of Object.values(SETTING_NODES)) {
+ if (n.value == n._default) continue;
+ settings.push(`${n.id}=${n.value}`);
+ }
+ settings = settings.join("&");
+ if (!settings) {
+ settings = "="; // Empty key-value pair is "publish with default settings"
+ }
+ window.location.search = "?" + settings;
+});
+ </script>
+ </body>
+</html>