Last active
October 6, 2017 12:07
-
-
Save mlconnor/7362062 to your computer and use it in GitHub Desktop.
Java Image Mosaic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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