7

This question is similar to How can I convert an image's pixels to actual LEGO colors? except that I need to fit a certain number of bricks of a certain colour.

For example, I have 20 white, 28 yellow, 10 orange, 18 red and 24 black (colours listed in increasing darkness) 1x1 bricks and a greyscale 10x10 pixel photo. How can I manipulate the photo so that it shows the 20 brightest pixels in white, next 28 in yellow and so on until the darkest 24 in black? If there is a tie, for example, the 19th, 20th and 21st brightest pixel have the same brightness, it highlights the ties and tells me that two of them should be white and one should be yellow.

Bonus: The above uses a 1-dimensional algorithm, but how can I have a 2-dimensional (or more one)? For example, if I have 10 white, 20 yellow, 30 blue and 40 black 1x1 bricks, and an image with shades of yellow, green and blue, the program should assign the most yellowish greens to yellow bricks (or white/black for brightest/darkest ones) and bluish greens to blue bricks (or white/black for brightest/darkest ones).

1 Answer 1

6

In case anyone familiar with JavaScript is interested, I wrote this script (tested in Firefox 19) which lets the user specify the number of tiles (bricks) of each colour and browse for an image file with the same number of pixels.

The luma (brightness) of each pixel is ranked and assigned one of the listed colours. Any ties are arbitrarily assigned to either possible colour (as JavaScript's sort is unstable).

http://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Mona_Lisa_headcrop.jpg/20px-Mona_Lisa_headcrop.jpg http://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Mona_Lisa_headcrop.jpg/20px-Mona_Lisa_headcrop.jpg is an image which fits the example tile count.

<html>
 <head><title>Make mosaic given tile colour count</title></head>
 <body><form onSubmit="return false;">
  <textarea id="counts" rows="3" cols="80">// Ordered from darkest to lightest
colours = [ '#000000', '#993300', '#cc0000', '#ff9900', '#ffff00', '#ffffff' ];
tiles   = [       110,        40,        80,        60,       120,        90 ];
tileWidth = tileHeight = 16;</textarea><br />
  <input type="file" id="file" size="80" />
  <img id="img" align="top" /><br />
  <input type="button" value="Go!" onClick="loadImage()" /><br /><br />
  <canvas id="canvas" style="background:#808080;border:2px groove;"></canvas>
  <script>
var tileWidth = 16, tileHeight = 16, colours, tiles;
// Based on http://jsfiddle.net/fWLJ9/
var canvas  = document.getElementById('canvas');
var context = canvas.getContext('2d');
var img     = document.getElementById('img');
var imgData;
function loadImage() {
 var url = window.URL || window.webkitURL;
 var src = url.createObjectURL(document.getElementById('file').files[0]);
 img.src = src;
 img.onload = function() {
  resizeCanvas();
  context.drawImage(img, 0, 0);
  url.revokeObjectURL(src);
  imgData = context.getImageData(0, 0, img.width, img.height).data;
  makeMosaic();
 }
}
function resizeCanvas() {
 canvas.width  = img.width  * tileWidth  - 1;
 canvas.height = img.height * tileHeight - 1;
}
function makeMosaic() {
 // Check counts are correct
 eval(document.getElementById('counts').value);
 var count = 0;
 for (var i = 0; i < colours.length; ++i) { count += tiles[i]; }
 if (count != img.width * img.height) {
  alert(img.width * img.height + ' pixels but ' + count + ' tiles!');
  return;
 }
 // Read pixels' luma
 var lumas = new Array();
 for (var y = 0; y < img.height; ++y) {
  for (var x = 0; x < img.width; ++x) {
   lumas.push({ x:x, y:y, luma:getPixelLuma(x, y) });
  }
 }
 // Sort in ascending order
 lumas.sort(function(a, b) { return a.luma - b.luma });
 // Assign pixels to colours
 resizeCanvas();
 count = 0;
 for (var i = 0; i < colours.length; ++i) {
  context.fillStyle = colours[i];
  for (var j = 0; j < tiles[i]; ++j) {
   context.fillRect(lumas[count].x * tileWidth, lumas[count].y * tileHeight,
                    tileWidth - 1, tileHeight - 1);
   ++count;
  }
 }
}
function getPixelLuma(x, y) {
 var pixelOffset = (x + y * img.width) * 4;
 return (0.21 * imgData[pixelOffset] + 0.72 * imgData[pixelOffset + 1] +
         0.07 * imgData[pixelOffset + 2]).toFixed(2);
}
  </script>
 </form></body>
</html>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.