Home | Download | Documents | Tips & Tutorials | Code Library | Help / User Group | Order

Cord of Life and stuff.

 

As you already noticed, FilterMeister allows us one (but not only) dramatic improvement compared to a well-known FF: instead of making just one pass from top left corner of the picture to bottom rignt corner, now we can do multipass algorithms. This simple ability brings us a whole new world of possibilities. Even simple algorithms, applied one-by-one many times, may lead to never-before seen results. Now you can "assemble" your long-term Photoshop experiense into one amazing filter!

Basically, a multipass algorithm consists of the following setps:
1) Setting buffer. Either surce image is copied to buffer or some "artificial" signal generated;
2) Processing buffer. The filter perform one or several action on a buffer, then storing the result back to buffer array and starting over;
3) Retrieving resulting values from buffer and setting the final image. Some additional post-processing can be made.

Below you will be shown an example of multipass filter utilizing a simple algorithm known as "Game of Life"

The starting idea is the following: imagine we have a grid with some amount of "cells" (say, white pixels over black background of the image); if a cell is surrounded by too many neighbours (say, 4 of 8 neighbour pixels are also white), it dies; if an empty grid cell is surrounded by, say, 3 non-empty living cells, a new living cell appear.

Oh well, I must say I don't like these rules (along with many others <g>). First and foremost, they are describing cells that are either living or dead, either on or off. Since we deal graphics here, that means black and white pictures only. This is the first thing I would like to change: let image to be a greyscale or even truecolor, each cell having a "value" (age? strength?) in {0..255} range instead of {0..1}.

Here goes the sample of algorithm described. For easy reading the filter code fragments are followed by explanations; do not paste explanations into FM window! Just get the filter code and open it with your copy of FM.

%ffp

Category: "FM Tutorial"
Title: "Life"
Author: "Ilyich the Toad"

Dialog:color=lightgrey

ctl[0]:"Iterations",range=(0,500),val=5,pagesize=5,linesize=1, fontcolor=black
ctl[1]:"Threshold Position", range=(0,200),val=200,pagesize=5,linesize=1, fontcolor=black
ctl[2]:"Threshold Width", range=(0,128),val=100,pagesize=2,linesize=1, fontcolor=black
ctl[3]:CHECKBOX, "Animated Preview", val=1, pos=(200,100), size=(100,10), fontcolor=black
ctl[4]:"Radius",range=(1,scaleFactor*X/2),val=1,pagesize=3,linesize=1, fontcolor=black

 

Brief explanations for controls: both "Threshold" sliders control what total neighbours weight cause cell die and how close to that splitting threshold is. "Radius" control sets the size of the kernel at which the neighbours are counted.


ForEveryTile:
{

int iterations; int die; int grow;
int i;
int neigh;
int xstart; int ystart;
int xstop; int ystop;
int xrange=max((2*ctl(4)+scaleFactor)/(2*scaleFactor), 1);
int yrange=max((2*ctl(4)+scaleFactor)/(2*scaleFactor), 1);
int xlook; int ylook; int zlook;
int temp; int randx; int randy;
bool anim;
int kernelwidth=2*xrange+1; int kernelheight=2*yrange+1; int value;
int currentx; int currenty; int total;

setCtlRange(0,0,500); setCtlRange(1,0,255); setCtlRange(2,0,255);
iterations=ctl(0);

 

Variables definition and controls setting.

die = ctl(1)/3+ctl(2)/2; grow = ctl(1)/3-ctl(2)/2;

 

Threshold settings are converted to "die if neighbours >" and "split if neighbours >" parameters. Those might be set directly, simplyfying controls definition - but I find this way more logical; besides, it prevents user from setting cell die defore it can split and so on.


setCtlRange(4,1,5);

anim=ctl(3);

 

Finishing settings.


for (y=0; y for (x=0; x < X; x++) {
for (z= 0; z < Z; z++) {
tset(x,y,z,src(x,y,z));
t2set(x,y,z,src(x,y,z));
}
}
};

 

Copying source image into buffer.


for (i=0; i<iterations; i++) { if(updateProgress(i,iterations)) break;

 

Preparing outer loop. Image passes will be made inside this loop.


for (y=0; y < Y; y++) {
for (x=0; x < X; x++) {
xstart=x-xrange; xstop=x+xrange; ystart=y-yrange; ystop=y+yrange;
randx=rnd(-1,1); randy=rnd(-1,1);
for (z= 0; z < 3; z++) {
temp = tget(x,y,z);
if (temp>0) { //is there anybody in there?
neigh=0;
for (ylook=ystart; ylook < ystop; ylook++) {
for (xlook=xstart; xlook < xstop; xlook++) {
for (zlook=0; zlook < 3; zlook++) {
neigh+=tget(xlook,ylook,zlook); //sum a brightness
} //for zlook
} //for xlook
} //for ylook

neigh=neigh/((2*xrange+1)*(2*yrange+1));
neigh=neigh/3;

 

Calculating total neighbour pixel brightness (namely, sum of all three RGB channes). This way we make filter to work "hue-protected". Once the total brightness (weight) is calculated, we proceed to make a decision: should the cell die or split or continue living.


if (neigh>die) {
t2set(x,y,z,0);}

 

Die by too many neighbours.


else if (neigh>grow) {
t2set((x-randx+X)%X,(y-randy+Y)%Y,z,temp);
t2set((x+randx+X)%X,(y+randy+Y)%Y,z,temp);
t2set(x,y,z,0);}

 

Splitting a cell into two new, randomly placed nearby. Take notice, the filter wrap image edges!


else t2set(x,y,z,0) ;

 

Die of weakness...


} //if empty
} //for z
} //fox x
} //for y

for (y=0; y < Y; y++) {
for (x=0; x < X; x++) {
for (z= 0; z < Z; z++) {
tset(x,y,z,t2get(x,y,z));
}
}
};

 

During calculating new pixels generation, we had to place them into another array called "t2". Otherwise every new pixel placed below or right during cell splitting get overwritten while processing next pixels. After finishing calculating all the "t2" array is copied to "t" array.


if (doingProxy && anim > 0) {
for (y=0; y for (x=0; x for (z= 0; z pset(x,y,z,tget(x,y,z));
}
}
}
updatePreview(-99);
};


}//for i

 

If the preview was set by user to be updated as filter works (user-defined boolean variable "anim" set to "true") and filter is applied to preview image, not to final image (doingProxy=true), the "t" array is copied back to source image and preview updated. Then we close the outer loop.


if (doingProxy * anim == 0) {

for (y=0; y < Y; y++) {
for (x=0; x < X; x++) {
for (z= 0; z < Z; z++) {
pset(x,y,z,tget(x,y,z));
}
}
}
}

updateProgress(0, 100);
true;
}

 

Finished processing, just copy the "t" array back to the image, reset progress indicator and finish the filter.

Animated, it should look something like this:

sample animation

So, I have a "last words" about it.

The preview is incorrect on any zoom except for 1:1. The reason is that splitting cells is performed within +/- 1 pixel range. While calculating the preview, it is equal to 1 pixel also since such a value just can't get scaled down. This is unavoidable - sorry, folks. Maybe you find a better solution...

 

Home | Download | Documents | Tips & Tutorials | Code Library | Help / User Group | Order