Joe Maller: FXScript Reference: Building Joe's Gradients

A description of the FXScripts behind my gradient filter for Final Cut Pro.

Visit the New FXScript Reference and Joe's Filters sites. These pages will be phased out soon and may already be out of date.

Joe's Filters
FXScript Reference

This page describes the FXScript concepts behind Joe's Gradients. If you're looking for the page about how to use this filter, click here.

Overview

Since Joe's Gradients was meant to simulate the basic effect of a real-world photographic filter, the script uses a lot of math to make sure the actual gradient effect is applied in the right place. Most of the code for this filter was also used in Building Joe's Soft Gradients.

Establish Direction

GradAngle = (GradAngle + 360 + 180) mod 360

The script uses GradAngle to measure the distance across the intended gradient. Adding 180° to GradAngle sets the correct angle which will become the gradient at the end of the script. The script could have specified the reverse position in other ways, but this was how it ended up. Removing the 180° would cause the gradient to start from the opposite of the specified angle. The actual HighLight() function will still add 90° to GradAngle to get the proper direction.

Adding 360 made sure the number was always positive. I could have forced the angle input control to be always positive, but I thought it worked better going in either direction. Mod 360 is a great trick that I didn't understand before I started building these filters. Modulo is the remainder of division. It's also something like a clock face, providing a circular numerical baseline. 15 mod 12 would equal 3. So would 123 mod 12.

Without Sin

Somehow I made it this far in life without learning about Sine and Cosine. Sine and Cosine, or Sin and Cos, refer to the vertical and horizontal measurements of the outside of a circle at a given angle. Sin is the x-coordinate, Cos is the y-coordinate. Here is a diagram showing the Sine and Cosine of the angle a:

Sin and Cos are used to place the coordinates of the gradient origin point based on the angle specified in GradAngle.

Points

Sin and Cos are used to establish pointer, the first point variable and the intersection of GradAngle with an oval that would fit inside the frame. It's an oval and not a circle because the angle is multiplied by different values (frame height and frame width).

pointer.x = w/2 * sin(360 - GradAngle);
pointer.y = h/2 * cos(360 - GradAngle);

Next, the pointer circle is compressed to match the aspect ratio of the image by multiplying the vertical dimension by the aspect ratio. This could have modified the existing pointer variable, but places the result into another variable for clarity.

aspectpointer = {pointer.x, pointer.y * aspectOf(dest)}

Finally, the pointer2 is filled with the contents of aspectpointer multiplying by -1 to invert their position.

pointer2.x = aspectpointer.x * -1
pointer2.y = aspectpointer.y * -1

FXScript includes a way to find the distance between two points, confusingly labeled DistTo(). (DistTween would have been a more accurate name.) The ending gradation will use Ramplength as the basis of the gradation.

ramplength = DistTo(aspectpointer, pointer2)

Next the script does some simple addition and subtraction to adjust the end points for the offset slider settings. Enclosing the value in an if statement skips these adjustments if the user chooses an offset of zero. In the following code, linebreaks are indicated by semicolons.

if GradOffset != 0;
   pointer.x += GradOffset/100 * sin(360 - GradAngle) * Ramplength;
   pointer.y += GradOffset/100 * cos(360 - GradAngle) * Ramplength / aspectOf(dest));
end if;

The last point operation again modifies pointer, increasing the coordinates to compensate for the gradient's width, the scale of the desktop monitor and finally increasing the circle by 120% so the origin always falls outside of the frame.

pointer.x += GradWidth * sin(360 - GradAngle) * zoomfactor * 1.2;
pointer.y += GradWidth * cos(360 - GradAngle) * zoomfactor * 1.2 / aspectOf(dest);

Preventing Halos

It's impossible to fade from one color to nothing. Even though that may seem to be what is happening, the gradation is actually fading between a fully opaque color and a fully transparent color. If the two colors are different (aside from their opacity), there will be a shift in the middle portion of the gradation.

Below is an example of what happens when a color-to-tranparent gradation uses two different base colors:

Gradation A

{R:255, G:255, B:0, A:0}  →  {R:255, G:255, B:0, A:255}

Gradation B

{R:0, G:0, B:255, A:0}  →  {R:255, G:255, B:0, A:255}

Notice how the blue from the first color pollutes the color of the gradient, even though it is completely transparent. Gradient A blended from yellow to yellow, with only the alpha channel information changing.

Flattening the alpha channels from Gradient B reveals the problem:

{R:0, G:0, B:255, A:255}  →  {R:255, G:255, B:0, A:255}

Colors

The alpha value for both gradation colors is set with the following lines. The script does this to guarantee each color's opacity value.

BGcolor.a = 255;
FGcolor.a = 255;

To avoid halos as shown above, the following lines are executed when the TransEnd checkbox is checked.

if TransEnd == 1;
   BGcolor = FGcolor;
   BGcolor.a = 0;
end if

First FGcolor is copied into BGcolor. Next the alpha value of BGcolor is set to zero, to be completely transparent.

Wrapping up

The HighLight() function is also described on the FXScript Functions page. The following code for drawing the HighLight() gradation must appear on the same line. HighLight() replaces whatever contents were in xbuffer with the new gradient.

highLight(xbuffer, pointer, (GradAngle +90), GradWidth * zoomfactor, FadeLength/100 * ramplength, DitherCheck, GaussianCheck, FGcolor, BGcolor, aspectof(dest));

The filter ends with my standard set of composite controls, combining xbuffer and src1 into dest.

The complete FXScript source code for Joe's Gradients is included with the paid version of Joe's Filters.

Buy Joe's Filters Download the Free Trial
 
page last modified: October 23, 2017
Copyright © 1996-2003 Joe Maller