Filter Factory (FF) is a plug-in for Adobe Photoshop 3.0x and above created by Joe Ternasky. The main idea of FF is to let users create their own filters and effects with an internal programming language (which resembles C) and compile them as separate plug-in files.

In Ultimate Paint's Filter Factory dialog (under the *Image* menu) you can design Adobe Filter Factory compatible filters, load and save them, and in the final version make an UPC out of them.

Filter Factory provides easily programable formulas to process the red, green and blue channel of each pixel of the image. You simply write your code in the R, G and B boxes, and the program does the processing.

The following section will help you understand what pixels and RGB are.

Painting programs are pixel-based programs. Images edited here are usually scanned pictures or painted with different tools. Your image's width and height are always measured in pixels. E.g. a common image has the width of 320 pixels and the height of 240 pixels. The top left corner is the origin of the image (0, 0), while the lower right corner has the coordinates of (319, 239).

Each pixel contains a color information and all pixels have the same width / height. Color TVs, computer monitors and scanners work all with RGB (red, green and blue) values. All colors are mixed out of these basic ones using additive mixing. Some examples are:

- no colors added: results in black,
- red + green = yellow,
- red + blue = magenta,
- green + blue = cyan.

When Filter Factory is activated to modify/create your image, it will start by scanning each pixel, from left to right, from top to bottom. You will be creating an algorithm which will always depend on the pixel position. It does not matter if you use the actual pixel information (modifying your image) or overwrite it with another value (creating a new image). In FF you can't create loops, meaning you have to create a very compact code as one line. Multiple commands are not allowed either (with the exception of get and put).

As a color value of one pixel can only be between 0 and 255, if a code returns a number bigger than 255, it will be limited to 255, or if it's less than 0, will be limited to 0.

One very important thing is that FF always returns integers. FF is also case sensitive, meaning that x and X return two different values. You can include spaces and carriage returns without affecting the code. You will only get an error if you have a typo in your code.

Constants are numbers you can use in FF. Examples are -5, 2, 55, 1000, 10+5, 55/2, etc. If you like to work with hexadecimal values, you should prefix the numbers with 0x, for example 0xaa (176 decimal), 0xf65 (3941 decimal), etc.

Variables are numbers which constantly change in their value. When you use variables, they will return values. Following variables and their return values can be used in the FF-code:

r | returns the red channel value of the current pixel |

g | returns the green channel value of the current pixel |

b | returns the blue channel value of the current pixel
all of the above return a value from 0 to 255 |

c | returns the color value of the current pixel in the current channel, would be the same as typing either r, g or b in their respective coding areas or using src(x,y,z) (see later) |

x | returns the x-coordinate (horizontal position) of the current pixel. Can be an integer from 0 to X-1 |

y | returns the y-coordinate (vertical position) of the current pixel. Can be an integer from 0 to Y-1 |

X | returns the width of the current image. X can be an integer from 1 to 16384 |

Y | returns the image height. It can be an integer from 1 to 16384 |

i, u, v | return the i,u,v values of the current pixel in YUV space (which is a different color space than RGB). i can be an integer from 0 to 255, u can be an integer from -56 to 56, and v can be an integer from -78 to 78 |

z | returns the channel index of the current pixel in the code area. Can have a value between 0 and 2, depending in which code area this variable is used. 0 means red, 1 means green, while 2 means blue |

Z | returns the total amount of channels. This is 3 in UP (r, g and b) |

d | returns the angle (direction) of the current pixel from the center of the image. It can have a value from -511 to 512 (see Polar coordinates) |

dmin | returns 0 |

D | returns the total amount of angles within the image. It is always 1024 |

m | returns the magnitude (distance) of the current pixel from the center of the image. It can be an integer greater than 0 |

mmin | returns 0 |

M | returns the total amount of magnitudes in the image. It is an integer half the diagonal size of the image |

Following operators can be used in the FF-code:

+ | adds two numbers, variables |

- | subtracts second number from the first |

* | multiplication |

/ | division |

% | modulo. Dividing one number with another you always have a remainder of 0 or greater. The modulo operator returns this remainder. E.g. 10%3 would return 1 |

&& | logical AND (between two expressions). Returns 1 if both expressions are not equal to 0, or 0 in any other case |

|| | logical OR (between two expressions). Returns 1 if one or both expressions are not equal to 0, or 0 in other cases |

