Sorting Filter

Fri, 08 Jun 2007

So here is a fun effect you can try at home. Take an image, break it up in to small squares (I used 5x5 pixels). For each of these squares, rearrange the pixels in that square so that they are in order of intensity from top left to bottom right. If you do this, you will get a 5x5 tiled pattern which looks ok, but hardly useful. So then if you repeat this process 25 more times, but take the squares from different starting positions each time, then you can get 25 of these tiled patterns where the tile boundaries don't line up at all. Mix all of these images together, and you have what I'm calling a sorting filter.

I didn't actually use intensity, I said that a (r1, g1, b1) preceeds (r2, g2, b2) if (r1r1 + g1g1 + b1b1) < (r2r2 + g2g2 + b2b2). I don't really know what that is called, but everyone likes to square things (I wanted to make strong colours beat medium greys... I don't know why).

So my implementation is super slow because I'm lazy (not because it is a complicated effect that needs to be CPU intensive) but here are two example images:

The actual result is very similar to a normal blur, but the shapes of objects change slightly when you use this method, which makes it a bit cooler.

A goat on a stick A filtered goat on a stick A friend jumping off a boat A filtered friend jumping off a boat

The C code used to generate this (so you can admire its inefficiency) is: (and one day I'll get around to giving some code that isn't so trivial that it deserves more than public domain (though I decided to include my name in this one, so I suppose I'm getting closer))

// Public domain
// by Andy Owen (http://www.ultra-premium.com/b)
#include <stdio.h>
#include <stdlib.h>
#include "image/image.h"

#define X_RADIUS (15)
#define Y_RADIUS (15)

static void swapPixels(Image img, int x1, int y1, int x2, int y2);
static Image mixImages(int numImages, Image *images);
static void sortArea(Image img, int left, int top, int w, int h);
static int rgbCompare(Rgb a, Rgb b);

int main(int argc, char* argv[]) {
    Image img = loadImage(argv[1]);
    int width = getWidth(img);
    int height = getHeight(img);

    Image copies[X_RADIUS * Y_RADIUS];

    int c = 0;
    for (int offX = 0; offX < X_RADIUS; offX++) {
        for (int offY = 0; offY < Y_RADIUS; offY++) {
            copies[c] = duplicateImage(img);        
            for (int y = offX; y < height + offX; y += Y_RADIUS) {
                for (int x = offY; x < width + offY; x += X_RADIUS) {
                    sortArea(copies[c], x, y, X_RADIUS, Y_RADIUS);
                }
            }
            c++;
        }
    }

    Image mix = mixImages(X_RADIUS * Y_RADIUS, copies);

    saveImage(mix, "copy.bmp");

    destroyImage(mix);
    for (int c = 0; c < (X_RADIUS * Y_RADIUS); c++) {
        destroyImage(copies[c]);
    }

    return 0;

}

static Image mixImages(int numImages, Image *images) {
    int width = getWidth(images[0]);
    int height = getHeight(images[1]);

    Image ret = createImage(width, height);

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            Rgb value;
            value.red = value.green = value.blue = 0;
            for (int i = 0; i < numImages; i++) {
                Rgb add = getPixelRgb(images[i], x, y);

                value.red += add.red;
                value.green += add.green;
                value.blue += add.blue;
            }
            value.red /= numImages;
            value.green /= numImages;
            value.blue /= numImages;
            setPixelRgb(ret, x, y, value);
        }               
    }

    return ret;
}

static void sortArea(Image img, int left, int top, int w, int h) {
    int imgWidth = getWidth(img);
    int imgHeight = getHeight(img);

    int sortOrder = 1;

    for (int y = top; y < top + h; y++) {
        for (int x = left; x < left + w; x++) {

            for (int y1 = top; y1 < top + h; y1++) {
                for (int x1 = left; x1 < left + w; x1++) {
                    if (rgbCompare(
                         getPixelRgb(img, x % imgWidth, y % imgHeight), 
                         getPixelRgb(img, x1 % imgWidth, y1 % imgHeight)) == sortOrder) {
                        swapPixels(img, x  % imgWidth, y  % imgHeight, 
                                        x1 % imgWidth, y1 % imgHeight);
                    }
                }
            }
        }
    }
}

static int rgbCompare(Rgb a, Rgb b) {
    int av2 = a.red * a.red + a.green * a.green + a.blue * a.blue;
    int bv2 = b.red * b.red + b.green * b.green + b.blue * b.blue;
    if (av2 < bv2) {
        return -1;
    }
    if (av2 > bv2) {
        return 1;
    }
    return 0;
}

static void swapPixels(Image img, int x1, int y1, int x2, int y2) {
    Rgb temp = getPixelRgb(img, x1, y1);
    setPixelRgb(img, x1, y1, getPixelRgb(img, x2, y2));
    setPixelRgb(img, x2, y2, temp);
}

The image library it is using is one of my own. It isn't quite ready to face the world. But you can just substitute your own in, the interface should be simple enough.

Name & email are optional. Email will not be obfuscated.
HTML tags will be removed except hyperlinks.
 

About

I'm a nerd living in Sydney. This is a place where I can write stuff about my interests and not care that no one else is reading.

I like music, maths, programming, pretty pictures, filters and other good things.

(more info)

It should be fairly obvious that this isn't connected to my employer at all.

Email me (not a catchpa)

Email policy

Subscribe

RSS Feed RSS

Get an aggregator

Liferea (Linux)

Vienna (OSX)

Feedreader (Windows)

Google Reader (Web based)

I've only used Liferea, so I can't vouch for the other ones.

About this site

This site runs a (modified) version of blosxom.

The host is GeekISP, and they seem to do an excellent job.