boolean drawMosaic = false;
void drawMosaicScreen() {
drawMainImage(255);
if (!mosaicComplete) {
makeMosaic();
mosaicComplete = true;
} else {
if (drawMosaic) {
drawMosaic();
if (useOverlay) {
drawMainImage(70);
}
}
}
}
void drawMainImage(int opacity) {
imageMode(CENTER);
tint(255, opacity);
image(mainImage, width/2, height/2, mosaicW, mosaicH);
tint(255, 255); // reset tint
}
void drawMosaic() {
for (int row=0; row<numRow; row++) {
for (int col=0; col<numCol; col++) {
PVector loc = getTilePos(row,col);
int tileRef = mosaicRef[row][col];
image(imgTiles[tileRef], loc.x+marginLR, loc.y+marginTB, tileW, tileH);
}
}
}
void mosaicScreenClick() {
drawMosaic = !drawMosaic;
}
void backToSelectScreen () {
// if user presses b, return to select screen
mosaicScreen = false;
selectScreen = true;
mosaicComplete = false;
drawMosaic = false;
}
void setupSelectScreen() {
float margin = 20;
float tileW = (width-margin*(numSelectCol+1)) / numSelectCol;
float tileH = (height-margin*(numSelectRow+1)) / numSelectRow;
int imgRef = imgPointer;
for (float i=0; i<numSelectRow; i++) { // i = row
for (float j=0; j<numSelectRow; j++) { // j = col
float xLoc, yLoc;
if (j==0) {
xLoc = margin + tileW/2;
} else {
xLoc = margin + tileW/2 + j*(margin + tileW);
}
if (i==0) {
yLoc = margin + tileH/2;
} else {
yLoc = margin + tileH/2 + i*(margin + tileH);
}
PImage curImage;
boolean refresher;
if (i==1 && j==1) {
curImage = loadImage("refresh.jpg");
refresher = true;
} else {
curImage = imgPool[imgRef].get();
refresher = false;
imgRef++;
if (imgRef >= poolSize) {
imgRef = 0;
}
}
selectTiles[int(j+i*numSelectRow)] = new ImageIcon(curImage, xLoc, yLoc, tileW, tileH, refresher);
}
}
}
void drawSelectScreen() {
background(0);
for (int i=0; i<numSelectTiles; i++) {
selectTiles[i].drawIcon();
}
}
void selectScreenClick() {
// run when a click is made on the select screen
for (int i=0; i<numSelectTiles; i++) {
if (selectTiles[i].hovered) {
if (selectTiles[i].refresher) {
println("refreshed");
refreshImages();
} else {
// select image as main image and begin mosaic-ing
PImage tempImg = selectTiles[i].img;
loadMainImage(tempImg.get());
selectScreen = false;
mosaicScreen = true;
}
}
}
}
void refreshImages() {
// queries 8 photos from flickr and draws tiles
imgPointer += 8;
if (imgPointer >= poolSize) {
imgPointer = imgPointer - poolSize - 1;
}
int imgRef = imgPointer;
for (int i=0; i<numSelectRow; i++) { // i = row
for (int j=0; j<numSelectRow; j++) { // j = col
if (!(i==1 && j==1)) {
// replace image
if (imgRef >= poolSize) {
imgRef = 0;
}
PImage curImage = imgPool[imgRef].get();
selectTiles[j+i*numSelectRow].img = curImage;
imgRef++;
}
}
}
}
// used to grab images from flickr
void flickrGet(String searchTerm, int numImages) {
println("Getting images from flickr...");
try {
searchTerm = URLEncoder.encode(searchTerm, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
exit();
return;
}
String url = "http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=bfaa37fbd00c1291ced65b903fb15fda&text="
+ searchTerm + "&format=rest";
XMLElement xml = new XMLElement(this, url);
xml = xml.getChild(0);
XMLElement[] photos = xml.getChildren();
int maximum = min(numImages, photos.length - 1);
for(int i=0; i < maximum; i++) {
XMLElement photo = photos[i];
String imgURL = "http://farm" + photo.getStringAttribute("farm")
+ ".static.flickr.com/"
+ photo.getStringAttribute("server") + "/"
+ photo.getStringAttribute("id") + "_"
+ photo.getStringAttribute("secret") + "_z.jpg";
println(imgURL);
imgPool[i] = loadImage(imgURL);
println("downloading " + (i+1) + "/" + maximum);
}
poolSize = maximum;
}
// used to populate images from file (not flickr)
void loadImages() {
for (int i=0; i<poolSize; i++) {
String filename = "img" + i + ".jpg";
imgPool[i] = loadImage(filename);
if (imgPool[i] == null) {
poolSize = i;
break;
}
}
}
class ImageIcon {
PImage img;
float h, w, origH, origW;
float xloc, yloc;
boolean hovered;
boolean refresher;
ImageIcon(PImage img_, float xloc_, float yloc_, float w_, float h_, boolean refresher_) {
img = img_.get();
xloc = xloc_;
yloc = yloc_;
w = w_;
h = h_;
refresher = refresher_;
hovered = false;
origW = w;
origH = h;
}
void drawIcon() {
checkHover();
// draw image
imageMode(CENTER);
image(img, xloc, yloc, w, h);
}
void checkHover() {
hovered = (mouseX > xloc-w/2 && mouseX < xloc+w/2) && (mouseY > yloc-h/2 && mouseY < yloc+h/2);
if (hovered) {
w = origW * 1.2;
h = origH * 1.2;
} else {
w = origW;
h = origH;
}
}
}
void makeMosaic() {
// function: creates mosaic of main image from available tiles
// create smaller tiles of image pool
createTiles();
// compute average color for each tile, for each quadrant separately
color[] avgTileColor0 = new color[poolSize];
color[] avgTileColor1 = new color[poolSize];
color[] avgTileColor2 = new color[poolSize];
color[] avgTileColor3 = new color[poolSize];
PImage tileQuad0,tileQuad1,tileQuad2,tileQuad3;
for (int i=0; i<poolSize; i++ ){
tileQuad0 = imgTiles[i].get(0,0,int(tileW/2),int(tileH/2));
tileQuad1 = imgTiles[i].get(int(tileW/2-1),0,int(tileW/2),int(tileH/2));
tileQuad2 = imgTiles[i].get(0,int(tileH/2-1),int(tileW/2),int(tileH/2));
tileQuad3 = imgTiles[i].get(int(tileW/2-1),int(tileH/2-1),int(tileW/2),int(tileH/2));
avgTileColor0[i] = averageColorRGB(tileQuad0);
avgTileColor1[i] = averageColorRGB(tileQuad1);
avgTileColor2[i] = averageColorRGB(tileQuad2);
avgTileColor3[i] = averageColorRGB(tileQuad3);
}
for (int row=0; row<numRow; row++) {
for (int col=0; col<numCol; col++) {
// get original section from main image to be replaced by a tile
PVector tilePos = getTilePos(row,col);
PImage origSection = mainImage.get(int(tilePos.x-tileW/2), int(tilePos.y-tileH/2), int(tileW), int(tileH));
//****search through images to find best match****
// find top 3 choices (so images dont directly border each other)
int rankingLength = minDist*minDist + (minDist-1)*(minDist-1);
int[] bestMatch = new int[rankingLength];
for (int i=0; i<rankingLength; i++) { bestMatch[i] = i; }
float[] bestVariance = new float[rankingLength];
for (int i=0; i<rankingLength; i++) { bestVariance[i] = 999999999; }
for (int p=0; p<poolSize; p++) { // loop thru image tile pool
color avgOrig, avgTile;
float variance = 0.0;
// split images into 4 quadrants and separately calculate color variance
for (int quadrant=0; quadrant<4; quadrant++) {
PImage origQuad;
switch (quadrant) {
case 0:
origQuad = origSection.get(0,0,int(tileW/2),int(tileH/2));
avgTile = avgTileColor0[p];
break;
case 1:
origQuad = origSection.get(int(tileW/2-1),0,int(tileW/2),int(tileH/2));
avgTile = avgTileColor1[p];
break;
case 2:
origQuad = origSection.get(0,int(tileH/2-1),int(tileW/2),int(tileH/2));
avgTile = avgTileColor2[p];
break;
default:
origQuad = origSection.get(int(tileW/2-1),int(tileH/2-1),int(tileW/2),int(tileH/2));
avgTile = avgTileColor3[p];
break;
}
avgOrig = averageColorRGB(origQuad);
variance += getColorDifRGB(avgOrig, avgTile);
} // end loop thru quadrants
for (int i=0; i<rankingLength; i++) {
if (variance < bestVariance[i]) {
// shift down and insert here
for (int j=rankingLength-1; j>i; j-- ) {
bestMatch[j] = bestMatch[j-1];
bestVariance[j] = bestVariance[j-1];
}
bestMatch[i] = p;
bestVariance[i] = variance;
break;
}
}
} // end loop thru image pool
// dont let same tile be used next to each other
boolean[] dontUse = new boolean[rankingLength];
for (int i=0; i<rankingLength; i++ ) { dontUse[i] = false; }
int colsLeft, rowsAbove, colsRight;
colsLeft = min(col, minDist-1);
rowsAbove = min(row, minDist-1);
colsRight = min(numCol-1, col+minDist-1);
for (int i = col-colsLeft; i <= colsRight; i++ ) {
for (int j = row-rowsAbove; j <= row; j++ ) {
for (int du=0; du < rankingLength; du++) {
if (i != col || j != row) {
dontUse[du] = dontUse[du] || (mosaicRef[j][i] == bestMatch[du]);
}
}
}
}
// select best match tile
mosaicRef[row][col] = bestMatch[rankingLength-1];
for (int du=0; du < rankingLength; du++) {
if (!dontUse[du]) {
// we found a usable tile
mosaicRef[row][col] = bestMatch[du];
break;
}
}
} // loop thru columns
} // loop thru rows
} // end function
void createTiles() {
// function: make smaller versions of the image pool with same aspect ratio
// as the mainImage
numCol = int(round(mosaicW / maxTileSize)) + 1;
numRow = int(round(mosaicH / maxTileSize)) + 1;
tileW = mosaicW / numCol;
tileH = mosaicH / numRow;
mosaicRef = new int[numRow][numCol];
for (int i=0; i<poolSize; i++) {
imgTiles[i] = imgPool[i].get();
imgTiles[i].resize(int(tileW), int(tileH));
}
}
PVector getTilePos(int row, int col) {
// returns location of center of tile in drawing screen
PVector answer = new PVector(0.0,0.0);
answer.x = tileW/2+col*tileW;
answer.y = tileH/2+row*tileH;
return answer;
}
void loadMainImage(PImage img) {
mainImage = img.get();
if (mainImage.width > mainImage.height) {
marginTB = (float(height) - float(mainImage.height)*(float(width)/float(mainImage.width))) / 2;
marginLR = 0;
mosaicH = float(mainImage.height)*(float(width)/float(mainImage.width));
mosaicW = width;
} else {
marginTB = 0;
marginLR = (float(width) - float(mainImage.width)*(float(height)/float(mainImage.height))) / 2;
mosaicW = float(mainImage.width)*(float(height)/float(mainImage.height));
mosaicH = height;
}
mainImage.resize(int(mosaicW), int(mosaicH));
}
color averageColorRGB(PImage img) {
colorMode(RGB, 255);
float avgH=0, avgS=0, avgB=0;
float totalPix = (img.width)*(img.height);
for (int j=0; j<img.width; j++) {
for (int i=0; i<img.height; i++) {
color pixel = img.get(j,i);
avgH += red(pixel) / totalPix;
avgS += green(pixel) / totalPix;
avgB += blue(pixel) / totalPix;
}
}
return color(avgH, avgS, avgB);
}
float getColorDifRGB(color c1, color c2) {
float variance;
colorMode(RGB, 255);
variance = pow(red(c1) - red(c2), 2) + pow(green(c1) - green(c2), 2) + pow(blue(c1) - blue(c2), 2);
return variance;
}
//------MODIFIERS-------//
String query = "flower,canon";
boolean grabFromFlickr = false; // if false, will instead grab images from file (of names 'img0.jpg', 'img1.jpg' etc)
boolean useOverlay = true; // "cheat" by displaying transparent original image over mosaic
int maxTileSize = 15; // max height/width in pixels of mosaic tiles
int poolSize = 99; // number of images to download from flickr (max 99)
int minDist = 4; // minimum distance between like tiles (max 6 for pool of 99)
//----------------------//
import processing.opengl.*;
import java.net.URLEncoder;
int numSelectTiles = 9, numSelectRow = 3, numSelectCol = 3;
ImageIcon[] selectTiles = new ImageIcon[numSelectTiles];
boolean selectScreen = true, mosaicScreen = false, mosaicComplete = false;
boolean mouseClick = false;
PImage mainImage;
PImage[] imgPool = new PImage[poolSize];
PImage[] imgTiles = new PImage[poolSize];
float tileW, tileH;
int numRow, numCol;
float mosaicW, mosaicH;
float marginLR, marginTB;
int[][] mosaicRef;
int imgPointer = 0;
void setup() {
size(600,600,OPENGL);
smooth();
// get images from flickr
if (grabFromFlickr) {
flickrGet(query, poolSize); // from flickr
} else {
loadImages(); // from file
}
// setup select screen
setupSelectScreen();
}
void draw() {
background(0);
if (selectScreen) {
drawSelectScreen();
} else if (mosaicScreen) {
drawMosaicScreen();
}
}
void mouseClicked() {
if (!mouseClick) {
mouseClick = true;
if (selectScreen) {
selectScreenClick();
} else if (mosaicScreen) {
mosaicScreenClick();
}
}
}
void mouseReleased() {
mouseClick = false;
}
void keyPressed() {
if (mosaicScreen && (key == 'b' || key == 'B')) {
// return to image select screen
backToSelectScreen();
} else if (mosaicScreen && (key == 's' || key == 'S')) {
// save frame to file
println("Image saved");
saveFrame();
}
}
This program implements an algorithm for creating image mosaics - creating one image by mosaicing many images together. There are some in-code parameters that can be changed (sorry, no GUI yet). One of these parameters let's you grab images from flickr, but it defaults to using a bunch of giraffe images I downloaded. The first scene lets you select an image to create a mosaic of. Once you have selected it and you see the image, click to toggle showing the mosaic or the original.