1. Simulating program solutions

In the Overview section you've learned what Filter Factory can do, what values it will return and its limitations. On the following pages solutions are given by explaining the objective. Then the code is given to each channel. Should you encounter the format: r,g,b: then you simply have to type the code in all three coding areas, r, g and b. If a slider is used, its function is mentioned after the code.
An example picture is also given for each code to show the result.

In this section we will try to simulate functions already built in. This might give you a better understanding of basic image data manipulation. Our example picture:

Invert the image

r: 255-r
g: 255-g
b: 255-b

we could also use
r,g,b: 255-c

Mirror the image horizontally

r,g,b: src(X-x-1,y,z)

Mirror the image vertically

r,g,b: src(x,Y-y-1,z)

Rotate the image 90 degrees clockwise

Attention: works properly only on squared images!

r,g,b: src(y,X-x-1,z)

Rotate the image 90 degrees counter-clockwise

Attention: works properly only on squared images!

r,g,b: src(Y-y-1,x,z)

Rotate the image 180 degrees

r,g,b: src(X-x-1,Y-y-1,z)

Desaturate the image

(turns it into RGB-grayscale)

r: put((76*r+150*g+29*b)/256,0),get(0)
g,b: get(0)

or simply:
r,g,b: i

2. Creating blends (gradient fills)

Linear blends

Please work with a 512x512 picture for this tutorial. When examining our pixel values in a blend, we see that the values are consecutive. Let's say, we have a grayscale image and we want a horizontal blend from black (0) to white (255). We could use the pixel positions (x,y) in order to create a pixel value for a blend. Let's create a black to white blend in an RGB image:

r,g,b: x

Problem is, if our image or selection width is greater or less than 256, then we would get a not quite complete blend. So, we need also the image's width information (X). In order to create a smooth blend from left to right in any image, we would use this code:

r,g,b: x*255/X

or

r,g,b: scl(x,0,X-1,0,255)


Now, let's say we need multiple blends. A way to do it is with the MODULO operator:

r,g,b: x%256

While the pixel position rises, we get consecutive numbers... 0, 1, 2, 3, ....254, 255, and now it starts all over again... 0, 1, 2, 3, 4, 5... Thus, we have two (512/256) black to white blends in our image. Do we want more blends?

r,g,b: x%128*2

Now we have four blends (512/128), but because the maximum value the modulo operator can return is 127, we have to multiply it with 2 (127*2=254), so we can see a nice contrast blend. You could also use following code:

r,g,b: scl(x%128,0,127,0,255)

If you watch the maths while doubling the amount of blends, you would notice following rule:

x % (256 / no of blends ) * no of blends

The code where we control the amount of blends with slider 0:

r,g,b: x%(256/ctl(0))*ctl(0)

Now, if you want - with one filter - be able to create either horizontal or vertical blends, use slider 1 to control the blend movement:

r,g,b: ctl(1)<128 ? x%(256/ctl(0))*ctl(0) : y%(256/ctl(0))*ctl(0)

The problem is that the amount of blends is not the value selected in slider 0. Now we also need the image's width (X). Remember, we used the following code for a blend in an image any size:

x*256/X

Now we want two blends. If we multiplied the code with 2 (result would be a value from 0 to 512) and use here our modulo operator (so we can have blends from 0 to 255):

r,g,b: (x * 512 / X) % 256

If we want four blends, we would need the following code:

r,g,b: (x * 1024 / X) % 256


The user can also use slider 0 to input his desired number of blends:

r,g,b: (x * 256 * ctl(0) / X ) % 256

If you want white-black blends instead of black-white, use

r,g,b: 255-(x*256*ctl(0)/X)%256

Again, if you need the possibility of selecting either horizontal or vertical blends, use slider 1:

r,g,b: ctl(1)<128 ? (x * 256 * ctl(0) / X ) % 256 : (y * 256 * ctl(0) / Y ) % 256

where
ctl(0) is the exact amount of blends,
ctl(1) is the selector of horizontal / vertical blending.

The following one combines everything together:

Name: Blender (V/H)
r,g,b: (ctl(1)<128)?
(ctl(2)<128?(x*ctl(0)*256/X)%256:255-(x*ctl(0)*256/X)%256):
(ctl(2)<128?(y*ctl(0)*256/Y)%256:255-(y*ctl(0)*256/Y)%256)

where
ctl(0) exact amount of blends,
ctl(1) horizontal/vertical blending,
ctl(2) black-white / white-black blending.

It's nice to have these blends, but they are all black & white. We can dispose now of the black-white / white-black blending method, leaving a black-white blending only:

r,g,b: ctl(1)<128 ? (x*ctl(0)*256/X)%256 : (y*ctl(0)*256/Y)%256

where
ctl(0) exact amount of blends,
ctl(1) horizontal/vertical blending.

We will use the 6 available sliders for our blend coloring. In order to do that, we will use the scl(a,il,ih,ol,oh)-function. We know that the resulting values are integers between 0 (il) and 255 (ih). If you examine the red channel code, you'll see that the lowest value is turned into the slider 2 value and the highest value is turned into the slider 3 value.