! | logical NOT (for use with one expression at the right) |

== | equals to (between two expressions) |

!= | not equal to (between two expressions) |

> | greater than (between two expressions) |

< | less than (between two expressions) |

>= | greater than or equal to (between two expressions) |

<= | less than or equal to (between two expressions) |

Logical operators can be best used with conditional operators. Example: r==5 will return 1 if the red pixel value equals 5, or will return 0 if the red pixel value does not equal 5.

& | bitwise AND (between two expressions) |

| | bitwise OR (between two expressions) |

^ | bitwise XOR (exclusive OR) (between two expressions) |

~ | bitwise NOT (before one expression) |

<< | shifts left (multiplies by 2) |

>> | shifts right (divides by 2) |

?: | question mark and colon |

- Example: r>120 ? r-50 : r+50

Above example will test the red value of the current pixel if it is greater than 120. If it is, the intensity of the red channel is lowered (r-50), if the value is less than or equal to 120, the intensity is pushed up by a value of 50.

- Nesting (condition in a condition) is allowed

r>120 ? (g<120 ? r+10 : r-10) : (g<120 ? r+20 : r-20)

If the red value of the current pixel is greater than 120, then the green channel is tested if less than 120. If it is not, then the red value is lowered by 10. This means that the amount changed in the red channel depends on the green channel value.

- More conditions

(r<120 && x>120) ? 50 : 100 is put in the green coding area

If the red channel value is less than 120 AND the pixel position is greater than 120, then the green channel value is set to 50, otherwise set the green channel value to 100.

The comma separates two or more expressions, evaluates both but returns the last one. This is mostly used when working with the get and put functions. One or more put-functions save the algorithm's return code into an internal memory, whereas the get-functions load the saved numbers into memory again.

For example, when part of the code is used many times, you can put the code into memory once and load it with the get function many times. This can speed up longer calculations significantly.

Now everything will get a bit more complicated (but more interesting, too). If you are a novice to FF, it is recommended to experiment with each function separately in order to fully understand what it does. We will try to explain each function in these sections and in the tutorial sections thoroughly, so you can start experimenting without doubts right away. The variables used here are only placeholders, it does not mean that the functions only work with them.

abs(a) |
returns the absolute value of a. If a=-50, a value of 50 is returned | |||

add(a,b,c) |
adds a and b together, compares the result with c and the lesser value is returned | |||

cnv(m11,m12,m13, m21,m22,m23, m31,m32,m33,d) |
the convolver is similar to the Custom Filter in Ultimate Paint. Each pixel and its eight neighboring pixels are multiplied with the proper m?? values to get a new result. The map looks like this:
m11 m12 m13
The target pixel is the one in the middle (m22). Each m??-value is multiplied with the pixels corresponding to the map (this is called weighting), all values are added together and divided by d (generally d is the total amount of the weights). E.g. cnv(0,1,0,1,4,1,0,1,0,8) blurs your picture, cnv(1,1,1,0,0,0,-1,-1,-1,1) neon kind of effectm21 m22 m23 m31 m32 m33 |
|||

ctl(i) |
returns the value of slider i (between 0 and 7): an integer between 0 and 255 | |||

dif(a,b) |
subtracts b from a and returns its absolute value | |||

get(i), put(v,i) |
FF has internally 256 cells, where you can store and request values. put(v,i) will evaluate the expression v and store the result in cell i. get will return the result in cell i.
As you can see, the image is mirrored horizontally and vertically, whereby the values are taken from the red channel and stored in cell 0. Finally, all code areas request the value of cell 0. Note that your image will turn grayscale (because only red channel values were stored, green and blue channel values were ignored) |
|||

max(a,b) |
returns the larger value, either a or b | |||

min(a,b) |
returns the lesser value, either a or b | |||

mix(a,b,n,d) |
calculates expression a*n/d + b*(d-n)/d. The two input values (a,b) are combined with the fraction n/d. If the fraction is close to 0, b is returned. If the fraction is close to 1, a is returned. If the fraction is close to 1/2, the average of a and b is returned | |||

rnd(a,b) |
returns a random number between a and b, where a<b | |||

scl(a,il,ih,ol,oh) |
the scale function maps the value a from an input range (il,ih) to an output range (ol,oh). scl(r,0,255,100,200) will evaluate the red channel value of the current pixel. According to the scale function, values near 0 will become 100 and values near 255 will become 200. Similarly, a value of 128 will turn into 150 | |||

