// Ultimate Sort Machine // Tomasz Sulej, generateme.blog@gmail.com, http://generateme.tumblr.com // Licence: http://unlicense.org/ // Usage: // * press SPACE to save // * press 'a' to save animation frames // * press 'b' for batch process files from set folder // Read ALL (I repeat ALL) text below until written "STOP READING HERE" :) // This is quite complicated machine for sort images in many various ways. // I'll try to explain how to use it and what all this stuff means. // // Here is the idea: // 1. You select pixels which are involved in sorting. Selection is made using few rules and logical operators. // You can think about this as more sophisticated threshold. Remember only chosen pixels are involved in sorting. // That means that any gaps between pixels are left untouched. // 2. You can define "select and sort" process as many times as you want // 3. You can sort pixels partly. That means you can interrupt sorting in defined moment. // It's very crutial, every sorting method has its own patterns and you can achieve various results // 4. You can sort in ascending/descending order // 5. You can sort negated pixels // 6. You can "lay down" result in 4 direction: from left to right, from top to bottom and opposite. It is different than sorting order (and can be combined) // 7. Having above 3 possibilities you can achive 16 possible way to produce result using one single sorting algorithm. // 8. Finally, you can use many different sorting algorithms (+3 extra bonus nonsorting methods :)) // Ok, that was idea and now let's go through configuration // First, set up filename you want to operate, remember when you see the result you can save it by pressing SPACE String filename = "test"; String fileext = ".jpg"; String foldername = "./"; // folder used for batch process also // Second, set up window size (image size can be huge, this parameter will help you to see it) int max_display_size = 800; // Third, you can choose to blend result with original image boolean do_blend = false; int blend_mode = OVERLAY; // blend type // Here are all blend modes available final static int[] blends = { ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN }; // If you want to animate process, set number of frames you want to save int animation_frames_count = 60; int animation_easing = LINEAR; // LINEAR or CUBIC // We are near to the crucial part, configuration of sorting machine // But first look at block below, you need to remember one thing here: name of the variable ie. HUE_RED, HUE_CYAN_W, etc... // See, there is also name 'TRUE' // It will be explained later, skip now IEval HUE_RED_W = OR(RANGE(HUE, hs(0), hs(20)), RANGE(HUE, hs(346), hs(359))); IEval HUE_RED = OR(RANGE(HUE, hs(0), hs(10)), RANGE(HUE, hs(355), hs(359))); IEval HUE_ORANGE_W = RANGE(HUE, hs(11), hs(50)); IEval HUE_ORANGE = RANGE(HUE, hs(21), hs(40)); IEval HUE_YELLOW_W = RANGE(HUE, hs(41), hs(80)); IEval HUE_YELLOW = RANGE(HUE, hs(51), hs(60)); IEval HUE_GREEN_W = RANGE(HUE, hs(61), hs(169)); IEval HUE_GREEN = RANGE(HUE, hs(81), hs(140)); IEval HUE_CYAN_W = RANGE(HUE, hs(141), hs(220)); IEval HUE_CYAN = RANGE(HUE, hs(170), hs(200)); IEval HUE_BLUE_W = RANGE(HUE, hs(201), hs(280)); IEval HUE_BLUE = RANGE(HUE, hs(221), hs(240)); IEval HUE_MAGENTA_W = RANGE(HUE, hs(241), hs(330)); IEval HUE_MAGENTA = RANGE(HUE, hs(281), hs(320)); IEval HUE_PINK_W = RANGE(HUE, hs(321), hs(355)); IEval HUE_PINK = RANGE(HUE, hs(331), hs(345)); IEval TRUE = new C_TRUE(); // HERE IT IS, machine configuration, every single step (select and sort process) begins with name RECIPE. // rules (recipes) are separated by comma // Find example configuration below. It means exactly this: make 2 steps where in // Step 1: Sort only RED pixels using HEAPSORT algorithm and stop sorting when you get 5% better sort than at beginning, lay down pixels from left to right, sort negated pixels in ascending order // Step 2: Sort only pixels which are not RED using QUICKSORT, interrupt when you get 20% improvement, sort in descent order, lay down pixels bottom to top // Run script now and see result. Configurator[] recipes = { // do not touch this line... RECIPE(QUICK, 0.5, DEFAULT, RIGHT, HUE_RED_W), // comma! RECIPE(MERGE, 0.95, DEFAULT, BOTTOM, NOT(HUE_RED_W)) // last rule do not have comma }; // ...neither this //another example below //Configurator[] recipes = { // do not touch this line... // RECIPE(PERMUTE, 0.523635, ABSOLUTE | DESC, RIGHT, OR(HUE_RED,HUE_PINK_W)), // comma! // RECIPE(SHELL, 0.01523635, DESC, BOTTOM, OR(HUE_RED,HUE_PINK_W)), // comma! // RECIPE(HEAP, 0.008751599, DEFAULT | DESC, TOP, NOT(OR(HUE_RED,HUE_PINK_W))) // }; // ...neither this // If you want to add/replace your rules for your step, you need to construct your own RECIPE line. You have to define 5 things: // 1. Set sort algorithm (full list below, look for "SORTING ALGORITHMS" line) // 2. Set amount of sort. This is a number between 0 (no sort) and 1 (full sort). You can have two meanings of this parameter: // a) in RELATIVE mode it means how much sort improvement you want to achive. Eg. 0.123 value means that you get 12,3% more sorted pixels than at beginning. // b) in ABSOLUTE mode it means how much sort you want to have regardless of current value. Eg. 0.75 means that sort stops after 75% of pixels will be sorted. // one note here, pixels taken from image are usually partly sorted (let's say it's 50% moreless), some algorithms can lower this value, some only improves, you need to experiment // 3. Set some specific flags. Flags can be combined together using "|" delimiter (obligatory) // a) DEFAULT - ascending sort with relative amount // b) RELATIVE (default) or ABSOLUTE - described in point 2 // c) ASC (default) or DESC - ascending or descending sort // d) POSITIVE (default) or NEGATIVE - work on image as it is or negate pixels // 4. Set sort direction: RIGHT (to right), LEFT (to left), TOP (to top), BOTTOM (to bottom) // 5. Threshold rule. This is hardest stuff. Rule has a grammar and it's as follows // a) Remember now block above with variable names starting HUE_... and TRUE at the end. These are predefined rules for you. // HUE_color means: choose pixels with particular color. There are two modes: normal range and wide range. // Eg. HUE_MAGENTA is normale range and chooses magenta pixels (hue range between 281 and 320) but // HUE_MAGENTA_W is wider version of magenta with pink and blue touch (hue range between 241 and 330). // All ranges are described under following link: http://www.workwithcolor.com/color-names-01.htm // TRUE means - sort me whole line, no treshold! // b) you can define your own range on any possible channel, syntax is: // RANGE( channel, minimum value, maximum value) // - list of all possible channels is below (look for "CHANNELS" line) // - minimum and maximum value can have 3 options: // Option a: integer number between 0 and 255, typical channels value. // Option b: float number between 0 and 0.99999999 (normalized form) // Option c: uniform random value between specified range in form RAND(from, to) // Example 1: RANGE(BRIGHTNESS,0,100) - choose pixels with brighntess value between 0 and 100. Only dark pixel. // Example 2: RANGE(RED, 0.5, 0.8) - choose pixels with red channel value between 50% and 80% // Example 3: RANGE(ALPHA, RAND(0,0.1), 0.5) - choose pixel with alpha value between random value (from 0 to 10%) and 50% // c) you can make nagation of every rule using NOT(your rule here) // Example 1: NOT(HUE_PINK) - choose not pink pixels // Example 2: NOT( RANGE(SATURATION, 50,100) ) - choose pixels with saturation between 0-49 and 101-255. // d) you can use logical operator AND and OR. // - AND(first rule, second rule) - first and second rule must be valid for choosen pixel // - OR(first rule, second rule) - first or second rule (or both) must be valid // Example 1: AND( NOT(HUE_RED), RANGE(BRIGHTNESS,0,50) ) - choose pixels which are not red and are dark (brightness between 0 and 50 // Example 2: OR( HUE_GREEN_W, AND( NOT(HUE_RED), RANGE(BRIGHTNESS,0,50) ) ) - choose pixels which are green (wide range) or are as described above (dark, not red) // NOTE: you can build threshold as complicated as you want // now names you will use // FLAGS (described in point 2 above) final static int DEFAULT = 0; final static int ABSOLUTE = 1; final static int RELATIVE = 0; final static int ASC = 0; final static int DESC = 2; final static int POSITIVE = 0; final static int NEGATE = 4; // SORTING ALGORITHMS final static int BUBBLE = 0; final static int INSERTION = 1; final static int SELECTION = 2; final static int HEAP = 3; // kind of echo, http://generateme.tumblr.com/post/121956583990/splice final static int SHELL = 4; final static int MERGE = 5; // nice blocky patterns, http://generateme.tumblr.com/post/121920710155/unmerged-sculpture-based-on-work-by-stefanie final static int QUICK = 6; // nice mess pattern final static int SMOOTH = 7; // lines // bonus, not sort methods final static int PERMUTE = 8; // pixels are just randomized, amount is not used final static int ONECOLOR = 9; // set pixels to one color, amount means which color use (0 first pixel, 1 last pixel, 0.5 middle pixel, etc) final static int ROLL = 10; // roll pixels left, amount means how much to roll (0.5 - halfway) final static int ONECOLORB = 11; // as one color but made in blocks (based on noise), amount means scale factor for noise function // more algorithms to come: coctail, gnome, comb, oddeven, maybe more... // CHANNELS // ALL Channels, Nxxx stand for negative (255-value) final static int RED = 0; final static int GREEN = 1; final static int BLUE = 2; final static int HUE = 3; final static int SATURATION = 4; final static int BRIGHTNESS = 5; final static int ALPHA = 6; final static int NRED = 7; final static int NGREEN = 8; final static int NBLUE = 9; final static int NHUE = 10; final static int NSATURATION = 11; final static int NBRIGHTNESS = 12; final static int NALPHA = 13; // EASING in animations final static int LINEAR = 0; final static int CUBIC = 1; // if you press 'b' every frame following code will be called, put here anything you want void batchCallback(float step) { // recipes[0].amount = map(sin(step*TWO_PI),-1,1,0.2,0.8); } // YOU ARE READY TO USE, EXPERIMENT, EXPLORE, DON'T GIVE UP, ENJOY // STOP READING HERE int sortType = HEAP; // sort method, list below float sortAmount = 0.01; // percent of sort to achive boolean relativeThr = true; // is above relative to current value or absolute boolean reverseSort = false; // reverse sort order boolean negateColors = false; int direction = LEFT; IEval eval; color[] bufw; color[] bufh; int[] posx; int[] posy; // working buffer PGraphics buffer; // image PImage img; PImage orig; String sessionid; void setup() { sessionid = hex((int)random(0xffff), 4); img = loadImage(foldername+filename+fileext); orig = img.get(); buffer = createGraphics(img.width, img.height); buffer.beginDraw(); buffer.noStroke(); buffer.smooth(8); buffer.background(0); buffer.image(img, 0, 0); buffer.endDraw(); // calculate window size float ratio = (float)img.width/(float)img.height; int neww, newh; if (ratio < 1.0) { neww = (int)(max_display_size * ratio); newh = max_display_size; } else { neww = max_display_size; newh = (int)(max_display_size / ratio); } size(neww, newh); bufw = new color[img.width]; bufh = new color[img.height]; posx = new int[img.width]; posy = new int[img.height]; processImage(); } float anim_step = 0; boolean animate = false; String anim_hash = "1234"; int anim_cnt = 1000; void draw() { if (animate) { buffer.image(orig, 0, 0); img = orig.get(); for (int i=0; i1.0) animate = false; } if(doBatch) { batchStep(); } } void processImage() { for (int i=0; i 0) relativeThr = false; else relativeThr = true; if ( (c.flags & DESC) > 0) reverseSort = true; else reverseSort = false; if ( (c.flags & NEGATE) > 0) negateColors = true; else negateColors = false; println("Sorting, pass: " + (i+1)); buffer.beginDraw(); if (direction == LEFT || direction == RIGHT) sortH(); else sortV(); buffer.endDraw(); img = buffer.get(0, 0, img.width, img.height); } buffer.beginDraw(); if (do_blend) buffer.blend(orig, 0, 0, img.width, img.height, 0, 0, buffer.width, buffer.height, blend_mode); buffer.endDraw(); image(buffer, 0, 0, width, height); } void keyPressed() { // SPACE to save if (keyCode == 32) { String fn = foldername + filename + "/res_" + sessionid + hex((int)random(0xffff), 4)+"_"+filename+fileext; buffer.save(fn); println("Image "+ fn + " saved"); } if(key == 'a') { anim_hash = hex((int)random(0xffff), 4); anim_step = 0; animate = true; anim_cnt = 1000; } if(key == 'b' && !doBatch) { batchProcess(); } } // void sortH() { for (int y=0; y2) { int i = (int)map(thr, 0, 1, 2, cnt-2); color[] tmp = new int[cnt-i]; arrayCopy(t, i, tmp, 0, cnt-i); arrayCopy(t, 0, t, cnt-i-1, i); arrayCopy(tmp, 0, t, 0, cnt-i); } } void permute(color[] t, int cnt, float thr) { for (int i=1; i 0.5; color c = t[0]; for (int i=0; i 0.5; if ( nv != cv ) { nv = cv; c = t[i+1]; } np += thr; } } void onecolor(color[] t, int cnt, float thr) { int i = (int)constrain(map(thr, 0, 1, 0, cnt-1), 0, cnt-1); color c = t[i]; for (i=0; i>>= 2; pshift += 2; } else { if (LP[pshift - 1] >= hi - head) { trinkle(t, p, pshift, head, false, cnt, thr); } else { sift(t, pshift, head, cnt, thr); } if (pshift==1) { p <<= 1; pshift--; } else { p <<= (pshift - 1); pshift = 1; } } p |= 1; head++; if (sortRatio(t, cnt)>=thr) return; } trinkle(t, p, pshift, head, false, cnt, thr); while (pshift != 1 || p != 1) { if (pshift<=1) { int trail = Integer.numberOfTrailingZeros(p & ~1); p >>>= trail; pshift += trail; } else { p <<= 2; p ^= 7; pshift -= 2; trinkle(t, p >>> 1, pshift + 1, head - LP[pshift] - 1, true, cnt, thr); if (sortRatio(t, cnt)>=thr) return; trinkle(t, p, pshift, head - 1, true, cnt, thr); if (sortRatio(t, cnt)>=thr) return; } head--; } } void sift(int[] m, int pshift, int head, int cnt, float thr) { int val = m[head]; while (pshift > 1) { int rt = head - 1; int lf = head - 1 - LP[pshift - 2]; if (val >= m[lf] && val >= m[rt]) { break; } if (m[lf] >= m[rt]) { m[head] = m[lf]; head = lf; pshift -= 1; } else { m[head] = m[rt]; head = rt; pshift -= 2; } } m[head] = val; } private void trinkle(int[] m, int p, int pshift, int head, boolean isTrusty, int cnt, float thr) { int val = m[head]; while (p != 1) { int stepson = head - LP[pshift]; if (m[stepson] <= val) { break; } if (!isTrusty && pshift > 1) { int rt = head - 1; int lf = head - 1 - LP[pshift - 2]; if (m[rt] > m[stepson] || m[lf] > m[stepson]) { break; } } m[head] = m[stepson]; head = stepson; int trail = Integer.numberOfTrailingZeros(p & ~1); p >>>= trail; pshift += trail; isTrusty = false; } if (!isTrusty) { m[head] = val; sift(m, pshift, head, cnt, thr); } } void smoothSort(color[] t, int cnt, float thr) { smoothSort(t, 0, cnt-1, cnt, thr); } void quickSort(color[] t, int a, int b, int cnt, float thr) { if (a>=b) return; if (sortRatio(t, cnt)>=thr) return; int pivotidx = a+(b-a)/2; color pivot = t[pivotidx]; swap(t, pivotidx, b); int i=a-1; int j=b; do { do { i++; } while (t[i] < pivot); do { j--; } while (t[j] > pivot && j>a); if (i=thr) return; quickSort(t, a, i-1, cnt, thr); quickSort(t, i+1, b, cnt, thr); } void quickSort(color[] t, int cnt, float thr) { quickSort(t, 0, cnt-1, cnt, thr); } void topdownMerge(color[] t, color[] b, int ibegin, int imiddle, int iend) { int i0=ibegin; int i1=imiddle; for (int j=ibegin; j= iend || t[i0] <= t[i1])) { b[j] = t[i0]; i0++; } else { b[j] = t[i1]; i1++; } } } void topdownSplitMerge(color[] t, color[] b, int ibegin, int iend, float thr, int cnt) { if (iend-ibegin<2) return; if (sortRatio(t, cnt)>=thr) return; int imiddle = (iend+ibegin)/2; topdownSplitMerge(t, b, ibegin, imiddle, thr, cnt); topdownSplitMerge(t, b, imiddle, iend, thr, cnt); if (sortRatio(t, cnt)>=thr) return; topdownMerge(t, b, ibegin, imiddle, iend); arrayCopy(b, ibegin, t, ibegin, iend-ibegin); } void mergeSort(color[] t, int cnt, float thr) { color[] buf = new color[cnt]; topdownSplitMerge(t, buf, 0, cnt, thr, cnt); } final static int[] shell_gaps = { 701, 301, 132, 57, 23, 10, 4, 1 }; void shellSort(color[] t, int cnt, float thr) { for (int g=0; g=gap && t[j-gap] > tmp; j-=gap) { t[j] = t[j-gap]; } t[j] = tmp; if (gap>=10 && sortRatio(t, cnt)>=thr) return; } if (gap<10 && sortRatio(t, cnt)>=thr) return; } } void heapify(color[] t, int cnt, float thr) { int start = floor((cnt - 2)/2.0); while (start>=0) { siftDown(t, start, cnt-1); start--; if (sortRatio(t, cnt)>=thr) return; } } void siftDown(color[] t, int start, int end) { int root = start; while ( (root*2+1)<=end) { int child = root*2+1; int sw = root; if (t[sw]=thr) return; heapify(t, cnt, thr); int end = cnt - 1; while (end>0) { swap(t, end, 0); end--; siftDown(t, 0, end); if (sortRatio(t, cnt)>=thr) return; } } void selectionSort(color[] t, int cnt, float thr) { for (int j=0; j=thr) return; } } void bubbleSort(color[] t, int cnt, float thr) { int n = cnt; do { int newn = 0; for (int i=1; it[i]) { swap(t, i-1, i); newn = i; } } if (sortRatio(t, cnt)>=thr) return; n = newn; } while (n>0); } void insertionSort(color[] t, int cnt, float thr) { for (int i=1; i0 && t[j-1]>x) { t[j] = t[j-1]; j--; } t[j] = x; if (sortRatio(t, cnt)>=thr) return; } } // helpers void swap(color[] t, int a, int b) { color tmp = t[a]; t[a] = t[b]; t[b] = tmp; } float sortRatio(color[] t, int cnt) { int n = 0; for (int i=1; i6?channel-7:channel; float cc; switch(ch) { case RED: cc = red(c); break; case GREEN: cc = green(c); break; case BLUE: cc = blue(c); break; case HUE: cc = hue(c); break; case SATURATION: cc = saturation(c); break; case ALPHA: cc = alpha(c); break; default: cc= brightness(c); break; } return (channel>6?255-cc:cc); } // Configurator class class Configurator { int sort; float amount; int flags; int direction; IEval evaluator; public Configurator(int s, float a, int f, int d, IEval e) { sort = s; amount = a; flags = f; direction = d; evaluator = e; } } // HUE ranges final int hs(float ang) { return (int)map(ang, 0, 359, 0, 255); } // evaluator part Configurator RECIPE(int s, float a, int f, int d, IEval e) { return new Configurator(s, a, f, d, e); } PVector RAND(float a, float b) { return new PVector(a, b); } IEval RANGE(int ch, float a, float b) { return new C_RANGE(ch, a, a, b, b); } IEval RANGE(int ch, PVector a, float b) { return new C_RANGE(ch, a.x, a.y, b, b); } IEval RANGE(int ch, float a, PVector b) { return new C_RANGE(ch, a, a, b.x, b.y); } IEval RANGE(int ch, PVector a, PVector b) { return new C_RANGE(ch, a.x, a.y, b.x, b.y); } IEval AND(IEval a, IEval b) { return new C_AND(a, b); } IEval OR(IEval a, IEval b) { return new C_OR(a, b); } IEval NOT(IEval a) { return new C_NOT(a); } color currentColor; interface IEval { public boolean eval(); } class C_TRUE implements IEval { public boolean eval() { return true; } } class C_AND implements IEval { IEval a, b; public C_AND(IEval aa, IEval bb) { a = aa; b = bb; } public boolean eval() { return a.eval() && b.eval(); } } class C_OR extends C_AND { public C_OR(IEval aa, IEval bb) { super(aa, bb); } public boolean eval() { return a.eval() || b.eval(); } } class C_NOT implements IEval { IEval a; public C_NOT(IEval aa) { a = aa; } public boolean eval() { return !a.eval(); } } class C_RANGE implements IEval { int channel; float a, b; boolean ra = false; boolean rb = false; float a2, b2; public C_RANGE(int ch, float aa, float aa2, float bb, float bb2) { a = aa < 1.0 ? map(aa, 0, 1, 0, 256) : aa; b = bb < 1.0 ? map(bb, 0, 1, 0, 256) : bb; if (aa2 > aa) { ra = true; a2 = aa2 < 1.0 ? map(aa2, 0, 1, 0, 256) : aa2; } if (bb2 > bb) { rb = true; b2 = bb2 < 1.0 ? map(bb2, 0, 1, 0, 256) : bb2; } channel = ch; } public boolean eval() { float v = getChannel(currentColor, channel); float newa = ra ? random(a, a2) : a; float newb = rb ? random(b, b2) : b; return (v >= newa) && (v <= newb); } } // code for batch processing void batchStep() { File n = batchList[batchIdx]; String name = n.getAbsolutePath(); if(name.endsWith(fileext)) { print(n.getName()+"... "); img = loadImage(name); buffer.image(img, 0, 0); batchCallback((float)batchIdx / batchFiles); processImage(); save(foldername+batchUID+"/"+n.getName()); println("saved"); } batchIdx++; if(batchIdx >= batchList.length) { doBatch = false; println("results saved in "+ foldername+batchUID + " folder"); } } File[] batchList; int batchIdx = 0; String batchUID; boolean doBatch = false; float batchFiles = 0; void batchProcess() { batchUID = sessionid + hex((int)random(0xffff),4); File dir = new File(sketchPath+'/'+foldername); batchList = dir.listFiles(); batchIdx = 0; batchFiles = 0; for(File n : batchList) { if(n.getName().endsWith(fileext)) batchFiles=batchFiles+1.0; } println("Processing "+int(batchFiles)+" images from folder: " + foldername); doBatch = true; }