Category: Neology
Title: HV-Blender
Copyright: (c) 1996 by Werner D. Streidt 100526.16@compuserve.com
Author: Werner D. Streidt
r: scl(ctl(1)<128?(x*ctl(0)*256/X)%256 : (y*ctl(0)*256/Y)%256,0,255,ctl(2),ctl(3))
g: scl(ctl(1)<128?(x*ctl(0)*256/X)%256 : (y*ctl(0)*256/Y)%256,0,255,ctl(4),ctl(5))
b: scl(ctl(1)<128?(x*ctl(0)*256/X)%256 : (y*ctl(0)*256/Y)%256,0,255,ctl(6),ctl(7))

where
ctl(0) No. of blends,
ctl(1) horizontal / vertical blending,
ctl(2) red amount of starting blend value,
ctl(3) red amount of ending blend value,
ctl(4) green amount of starting blend value,
ctl(5) green amount of ending blend value,
ctl(6) blue amount of starting blend value,
ctl(7) blue amount of ending blend value.

Radial blending

Remember, there is a variable called magnitude m (or distance from the image / selection center). Here we will use a 512 x 512 picture again.

r,g,b: m

This will give us a radial blend from the black in the center to white outwards. Let's use multiple blends with m % 256, but as we can't see much, let's give it more blends:

r,g,b: m * 8 % 256

And because it's so cool using sliders for blend amount and coloring, let's jump right into it:

Category: Neology
Title: Rad-Blender
Copyright: (c) 1996 by Werner D. Streidt 100526.16@compuserve.com
Author: Werner D. Streidt
r: scl(m*ctl(0)%256,0,255,ctl(2),ctl(3))
g: scl(m*ctl(0)%256,0,255,ctl(4),ctl(5))
b: scl(m*ctl(0)%256,0,255,ctl(6),ctl(7))

where
ctl(0) Blend amount,
ctl(2) red amount of starting blend value,
ctl(3) red amount of ending blend value,
ctl(4) green amount of starting blend value,
ctl(5) green amount of ending blend value,
ctl(6) blue amount of starting blend value,
ctl(7) blue amount of ending blend value.

Rotational blending

To have the blends rotate around the center like a helicopter propeller, we have to work with the angle (direction) d. Problem is, d returns values from -512 to 512, so we can scale it from 0 to 1024. Then we use our modulo %256 and finished.

r,g,b: scl(d,-512,512,0,1024) % 256

To make a user controlled amount of blends, we'll have to change the last term in the scl function. The above function gives us four blends. So, it should read as follows:

r,g,b: scl(d,-512,512,0,ctl(0)*256)%256

And because it's so nice using sliders for blend amount and coloring...

Category: Neology
Title: Rot-Blender
Copyright: (c) 1996 by Werner D. Streidt 100526.16@compuserve.com
Author: Werner D. Streidt
r: scl(scl(d,-512,512,0,ctl(0)*256)%256,0,255,ctl(2),ctl(3))
g: scl(scl(d,-512,512,0,ctl(0)*256)%256,0,255,ctl(4),ctl(5))
b: scl(scl(d,-512,512,0,ctl(0)*256)%256,0,255,ctl(6),ctl(7))

where
ctl(0) Blend amount,
ctl(2) red amount of starting blend value,
ctl(3) red amount of ending blend value,
ctl(4) green amount of starting blend value,
ctl(5) green amount of ending blend value,
ctl(6) blue amount of starting blend value,
ctl(7) blue amount of ending blend value.

3. Creating checkered tiling

Imagine 2x2 squares, each of them is 5 pixels wide and 5 pixels high. There are two different squares: white and black ones. The first line (y=0) of this image gives us 255 (white) for the first 5 pixels (0<=x<5), and 0 (black) for the last 5 pixels (5<=x<10).
Let's take an expression x%10, which returns the following values as we move from pixel to pixel on a larger image:
0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,....
We would like to fill the pixels with white and black. This can be done with the following code

r,g,b: (x % 10) < 5 ? 0 : 255

We can also work with boolean maths. For (x%10)<5 Filter Factory will return 1 if it's true, 0 if false. Multiplying it with 255 will return values either 0 or 255:

((x%10) < 5) * 255

The vertical code would look like the same but only with y instead of x. How to combine both? We use the exclusive OR (XOR) operator in our expression:

((x % 10) < 5) ^ ((y % 10) < 5)

Now the whole thing multiplied with 255 returns the values 0 or 255:

(((x % 10) < 5) ^ ((y % 10) < 5)) * 255


What if we want the first square to be white and not black? There are two possible solutions. The easy one:

(((x % 10) >= 5) ^ ((y % 10) < 5)) * 255

and the boolean one:

(!((x % 10) < 5) ^ ((y % 10) < 5)) * 255

Sliders representing pixel values

Let's say we would like to use sliders to tell Filter Factory how thick each checker cell is (in pixels). In our code

((x % 10) < 5)

the first number represents the width of two cells and the second number the width of one cell. Thus, our code with a slider would look like this:

(!((x % (ctl(0)*2)) < ctl(0)) ^ ((y % (ctl(0)*2)) < ctl(0))) * 255

Note: There is also a radial kind of checker tiling you may want to try:

(!((m % (ctl(0)*2)) < ctl(0)) ^ (((d+512) % (ctl(0)*2)) < ctl(0))) * 255


Sliders representing the total amount of cells in a row / column

This one's a bit tougher. The slider setting has to be converted into a cell width / height. Thus, dividing the width (X) of the image with the number of cells (ctl(0)) would result in the following code: X/ctl(0). Let's say the image is 512 pixels wide and we have a slider setting of 8. X/ctl(0)=512/8=64. Each cell is 64 pixels wide. The complete code would be:

Category: Neology
Title: Checkered Tiling
Copyright: (c) 1996 by Werner D. Streidt 100526.16@compuserve.com
Author: Werner D. Streidt
r,g,b: (!((x %( X/ctl(0)*2)) < (X/ctl(0))) ^ ((y % (Y/ctl(0)*2) ) < (Y/ctl(0))))* 255

where
ctl(0) is the number of cells in a row / column.

Note: You'll notice that sometimes you'll get partial cells at the right / bottom. This is due to the fact that the image's width and/or height is not a multiple of the slider setting. To visualize this problem create a new image with 500x500 pixels and another one with 512x512 pixels. Use the filter with a setting of 8. The partial cells will appear on the 500x500 image, because 500 divided by 8 results in 62.5.

4. Adding borders to your images

There are different possibilities for creating interesting borders in your images. Let's start with a very simple task of creating a black border that is 5 pixels wide. Since our border has four sides, we have to check the current position while scanning the image:

r,g,b: (x<5 || x>X-6 || y<5 || y>Y-6) ? 0 : c

The code says: IF we are on an x-position less than 5 (0, 1, 2, 3, 4) OR on an x-position greater than X-6 (e.g. in a 20x30 image this would be: 15, 16, 17, 18, 19) OR on a y-position less than 5 OR on a y-position greater than Y-6 (24) THEN fill these pixels with black (0) ELSE use the original pixel value (c).

Let's add a slider for the border thickness (notice that the parentheses before the question mark aren't needed; this is because all || (OR) are of higher priority than the ?):

r,g,b: x<ctl(0) || x>X-1-ctl(0) || y<ctl(0) || y>Y-ctl(0)-1 ? 0 : c

where
ctl(0): Border thickness (in pixels)

An interesting feature would be to invert the border and reuse the filter with different border thickness. The code to invert the border would look like this:

r,g,b: x<ctl(0) || x>X-1-ctl(0) || y<ctl(0) || y>Y-ctl(0)-1 ? 255-c : c

Back to our original code, black is nice but boring, users need colors, so we will use sliders:

r,g,b: x<ctl(0) || x>X-1-ctl(0) || y<ctl(0) || y>Y-ctl(0)-1 ? ctl(z+1) : c

where
ctl(0): Border thickness (in pixels)
ctl(1): Red value for border
ctl(2): Green value for border
ctl(3): Blue value for border

Notice how we used ctl(z+1). The slider value depends on the channel value.

5. Casting a shadow

The idea is to create a shadow on the left and top of the image or selection and move the image data (in the selection) by a certain amount of pixels. The effect would be like having two equal images, one above the other, while the top one has a cut out area, so you can view the image beneath.
To achieve this, we have to apply four different codes:

  1. applying the left shadow,
  2. applying the top shadow,
  3. applying no shadow,
  4. applying both shadows (top + left).
And on all of them, we should move the pixels by a slider value, which will be the amount of shadow.

To make things easier, let the shadow be 10 pixels wide. The first thing we do is to test whether we're in section c (where should be no shadow applied):

(x>9 && y>9) ? c : (cont.)

if so, we use the current pixel value, or ELSE... we test whether we're in section a (apply the left shadow):

(x<10 && y>9) ? ... : (cont.)

if so, we will multiply the blend with the pixel information and divide it by 255. Because x is between 0 and 9 here, we have to use the scale function:

scl(x,0,9,100,255) * c / 255

The above code will create a 10 pixel wide blend from 100 to 255 in the first ten pixels while using image data as well. So the complete code for section a looks like

(x<10 && y>9 ) ? scl(x,0,9,100,255) * c/255 : (cont.)

Now let's check section b (apply the top shadow), which is the same as a, only we use the y coordinates:

(x>9 && y<10) ? scl(y,0,9,100,255) * c/255 : (cont.)

The last part section d (apply both shadows) has to combine both shadow blends and the image data underneath. The function we use for this is min(x,y), which will return the lower value:

min(scl(x,0,9,100,255),scl(y,0,9,100,255))

Combined with the image data:

min(scl(x,0,9,100,255),scl(y,0,9,100,255))*c/255

Now we got all parts, all we have to do is combine them all:

(x>9 && y>9) ? c :
(x<10 && y>9 ) ? scl(x,0,9,100,255) * c/255 :
(x>9 && y<10 ) ? scl(y,0,9,100,255) * c/255 :
min(scl(x,0,9,100,255),scl(y,0,9,100,255))*c/255

If you use 0 instead of 100 for creating the shadow, you'll get a strong shadow. If you use a greater number, the shadow will be softer. (Maybe you should let the user manipulate it with a slider).
All we need now is to add the slider to specify the shadow's width, which will also offset the image or selection by the specified amount. The code to offset the image data would be to change all c variables (remember, using c is the same as using src(x,y,z)) to src(x-ctl(0),y-ctl(0),z).
So here's the complete code:

r,g,b: (x>ctl(0)-1 && y>ctl(0)-1) ? src(x-ctl(0),y-ctl(0),z) :
(x<ctl(0) && y>ctl(0)-1 ) ? scl(x,0,ctl(0)-1,100,255) * src(x-ctl(0),y-ctl(0),z)/255 :
(x>ctl(0)-1 && y<ctl(0) ) ? scl(y,0,ctl(0)-1,100,255) * src(x-ctl(0),y-ctl(0),z)/255 :
min(scl(x,0,ctl(0)-1,100,255),scl(y,0,ctl(0)-1,100,255))*src(x-ctl(0),y-ctl(0),z)/255

where
ctl(0) is the shadow width.

Now you can change the blending for each color:

Category: Neology
Title: Shadowcaster
Copyright: (c) 1997 by Werner D. Streidt 100526.16@compuserve.com
Author: Werner D. Streidt

r: (x>ctl(0)-1 && y>ctl(0)-1) ? src(x-ctl(0),y-ctl(0),z) :
(x<ctl(0) && y>ctl(0)-1) ? scl(x,0,ctl(0)-1,ctl(1),255) * src(x-ctl(0),y-ctl(0),z)/255 :
(x>ctl(0)-1 && y<ctl(0)) ? scl(y,0,ctl(0)-1,ctl(1),255) * src(x-ctl(0),y-ctl(0),z)/255 :
min(scl(x,0,ctl(0)-1,ctl(1),255),scl(y,0,ctl(0)-1,ctl(1),255))*src(x-ctl(0),y-ctl(0),z)/255

g: (x>ctl(0)-1 && y>ctl(0)-1) ? src(x-ctl(0),y-ctl(0),z) :
(x<ctl(0) && y>ctl(0)-1) ? scl(x,0,ctl(0)-1,ctl(2),255) * src(x-ctl(0),y-ctl(0),z)/255 :
(x>ctl(0)-1 && y<ctl(0)) ? scl(y,0,ctl(0)-1,ctl(2),255) * src(x-ctl(0),y-ctl(0),z)/255 :
min(scl(x,0,ctl(0)-1,ctl(2),255),scl(y,0,ctl(0)-1,ctl(2),255))*src(x-ctl(0),y-ctl(0),z)/255

b: (x>ctl(0)-1 && y>ctl(0)-1) ? src(x-ctl(0),y-ctl(0),z) :
(x<ctl(0) && y>ctl(0)-1) ? scl(x,0,ctl(0)-1,ctl(3),255) * src(x-ctl(0),y-ctl(0),z)/255 :
(x>ctl(0)-1 && y<ctl(0)) ? scl(y,0,ctl(0)-1,ctl(3),255) * src(x-ctl(0),y-ctl(0),z)/255 :
min(scl(x,0,ctl(0)-1,ctl(3),255),scl(y,0,ctl(0)-1,ctl(3),255))*src(x-ctl(0),y-ctl(0),z)/255

where
ctl(0) Shadow width,
ctl(1) Red intensity,
ctl(2) Green intensity,
ctl(3) Blue intensity.

6. Mosaic filter

To get the desired effect we need the modulo operator and the source function:

r,g,b: src(x-x%10,y-y%10,z)

The above code would result in 10x10 pixel blocks. The source of these blocks is always the pixel in their top-left corner. While we are moving further away from it, the modulo helps us still getting the first pixel (in the 10x10 block) as the source.

Now, we want to give the user more freedom. Let's add a slider that determines the block size:

r,g,b: src(x-x%ctl(0),y-y%ctl(0),z)

Of course, you could use another idependent slider for the block height.

Let's say we want to add a border around each block. We would have to check if a certain value is reached by x and y:

r,g,b: (x%ctl(0)==0 || y%ctl(0)==0) ? 0: src(x-x%ctl(0),y-y%ctl(0),z)

A nice feature would be adding a border color with slider values (changing the 0 to ctl(z+1)):

r,g,b: (x%ctl(0)==0 || y%ctl(0)==0) ? ctl(z+1): src(x-x%ctl(0),y-y%ctl(0),z)

where
ctl(0) is the mosaic block size,
ctl(1) is the border color (red),
ctl(2) is the border color (green),
ctl(3) is the border color (blue).

7. Weaving the image

Weaving a picture is not so complex as it sounds. A weave pattern is a neverending pattern on the image, so we can work on its smallest possible part, a 2x2 area.

AB
CD

Tiles A and D are equal, inside them we create a vertical black-white-black blend. Tiles B and C are horizontal black-white-black blends. Let's say each tile is 8 pixels wide and 8 pixels high, giving us a rendering area of 16 x 16. First we should check whether the current coordinates are in area A or D. This is done with expression

(x<8 && y<8) || (x>7 && y>7)

We have to create a vertical black-white-black blend into the areas A and D, so because it is composed by two blends (black to white and white to black), we have to check again if we're already passing half the tile height. In our case the tile height equals 8. The blend is created with the modulo operator. The highest value in our case is 4, so in order to get a good blend from black to white, we have to multiply it with 64. 64*4 = 256:

y%8 < 4 ? (y%8+1)*64 : (8-y%8)*64

The first modulo operator returns 1, 2, 3 and 4, which we multiply with 64. The second modulo operator is subtracted from 8, then is also multiplied with 64. In both cases, Filter Factory returns values 64, 128, 192, and 256 (which will be limited to 255). The same is done with the horizontal values. So we first check if we are dealing with areas A and D and apply the vertical blending if necessary, otherwise apply a horizontal blending.
We also included two put-functions, to keep common values in memory.

r: put(x%16,0), put(y%16,1),
(get(0)<8 && get(1)<8) || (get(0)>7 && get(1)>7)?
(y%8)<4?(y%8+1)*64:(8-y%8)*64:
(x%8)<4?(x%8+1)*64:(8-x%8)*64

g,b: (get(0)<8 && get(1)<8) || (get(0)>7 && get(1)>7)?
(y%8)<4?(y%8+1)*64:(8-y%8)*64:
(x%8)<4?(x%8+1)*64:(8-x%8)*64

Let's use a slider to tell Filter Factory which tile size we want. We have to change all numerical values above to the slider value. In the above example, the tile size was 8x8. Every 8 has to be changed to ctl(0), every 7 to (ctl(0)-1), 16 into (ctl(0)*2) and 64 into (256*2/ctl(0)). So the code will look like this:

r: put(x%(ctl(0)*2),0), put(y%(ctl(0)*2),1),
(get(0)<ctl(0) && get(1)<ctl(0)) || (get(0)>(ctl(0)-1)&&get(1)>(ctl(0)-1))?
(y%ctl(0))<(ctl(0)/2)?(y%ctl(0)+1)*(256*2/ctl(0)):(ctl(0)-y%ctl(0))*(256*2/ctl(0)):
(x%ctl(0))<(ctl(0)/2)?(x%ctl(0)+1)*(256*2/ctl(0)):(ctl(0)-x%ctl(0))*(256*2/ctl(0))

g,b: (get(0)<ctl(0) && get(1)<ctl(0)) || (get(0)>(ctl(0)-1)&&get(1)>(ctl(0)-1))?
(y%ctl(0))<(ctl(0)/2)?(y%ctl(0)+1)*(256*2/ctl(0)):(ctl(0)-y%ctl(0))*(256*2/ctl(0)):
(x%ctl(0))<(ctl(0)/2)?(x%ctl(0)+1)*(256*2/ctl(0)):(ctl(0)-x%ctl(0))*(256*2/ctl(0))

where
ctl(0) is the tile size.

It's okay, but the blend should not go further as the center of the tile. So with another slider we can control how deep the blend is. You can call it fall-off control. The problem is that we have three different areas in a tile: it starts with a small black-white blend, stays white a while and finally there's a white-black blend. So the second part of the blend's code has to check whether we're in a non-blend area or in the white-black area. The reason we're doing this will be clear when we get to the ultimate version of this filter, where blends are combined with the image underneath creating a weave pattern over the image. Let's check the vertical blend:

(y%ctl(0)) < (ctl(0)/ctl(1)) ? (y%ctl(0)+1)*(256*ctl(1)/ctl(0)) : (y%ctl(0) > (ctl(0)-1-ctl(0)/ctl(1))) ? (ctl(0)-y%ctl(0))*(256*ctl(1)/ctl(0)) : 255

Slider 0 is the tile size, slider 1 is the brightness fall-off. The code before the question mark checks if we're within the first boundary. If so, the black-white blend is applied. If not, we check whether we're within the second area. Here the white-black blend is applied. If not, the pixel is colored white (255). Now the whole code looks:

r: put(x%(ctl(0)*2),0), put(y%(ctl(0)*2),1),
(get(0)<ctl(0) && get(1)<ctl(0)) || (get(0)>(ctl(0)-1)&&get(1)>(ctl(0)-1))?
(y%ctl(0))<(ctl(0)/ctl(1))?(y%ctl(0)+1)*(256*ctl(1)/ctl(0)):(y%ctl(0)>(ctl(0)-1-ctl(0)/ctl(1)))?(ctl(0)-y%ctl(0))*(256*ctl(1)/ctl(0)):255:
(x%ctl(0))<(ctl(0)/ctl(1))?(x%ctl(0)+1)*(256*ctl(1)/ctl(0)):(x%ctl(0)>(ctl(0)-1-ctl(0)/ctl(1)))?(ctl(0)-x%ctl(0))*(256*ctl(1)/ctl(0)):255

g,b: (get(0)<ctl(0) && get(1)<ctl(0)) || (get(0)>(ctl(0)-1)&&get(1)>(ctl(0)-1))?
(y%ctl(0))<(ctl(0)/ctl(1))?(y%ctl(0)+1)*(256*ctl(1)/ctl(0)):(y%ctl(0)>(ctl(0)-1-ctl(0)/ctl(1)))?(ctl(0)-y%ctl(0))*(256*ctl(1)/ctl(0)):255:
(x%ctl(0))<(ctl(0)/ctl(1))?(x%ctl(0)+1)*(256*ctl(1)/ctl(0)):(x%ctl(0)>(ctl(0)-1-ctl(0)/ctl(1)))?(ctl(0)-x%ctl(0))*(256*ctl(1)/ctl(0)):255

where
ctl(0) is the tile size,
ctl(1) is the brightness.

Slider values greater than 20 for slider 1 won't change much, so we changed all ctl(1) into val(1,0,20) limiting it's maximum in 20.

r: put(x%(ctl(0)*2),0), put(y%(ctl(0)*2),1),
(get(0)<ctl(0) && get(1)<ctl(0)) || (get(0)>(ctl(0)-1)&&get(1)>(ctl(0)-1))?
(y%ctl(0))<(ctl(0)/val(1,0,20))?(y%ctl(0)+1)*(256*val(1,0,20)/ctl(0)):
(y%ctl(0)>(ctl(0)-1-ctl(0)/val(1,0,20)))?(ctl(0)-y%ctl(0))*(256*val(1,0,20)/ctl(0)):255:
(x%ctl(0))<(ctl(0)/val(1,0,20))?(x%ctl(0)+1)*(256*val(1,0,20)/ctl(0)):
(x%ctl(0)>(ctl(0)-1-ctl(0)/val(1,0,20)))?(ctl(0)-x%ctl(0))*(256*val(1,0,20)/ctl(0)):255

