// // Craig Mod -- July, 2005 // import processing.net.*; XMLElement xml; Stack imageURLs = null; // Hold the URLs grabbed from the XML feed Vector images = null; // Hold all of the original downloaded images Vector imageStages = null; // Store the various stages of blending to facilitate smooth/long animations int imagesLoaded = 0; // The number of images the GetImages thread has sucessfully loaded into Vector images int lastProcessed = 0; // The last image we've displayed as a thumbnail int currentBlending = 0; // Which image in imageStages are we blending mainImage towards? int thumbSize = 20; int numOfResults = 20; int thumbMargin = 0; // This will be dynamically decided in drawThumbnailsBox() float velocity = .02; // Range: 0-1. Speed at which the pixel averaging occurs. 1 = instantaneous float blendPosition = 0; // Range: 0-1. Keeps track of how fully blended the current image is. int finalImageSize = 200; // Size of the "output" image // Note: 300+ looks nice but is SLOW on my 1ghz G4. StoredImage mainImage; // The image displayed at the top of the app StoredImage targetImage; // The "hidden" image we're pushing the mainImage towards String flickrSize = "_m"; // _s = small, blank = uploaded size, _m = medium, _b = large boolean galleryMode = true; // Do we blend each image individually or as a quick aggregate. True = slow, false = quick boolean hovering = false; // Are we hovering about a thumbnail? (check to change cursor) boolean XMLProcessed = false; // Has the XML been processed yet? boolean stopThreads = false; // Should we stop our threads from taking up BG resources? boolean allLoaded = false; boolean started = false; String searchString = ""; PFont searchFont; void setup () { //size(400, 500, P3D); size(400, 500); background(#ffffff); framerate(30); // Draw main BG fill(230); rect(0,0,width,height); fill(255); stroke(100); rect(10, 10, width-20, height-20); drawThumbnailsBox(); imageURLs = new Stack(); images = new Vector(); imageStages = new Vector(); mainImage = new StoredImage(finalImageSize, finalImageSize); targetImage = new StoredImage(finalImageSize, finalImageSize); targetImage.y = finalImageSize + 20; // To offet from mainImage for debugging purposes .. normally not displayed fill(100, 100, 100); searchFont = createFont("Galaxie Polaris", 18); // This makes Processing build a font based on a system font textFont(searchFont); XMLProcessed = true; // When XMLProcessed = false, the following GetImages thread takes over new Thread ( new GetImages() ).start(); // This is the background image download thread } // Cleans and re-draws the thumbnail box in the middle of the app // void drawThumbnailsBox() { fill (240); stroke(100); int perRow = 10; if (numOfResults < 10) { perRow = numOfResults; } thumbMargin = (int) (width-(perRow*thumbSize))/2; rect(thumbMargin-2, 350-2, width-(2*thumbMargin)+3, ((numOfResults / perRow)*thumbSize)+3); } void draw() { //println(framerate); if (started && lastProcessed < imagesLoaded) { displayImage(); averageImages(); smoothPixels(); } else if (started && lastProcessed > 0 &! allLoaded) { // This is so the averaging continues even when we're waiting for new smoothPixels(); // images to be downloaded and processed .. We need this. } if (allLoaded) { smoothPixels(); } for (int i = 0; i < images.size(); i++) { StoredImage s = (StoredImage)images.elementAt(i); s.update(); } if (!hovering) cursor(ARROW); hovering = false; } void keyPressed() { if(keyPressed) { switch (int(key)) { case 10: // Enter if (searchString.length() > 0) { drawThumbnailsBox(); parseXML(searchString, numOfResults); startProcessing(); } break; case 32: // Space break; case 8: // Backspace if (searchString.length() > 0) { searchString = searchString.substring(0, searchString.length()-1); clearTypedText(); fill(100); text(searchString, thumbMargin, height-50); } break; case '=': if (velocity < 1) { velocity += .02; } else { velocity = 1; } println("New Velocity: " + velocity); break; case '-': if (velocity > .02) { velocity -= .02; } else { velocity = .02; } println("New Velocity: " + velocity); break; case '0': if (galleryMode) { galleryMode = false; } else { galleryMode = true; } println("Gallery mode: " + galleryMode); break; default: if (searchString.length() == 0) clearTypedText(); searchString += key; clearTypedText(); fill(100); text(searchString, thumbMargin, height-50); break; } } } void clearTypedText() { stroke(255); fill(255); rect(thumbMargin-10, height-75, 200, 40); } // Clean up our variable space // void startProcessing() { started = true; allLoaded = false; searchString = ""; // Clear our search string currentBlending = 0; blendPosition = 0; imageStages.clear(); // Flush the vector cache } // Let's grab the images with a Thread so we can do processing as they get loaded // -- let's us show people what's going on / make things a little more fun // public class GetImages implements Runnable { public void run() { println("GetImages started"); while (!stopThreads) { if (!XMLProcessed) { imagesLoaded = 0; println("Getting Images"); for (int i = 0; imageURLs.size() > 0; i++) { StoredImage simg = new StoredImage((String)imageURLs.pop(), thumbSize, thumbSize); println("Loaded: " + simg.url); if (simg.img != null) { images.addElement(simg); imagesLoaded++; println(imagesLoaded); } } XMLProcessed = true; if (started) { allLoaded = true; //started = false; } } } } } void displayImage() { StoredImage simg = (StoredImage)images.elementAt(lastProcessed); simg.drawThumb(); lastProcessed++; } void smoothPixels() { int mw = mainImage.img.width; int mh = mainImage.img.height; StoredImage blendTarget; if (blendPosition >= 1 && currentBlending < lastProcessed) { // If we're past a full blend between images and currentBlending++; // we're not out of the vector bounds then ... blendPosition = 0; } if (galleryMode && currentBlending < lastProcessed) { blendTarget = (StoredImage) imageStages.elementAt(currentBlending); } else { blendTarget = targetImage; } // break into one loop // shift bit functions for speed for (int x = 0; x < finalImageSize; x++) { for(int y = 0; y < finalImageSize; y++) { float r = red(mainImage.img.pixels[y*mw+x]); // Load the pixels from the current main Image float g = green(mainImage.img.pixels[y*mw+x]); float b = blue(mainImage.img.pixels[y*mw+x]); float rt = red(blendTarget.img.pixels[y*mw+x]); // Load the pixels from the undisplayed target imjage float gt = green(blendTarget.img.pixels[y*mw+x]); float bt = blue(blendTarget.img.pixels[y*mw+x]); float rf = abs((r + ((rt - r) * velocity))); // Start to move the pixels in the main image towards the float gf = abs((g + ((gt - g) * velocity))); // target image at some "velocity" float bf = abs((b + ((bt - b) * velocity))); mainImage.img.pixels[y*mainImage.img.width+x] = color(rf, gf, bf); } } mainImage.draw(); blendPosition += velocity; } void averageImages() { float r = 0; float g = 0; float b = 0; int imgWidth = finalImageSize; int imgHeight = finalImageSize; //println("in avg : " + imagesLoaded + " :: " + lastProcessed); StoredImage tmp = new StoredImage(finalImageSize, finalImageSize); targetImage.img.loadPixels(); if (lastProcessed > 0) { for (int x = 0; x < imgWidth-1; x++) { for (int y = 0; y < imgHeight-1; y++ ) { r = g = b = 0; for (int i = 0; i < lastProcessed; i++ ) { // lastProcessed was images.size() StoredImage simg = (StoredImage)images.elementAt(i); r += red(simg.img.pixels[y*simg.img.width+x]); g += green(simg.img.pixels[y*simg.img.width+x]); b += blue(simg.img.pixels[y*simg.img.width+x]); } r = (int) r/lastProcessed; g = (int) g/lastProcessed; b = (int) b/lastProcessed; targetImage.img.pixels[y*targetImage.img.width+x] = color(r, g, b); tmp.img.pixels[y*targetImage.img.width+x] = color(r, g, b); } } } targetImage.img.updatePixels(); imageStages.add(tmp); } void parseXML(String keyword, int perPage) { XMLProcessed = true; // Stop from trying to load images with old XML data lastProcessed = 0; imagesLoaded = 0; images.clear(); while (imageURLs.size() > 0) imageURLs.pop(); // clear our stack -- more effecient way to do this?? // Note : Cleanup all the variables so if a new search is // run after the first things will run well. String baseurl = "http://www.flickr.com/services/rest/?"; String request = baseurl + "method=flickr.photos.search&api_key=insert_you_api_here_innit&tags=" + keyword + "&per_page=" + perPage + "&sort=date-posted-asc"; println(request); // load the XML from the request into a xmlString variable then parse the String // using nanoxml's XMLElement.parseString() String xmlString=""; String[] lines=loadStrings(request); for (int i = 0; i 0 && owner.length() > 0) { println("ID: " + id + " // Owner: " + owner + " // Title: " + title); imageURLs.push("http://photos" + server + ".flickr.com/" + id + "_" + owner + flickrSize + ".jpg"); } } } XMLProcessed = false; } // Class to save each of the images we're grabbing online // public class StoredImage { PImage img; float x,y; float scale; int xsize, ysize; int num = imagesLoaded; String url; String title; StoredImage() { x = y = width / 2; // Center the image } StoredImage(String location, int xs, int ys) { img = loadImage(location); x = width / 2; // Center the image y = height / 2; url = location; xsize = xs; ysize = ys; scaleImage(); } StoredImage(int xs, int ys) { xsize = xs; ysize = ys; x = (width-xs)/2; y = 30; img = new PImage(xs, ys); for (int i = 0; i < xs*ys; i++) { img.pixels[i] = color(255, 255, 255); } draw(); println(xs*ys); } void draw() { image(img, x, y, xsize, ysize); } // Function : drawThumb // Purpose : To draw the thumbnails of the images we've downloaded into // the thumbnail box // Notes : I've hardcoded 10 as the maximum row size // void drawThumb() { x = (int) thumbMargin + ((thumbSize * (num % 10))); y = (int) 350 + ((num / 10) * thumbSize); image(img, x, y, xsize, ysize); } void scaleImage() { PImage r = new PImage(finalImageSize,finalImageSize); r.copy(img,0,0,img.width-1,img.height-1,0,0,r.width-1,r.height-1); img = r; } void update() { if (!hovering && mouseX > x && mouseY > y && mouseX < x+xsize && mouseY < y+ysize) { cursor(HAND); hovering = true; if(mousePressed) { link(url, "flickr"); } } } String getUrl() { return url; } }