Difference between revisions of "User:Tom"
(Created page with "hi") |
m |
||
(46 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | + | = Pixel Sorting= | |
+ | |||
+ | I ran into a couple of gifs that visualised how certain sorting algorithms worked. Normally I don’t understand algorithms, but these visualisations made it so easy and insightful to understand what is going on. Besides that I think it’s cool to see an algorithm at work like this, I also think it has some artistic value. You could call it [https://en.wikipedia.org/wiki/Glitch_art Glitch Art]. | ||
+ | |||
+ | These are some examples of different codes working on a color palette: | ||
+ | |||
+ | [[File: PixelSortingExample.gif]] | ||
+ | |||
+ | It is a code creating a new kind of image by rearranging the original. This results in an image made anonymous. Or maybe more like a vague memory, where everything is still there but over time you start to forget what it looks like. This creates a new memory, a distorted version of the original. | ||
+ | |||
+ | {|style="margin: 0 auto;" | ||
+ | |[[File:Examplesort1.jpg|480x240px]] | ||
+ | |[[File:Examplesort2.jpg|480x240px]] | ||
+ | |} | ||
+ | {|style="margin: 0 auto;" | ||
+ | |[[File:Examplesort3.jpg|480x240px]] | ||
+ | |[[File:Examplesort4.jpg|480x240px]] | ||
+ | |} | ||
+ | |||
+ | == What is Pixel Sorting? == | ||
+ | |||
+ | Pixel Sorting is done with a program called Processing. It takes the pixels in a digital image and places them into a semblance of order. | ||
+ | Pixel sorting was made popular by an artist of the name [https://www.kimasendorf.com Kim Asendorf]. He began sorting by developing a code in Processing. | ||
+ | |||
+ | |||
+ | = Experiment = | ||
+ | |||
+ | == Processing == | ||
+ | |||
+ | This is a modified version of the code written bij Kim Asendorf. It is written in Processing language, and requires a folder called ‘data’ in the same location as the .pde processing file. I used it to experiment with different images to discover what would happen. | ||
+ | |||
+ | <nowiki> | ||
+ | PImage img; | ||
+ | PImage sorted; | ||
+ | int index = 0; | ||
+ | |||
+ | void setup() { | ||
+ | size(800, 400); | ||
+ | |||
+ | img = loadImage("afbeelding.jpg"); | ||
+ | sorted = createImage(img.width, img.height, RGB); | ||
+ | sorted = img.get(); | ||
+ | } | ||
+ | |||
+ | void draw() { | ||
+ | println(frameRate); | ||
+ | |||
+ | sorted.loadPixels(); | ||
+ | |||
+ | // Selection sort! | ||
+ | for (int y = 0; y < sorted.height; y++) { | ||
+ | |||
+ | float record = -1; | ||
+ | int selectedPixel = index; | ||
+ | |||
+ | for (int x = index; x < sorted.width; x++) { | ||
+ | int loc = y * sorted.width + x; | ||
+ | color pix = sorted.pixels[loc]; | ||
+ | float b = brightness(pix); | ||
+ | if (b > record) { | ||
+ | selectedPixel = loc; | ||
+ | record = b; | ||
+ | } | ||
+ | } | ||
+ | // Swap selectedPixel with i | ||
+ | color temp = sorted.pixels[y * sorted.width + index]; | ||
+ | sorted.pixels[y * sorted.width + index] = sorted.pixels[selectedPixel]; | ||
+ | sorted.pixels[selectedPixel] = temp; | ||
+ | } | ||
+ | if (index < sorted.width -1) { | ||
+ | index++; | ||
+ | } else { | ||
+ | save("sorted.jpg"); | ||
+ | frameRate(0); | ||
+ | } | ||
+ | |||
+ | sorted.updatePixels(); | ||
+ | |||
+ | background(0); | ||
+ | image(img, 0, 0); | ||
+ | image(sorted, 400, 0); | ||
+ | } | ||
+ | </nowiki> | ||
+ | |||
+ | The code results in a pixel sorting visualisation: | ||
+ | |||
+ | [[File:Airplanesort.gif]] | ||
+ | |||
+ | The result was alright, only I had less control than I wanted to. I did research for some more codes and finally found one called 'Butter'. This code gave me a lot more control. I now had the option to sort the 'black', 'white' and 'bright' of the image. | ||
+ | |||
+ | == Butter == | ||
+ | |||
+ | \begin{lstlisting} | ||
+ | |||
+ | (function () { | ||
+ | var validModes = ['black', 'bright', 'white']; | ||
+ | var defaultMode = validModes[0]; | ||
+ | |||
+ | function Butter(mode, threshold) { | ||
+ | this.mode = mode || defaultMode; | ||
+ | if (validModes.indexOf(this.mode) === -1) { | ||
+ | console.log('Butter has no mode called "' + this.mode + '".'); | ||
+ | this.mode = defaultMode; | ||
+ | } | ||
+ | |||
+ | // defaults | ||
+ | this.threshold = { | ||
+ | black: -10000000, | ||
+ | white: -6000000, | ||
+ | bright: 30 | ||
+ | }; | ||
+ | |||
+ | if (typeof threshold !== 'undefined' && threshold !== null) { | ||
+ | this.threshold[this.mode] = threshold; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | Butter.prototype.sort = function sort(canvas, iterations) { | ||
+ | if (!canvas) { | ||
+ | throw 'Butter needs a <canvas> to sort'; | ||
+ | } | ||
+ | var context = canvas.getContext('2d'), | ||
+ | width = canvas.width, | ||
+ | height = canvas.height, | ||
+ | // Get the current data | ||
+ | imageData = context.getImageData(0, 0, width, height), | ||
+ | // And sort it | ||
+ | sortedImage = this.sortImageData(imageData, width, height, iterations); | ||
+ | |||
+ | context.putImageData(sortedImage, 0, 0); | ||
+ | }; | ||
+ | |||
+ | Butter.prototype.sortImageData = function sortImageData(imageData, width, height, iterations) { | ||
+ | this.imageData = imageData; | ||
+ | this.width = width; | ||
+ | this.height = height; | ||
+ | iterations || (iterations = 1); | ||
+ | |||
+ | for (var i = 0; i < iterations; i++) { | ||
+ | |||
+ | for (var column = 0; column < this.width; column++) { | ||
+ | this.sortColumn(column); | ||
+ | } | ||
+ | |||
+ | for (var row = 0; row < this.height; row++) { | ||
+ | this.sortRow(row); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | return this.imageData; | ||
+ | }; | ||
+ | |||
+ | Butter.prototype.setThreshold = function setThreshold(value) { | ||
+ | this.threshold[this.mode] = value; | ||
+ | }; | ||
+ | |||
+ | Butter.prototype.sortColumn = function sortColumn(x) { | ||
+ | var ranges = this.getRangesForColumn(x), | ||
+ | range, width, pixelData; | ||
+ | |||
+ | // For each range... | ||
+ | for (var i = 0; i < ranges.length; i++) { | ||
+ | range = ranges[i]; | ||
+ | width = range.end - range.start; | ||
+ | |||
+ | pixelData = new Array(width); | ||
+ | |||
+ | // Get all the pixels in that range | ||
+ | for (var j = 0; j < width; j++) { | ||
+ | pixelData[j] = this.getPixelValue(x, range.start + j); | ||
+ | } | ||
+ | |||
+ | // Sort them! | ||
+ | pixelData.sort(); | ||
+ | |||
+ | // And put the new pixels back | ||
+ | for (var j = 0; j < width; j++) { | ||
+ | this.setPixelValue(x, (range.start + j), pixelData[j]); | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | Butter.prototype.sortRow = function sortRow(y) { | ||
+ | var ranges = this.getRangesForRow(y), | ||
+ | range, width, pixelData; | ||
+ | |||
+ | // For each range... | ||
+ | for (var i = 0; i < ranges.length; i++) { | ||
+ | range = ranges[i]; | ||
+ | width = range.end - range.start; | ||
+ | |||
+ | pixelData = new Array(width); | ||
+ | |||
+ | // Get all the pixels in that range | ||
+ | for (var j = 0; j < width; j++) { | ||
+ | pixelData[j] = this.getPixelValue(range.start + j, y); | ||
+ | } | ||
+ | |||
+ | // Sort them! | ||
+ | pixelData.sort(); | ||
+ | |||
+ | // And put the new pixels back | ||
+ | for (var j = 0; j < width; j++) { | ||
+ | this.setPixelValue((range.start + j), y, pixelData[j]); | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | Butter.prototype.getRangesForColumn = function getRangesForColumn(x) { | ||
+ | var ranges = [], | ||
+ | start = 0, | ||
+ | end = 0, | ||
+ | findFirst, findNext; | ||
+ | |||
+ | switch(this.mode) { | ||
+ | case 'black': | ||
+ | findFirst = this.getFirstNotBlackY; | ||
+ | findNext = this.getNextBlackY; | ||
+ | break; | ||
+ | |||
+ | case 'bright': | ||
+ | findFirst = this.getFirstBrightY; | ||
+ | findNext = this.getNextDarkY; | ||
+ | break; | ||
+ | |||
+ | case 'white': | ||
+ | findFirst = this.getFirstNotWhiteY; | ||
+ | findNext = this.getNextWhiteY; | ||
+ | break; | ||
+ | } | ||
+ | |||
+ | for ( ; end < this.height; start = (end + 1)) { | ||
+ | start = findFirst.call(this, x, start); | ||
+ | end = findNext.call(this, x, start); | ||
+ | |||
+ | // No more ranges | ||
+ | if (start < 0 || start >= this.height) break; | ||
+ | |||
+ | ranges.push({start: start, end: end}); | ||
+ | } | ||
+ | return ranges; | ||
+ | }; | ||
+ | |||
+ | Butter.prototype.getRangesForRow = function getRangesForRow(y) { | ||
+ | var ranges = [], | ||
+ | start = 0, | ||
+ | end = 0, | ||
+ | findFirst, findNext; | ||
+ | |||
+ | switch(this.mode) { | ||
+ | case 'black': | ||
+ | findFirst = this.getFirstNotBlackX; | ||
+ | findNext = this.getNextBlackX; | ||
+ | break; | ||
+ | |||
+ | case 'bright': | ||
+ | findFirst = this.getFirstBrightX; | ||
+ | findNext = this.getNextDarkX; | ||
+ | break; | ||
+ | |||
+ | case 'white': | ||
+ | findFirst = this.getFirstNotWhiteX; | ||
+ | findNext = this.getNextWhiteX; | ||
+ | break; | ||
+ | } | ||
+ | |||
+ | for ( ; end < this.width; start = (end + 1)) { | ||
+ | start = findFirst.call(this, start, y); | ||
+ | end = findNext.call(this, start, y); | ||
+ | |||
+ | // No more ranges | ||
+ | if (start < 0 || start >= this.width) break; | ||
+ | |||
+ | ranges.push({start: start, end: end}); | ||
+ | } | ||
+ | return ranges; | ||
+ | }; | ||
+ | |||
+ | /* | ||
+ | Finders | ||
+ | */ | ||
+ | |||
+ | Butter.prototype.getFirstNotBlackX = function getFirstNotBlackX(x, y) { | ||
+ | // Loop until we find a match | ||
+ | for ( ; this.getPixelValue(x, y) < this.threshold.black; x++) { | ||
+ | // Oh no, we've reached the edge! | ||
+ | if (x >= this.width) return -1; | ||
+ | } | ||
+ | // Return the match | ||
+ | return x; | ||
+ | } | ||
+ | |||
+ | Butter.prototype.getNextBlackX = function getNextBlackX(x, y) { | ||
+ | // We want the _next_ one | ||
+ | x += 1; | ||
+ | for ( ; this.getPixelValue(x, y) > this.threshold.black; x++) { | ||
+ | if (x >= this.width) return this.width - 1; | ||
+ | } | ||
+ | return x; | ||
+ | } | ||
+ | |||
+ | |||
+ | Butter.prototype.getFirstBrightX = function getFirstBrightX(x, y) { | ||
+ | for ( ; this.getPixelBrightness(x, y) < this.threshold.bright; x++) { | ||
+ | if (x >= this.width) return -1; | ||
+ | } | ||
+ | return x; | ||
+ | } | ||
+ | |||
+ | Butter.prototype.getNextDarkX = function getNextDarkX(x, y) { | ||
+ | x += 1; | ||
+ | for ( ; this.getPixelBrightness(x, y) > this.threshold.bright; x++) { | ||
+ | if (x >= this.width) return this.width - 1; | ||
+ | } | ||
+ | return x; | ||
+ | } | ||
+ | |||
+ | |||
+ | Butter.prototype.getFirstNotWhiteX = function getFirstNotWhiteX(x, y) { | ||
+ | for ( ; this.getPixelValue(x, y) > this.threshold.white; x++) { | ||
+ | if (x >= this.width) return -1; | ||
+ | } | ||
+ | return x; | ||
+ | } | ||
+ | |||
+ | Butter.prototype.getNextWhiteX = function getNextWhiteX(x, y) { | ||
+ | x += 1; | ||
+ | for ( ; this.getPixelValue(x, y) < this.threshold.white; x++) { | ||
+ | if (x >= this.width) return this.width - 1; | ||
+ | } | ||
+ | return x; | ||
+ | } | ||
+ | |||
+ | |||
+ | Butter.prototype.getFirstNotBlackY = function getFirstNotBlackY(x, y) { | ||
+ | for ( ; this.getPixelValue(x, y) < this.threshold.black; y++) { | ||
+ | if (y >= this.height) return -1; | ||
+ | } | ||
+ | return y; | ||
+ | } | ||
+ | |||
+ | Butter.prototype.getNextBlackY = function getNextBlackY(x, y) { | ||
+ | y += 1; | ||
+ | for ( ; this.getPixelValue(x, y) > this.threshold.black; y++) { | ||
+ | if (y >= this.height) return this.height - 1; | ||
+ | } | ||
+ | return y; | ||
+ | } | ||
+ | |||
+ | |||
+ | Butter.prototype.getFirstBrightY = function getFirstBrightY(x, y) { | ||
+ | for ( ; this.getPixelBrightness(x, y) < this.threshold.bright; y++) { | ||
+ | if (y >= this.height) return -1; | ||
+ | } | ||
+ | return y; | ||
+ | } | ||
+ | |||
+ | Butter.prototype.getNextDarkY = function getNextDarkY(x, y) { | ||
+ | y += 1; | ||
+ | for ( ; this.getPixelBrightness(x, y) > this.threshold.bright; y++) { | ||
+ | if (y >= this.height) return this.height - 1; | ||
+ | } | ||
+ | return y; | ||
+ | } | ||
+ | |||
+ | |||
+ | Butter.prototype.getFirstNotWhiteY = function getFirstNotWhiteY(x, y) { | ||
+ | for ( ; this.getPixelValue(x, y) > this.threshold.white; y++) { | ||
+ | if (y >= this.height) return -1; | ||
+ | } | ||
+ | return y; | ||
+ | } | ||
+ | |||
+ | Butter.prototype.getNextWhiteY = function getNextWhiteY(x, y) { | ||
+ | y += 1; | ||
+ | for ( ; this.getPixelValue(x, y) < this.threshold.white; y++) { | ||
+ | if (y >= this.height) return this.height - 1; | ||
+ | } | ||
+ | return y; | ||
+ | } | ||
+ | |||
+ | /* | ||
+ | Utilities | ||
+ | */ | ||
+ | |||
+ | Butter.prototype.getPixelOffset = function getPixelOffset(x, y) { | ||
+ | return (x + y * this.width) * 4; | ||
+ | }; | ||
+ | |||
+ | Butter.prototype.setPixelValue = function setPixelValue(x, y, val) { | ||
+ | var offset = this.getPixelOffset(x, y), | ||
+ | r = (val >> 16) & 255, | ||
+ | g = (val >> 8) & 255, | ||
+ | b = val & 255, | ||
+ | data = this.imageData.data; | ||
+ | |||
+ | data[offset] = r; | ||
+ | data[offset + 1] = g; | ||
+ | data[offset + 2] = b; | ||
+ | } | ||
+ | |||
+ | Butter.prototype.getPixelValue = function getPixelValue(x, y) { | ||
+ | var offset = this.getPixelOffset(x, y), | ||
+ | data = this.imageData.data, | ||
+ | r = data[offset], | ||
+ | g = data[offset + 1], | ||
+ | b = data[offset + 2]; | ||
+ | |||
+ | return ( ((255 << 8) | r) << 8 | g) << 8 | b; | ||
+ | } | ||
+ | |||
+ | Butter.prototype.getPixelBrightness = function getPixelBrightness(x, y) { | ||
+ | var offset = this.getPixelOffset(x, y), | ||
+ | data = this.imageData.data, | ||
+ | r = data[offset], | ||
+ | g = data[offset + 1], | ||
+ | b = data[offset + 2]; | ||
+ | |||
+ | return Math.max(r, g, b) / 255 * 100; | ||
+ | } | ||
+ | |||
+ | // Let it loose | ||
+ | if ((typeof module !== "undefined") && (module.exports)) { | ||
+ | module.exports = Butter; | ||
+ | } else { | ||
+ | this.Butter = Butter; | ||
+ | } | ||
+ | |||
+ | This code gave me a lot more control. For instance with the old Apple universe background image: | ||
+ | |||
+ | [[File:apple.jpg]] | ||
+ | [[File:apple1.png]] | ||
+ | [[File:apple2.png]] | ||
+ | [[File:apple3.png]] | ||
+ | |||
+ | = Other Experiments = | ||
+ | |||
+ | These are experiments i tried with simpler codes. | ||
+ | |||
+ | [[File:Favicon.png | 250px]] | ||
+ | |||
+ | Sorted horizontally from dark to light colors. | ||
+ | |||
+ | [[File:Favicon Sort.png | 250px]] | ||
+ | |||
+ | [[File:1.png | 250px]] | ||
+ | |||
+ | [[File:2.png | 250px]] | ||
+ | |||
+ | [[File:3ico.png | 250px]] | ||
+ | |||
+ | [[File:4ico.png | 250px]] | ||
+ | |||
+ | = Former Glory: Logo's & Icons = | ||
+ | |||
+ | As I mentioned before, the result of pixel sorting reminds me of some distant memory, distorted over time. I want to recreate this feeling in a series of images. | ||
+ | |||
+ | First I needed something that counts as some sort of memory, something recognisable. Second, it had to be recognisable for a larger audience. So I had to find some sort of 'collective memory'. For this I chose to use iconic logo's of worldwide brands like McDonalds or Coca Cola. These brands are known worldwide and have been around for many years. Chances are high people will recognise them, even when the logos appear distorted. | ||
+ | |||
+ | [[File:billboardold.jpg | center | 240px ]] | ||
+ | |||
+ | == Chanel == | ||
+ | [[File:ChanelLogo.jpg | 400px | center ]] | ||
+ | |||
+ | === Sorted + Contact Sheet === | ||
+ | |||
+ | Click to enlarge!!! | ||
+ | |||
+ | [[File:ChanelLogo6.png | center]] | ||
+ | |||
+ | [[File:ChanelSheet.jpg | center| 720px]] | ||
+ | |||
+ | == Coca Cola == | ||
+ | [[File:Coca Cola logo.jpg | 400px | center ]] | ||
+ | |||
+ | === Sorted + Contact Sheet === | ||
+ | |||
+ | Click to enlarge!!! | ||
+ | |||
+ | [[File:CocaColaLogo17.png | center]] | ||
+ | |||
+ | [[File:CocaColaSheet.jpg | center| 720px]] | ||
+ | |||
+ | == Marlboro == | ||
+ | [[File:MarlboroLogo.jpg | 400px | center ]] | ||
+ | |||
+ | === Sorted + Contact Sheet === | ||
+ | |||
+ | Click to enlarge!!! | ||
+ | |||
+ | [[File:MarlboroLogo2.png | 400px | center]] | ||
+ | |||
+ | [[File:MarlboroSheet.jpg | center| 720px]] | ||
+ | |||
+ | == Mcdonalds == | ||
+ | [[File:McdonaldsLogo.png | center ]] | ||
+ | |||
+ | === Sorted + Contact Sheet === | ||
+ | |||
+ | Click to enlarge!!! | ||
+ | |||
+ | [[File:McDonaldsLogo21.png | 400px | center ]] | ||
+ | |||
+ | [[File:McDonaldsSheet.jpg | center| 720px]] | ||
+ | |||
+ | == Pepsi == | ||
+ | [[File:PepsiLogo.jpg | 400px | center ]] | ||
+ | |||
+ | === Sorted + Contact Sheet === | ||
+ | |||
+ | Click to enlarge!!! | ||
+ | |||
+ | [[File:PepsiLogo3.png | 400 px | center]] | ||
+ | |||
+ | [[File:PepsiSheet.jpg | center| 720px]] | ||
+ | |||
+ | == Starbucks == | ||
+ | [[File:StarbucksLogo.png | 400px | center ]] | ||
+ | |||
+ | === Sorted + Contact Sheet === | ||
+ | |||
+ | Click to enlarge!!! | ||
+ | |||
+ | [[File:Starbuckslogo14.png | 400px | center]] | ||
+ | |||
+ | [[File:StarbucksSheet.jpg | center| 720px]] | ||
+ | |||
+ | == Volkswagen == | ||
+ | [[File:VolkswagenLogo.png | 300px | center ]] | ||
+ | |||
+ | === Sorted + Contact Sheet === | ||
+ | |||
+ | Click to enlarge!!! | ||
+ | |||
+ | [[File:VolkswagenLogo20.png | center]] | ||
+ | |||
+ | [[File:VolkswagenSheet.jpg | center| 720px]] |
Latest revision as of 20:42, 4 February 2018
Pixel Sorting
I ran into a couple of gifs that visualised how certain sorting algorithms worked. Normally I don’t understand algorithms, but these visualisations made it so easy and insightful to understand what is going on. Besides that I think it’s cool to see an algorithm at work like this, I also think it has some artistic value. You could call it Glitch Art.
These are some examples of different codes working on a color palette:
It is a code creating a new kind of image by rearranging the original. This results in an image made anonymous. Or maybe more like a vague memory, where everything is still there but over time you start to forget what it looks like. This creates a new memory, a distorted version of the original.
What is Pixel Sorting?
Pixel Sorting is done with a program called Processing. It takes the pixels in a digital image and places them into a semblance of order. Pixel sorting was made popular by an artist of the name Kim Asendorf. He began sorting by developing a code in Processing.
Experiment
Processing
This is a modified version of the code written bij Kim Asendorf. It is written in Processing language, and requires a folder called ‘data’ in the same location as the .pde processing file. I used it to experiment with different images to discover what would happen.
PImage img; PImage sorted; int index = 0; void setup() { size(800, 400); img = loadImage("afbeelding.jpg"); sorted = createImage(img.width, img.height, RGB); sorted = img.get(); } void draw() { println(frameRate); sorted.loadPixels(); // Selection sort! for (int y = 0; y < sorted.height; y++) { float record = -1; int selectedPixel = index; for (int x = index; x < sorted.width; x++) { int loc = y * sorted.width + x; color pix = sorted.pixels[loc]; float b = brightness(pix); if (b > record) { selectedPixel = loc; record = b; } } // Swap selectedPixel with i color temp = sorted.pixels[y * sorted.width + index]; sorted.pixels[y * sorted.width + index] = sorted.pixels[selectedPixel]; sorted.pixels[selectedPixel] = temp; } if (index < sorted.width -1) { index++; } else { save("sorted.jpg"); frameRate(0); } sorted.updatePixels(); background(0); image(img, 0, 0); image(sorted, 400, 0); }
The code results in a pixel sorting visualisation:
The result was alright, only I had less control than I wanted to. I did research for some more codes and finally found one called 'Butter'. This code gave me a lot more control. I now had the option to sort the 'black', 'white' and 'bright' of the image.
Butter
\begin{lstlisting}
(function () {
var validModes = ['black', 'bright', 'white']; var defaultMode = validModes[0];
function Butter(mode, threshold) { this.mode = mode || defaultMode; if (validModes.indexOf(this.mode) === -1) { console.log('Butter has no mode called "' + this.mode + '".'); this.mode = defaultMode; }
// defaults this.threshold = { black: -10000000, white: -6000000, bright: 30 };
if (typeof threshold !== 'undefined' && threshold !== null) { this.threshold[this.mode] = threshold; } }
Butter.prototype.sort = function sort(canvas, iterations) { if (!canvas) { throw 'Butter needs a <canvas> to sort'; } var context = canvas.getContext('2d'), width = canvas.width, height = canvas.height, // Get the current data imageData = context.getImageData(0, 0, width, height), // And sort it sortedImage = this.sortImageData(imageData, width, height, iterations);
context.putImageData(sortedImage, 0, 0); };
Butter.prototype.sortImageData = function sortImageData(imageData, width, height, iterations) { this.imageData = imageData; this.width = width; this.height = height; iterations || (iterations = 1);
for (var i = 0; i < iterations; i++) {
for (var column = 0; column < this.width; column++) { this.sortColumn(column); }
for (var row = 0; row < this.height; row++) { this.sortRow(row); } }
return this.imageData; };
Butter.prototype.setThreshold = function setThreshold(value) { this.threshold[this.mode] = value; };
Butter.prototype.sortColumn = function sortColumn(x) { var ranges = this.getRangesForColumn(x), range, width, pixelData;
// For each range... for (var i = 0; i < ranges.length; i++) { range = ranges[i]; width = range.end - range.start;
pixelData = new Array(width);
// Get all the pixels in that range for (var j = 0; j < width; j++) { pixelData[j] = this.getPixelValue(x, range.start + j); }
// Sort them! pixelData.sort();
// And put the new pixels back for (var j = 0; j < width; j++) { this.setPixelValue(x, (range.start + j), pixelData[j]); } } };
Butter.prototype.sortRow = function sortRow(y) { var ranges = this.getRangesForRow(y), range, width, pixelData;
// For each range... for (var i = 0; i < ranges.length; i++) { range = ranges[i]; width = range.end - range.start;
pixelData = new Array(width);
// Get all the pixels in that range for (var j = 0; j < width; j++) { pixelData[j] = this.getPixelValue(range.start + j, y); }
// Sort them! pixelData.sort();
// And put the new pixels back for (var j = 0; j < width; j++) { this.setPixelValue((range.start + j), y, pixelData[j]); } } };
Butter.prototype.getRangesForColumn = function getRangesForColumn(x) { var ranges = [], start = 0, end = 0, findFirst, findNext;
switch(this.mode) { case 'black': findFirst = this.getFirstNotBlackY; findNext = this.getNextBlackY; break;
case 'bright': findFirst = this.getFirstBrightY; findNext = this.getNextDarkY; break;
case 'white': findFirst = this.getFirstNotWhiteY; findNext = this.getNextWhiteY; break; }
for ( ; end < this.height; start = (end + 1)) { start = findFirst.call(this, x, start); end = findNext.call(this, x, start);
// No more ranges if (start < 0 || start >= this.height) break;
ranges.push({start: start, end: end}); } return ranges; };
Butter.prototype.getRangesForRow = function getRangesForRow(y) { var ranges = [], start = 0, end = 0, findFirst, findNext;
switch(this.mode) { case 'black': findFirst = this.getFirstNotBlackX; findNext = this.getNextBlackX; break;
case 'bright': findFirst = this.getFirstBrightX; findNext = this.getNextDarkX; break;
case 'white': findFirst = this.getFirstNotWhiteX; findNext = this.getNextWhiteX; break; }
for ( ; end < this.width; start = (end + 1)) { start = findFirst.call(this, start, y); end = findNext.call(this, start, y);
// No more ranges if (start < 0 || start >= this.width) break;
ranges.push({start: start, end: end}); } return ranges; };
/* Finders */
Butter.prototype.getFirstNotBlackX = function getFirstNotBlackX(x, y) { // Loop until we find a match for ( ; this.getPixelValue(x, y) < this.threshold.black; x++) { // Oh no, we've reached the edge! if (x >= this.width) return -1; } // Return the match return x; }
Butter.prototype.getNextBlackX = function getNextBlackX(x, y) { // We want the _next_ one x += 1; for ( ; this.getPixelValue(x, y) > this.threshold.black; x++) { if (x >= this.width) return this.width - 1; } return x; }
Butter.prototype.getFirstBrightX = function getFirstBrightX(x, y) { for ( ; this.getPixelBrightness(x, y) < this.threshold.bright; x++) { if (x >= this.width) return -1; } return x; }
Butter.prototype.getNextDarkX = function getNextDarkX(x, y) { x += 1; for ( ; this.getPixelBrightness(x, y) > this.threshold.bright; x++) { if (x >= this.width) return this.width - 1; } return x; }
Butter.prototype.getFirstNotWhiteX = function getFirstNotWhiteX(x, y) { for ( ; this.getPixelValue(x, y) > this.threshold.white; x++) { if (x >= this.width) return -1; } return x; }
Butter.prototype.getNextWhiteX = function getNextWhiteX(x, y) { x += 1; for ( ; this.getPixelValue(x, y) < this.threshold.white; x++) { if (x >= this.width) return this.width - 1; } return x; }
Butter.prototype.getFirstNotBlackY = function getFirstNotBlackY(x, y) { for ( ; this.getPixelValue(x, y) < this.threshold.black; y++) { if (y >= this.height) return -1; } return y; }
Butter.prototype.getNextBlackY = function getNextBlackY(x, y) { y += 1; for ( ; this.getPixelValue(x, y) > this.threshold.black; y++) { if (y >= this.height) return this.height - 1; } return y; }
Butter.prototype.getFirstBrightY = function getFirstBrightY(x, y) { for ( ; this.getPixelBrightness(x, y) < this.threshold.bright; y++) { if (y >= this.height) return -1; } return y; }
Butter.prototype.getNextDarkY = function getNextDarkY(x, y) { y += 1; for ( ; this.getPixelBrightness(x, y) > this.threshold.bright; y++) { if (y >= this.height) return this.height - 1; } return y; }
Butter.prototype.getFirstNotWhiteY = function getFirstNotWhiteY(x, y) { for ( ; this.getPixelValue(x, y) > this.threshold.white; y++) { if (y >= this.height) return -1; } return y; }
Butter.prototype.getNextWhiteY = function getNextWhiteY(x, y) { y += 1; for ( ; this.getPixelValue(x, y) < this.threshold.white; y++) { if (y >= this.height) return this.height - 1; } return y; }
/* Utilities */
Butter.prototype.getPixelOffset = function getPixelOffset(x, y) { return (x + y * this.width) * 4; };
Butter.prototype.setPixelValue = function setPixelValue(x, y, val) { var offset = this.getPixelOffset(x, y), r = (val >> 16) & 255, g = (val >> 8) & 255, b = val & 255, data = this.imageData.data;
data[offset] = r; data[offset + 1] = g; data[offset + 2] = b; }
Butter.prototype.getPixelValue = function getPixelValue(x, y) { var offset = this.getPixelOffset(x, y), data = this.imageData.data, r = data[offset], g = data[offset + 1], b = data[offset + 2];
return ( ((255 << 8) | r) << 8 | g) << 8 | b; }
Butter.prototype.getPixelBrightness = function getPixelBrightness(x, y) { var offset = this.getPixelOffset(x, y), data = this.imageData.data, r = data[offset], g = data[offset + 1], b = data[offset + 2];
return Math.max(r, g, b) / 255 * 100; }
// Let it loose if ((typeof module !== "undefined") && (module.exports)) { module.exports = Butter; } else { this.Butter = Butter; }
This code gave me a lot more control. For instance with the old Apple universe background image:
Other Experiments
These are experiments i tried with simpler codes.
Sorted horizontally from dark to light colors.
Former Glory: Logo's & Icons
As I mentioned before, the result of pixel sorting reminds me of some distant memory, distorted over time. I want to recreate this feeling in a series of images.
First I needed something that counts as some sort of memory, something recognisable. Second, it had to be recognisable for a larger audience. So I had to find some sort of 'collective memory'. For this I chose to use iconic logo's of worldwide brands like McDonalds or Coca Cola. These brands are known worldwide and have been around for many years. Chances are high people will recognise them, even when the logos appear distorted.
Chanel
Sorted + Contact Sheet
Click to enlarge!!!
Coca Cola
Sorted + Contact Sheet
Click to enlarge!!!
Marlboro
Sorted + Contact Sheet
Click to enlarge!!!
Mcdonalds
Sorted + Contact Sheet
Click to enlarge!!!
Pepsi
Sorted + Contact Sheet
Click to enlarge!!!
Starbucks
Sorted + Contact Sheet
Click to enlarge!!!
Volkswagen
Sorted + Contact Sheet
Click to enlarge!!!