sqr(x) |
returns the square root of x | |||

src(x,y,z) |
returns the channel pixel value in coordinates x,y in channel z, whereby the return value equals an integer between 0 and 255. src(400,200,c) will return the pixel value at 400/200 in the current channel and src(x,y,z) equals the variable c | |||

sub(a,b,c) |
difference between a and b (absolute value), compares the result with c and the greater value is returned | |||

val(i,a,b) |
returns the value of slider i (0..7) projected onto the interval [a,b] |

Before explaining the functions, you should have some knowledge on *polar coordinates*. As you know, *Cartesian coordinates* have the origin on the top-leftmost pixel of our image (0,0). The origin of *polar coordinates* is in the center of the image, which is (X/2, Y/2). *Cartesian coordinates* are defined by a horizontal position (*x*) and a vertical position (*y*). *Polar coordinates* are defined by an angle (*d* for direction) and a magnitude (*m*) (radius or distance from the origin, i.e. from the image center).

Notice that the angle *d* can be any positive or negative integer (0 inclusive). A value of 1024 means a full rotation, while multiples of 1024 (2048, 3072, 4096, etc.) mean multiple rotations. The magnitude *m* can be an integer from 0 to M (half the diagonal image size). There are ways to convert Cartesian into polar coordiantes and vice-versa.

This means we have the x and y coordinates and convert them to d and m, i.e. horizontal / vertical coordinates to direction / magnitude conversion. The length of the hypotenuse (distance or magnitude) is the square root of the sum of x squared and y squared. The angle (direction) is the arctan of y divided by x.

Now we have d and m and want to convert these to x and y. The cosine of the angle is the x-length divided by the magnitude. In order to get x, we multiply the cosine of the angle with the magnitude. The sine of the angle is the y-length divided by the magnitude. So in order to get y, we multiply the sine of the angle with the magnitude.

c2d(x,y) |
Returns the direction (angle) of the pixel at (x,y) from the top left corner (0,0). Mathematically, this is the arctan of y divided by x. Values returned are integers from -512 to 512 |

c2m(x,y) |
Returns the magnitude (distance) of the pixel at (x,y) from (0,0). Mathematically, this sums the squared values of x and y and returns the square root. The values returned are integers from 0 to M*2 |

cos(x) |
Remember trigonometry and terms like amplitude and wavelength? Let's picture a full wavelength from 0 to 1024. The value returned is an integer between -512 and 512. Cos(0) returns full amplitude (512), while cos(256) returns 0, cos(512) returns -512, cos(768) returns 0 again and cos(1024) returns 512. Values greater than 1024 start the whole thing all over again |

r2x(d,m) |
This returns the x-coordinate of polar coordinate (d,m) |

r2y(d,m) |
This returns the y-coordinate of polar coordinate (d,m) |

rad(d,m,z) |
This function resembles the src(x,y,z)-function in Cartesian coordinates. rad(d,m,z) returns the pixel value that is m pixels away at the direction of d from the origin (which is the center of the image here). Using rad(d,m,z) is the same as using src(x,y,z) or simply c |

sin(x) |
Works the same way as cos(x), returns an integer between -512 and 512. Sin(0) returns 0, sin(256) returns 512, sin(512) returns 0, sin(768) returns -512 and sin(1024) returns 0 |

tan(x) |
Works almost like cos(x) and sin(x), returning an integer between -167772 and 167772 (instead of going to infinity). tan(0) returns -6, tan(255) returns 167761, tan(256) returns -167772, tan(512) returns 6, and everything starts all over again |

Creating filters with one option is boring. Sliders were included to give user control over the input. There are eight sliders you can use in FF, they are refered to as controllers. You can access the values of the sliders with the function *ctl(i)*, where i is a value from 0 to 7 and the value returned is an integer between 0 and 255. So i=0 refers to the first slider, 1 to the second, while 7 to the eighth. You can also assign a name to each slider (left to the slider).

It seems that having slider values between 0 and 255 won't help much. However with the *val(i,a,b)* function, you can give slider i the range from a to b (b can also be lesser than a). Note that ctl(i) and val(i,a,b) are two different functions which returns slider values. E.g. ctl(2) will return an integer from 0 to 255, depending on the slider 2 setting, while val(2,-128,128) will map this value to the [-128, 128] interval.

