Skip to content

Instantly share code, notes, and snippets.

@fserb
Created March 13, 2017 19:48
Show Gist options
  • Save fserb/a22ab8bd708f0a2ea3c5b4ede4c6156e to your computer and use it in GitHub Desktop.
Save fserb/a22ab8bd708f0a2ea3c5b4ede4c6156e to your computer and use it in GitHub Desktop.
Context2D getImageData float hell
<!doctype html>
<style>
body {
font-family: verdana;
}
p {
width: 750px;
}
h3 {
margin-bottom: 0;
}
th, td {
font-size: 12px;
width: 100px;
height: 25px;
text-align: center;
}
tr:first-child th {
padding: 8px;
}
th:first-child {
width: 200px;
}
td.t, td.f, td.n, td.n2 { width: 25px; }
td.n2 { background-color: #EEE; }
td.t { background-color: rgb(177, 223, 173); color: rgba(0,0,0,0.3); }
td.f { background-color: rgb(221, 121, 121); }
th {
background-color: #bcd5e0;
}
</style>
<h1>context2D getImageData()</h1>
<div id="table"></div>
<div id="notes">
<p><b>vs Chrome</b>: current implementation by Chrome, Safari, Opera. If
<i>0 < [w or h] < 1</i> then it forces it to be <i>1</i>
(it is still an error to pass <i>0</i>, but <i>0.00001</i> would be equivalent to <i>1</i>).
Then it returns all the pixels partially or fully inside <i>(x, y, x + w, y + h)</i>.
<p><b>vs Firefox</b>: current implementation by Firefox. It fails for <i>w,h == 0</i>.
Otherwise, calls <i>ToInt32</i> on all parameters and if <i>w,h == 0</i> then it forces it to <i>1</i>.
Returns all pixels defined by the int versions of <i>(x, y, x + w, y + h)</i>.
<p><b>vs partial bounding box</b>: Same as Chrome, but without the pre-min to <i>1</i>.
The pre-min makes little sense when the size would go across pixel boundaries anyway.
<p><b>vs full bounding box</b>: Only returns pixels completely inside the float <i>(x, y, x + w, y + h)</i>.
<p><b>vs int</b>: Same as firefox, but dont force </i>(w,h)</i> to <i>1</i>.
Equivalent to changing the signature of getImageData to int.
</div>
<script>
var inputs = [
[0.001, 0.5, 1.0, 0.5],
[0.499, 0.501, 0.499, 0.501],
[-0.001, 0.001, -1, -0.5],
[0, 0.5, -1.01, -0.99],
];
var canvas = document.createElement("canvas");
canvas.width = 20;
canvas.height = 20;
const ctx = canvas.getContext("2d");
var toHex = (x) => { if (x < 16) return "0" + x.toString(16); else return x.toString(16) };
for (var x = 0; x < 20; ++x) {
for (var y = 0; y < 20; ++y) {
ctx.fillStyle = "#" + toHex(x) + "00" + toHex(y);
ctx.fillRect(x, y, 1, 1);
}
}
var getImageDataSquare = function(x, y, w, h) {
const m = ctx.getImageData(x, y, w, h);
var px = -1, py = -1;
if (m.width > 0 && m.height > 0) {
px = m.data[0];
py = m.data[2];
}
return {x: px, y: py, w: m.width, h: m.height};
}
var equals = function(a, b) { return a.x == b.x && a.y == b.y && a.w == b.w && a.h == b.h; }
var tests = [
{ name: "Chrome (bbox, min size 1)", func: r => {
var x = r.x;
var y = r.y;
var w = r.w;
var h = r.h;
if (w < 0) { x += w; w = -w; }
if (h < 0) { y += h; h = -h; }
if (w < 1) w = 1;
if (h < 1) h = 1;
return { x: Math.floor(x), y: Math.floor(y),
w: Math.ceil(x + w) - Math.floor(x), h: Math.ceil(y + h) - Math.floor(y) };
}},
{ name: "Firefox (ToInt32, force 1 size)", func: r => {
var x = Math.floor(r.x);
var y = Math.floor(r.y);
var w = r.w < 0 ? Math.ceil(r.w) : Math.floor(r.w);
var h = r.h < 0 ? Math.ceil(r.h) : Math.floor(r.h);
if (r.w < 0) { w = -w; x -= w; }
if (r.h < 0) { h = -h; y -= h; }
if (w == 0) { w = 1; }
if (h == 0) { h = 1; }
return {x:x, y:y, w:w, h:h};
}},
{ name: "partial bounding box", func: r => {
var x = r.x;
var y = r.y;
var w = r.w;
var h = r.h;
if (w < 0) { x += w; w = -w; }
if (h < 0) { y += h; h = -h; }
return { x: Math.floor(x), y: Math.floor(y),
w: Math.ceil(x + w) - Math.floor(x), h: Math.ceil(y + h) - Math.floor(y) };
}},
{ name: "full bounding box", func: r => {
var x = r.x;
var y = r.y;
var w = r.w;
var h = r.h;
if (w < 0) { x += w; w = -w; }
if (h < 0) { y += h; h = -h; }
return { x: Math.ceil(x), y: Math.ceil(y),
w: Math.floor(x + w) - Math.ceil(x), h: Math.floor(y + h) - Math.ceil(y) };
}},
{ name: "int", func: r => {
var x = Math.floor(r.x);
var y = Math.floor(r.y);
var w = r.w < 0 ? Math.ceil(r.w) : Math.floor(r.w);
var h = r.h < 0 ? Math.ceil(r.h) : Math.floor(r.h);
if (r.w < 0) { w = -w; x -= w; }
if (r.h < 0) { h = -h; y -= h; }
return {x:x, y:y, w:w, h:h};
}},
]
var table = "<table>";
table += "<tr><th>args \ method</th><th colspan=4>this browser</th>";
for (var t of tests) {
table += "<th colspan=4>vs " + t.name + "</th>";
}
table += "</tr>";
for (var i of inputs) {
table += "<tr><th>(" + i[0] + ", " + i[1] + ", " + i[2] + ", " + i[3] + ")</th>";
var r = {x: 10 + i[0], y: 10 + i[1], w: i[2], h: i[3]};
var d = getImageDataSquare(r.x, r.y, r.w, r.h);
table += "<td class='n'>" + (d.x-10) + "</td>";
table += "<td class='n'>" + (d.y-10) + "</td>";
table += "<td class='n2'>" + d.w + "</td>";
table += "<td class='n2'>" + d.h + "</td>";
for (var t of tests) {
var g = t.func(r);
table += "<td class='" + (g.x != d.x ? "f" : "t") + "'>" + (g.x-10) + "</td>";
table += "<td class='" + (g.y != d.y ? "f" : "t") + "'>" + (g.y-10) + "</td>";
table += "<td class='" + (g.w != d.w ? "f" : "t") + "'>" + (g.w) + "</td>";
table += "<td class='" + (g.h != d.h ? "f" : "t") + "'>" + (g.h) + "</td>";
}
table += "</tr>";
}
table += "</table>";
document.getElementById("table").innerHTML = table;
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment