\$\begingroup\$

Credits to Calvin's Hobbies for nudging my challenge idea in the right direction.

Consider a set of points in the plane, which we will call sites, and associate a colour with each site. Now you can paint the entire plane by colouring each point with the colour of the closest site. This is called a Voronoi map (or Voronoi diagram). In principle, Voronoi maps can be defined for any distance metric, but we'll simply use the usual Euclidean distance r = √(x² + y²) . (Note: You do not necessarily have to know how to compute and render one of these to compete in this challenge.)

Here is an example with 100 sites:

If you look at any cell, then all points within that cell are closer to the corresponding site than to any other site.

Your task is to approximate a given image with such a Voronoi map. You're given the image in any convenient raster graphics format, as well as an integer N. You should then produce up to N sites, and a colour for each site, such that the Voronoi map based on these sites resembles the input image as closely as possible.

You can use the Stack Snippet at the bottom of this challenge to render a Voronoi map from your output, or you can render it yourself if you prefer.

You may use built-in or third-party functions to compute a Voronoi map from a set of sites (if you need to).

This is a popularity contest, so the answer with the most net votes wins. Voters are encouraged to judge answers by

how well the original images and their colours are approximated.

how well the algorithm works on different kinds of images.

how well the algorithm works for small N .

. whether the algorithm adaptively clusters points in regions of the image that require more detail.

Test Images

Here are a few images to test your algorithm on (some of our usual suspects, some new ones). Click the pictures for larger versions.

The beach in the first row was drawn by Olivia Bell, and included with her permission.

If you want an extra challenge, try Yoshi with a white background and get his belly line right.

You can find all of these test images in this imgur gallery where you can download all of them as a zip file. The album also contains a random Voronoi diagram as another test. For reference, here is the data that generated it.

Please include example diagrams for a variety of different images and N, e.g. 100, 300, 1000, 3000 (as well as pastebins to some of the corresponding cell specifications). You may use or omit black edges between the cells as you see fit (this may look better on some images than on others). Do not include the sites though (except in a separate example maybe if you want to explain how your site placement works, of course).

If you want to show a large number of results, you can create a gallery over on imgur.com, to keep the size of the answers reasonable. Alternatively, put thumbnails in your post and make them links to larger images, like I did in my reference answer. You can get the small thumbnails by appending s to the file name in the imgur.com link (e.g. I3XrT.png -> I3XrTs.png ). Also, feel free to use other test images, if you find something nice.

Renderer

Paste your output into the following Stack Snippet to render your results. The exact list format is irrelevant, as long as each cell is specified by 5 floating point numbers in the order x y r g b , where x and y are the coordinates of the cell's site, and r g b are the red, green and blue colour channels in the range 0 ≤ r, g, b ≤ 1 .

The snippet provides options to specify a line width of the cell edges, and whether or not the cell sites should be shown (the latter mostly for debugging purposes). But note that the output is only re-rendered when the cell specifications change - so if you modify some of the other options, add a space to the cells or something.

function draw() { document.getElementById("output").innerHTML = svg } function drawMap() { var cells = document.getElementById("cells").value; var showSites = document.getElementById("showSites").checked; var showCells = document.getElementById("showCells").checked; var lineWidth = parseFloat(document.getElementById("linewidth").value); var width = parseInt(document.getElementById("width").value); var height = parseInt(document.getElementById("height").value); var e = prefix.replace('{{WIDTH}}', width) .replace('{{HEIGHT}}', height); cells = cells.match(/(?:\d*\.\d+|\d+\.\d*|\d+)(?:e-?\d+)?/ig); var sitesDom = ''; var sites = [] for (i = 0; i < cells.length; i+=5) { x = parseFloat(cells[i]); y = parseFloat(cells[i+1]); r = Math.floor(parseFloat(cells[i+2])*255); g = Math.floor(parseFloat(cells[i+3])*255); b = Math.floor(parseFloat(cells[i+4])*255); sitesDom += '<circle cx="' + x + '" + cy="' + y + '" r="1" fill="black"/>'; sites.push({ x: x, y: y, r: r, g: g, b: b }); } if (showCells) { var bbox = { xl: 0, xr: width, yt: 0, yb: height }; var voronoi = new Voronoi(); var diagram = voronoi.compute(sites, bbox); for (i = 0; i < diagram.cells.length; ++i) { var cell = diagram.cells[i]; var site = cell.site; var coords = ''; for (var j = 0; j < cell.halfedges.length; ++j) { var vertex = cell.halfedges[j].getStartpoint(); coords += ' ' + vertex.x + ',' + vertex.y; } var color = 'rgb('+site.r+','+site.g+','+site.b+')'; e += '<polygon fill="'+color+'" points="' + coords + '" stroke="' + (lineWidth ? 'black' : color) + '" stroke-width="'+Math.max(0.6,lineWidth)+'"/>'; } } if (showSites) e += sitesDom; e += suffix; e += '<br> Using <b>'+sites.length+'</b> cells.'; svg = e; draw(); } var prefix = '<svg width="{{WIDTH}}" height="{{HEIGHT}}">', suffix = "</svg>", scale = 0.95, offset = 225, radius = scale*offset, svg = ""; svg { position: relative; } <script src="http://www.raymondhill.net/voronoi/rhill-voronoi-core.js"></script> Line width: <input id="linewidth" type="text" size="1" value="0" /> <br> Show sites: <input id="showSites" type="checkbox" /> <br> Show cells: <input id="showCells" type="checkbox" checked="checked" /> <br> <br> Dimensions: <input id="width" type="text" size="1" value="400" /> x <input id="height" type="text" size="1" value="300" /> <br> Paste cell specifications here <br> <textarea id="cells" cols="60" rows="10" oninput='drawMap()'></textarea> <br> <br> <div id="output"></div>

Massive credits to Raymond Hill for writing this really nice JS Voronoi library.

Related Challenges