10,000 best-candidate samples of Van Gogh’s Starry Night. Compare to poisson-disc samples.
With Olive Trees instead
10,000 best-candidate samples of Van Gogh’s Starry Night. Compare to poisson-disc samples.
With Olive Trees instead
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script> | |
var width = 800, | |
height = 639; | |
var sample = bestCandidateSampler(width, height, 10, 10000), | |
samples = [], | |
s; | |
while (s = sample()) samples.push(s); | |
var voronoi = d3.geom.voronoi() | |
.clipExtent([[0, 0], [width, height]]); | |
var canvas = d3.select("body").append("canvas") | |
.attr("width", width) | |
.attr("height", height); | |
var context = canvas.node().getContext("2d"); | |
var image = new Image; | |
image.src = "olive-trees.jpg"; | |
image.onload = start; | |
function start() { | |
context.drawImage(image, 0, 0); | |
image = context.getImageData(0, 0, width, height); | |
voronoi(samples).forEach(function(cell) { | |
var x = Math.floor(cell.point[0]), | |
y = Math.floor(cell.point[1]), | |
i = (y * width + x) << 2; | |
context.fillStyle = d3.rgb(image.data[i + 0], image.data[i + 1], image.data[i + 2]) + ""; | |
context.beginPath(); | |
context.moveTo(cell[0][0], cell[0][1]); | |
for (var i = 1, n = cell.length; i < n; ++i) context.lineTo(cell[i][0], cell[i][1]); | |
context.closePath(); | |
context.fill(); | |
}); | |
} | |
function bestCandidateSampler(width, height, numCandidates, numSamplesMax) { | |
var numSamples = 0; | |
var quadtree = d3.geom.quadtree() | |
.extent([[0, 0], [width, height]]) | |
([[Math.random() * width, Math.random() * height]]); | |
return function() { | |
if (++numSamples > numSamplesMax) return; | |
var bestCandidate, bestDistance = 0; | |
for (var i = 0; i < numCandidates; ++i) { | |
var c = [Math.random() * width, Math.random() * height], | |
d = distance(search(c[0], c[1]), c); | |
if (d > bestDistance) { | |
bestDistance = d; | |
bestCandidate = c; | |
} | |
} | |
quadtree.add(bestCandidate); | |
return bestCandidate; | |
}; | |
function distance(a, b) { | |
var dx = a[0] - b[0], | |
dy = a[1] - b[1]; | |
return dx * dx + dy * dy; | |
}; | |
// Find the closest node to the specified point. | |
function search(x, y) { | |
var x0 = 0, | |
y0 = 0, | |
x3 = width, | |
y3 = width, | |
minDistance2 = Infinity, | |
closestPoint; | |
(function find(node, x1, y1, x2, y2) { | |
var point; | |
// stop searching if this cell can’t contain a closer node | |
if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return; | |
// visit this point | |
if (point = node.point) { | |
var dx = x - point[0], | |
dy = y - point[1], | |
distance2 = dx * dx + dy * dy; | |
if (distance2 < minDistance2) { | |
var distance = Math.sqrt(minDistance2 = distance2); | |
x0 = x - distance, y0 = y - distance; | |
x3 = x + distance, y3 = y + distance; | |
closestPoint = point; | |
} | |
} | |
// bisect the current node | |
var children = node.nodes, | |
xm = (x1 + x2) * .5, | |
ym = (y1 + y2) * .5, | |
right = x > xm, | |
below = y > ym; | |
// visit closest cell first | |
if (node = children[below << 1 | right]) find(node, right ? xm : x1, below ? ym : y1, right ? x2 : xm, below ? y2 : ym); | |
if (node = children[below << 1 | !right]) find(node, right ? x1 : xm, below ? ym : y1, right ? xm : x2, below ? y2 : ym); | |
if (node = children[!below << 1 | right]) find(node, right ? xm : x1, below ? y1 : ym, right ? x2 : xm, below ? ym : y2); | |
if (node = children[!below << 1 | !right]) find(node, right ? x1 : xm, below ? y1 : ym, right ? xm : x2, below ? ym : y2); | |
})(quadtree, x0, y0, x3, y3); | |
return closestPoint; | |
} | |
} | |
</script> | |
<img src="https://api.artsmia.org/images/1218/large.jpg"></img> | |
<style> | |
body { margin: 0; } | |
img { | |
position: absolute; | |
top: 0; | |
left: 0; | |
opacity: 0; | |
-webkit-transition: opacity 1s ease-in; | |
} | |
img:hover { | |
opacity: 1 | |
} | |
</style> |
olive-trees.jpg: | |
curl -O -o olive-trees.jpg https://api.artsmia.org/images/1218/large.jpg |