While typing you might encounter a red exclamation mark beside the current field that appears / disappears. This mark tells you simply that the code you typed as of your last keypress has a syntax error. Check out the parentheses, commas, and the upper- or lowercase functions / variables.

Pressing *Ctrl-Z* will undo your last typing in the boxes. Pressing *Tab* will get you to the next, *Shift-Tab* to the previous text field. You can cut and paste text to and from the clipboard using *Shift-Del* and *Shift-Ins*.

There might also be some semantical errors in your code. Check the values in your code, for example ctl(20) won't work, as there is no slider 21.

Filter Factory is memory-hungry. You should have at least twice the virtual memory needed to store the image to be processed (well, this is also true for all UPCs). So if you have a memory problem make sure that the clipboard is empty, no snapshot is in memory and no other images are opened. If you still have problems you have to try to do the filtering in two or more parts.

The Filter Factory of UP limits the compiled code in 4 KB per channel. To work this around try to eliminate common sub-expressions (with the help of the get/put functions and the comma operator): evaluate parts of the expression that need to be calculated more than once, put them into anonymous variables (put) and then read them back when needed (get).

Filter Factory calculates with integer mathematics. This speeds calculations up, however it has some side effects. Especially on filters that use sine / cos / tan functions you will discover stairs that gives the processed image a jaggy look (it is also called aliasing). Unfortunately there is no way to solve this problem other than to apply a blur after applying the FF filter.

Also because of the integer arithmetic, always multiply first and divide only after that to save precision as fractions are lost after division operations.

The sliders do always show values between 0 and 255, no matter what your filter needs or what do you do inside your FF code. Also, the resolution can't be increased over 256 steps.

The preview window is at the top left corner of the dialog. Here you will immediately see all changes you make in the code or in slider values. This window is generally smaller than your image, which in some cases may result in quite different effect on the preview and original images. You can correct this by using the X, Y and M variables (see tutorial section on code optimization).

After writing your code don't forget to name the sliders used (all sliders that don't have a name will be excluded from the final compiled code, so their values cannot be controlled!). Use a name that will tell the user what the slider does (e.g. 'radius', 'line color', 'amplitude', etc.). These names can be typed into the input fields placed right to the preview window and left to the sliders themselves.

Before saving and/or compiling your filter also fill in the Category, Title, Author, Copyright fields.

Category is ignored in UP, but it is what you first see when you click on the Filter menu of most FF hosts. It can be a completely new name, a name where you can relate it to (blur filter, stylize, etc.).

Title is the actual name of the filter. Give a name that will tell a newbie right away what it does.

Author is the place of your signature or the signature of the one who created the filter.

Copyright is obvious. Type the copyright (if you are the author) or type the one suggested by the author.

These data will be saved along with the source.

Saving the code is fairly easy. Press the Save button and enter the name of the code data file in the save dialog that will appear (the extension will be .afs). It is adviseable to create a new directory (for example FFCODE or AFS) and save all data there. Although UP saves slider names, Category, Title, Author and Copyright texts in the .afs file, most of the FF host programs do not. Be careful not to lose these valuable information.

Loading the code is easier. Simply press the Load button and select the code data file. The channel data and all common information will be loaded.

First enter the filter data or load a previously stored filter. Check the syntax (should be no exclamation marks). Don't forget to name all sliders used (this tells UP that the component should display them). Finally press the Make Button. Now a save dialog is displayed. Just enter the directory you want to save the filter and type in a filename.

Now all you have to do is press either Cancel or OK. Pressing OK will create the component and save it. You must however, exit the program and restart it in order for it to recognize the new component(s).

The above sections are a shortened and slightly altered version of the *Filter Factory Programming Guide*. The complete version of this document can be located on the following Web page:

- Tatsuya Sasaki shows some interesting code dealing with FF.
- The Plugin Site

Filter Factory Programming Guide

Copyright © 1996-98 by Werner D. Streidt and Harald Heim.

Adobe Photoshop and Adobe Premiere are trademarks of Adobe Systems, Inc. All rights reserved.

No part of this tutorial may be reproduced in any form or by any means without written permission of the author. The authors of this document have used their best efforts in creating it. However, the author makes no warranties of any kind, express or implied, with regard to the documentation or filter codes contained. In no event shall the author be responsible or liable of any loss of profit or any commercial damages in connection with the use of this document or filter codes.

Back to Tutorials