g,b: (get(0)<ctl(0) && get(1)<ctl(0)) || (get(0)>(ctl(0)-1)&&get(1)>(ctl(0)-1))?
(y%ctl(0))<(ctl(0)/val(1,0,20))?(y%ctl(0)+1)*(256*val(1,0,20)/ctl(0)):
(y%ctl(0)>(ctl(0)-1-ctl(0)/val(1,0,20)))?(ctl(0)-y%ctl(0))*(256*val(1,0,20)/ctl(0)):255:
(x%ctl(0))<(ctl(0)/val(1,0,20))?(x%ctl(0)+1)*(256*val(1,0,20)/ctl(0)):
(x%ctl(0)>(ctl(0)-1-ctl(0)/val(1,0,20)))?(ctl(0)-x%ctl(0))*(256*val(1,0,20)/ctl(0)):255

where
ctl(0) is the tile size,
ctl(1) is the brightness.

The last step is to combine the pattern with the image and the multiply idea: c1*c2/256. We simply add the *c/256 to the blends and change 255 to c:

Category: Neology
Title: Digital weaver
Copyright: (c) 1997 by Werner D. Streidt 100526.16@compuserve.com
Author: Werner D. Streidt

r: put(x%(ctl(0)*2),0), put(y%(ctl(0)*2),1),
(get(0)<ctl(0) && get(1)<ctl(0)) || (get(0)>(ctl(0)-1)&&get(1)>(ctl(0)-1))?
(y%ctl(0))<(ctl(0)/val(1,0,20))?(y%ctl(0)+1)*(256*val(1,0,20)/ctl(0))*c/256:
(y%ctl(0)>(ctl(0)-1-ctl(0)/val(1,0,20)))?(ctl(0)-y%ctl(0))*(256*val(1,0,20)/ctl(0))*c/256:c:
(x%ctl(0))<(ctl(0)/val(1,0,20))?(x%ctl(0)+1)*(256*val(1,0,20)/ctl(0))*c/256:
(x%ctl(0)>(ctl(0)-1-ctl(0)/val(1,0,20)))?(ctl(0)-x%ctl(0))*(256*val(1,0,20)/ctl(0))*c/256:c

g,b: (get(0)<ctl(0) && get(1)<ctl(0)) || (get(0)>(ctl(0)-1)&&get(1)>(ctl(0)-1))?
(y%ctl(0))<(ctl(0)/val(1,0,20))?(y%ctl(0)+1)*(256*val(1,0,20)/ctl(0))*c/256:
(y%ctl(0)>(ctl(0)-1-ctl(0)/val(1,0,20)))?(ctl(0)-y%ctl(0))*(256*val(1,0,20)/ctl(0))*c/256:c:
(x%ctl(0))<(ctl(0)/val(1,0,20))?(x%ctl(0)+1)*(256*val(1,0,20)/ctl(0))*c/256:
(x%ctl(0)>(ctl(0)-1-ctl(0)/val(1,0,20)))?(ctl(0)-x%ctl(0))*(256*val(1,0,20)/ctl(0))*c/256:c

where
ctl(0) is weave size,
ctl(1) is the shadow.

8. Waving around

A simple horizontal / vertical sine wave distortion

The objective is to put a (horizontal and a vertical) sine movement onto the image (without wrapping).

To get information from neighbouring pixels we need the src(x,y,z) function. The x and y values have to be changed with sinus values (remember, sinus also returns negative values). The horizontal displacement (x) depends on its vertical position (y). Therefore we have to use src(x+sin(y),y,z) as the base expression. Let's suppose the cycle length is 4 pixels; y values 0,1,2,3 have to return 0,512,0,-512, respectively, so the changed expression will be

src(x + sin (y*1024/4), y, z)

Using this code doesn't give the result we want. Let's work on the amplitude a bit. We have the possibility of getting values between -512 and 512. Dividing the values by 512 would return values between -1 and 1. Dividing the values by 256 would return values between -2 and 2. This means we have to use smaller values in order to get greater amplitude values: sin(?)/(512/?). Our code would then look like this - with sliders as cycle lengths and amplitudes:

x + sin(y*1024/ctl(0)) / (512/ctl(2))

Now the complete filter is

Category: Neology
Title: Sine Waves
Copyright: (c) 1996 by Werner D. Streidt 100526.16@compuserve.com
Author: Werner D. Streidt

r,g,b: src(x + sin(y*1024/ctl(0)) / (512/ctl(2)),y + sin(x*1024/ctl(1)) / (512/ctl(3)),z)

where
ctl(0) Horizontal cycle width (in pixels),
ctl(1) Vertical cycle width (in pixels),
ctl(2) Horizontal wave amplitude (in pixels),
ctl(3) Vertical wave amplitude (in pixels).

Doing the same with polar coordinates

We simply change src(x,y,z) to rad(d,m,z):

Category: Neology
Title: Polar Waves
Copyright: (c) 1996 by Werner D. Streidt 100526.16@compuserve.com
Author: Werner D. Streidt

r,g,b: rad(d+sin(m*1024/ctl(0))/(512/ctl(2)),m+sin(d*1024/ctl(1))/(512/ctl(3)),z)

