/** //<>// * PhotoArchMontage supplying images for my video work accepted at * https://sites.google.com/view/eccvfashion/ early Fall 2018. * PhotoMontage is a combined version of June 2017's PosterizeSegmentInteractive * and June 2017's BlurPosterizeInteractive. D. Parson, August 2017. * This version combines features and cleans up some code structure, after * abandoning the idea of parallelizing the BLUR code from PImage.java due to * complexity and apparent inadequate acceleration. PosterizeSegmentInteractive * built on BlurPosterizeInteractive by segmenting * photos based on 8 most prominent color areas derived via * PImage.filter(POSTERIZE,2) to set copy masks for original image. * See PosterizeSegmentInteractive.pde & BlurPosterizeInteractive.pde * for background. After updating this code, coming up with a good set of * images for a show is the next thing. * Enhanced 2/12/2018 with the Ln command to allow multiple PImages to plot in one round. * * KEYBOARD COMMANDS: * * 'a' toggle auto-advance images * 'p' toggles planetarium mode * 'm' toggles smearing versus scattering modes of display * 'M' displays ONLY smearing when 'm' is toggled on, disables other effects * 'F' toggles freeze image mode * 'R' cancels all rotations and shears * 'S' toggle isSaturate vs. not-isSaturate * 'e' toggles etching mode * 'xN\n' or "yN\n" or "zN\n" rotateX|Y|Z with this increment per frame in degrees, 0 to remove rot * 'xN\n' or "yN\n" or "zN\n" affects particle layers only, 'xN\n' and "yN\n" disabled in planetarium * 'XN\n' toggles shearX upper bound from 0..360, shears only the particle layers * 'YN\n' toggles shearY upper bound from 0..360, shears only the particle layers * 'ZN\n' is rotate for entire image, including main image and particle layers * '@N\n' sets frameRate 1..120 * 's' save a single frame as a .jpg * SLOW OR LESS-USED FEATURES * 'n' load and show next image frame * 'g' toggle gray scale vs. color * 'PN\n' set posterize level 2..255 * 'bN\n' set blur level 0..? * 'TN\n' threshold 0.0..1.0, where 0.0 used a NONE * 'BN\n' blend mode 0 (none) .. 14 * 'fN\n' feathering 0.0..1.0 of edges of overlay, where 0.0 is none, else radius from center * 'CN\n' sets alpha clipping 0..255 off planetarium dome, 255 for full clip, 0 for none * 'LN\n' sets linked list history length 1..8 * DEPRECATED 'f' toggle frame capture for MovieMaker as .png (SLOW) **/ import java.io.File ; import java.util.LinkedList ; import java.util.Map ; import java.util.TreeMap ; import java.util.Arrays ; import java.util.concurrent.SynchronousQueue ; import java.util.concurrent.CopyOnWriteArrayList ; import java.lang.Runnable ; import PixelVisitor.* ; // See tabbed helper class ImageLoader for thread-safe data fields used for inter-thread communication. java.lang.Thread imageLoaderThread = null ; // Use interrupt() on the 'n' next command. TreeMap pixelmap = new TreeMap(); // map color to count PImage [] curimgs = null; String command = ""; int framesTofade = 960 ; float f_framesTofade = (float) (framesTofade) ; int framenum = 0 ; boolean autoAdjustments = true ; boolean savingFrame = false ; boolean isComputeCount = false ; int blendIndex = 0 ; int blendCounter = 0 ; float rotXIncr = 0 /* 90.0 */, rotXSum = 45.0 ; float rotYIncr = 0.0, rotYSum = 0.0 ; float rotZIncr = 0.0, rotZSum = 0.0 ; float rotZZIncr = 0.05, rotZZSum = 0.0 ; float shearXlimit = 0.0, shearYlimit = 0.0 ; boolean shiftToX = true, smearing = false, smearingonly = false, smearbig = true ; final int blendLimit = 60 ; boolean freezeFrame = false ; int autoPosterIndex = 0 ; static final int [] autoPosterSteps = { /* 255, 30, 20, 8, 4, 3, 4, 8, 20, 30, 255 */ 255 }; /* TODO int posterUpperLimit = 0 ; // 2..255 are used, anything else means no POSTERIZATION int posterLowerLimit = 0 ; // 2..255 && <= posterUpperLimit are used int posterSpeed = 0 ; // ramp up for ramp down, one per new image. */ // Following are the blends supported by blendMode. DIFFERENCE fails on Mac. final String [] blendModeName = { "BLEND", "ADD", "SUBTRACT", "DARKEST", "LIGHTEST", "EXCLUSION", "MULTIPLY", "SCREEN", "REPLACE" // Every other one darkens & lightens }; final int [] blendModeInt = { BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, EXCLUSION, MULTIPLY, SCREEN, REPLACE }; PShape etchingMASK = null ; float etchingScale = 0.5, etchingRotate = 0.0 ; int etchingWeight = 0 ; boolean isetching = false ; // START OF VARIABLES USED ONLY FOR PLANETARIUM MODE. boolean isPlanetarium = false ; // true ; // toggle on 'p' boolean lastClip = false ; // clip() sticks, does it need to be undone? int diameter = 0, radius = 0, imageside = 0, leftoffset = 0, topoffset = 0 ; // See RadiusCalc.pdf in this sketch directory for details on the above variables. PImage clippingCircle = null ; int clipalpha = 255 ; // alpha for clipping corners, 0..255, where 0 == no alpha, try 150 // END OF VARIABLES USED ONLY FOR PLANETARIUM MODE. // ADDED 2/12/2018 for MULTIIMAGES: LinkedList history = new LinkedList(); int historyLimit = 1 ; void setup() { fullScreen(P3D); // size(1000,800,P3D); //frameRate(10); background(0); String path = sketchPath(""); println("SP: " + path); File curdir = new File(path); String [] filenames = curdir.list(); java.util.Arrays.sort(filenames); // make order predictable LinkedList tnames = new LinkedList(); for (String f : filenames) { if (f.length() > 4 && (f.endsWith(".JPG") || f.endsWith(".jpg"))) { print(f + " "); tnames.add(f); } } fnames = new CopyOnWriteArrayList(tnames); imageMode(CENTER); println(""); println("TOTAL IMAGE FILES: " + fnames.size()); if (fnames.size() > 0) { curloc = 0 ; // (int) random(0, fnames.size()); curloc = curloc % fnames.size() ; imageLoaderThread = new java.lang.Thread(new ImageLoader()); imageLoaderThread.start(); curimgs = getNextImg(); history.add(curimgs); println("LOADED " + fnames.get(curloc) + " in setup()."); } else { println("NO IMAGE FILES, ABORTING!"); exit(); } clippingCircle = loadImage("CenteredClippingCircle.png"); } void draw() { if (freezeFrame) { return ; } if (diameter == 0) { if (width >= height) { // The usual case. diameter = height ; topoffset = 0 ; leftoffset = (width - height) / 2 ; // left margin } else { diameter = width ; topoffset = (height - diameter) / 2 ; leftoffset = 0 ; } radius = diameter / 2 ; imageside = round(sqrt(radius * radius * 2)); etchingMASK = createEtchingMask(); } pushMatrix(); if (rotZZIncr != 0.0) { translate(width/2, height/2); //println("rotZ " + rotZZSum); rotateZ(radians(rotZZSum)); rotZZSum += rotZZIncr ; if (rotZZSum >= 360.0) { rotZZSum -= 360.0 ; } translate(-width/2, -height/2); } int limit = isPlanetarium ? (9 * framesTofade / 10) : (7 * framesTofade / 8); // limit = isPlanetarium ? (8 * framesTofade / 10) : (6 * framesTofade / 8); // TODO int etchLimit = 255 * framenum / limit ; if (framenum < limit) { //background(0); int alphaEndpoint = (int)((float)framenum / f_framesTofade * 255.0) ; int Xshift = (int)((float)framenum / f_framesTofade * width) ; int Yshift = (int)((float)framenum / f_framesTofade * height) ; if (isPlanetarium) { Xshift = leftoffset + ((int)((float)framenum / f_framesTofade * imageside)); Yshift = topoffset + ((int)((float)framenum / f_framesTofade * imageside)); } float shearXdegrees = 0.0, shearYdegrees = 0.0 ; if (shearXlimit != 0.0) { shearXdegrees = constrain(shearXlimit - ((float)framenum / f_framesTofade * shearXlimit), 0.0, shearXlimit) ; } if (shearYlimit != 0.0) { shearYdegrees = constrain(shearYlimit - ((float)framenum / f_framesTofade * shearYlimit), 0.0, shearYlimit) ; } framenum++; if (! (smearingonly && smearing)) { // Do these effects only if we are not smearing the image transition. for (int pi = 0; pi < curimgs.length; pi++) { if (pi == 0) { if (framenum > framesTofade / 4) { tint(255, alphaEndpoint); if (blendCounter > 0 || blendIndex == 0) { if (isPlanetarium) { image(curimgs[pi], width/2, height/2, imageside, imageside); } else { image(curimgs[pi], width/2, height/2, width, height); } } } } else { tint(255, (255-alphaEndpoint)); pushMatrix(); if (rotXIncr != 0.0 && !isPlanetarium) { // For some unclear (to me) reason the semi-transparent // pixels pile up in the z plane and permanently paper over // the main image at curimgs[0] in planetarium mode, but // not in non-planetarium mode. translate(0, height/2); rotateX(radians(rotXSum)); // rotateX(radians(rotXSum + pi * 45.0)); // pi * 45.0 added 9/23/2017 rotXSum += rotXIncr ; if (rotXSum >= 360.0) { rotXSum -= 360.0 ; } translate(0, -height/2); } if (rotYIncr != 0.0 && !isPlanetarium) { translate(width/2, 0); rotateY(radians(rotYSum)); // rotateY(radians(rotYSum + pi * 45.0)); // pi * 45.0 added 9/23/2017 rotYSum += rotYIncr ; if (rotYSum >= 360.0) { rotYSum -= 360.0 ; } translate(-width/2, 0); } if (rotZIncr != 0.0) { translate(width/2, height/2); rotateZ(radians(rotZSum)); // rotateZ(radians(rotZSum + pi * 45.0)); // pi * 45.0 added 9/23/2017 rotZSum += rotZIncr ; if (rotZSum >= 360.0) { rotZSum -= 360.0 ; } translate(-width/2, -height/2); } /* Before 9/23/2017: -Xshift, -Yshift -Xshift, Yshift -Xshift, -Yshift -Xshift, Yshift Xshift, -Yshift Xshift, Yshift Xshift,-Yshift Xshift, Yshift */ float xshft = (pi < 5) ? - Xshift : Xshift ; xshft += ((((pi-1) >> 1) & 1) * xshft / 2.0) ; // added 9/23/2017 to knock out an alias on ever-other entry float yshft = ((pi & 1) == 1) ? (-Yshift) : Yshift ; yshft += ((((pi-1) >> 1) & 1) * yshft / 2.0) ; // added 9/23/2017 to knock out an alias on ever-other entry translate(xshft/2, yshft/2); if (shearXdegrees != 0.0) { translate(width/2, height/2); shearX(radians(shearXdegrees)); translate(-width/2, -height/2); } if (shearYdegrees != 0.0) { translate(width/2, height/2); shearY(radians(shearYdegrees)); translate(-width/2, -height/2); } if (blendCounter > 0 || blendIndex == 0) { for (PImage [] manyimgs : history) { if (isPlanetarium) { image(manyimgs[pi], width/2, height/2, (int)(imageside*2), (int)(imageside*2)); } else { image(manyimgs[pi], width/2, height/2, width*2, height*2); } } } if (blendIndex == 0 || blendCounter <= 0) { blendCounter = 0 ; if (blendIndex == 0) { blendMode(blendModeInt[0]); } } else { blendMode(blendModeInt[blendIndex-1]); blendCounter-- ; if (blendCounter == 0) { //freezeFrame = true ; } } popMatrix(); } } } smearXorY(); // conditionally smear whatever we have done either horizontally or vertically if smearing showEtchingMask(etchLimit); } else { if (autoAdjustments) { posterizefactor = autoPosterSteps[autoPosterIndex++] ; println("UPDATE posterizefactor = " + posterizefactor + ", frameRate = " + frameRate); framenum = 0 ; curimgs = getNextImg(); history.add(curimgs); while (history.size() > historyLimit) { history.remove(); } if (autoPosterIndex >= autoPosterSteps.length) { autoPosterIndex = 0 ; } shiftToX = ! shiftToX ; // has effect only if smearing if (shiftToX) { smearbig = ! smearbig ; // alternat at 1/2 the shiftToX rate } etchingMASK = createEtchingMask(); } } popMatrix(); if (isPlanetarium) { // tint(255,255); // make sure clippingCircle is fully opaque tint(0, 0, 0, clipalpha); // an experiment image(clippingCircle, width/2, height/2, diameter, diameter); //clip(width/2, height/2, diameter, diameter); rectMode(CORNER); fill(0); strokeWeight(1); stroke(0); rect(0,0, (width-diameter)/2, height); rect((width-diameter)/2+diameter,0, (width-diameter)/2, height); tint(0, 0, 0, clipalpha); clip(width/2, height/2, diameter, diameter); lastClip = true ; } else if (lastClip) { clip(width/2, height/2, width, height); // unset sticky clip() lastClip = false ; } if (savingFrame) { saveFrame("poster1-######.png"); } } PImage [] getNextImg() { boolean pending = true ; PImage [] result = null ; while (pending) { try { result = loaded.take(); pending = false ; } catch (java.lang.InterruptedException xx) { } } println("LOADED " + fnames.get(curloc) + " frameRate " + frameRate); return result ; } PShape createEtchingMask() { PShape result = null ; int r = (int)(random(0,1000)) % 3 ; if (r == 0) { result = createShape(RECT,0,0,width,height); } else if (r == 1) { result = createShape(ELLIPSE,0,0,width,height); } else { result = createShape(TRIANGLE,0,-height/2, -width/2, height/2, width/2, height/2); } result.setFill(color(0,0,0,0)); result.setStroke(color(0)); // Change some globals at the same time. etchingRotate = random(0, 360); etchingScale = random(0.1, 0.75); etchingWeight = (int)random(0, 11); return result ; } void showEtchingMask(int alphaEndpoint) { if (isetching && smearing) { float rot = radians(etchingRotate); color strk = color(0, constrain(255-alphaEndpoint, 0, 255)); stroke(strk); etchingMASK.setStroke(strk); // println("alphaEndpoint " + alphaEndpoint); strokeWeight(etchingWeight); for (float escale = etchingScale ; escale < 1.0 ; escale += etchingScale) { for (int x = 0 ; x < width ; x += (int)(width*escale)) { for (int y = 0 ; y < height ; y += (int)(height*escale)) { pushMatrix(); translate(x, y); rotate(rot); scale(escale); shape(etchingMASK, 0,0); popMatrix(); } } } } } void smearXorY() { pushMatrix(); int alphaEndpoint = (int)((float)framenum / f_framesTofade * 255.0) ; int Xshift = (int)((float)framenum / f_framesTofade * width) ; int Yshift = (int)((float)framenum / f_framesTofade * height) ; int vwidth = width ; int vheight = height ; int loff = 0 ; int toff = 0 ; if (smearing && framenum != 0 && framenum != framesTofade && ((shiftToX && Xshift != 0) || ((!shiftToX) && Yshift != 0))) { tint(255,alphaEndpoint); if (rotZZIncr != 0.0) { translate(width/2, height/2); rotateZ(radians(rotZZSum)); translate(-width/2, -height/2); } if (isPlanetarium) { image(curimgs[0], width/2, height/2, imageside, imageside); vwidth = smearbig ? min(width, height) : imageside ; // min(width, height); //imageside ; // min(width, height); ; vheight = vwidth ; loff = (width-vwidth) / 2 ; toff = (height-vheight) / 2 ; Xshift = loff + ((int)((float)framenum / f_framesTofade * vwidth)); Yshift = toff + ((int)((float)framenum / f_framesTofade * vheight)); } else { image(curimgs[0], width/2, height/2, width, height); } loadPixels(); int [] pixcopy = Arrays.copyOf(pixels, pixels.length); if (shiftToX) { for (int x = Xshift, xfrom = loff, dist = 0 ; dist < vwidth && x < (vwidth+loff) && xfrom < (vwidth+loff) ; x++, xfrom++, dist++) { for (int y = toff ; y < (vheight+toff) && y < height ; y++) { //println("DEBUG x " + x + " y " + y + " xfrom " + xfrom + " width " + width + " height " + height + " dist " + dist); pixels[y * width + x] = pixcopy[y * width + xfrom]; } } for (int x = loff, xfrom = Xshift, dist = 0 ; dist < vwidth && x < (vwidth+loff) && xfrom < (vwidth+loff) ; x++, xfrom++, dist++) { for (int y = toff ; y < (vheight+toff) && y < height ; y++) { pixels[y * width + x] = pixcopy[y * width + xfrom]; } } } else { for (int y = Yshift, yfrom = toff, dist = 0 ; dist < vheight && y < height && yfrom < height ; y++, yfrom++, dist++) { for (int x = loff ; x < (loff+vwidth) && x < width ; x++) { pixels[y * width + x] = pixcopy[yfrom * width + x]; } } for (int y = toff, yfrom = Yshift, dist = 0 ; dist < vheight && y < height && yfrom < height ; y++, yfrom++, dist++) { for (int x = loff ; x < (loff+vwidth) && x < width ; x++) { pixels[y * width + x] = pixcopy[yfrom * width + x]; } } } updatePixels(); } popMatrix(); } void keyPressed() { if (key == 'b' || key == 'P' || key == 'T' || key == 'B' || key == 'f' || key == 'x' || key == 'y' || key == 'z' || key == 'Z' || key == 'E' || key == '@' || key == 'X' || key == 'Y' || key == 'C' || key == 'L') { command = "" + key ; } else if ((key >= '0' && key <= '9') || key == '.') { if (command.length() > 0) { command = command + key ; } } else if (key == 'R') { rotXIncr = rotXSum = rotYIncr = rotYSum = rotZIncr = rotZSum = rotZZIncr = rotZZSum = 0.0 ; shearXlimit = shearYlimit = 0.0; } else if (key == 'n') { // AWT libraries do not rethrow InterruptedException: imageLoaderThread.interrupt(); curimgs = getNextImg(); history.add(curimgs); while (history.size() > historyLimit) { history.remove(); } println("LOADED " + fnames.get(curloc)); } else if (key == 's') { save("savers/" + fnames.get(curloc) + "_B" + blurfactor + "_P" + posterizefactor + ".jpg"); println("SAVED!"); } else if (key == 'm') { smearing = ! smearing ; println("m smearing is " + smearing + ", smearingonly is " + smearingonly); /* if (smearing && smearingonly) { background(0); } */ } else if (key == 'M') { smearingonly = ! smearingonly ; println("m smearing is " + smearing + ", smearingonly is " + smearingonly); /* if (smearing && smearingonly) { background(0); } */ } else if (key == 'e') { isetching = ! isetching ; println("m isetching is " + isetching); } else if (key == 'a') { autoAdjustments = ! autoAdjustments ; println("autoAdjustments = " + autoAdjustments); } else if (key == 'p') { isPlanetarium = ! isPlanetarium ; if (isPlanetarium) { background(0); } println("isPlanetarium = " + isPlanetarium); } else if (key == 'F') { // savingFrame = ! savingFrame ; // println("savingFrame = " + savingFrame); freezeFrame = ! freezeFrame ; println("freezeFrame = " + freezeFrame); } else if (key == 'g') { isgray = ! isgray ; println("isgray = " + isgray); } else if (key == 'S') { isSaturate = ! isSaturate ; println("isSaturate = " + isSaturate); } else if (key == '\n') { if (command.length() > 1) { try { float num = java.lang.Float.parseFloat(command.substring(1)); char cmd = command.charAt(0); if (cmd == 'b') { blurfactor = (int)constrain(num, 0, 10000); } else if (cmd == 'P') { posterizefactor = (int)constrain(num, 0, 255); } else if (cmd == 'B') { blendIndex = (int)constrain(num, 0, blendModeInt.length); blendCounter = blendIndex > 0 ? blendLimit : 0 ; } else if (cmd == 'f') { feathering = constrain(num, 0.0, 1.0); } else if (cmd == 'T') { thresholdfactor = constrain(num, 0.0, 1.0); } else if (cmd == '@') { int fr = constrain(round(num), 1, 120); frameRate(fr); println("Setting frameRate to " + fr); } else if (cmd == 'x') { rotXIncr = constrain(num, 0.0, 360.0); if (rotXIncr == 0.0) { rotXSum = 0.0 ; } } else if (cmd == 'y') { rotYIncr = constrain(num, 0.0, 360.0); if (rotYIncr == 0.0) { rotYSum = 0.0 ; } } else if (cmd == 'z') { rotZIncr = constrain(num, 0.0, 360.0); if (rotZIncr == 0.0) { rotZSum = 0.0 ; } } else if (cmd == 'Z') { rotZZIncr = constrain(num, 0.0, 360.0); if (rotZZIncr == 0.0) { rotZZSum = 0.0 ; } } else if (cmd == 'X') { shearXlimit = constrain(num, 0.0, 360.0); } else if (cmd == 'Y') { shearYlimit = constrain(num, 0.0, 360.0); } else if (cmd == 'C') { clipalpha = (int)constrain(num, 0, 255); } else if (cmd == 'L') { historyLimit = (int)constrain(num, 1, 8); } println("COMMAND: " + command + ", num = " + num + ", blur = " + blurfactor + ", poster = " + posterizefactor + ", thresholdfactor = " + thresholdfactor + ", feathering = " + feathering + ", " + " rotZZIncr = " + rotZZIncr + ", rotXIncr = " + rotXIncr + ", rotYIncr = " + rotYIncr + ", rotZIncr = " + rotZIncr + ", blendIndex = " + blendIndex + ", shearXlimit = " + shearXlimit + ", shearYlimit = " + shearYlimit + ", autoAdjustments = " + autoAdjustments + ", clipalpha = " + clipalpha + ", frameRate = " + frameRate); command = ""; } catch (java.lang.NumberFormatException xx) { println("BAD INT: " + command + " : " + xx.getMessage()); command = ""; } } } }