Created
March 13, 2017 19:48
-
-
Save fserb/a22ab8bd708f0a2ea3c5b4ede4c6156e to your computer and use it in GitHub Desktop.
Context2D getImageData float hell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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