where
ctl(0) Angle cycle width (in pixels),
ctl(1) Magnitude cycle width (in pixels),
ctl(2) Angle wave amplitude (in pixels),
ctl(3) Magnitude wave amplitude (in pixels).

9. Offsetting the image - Part I

by Alfredo Mateus

The following filters are taken from Alf's Power Toys.

Channel Offset

Let's start with a very simple filter. All this filter does is displace your picture in the x or y direction, with sliders for each channel and each direction.
This filter is based on the src(x,y,z) function. The src function returns the value for the pixel located in the (x,y) coordinates in the channel z. (0,0) is located in the upper left corner of the picture.
Also, the R,G and B channels have z equal to 0, 1 and 2, respectively. So, if you enter

src(0,0,z)

it will replace every pixel of your picture with the value of the first pixel. So, if you enter src(x,y,z) this will not change the picture at all. For each pixel at position (x,y) it returns the value of itself. This is the same as using c in an expression.

What if we want the value of the neighbouring pixel? src(x+1,y,z) will displace the whole picture 1 pixel to the left. What happens when you get to the last pixel in a row, and you don't have a x+1 to get the value from (x is greater than the image's width)? In this case, the source pixel will equal the last available pixel (X-1). So src(x+10,y,z) will result in a streak of horizontal lines in the last 10 pixels.
In order to displace the image with slider values we will use

src(x+ctl(0),y+ctl(1),z)

This will get the value of the slider 0 (ctl(0)) and displace the image to the left and the value of slider 1 (ctl(1)) and displace the picture upward. What if we want to go to the other direction?

src(x+((ctl(0)-128)*X/128),y+((ctl(1)-128)*Y/128),z)

When the slider is in the middle (value 128), there is no displacement. Taking the slider to the left or right will cause displacement to opposite directions. We are multiplying with X/128, so that a maximum slider setting (0 or 255) returns the leftmost / rightmost available pixels on our image. Another, more elegant way to do this is to use val instead of ctl:

src(x+val(0,-X,X)-1,y+val(1,-Y,Y)-1,z)

val takes the value of the slider and scales it between the two parameters, in this case the dimensions of the picture (X and Y).

All right, all we have to do now is place different sliders for each channel:

r: src(x+val(0,-X,X),y+val(1,-Y,Y),z)
g: src(x+val(2,-X,X),y+val(3,-Y,Y),z)
b: src(x+val(4,-X,X),y+val(5,-Y,Y),z)

The control names are:
ctl(0) = Red Horizontal,
ctl(1) = Red Vertical,
ctl(2) = Green Horizontal,
ctl(3) = Green Vertical,
ctl(4) = Blue Horizontal,
ctl(5) = Blue Vertical.

Now, as a homework, make the radial version of this filter using the rad function. You will get the Channel Spin filter.

This filter will not wrap around the image. Let's see if we can get this into Filter Factory to have a very good Offset filter and maybe more.

The Wrapper

From the filter above we see that after displacing an image too much we get a portion of the image where there is no valid data, so it repeats the last valid pixel. This portion of the image is where we want to have the opposite end of the picture repeated.
Let's see, if we displace the picture with src(x+val(0,0,X),y,z), the portion which gets the pixel repeated is given by X-val(0,0,X). Using the question mark we can select what Filter Factory will do in each area:

x<X-val(0,0,X)?src(x+val(0,0,X),y,z):src(x-(X-val(0,0,X)),y,z)

For x in the left of the repeated pixel area, it displaces the image just like before. For the repeated pixel area, it will place the info from the area that got bumped out to the left. We got wrapping! Let's try this for the vertical direction:

y<Y-val(1,0,Y)?src(x,y+val(1,0,Y),z):src(x,y-(Y-val(1,0,Y)),z)

Now, a little trick to get both in the same filter: the mode control.

ctl(2)<128 ? x<X-val(0,0,X) ? src(x+val(0,0,X),y,z) : src(x-(X-val(0,0,X)),y,z) : y<Y-val(0,0,Y) ? src(x,y+val(0,0,Y),z) : src(x,y-(Y-val(0,0,Y)),z)

ctl(2) is the mode control. If the slider is to the left of 128 it activates the horizontal wrapping. If it's greater than 128, we get vertical wrapping. And what about both at the same time? The final filter will look something like:

Name: The Wrapper
Category : Alf's Power Toys
Copyright: 1996 Alfredo Mateus
Author: Alfredo Mateus

r,g,b: ctl(3)<85 ? x<X-val(0,0,X) ? src(x+val(0,0,X),y,z) : src(x-(X-val(0,0,X)),y,z) :
ctl(3)<170 ? y<Y-val(1,0,Y) ? src(x,y+val(1,0,Y),z) : src(x,y-(Y-val(1,0,Y)),z) :
mix(y<Y-val(1,0,Y) ? src(x,y+val(1,0,Y),z) : src(x,y-(Y-val(1,0,Y)),z),
x<X-val(0,0,X) ? src(x+val(0,0,X),y,z) : src(x-(X-val(0,0,X)),y,z),ctl(5),255)

where
ctl(0) = Horizontal Wrapping,
ctl(1) = Vertical Wrapping,
ctl(3) = Mode,
ctl(5) = Mixing.

I have tried the mixing of wrapping in both directions and if you have a tileable texture to start with, you'll get an interesting effect.

10. Offsetting the image - Part II

