How can I make a mosaic from a given number of 1x1 bricks and a picture?

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).

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).

``````<html>
<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;
var url = window.URL || window.webkitURL;
var src = url.createObjectURL(document.getElementById('file').files[0]);
img.src = src;
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;
}
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>
``````