6
After the last Ludum Dare (LD) game programming contest a friend of mine made a nice mosaic of all the LD game thumbnails:
After searching for my own game I quickly gave up and decided to solve it like a real programmer... by writing code that finds my game thumbnail.
This is the thumbnail of my game:
More examples can be found by going to the Ludum Dare list of games.
So what is the challenge? Write a program that has the following input:
<filename_mosaic> <amount_of_tiles_x> <amount_of_tiles_y> <filename_target>
For example using the images above:
mosaic.jpg 59 43 target.png
The output should be the index of the target image. For the mosaic and screenshot above the correct index is:
48,18
The following (non-golfed) code provides a working example:
public class MosaicFinder {
//Start with java MosaicFinder mosaic.jpg 59 43 target.png
public static void main(String[] args) throws Exception {
String mosaicFilename = args[0];
int tilesX = Integer.parseInt(args[1]);
int tilesY = Integer.parseInt(args[2]);
String targetFilename = args[3];
BufferedImage mosaic = ImageIO.read(new File(mosaicFilename));
int tileHeight = mosaic.getHeight()/tilesY;
int tileWidth = mosaic.getWidth()/tilesX;
BufferedImage tofind = ImageIO.read(new File(targetFilename));
//Translate target.png to correct size:
BufferedImage matchCanvas = new BufferedImage(tileWidth, tileHeight, BufferedImage.TYPE_INT_RGB);
AffineTransform at = AffineTransform.getScaleInstance((tileWidth*1.0)/tofind.getWidth(), (tileHeight*1.0)/tofind.getHeight());
((Graphics2D)matchCanvas.getGraphics()).drawRenderedImage(tofind, at);
int bestIndexX = 0, bestIndexY = 0;
BigInteger lowest = null;
for(int x = 0; x<tilesX;x++) {
for(int y = 0; y<tilesY;y++) {
//For all tiles:
BigInteger error = BigInteger.ZERO;
for(int x1 = 0; x1<tileWidth;x1++) {
for(int y1 = 0; y1<tileHeight;y1++) {
int rgb1 = matchCanvas.getRGB(x1, y1);
int rgb2 = mosaic.getRGB((x*tileWidth)+x1, (y*tileHeight)+y1);
int r1 = (rgb1 >> 16) & 0xFF;
int r2 = (rgb2 >> 16) & 0xFF;
error = error.add(BigInteger.valueOf(Math.abs(r1-r2)));
int g1 = (rgb1) & 0xFF;
int g2 = (rgb2) & 0xFF;
error = error.add(BigInteger.valueOf(Math.abs(g1-g2)));
int b1 = rgb1 & 0xFF;
int b2 = rgb2 & 0xFF;
error = error.add(BigInteger.valueOf(Math.abs(b1-b2)));
}
}
if(lowest == null || lowest.compareTo(error) > 0) {
lowest = error;
bestIndexX = (x+1);
bestIndexY = (y+1);
}
}
}
System.out.println(bestIndexX+","+bestIndexY);
}
}
This is a visual example of the code running:
This contest is a code-golf contest, the shortest code wins the challenge.
Update: It seems stackexchange messes up the images a bit, on my blog you can find the source images for the examples in correct resolution. Also: I've fixed the amount of images from 42 to 43
Current best: David Carraher, Mathematica (280)
in principal, the solution in the original post can return the wrong result as well. Even if he used the identical scaling algorithm as the original creator used, 2 different images can scale to the same image (pigeon hole principal). In this case, I guess you'd be unable to discern the difference as a human as well though, so the answer would effectively still be right. – Cruncher – 2014-08-28T14:35:00.240
Cruncher, Interesting point. I was wondering about that. – DavidC – 2014-08-28T14:45:24.640
I like this approach, and just want to comment generally on using a single average colour. When I was placing the tiles to make the mosaic, I found using MSE greatly improved the patterns. You actually see tiles chosen that match detail within the original image too. There were other ideas I didn't explore, such as lab colour, gamma corrected scaling, SSIM, PSE etc. But per-pixel was, generally, very important. – Will – 2014-08-29T05:25:37.140