Let's move our image around for a while. If we want to move our image 40 pixels to the right and 30 pixels downwards, we have to tell Filter Factory to put in the current pixel position the pixel value 40 pixels from the left and 30 over it:

src(x-40,y-30,z)

In the coordinates between (0,0) and (39,29) FF will take the pixel values from (0,0) and repeat the edge pixels. But we would like to wrap the image around. We have to check the limits first (in the following example for the x-coordinate, only):

(x<40 ? X-39+x : x-40)

When FF evaluates pixels 0 to 39, it uses pixels from the right side of the image to wrap around. If FF evaluates pixels in x-coordinates above 39, then it takes the pixel values from the pixel 40 pixels left. Let's do that for the y-coordinates, too:

r,g,b: src((x<40 ? X-39+x : x-40), (y<30 ? Y-29+y : y-30), z)

With slider 0 we control the horizontal movement, with slider 1 we control the vertical movement:

r,g,b: src((x<ctl(0) ? X-ctl(0)+x : x-ctl(0)), (y<ctl(1) ? Y-ctl(1)+y : y-ctl(1)), z)

You would use this filter to wrap around the image and edit the edges for seamless tiling.


11. Seamless tiling

The idea of the seamless tiling is to take part of the image, mirror it horizontally and vertically to create a seamless transition. An example:

r,g,b: src(x%100,y%100,z)

will fill your image with the 100x100 pixels upper left part of your image. If you want to change the width and height with sliders:

r,g,b: src(x%ctl(0), y%ctl(1),z)

where
ctl(0) is tile width,
ctl(1) is tile height.

You don't necessarily have to take the upper left part:

r,g,b: src(x%ctl(0)+ctl(2), y%ctl(1)+ctl(3),z)

where
ctl(0) tile width,
ctl(1) tile height,
ctl(2) horizontal offset,
ctl(3) vertical offset.

Now let's see the following code:

r,g,b: src(x%200<100 ? x%200 : 200-x%200-1,y%200<100 ? y%200 : 200-y%200-1,z)

it will take your 100x100 tile, mirror it sideways and mirror it downwards, thus creating a whole seamless tile pic.

And now with sliders for tile width, height and x/y offset:

Category: Neology
Title: Tiler1
Copyright: 1997 Werner D. Streidt http://www.fhd-stuttgart.de/~ws01
Author: Werner D. Streidt

r,g,b: src(x%(ctl(0)*2)<ctl(0) ? x%(ctl(0)*2)+ctl(2) : (ctl(0)*2)-x%(ctl(0)*2)-1+ctl(2),
y%(ctl(1)*2)<ctl(1) ? y%(ctl(1)*2)+ctl(3) : (ctl(1)*2)-y%(ctl(1)*2)-1+ctl(3),z)

where
ctl(0): Tile width,
ctl(1): Tile height,
ctl(2): Horizontal offset,
ctl(3): Vertical offset.

12. Preview window corrections

by Mario Klingemann

The following sections will help you with code optimization.

When you start programming FF-filters one of the first things you might be wondering about is that the final result looks somewhat different from the small preview. This doesn't happen with color manipulation filters, but whenever there is a distortion you'll encounter it.

Why does this happen and what can be done about it? The reason for this effect is, that FF scales down the original image for the preview and does all the calculations for that preview with the measures of the small image you see in the window. That means that X is not 768 for example, but just 120. The problem occurs when you start moving around pixels. src(x-ctl(0),y,z) with ctl(0) set to 50 will shift the image 50 pixels to the right. In the preview this is more than 4070134f the image, but in the original image, if it is 1000 pixels wide, it is just a small hop.

There is of course a solution. You will have to scale the values of the sliders so they give you different results on the small and on the original image. Luckily this is pretty easy. Just replace the ctl(0) with val(0,0,X). That's it. If you fiddle around with y take val(0,0,Y) or val(0,-Y,Y) or val(0,40,5*Y) - as long as there is X,Y or M inside, it will look right.

It gets a bit more complicated if it comes to the sin and cos functions. They will always return a result between -512 and 512 whatever you put in. So convert their input in a way that always get a whole phase or a multiple of it. Therefore one of your favorite command will be the scl function.

You put in a value that comes in a certain range and you'll receive something in a range that you need. Of course you could do that by adding and dividing, but it is much easier.

So back to the sin problem. We know that to get one sin phase we need to feed the function with values from -512 to 512. x will never become negative and especially in the preview never get over 120.

Just for repetition here is the scl function:

scl([input value], [minimum expected input value], [maximum expected input value], [minimum output value], [maximum output value])

What we know is that x goes from 0 to X. We should scale it then: sin(scl(x,0,X,-512,512)).

If you want two phases, just multiply:

sin(2*scl(x,0,X,-512,512))

If you want to vary it use ctl():

sin(ctl(0)*scl(x,0,X,-511,512))

If you don't want the whole phase for some soft gradient for example you'll have to reduce the output of the scl function:

sin(scl(x,0,X,-254,255)) or sin(scl(x,0,X,-64,64))

Another thing is that sin produces values from -512 to 512 but pixels accept only values from 0 to 255. What we need is just another scale:

scl(sin(ctl(0)*scl(x,0,X,-512,512)),-512,512,0,255)

Of course (sin(ctl(0)*scl(x,0,X,-511,512))+511)/4 would be the same, but scl is much easier to understand and easy to play around with it for experiments with variations of your final filter.

Back to Tutorials