Skip to content

Instantly share code, notes, and snippets.

@mlconnor
Last active October 6, 2017 12:07
Show Gist options
  • Save mlconnor/7362062 to your computer and use it in GitHub Desktop.
Save mlconnor/7362062 to your computer and use it in GitHub Desktop.
Java Image Mosaic
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.imageio.ImageIO;
public class Mosaic {
private static final String TILES_DIR = "tiles";
private static final String INPUT_IMG = "input.jpg";
private static final String OUTPUT_IMG = "output.png";
private static final int TILE_WIDTH = 25; //32;
private static final int TILE_HEIGHT = 25; //24;
private static final int TILE_SCALE = 8;
private static final boolean IS_BW = false;
private static final int THREADS = 2;
//167x222
private static void log(String msg){
System.out.println(msg);
}
public static void main(String[] args) throws IOException, InterruptedException{
log("Reading tiles...");
final Collection<Tile> tileImages = getImagesFromTiles(new File(TILES_DIR));
log("Processing input image...");
File inputImageFile = new File(INPUT_IMG);
Collection<BufferedImagePart> inputImageParts = getImagesFromInput(inputImageFile);
final Collection<BufferedImagePart> outputImageParts = Collections.synchronizedSet(new HashSet<BufferedImagePart>());
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(THREADS);
final AtomicInteger i = new AtomicInteger();
final int partCount = inputImageParts.size();
for (final BufferedImagePart inputImagePart : inputImageParts) {
newFixedThreadPool.execute(new Runnable(){
public void run() {
Tile bestFitTile = getBestFitTile(inputImagePart.image, tileImages);
log(String.format("Matching part %s of %s", i.incrementAndGet(), partCount));
outputImageParts.add(new BufferedImagePart(bestFitTile.image, inputImagePart.x, inputImagePart.y));
}
});
}
newFixedThreadPool.shutdown();
newFixedThreadPool.awaitTermination(10000000, TimeUnit.SECONDS);
log("Writing output image...");
BufferedImage inputImage = ImageIO.read(inputImageFile);
int width = inputImage.getWidth();
int height = inputImage.getHeight();
BufferedImage output = makeOutputImage(width, height, outputImageParts);
ImageIO.write(output, "png", new File(OUTPUT_IMG));
log("FINISHED");
}
private static BufferedImage makeOutputImage(int width, int height, Collection<BufferedImagePart> parts){
BufferedImage image = new BufferedImage(width * TILE_SCALE, height * TILE_SCALE, BufferedImage.TYPE_3BYTE_BGR);
java.awt.Graphics2D g2 = (java.awt.Graphics2D) image.getGraphics();
for(BufferedImagePart part : parts){
System.out.println("IW=" + image.getWidth() + " IH=" + image.getHeight() + " X=" + (part.x * TILE_SCALE) + " Y=" + (part.y*TILE_SCALE) + " W=" + TILE_WIDTH + " FX=" + (part.x * TILE_SCALE + TILE_WIDTH) + " FY=" + (part.y * TILE_SCALE + TILE_HEIGHT));
System.out.println("building width=" + width + " height=" + height + " part.x=" + part.x + " SCALE=" + TILE_SCALE + " y=" + part.y + " TW=" + TILE_WIDTH + " TH=" + TILE_HEIGHT + " iw=" + part.image.getWidth());
//BufferedImage imagePart = image.getSubimage(
// Math.min(width * TILE_SCALE, part.x * TILE_SCALE),
// Math.min(height * TILE_SCALE, part.y * TILE_SCALE), TILE_WIDTH, TILE_HEIGHT);
//imagePart.setData(part.image.getData());
//image.get
// g2.drawImage(part.image, null, part.x * TILE_SCALE, part.y * TILE_SCALE);
g2.drawImage(part.image,
part.x * TILE_SCALE,
part.y * TILE_SCALE,
part.x * TILE_SCALE + TILE_WIDTH,
part.y * TILE_SCALE + TILE_HEIGHT,
0,
0,
part.image.getWidth() - 1,
part.image.getHeight() - 1,
null);
/*
dx1 - the x coordinate of the first corner of the destination rectangle.
dy1 - the y coordinate of the first corner of the destination rectangle.
dx2 - the x coordinate of the second corner of the destination rectangle.
dy2 - the y coordinate of the second corner of the destination rectangle.
sx1 - the x coordinate of the first corner of the source rectangle.
sy1 - the y coordinate of the first corner of the source rectangle.
sx2 - the x coordinate of the second corner of the source rectangle.
sy2 - the y coordinate of the second corner of the source rectangle.
*/
}
return image;
}
private static Tile getBestFitTile(BufferedImage target, Collection<Tile> tiles) {
Tile bestFit = null;
int bestFitScore = -1;
for(Tile tile : tiles){
int score = getScore(target, tile);
if (score > bestFitScore){
bestFitScore = score;
bestFit = tile;
}
}
return bestFit;
}
private static int getScore(BufferedImage target, Tile tile){
assert target.getHeight() == Tile.SCALED_HEIGHT;
assert target.getWidth() == Tile.SCALED_WIDTH;
int total = 0;
for(int x = 0; x<Tile.SCALED_WIDTH; x++){
for(int y = 0; y<Tile.SCALED_HEIGHT; y++){
int targetPixel = target.getRGB(x, y);
int diff = getDiff(targetPixel, tile.r, tile.g, tile.b);
int score;
if (IS_BW){
score = 255 - diff;
} else {
score = 255 * 3 - diff;
}
total += score;
}
}
return total;
}
private static int getDiff(int target, int tileR, int tileG, int tileB){
if (IS_BW){
return Math.abs(getRed(target) - tileR);
} else {
return Math.abs(getRed(target) - tileR) +
Math.abs(getGreen(target) - tileG) +
Math.abs(getBlue(target) - tileB);
}
}
private static int getRed(int pixel){
return (pixel >>> 16) & 0xff;
}
private static int getGreen(int pixel){
return (pixel >>> 8) & 0xff;
}
private static int getBlue(int pixel){
return pixel & 0xff;
}
private static Collection<Tile> getImagesFromTiles(File tilesDir) throws IOException{
Collection<Tile> tileImages = Collections.synchronizedSet(new HashSet<Tile>());
File[] files = tilesDir.listFiles();
for(File file : files){
BufferedImage img = ImageIO.read(file);
if (img != null){
System.out.println("reading tile " + file.getName() + " width=" + img.getWidth());
tileImages.add(new Tile(img));
} else {
System.err.println("null image for file " + file.getName());
}
}
return tileImages;
}
private static Collection<BufferedImagePart> getImagesFromInput(File inputImgFile) throws IOException{
Collection<BufferedImagePart> parts = new HashSet<BufferedImagePart>();
BufferedImage inputImage = ImageIO.read(inputImgFile);
int totalHeight = inputImage.getHeight();
int totalWidth = inputImage.getWidth();
int x=0, y=0, w=Tile.SCALED_WIDTH, h=Tile.SCALED_HEIGHT;
while(x+w <= totalWidth){
while(y+h <= totalHeight){
BufferedImage inputImagePart = inputImage.getSubimage(x, y, w, h);
parts.add(new BufferedImagePart(inputImagePart, x, y));
y+=h;
}
y=0;
x+= w;
}
return parts;
}
public static class Tile {
public static int SCALED_WIDTH = TILE_WIDTH / TILE_SCALE;
public static int SCALED_HEIGHT = TILE_HEIGHT / TILE_SCALE;
//public Pixel[][] pixels = new Pixel[SCALED_WIDTH][SCALED_HEIGHT];
public BufferedImage image;
public int r = 0;
public int g = 0;
public int b = 0;
public Tile(BufferedImage image) {
this.image = image;
long lR = 0;
long lG = 0;
long lB = 0;
for ( int x = 0; x < image.getWidth(); x++ ) {
for ( int y = 0; y < image.getHeight(); y++ ) {
int rgb = image.getRGB(x,y);
lR += getRed(rgb);
lG += getGreen(rgb);
lB += getBlue(rgb);
}
}
int totalPixels = image.getWidth() * image.getHeight();
r = (int) (lR / totalPixels);
g = (int) (lG / totalPixels);
b = (int) (lB / totalPixels);
System.out.println("r=" + r + " g=" + g + " b=" + b);
}
}
public static class BufferedImagePart{
public BufferedImagePart(BufferedImage image, int x, int y) {
this.image = image;
this.x = x;
this.y = y;
}
public BufferedImage image;
public int x;
public int y;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment