products/Sources/formale Sprachen/C/Firefox/dom/canvas/test/reftest/color_quads.html
<!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 >
Messung V0.5 C=96 H=91 G=93
¤ Dauer der Verarbeitung: 0.4 Sekunden
¤
*© Formatika GbR, Deutschland