Joe Maller: FXScript Reference: Building Joe's Minimum Maximum

The FXScript concepts behind my minimum maximum 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 Minimum Maximum. If you're looking for the page about how to use the filter, click here.


Joe's Minimum Maximum recreates a grayscale Erode/Dissolve effect, similar to Photoshop's Minimum and Maximum filters. Normally this would be accomplished with a logical matrix operation, but FXScript doesn't seem to include one. The workaround is to nudge and composite the image using the logical composite modes Lighten and Darken. These two modes are logical, which is why they tend to look synthetic and unnatural:

Lighten: If Pixel A is lighter than Pixel B, then keep Pixel A, else keep pixel B.

Darken: If Pixel A is darker than Pixel B, then keep Pixel A, else keep pixel B.

To preview the effect, duplicate a layer in Photoshop, nudge it up and apply as Lighten or Darken. Repeat with the remaining three directions, finishing one direction before the perpendicular.

This is the most processor-intensive filter I've written. The recursive looping means four composite operations will be executed for each iteration. With high numbers Final Cut Pro gets really bogged down.

This filter also gave me the chance to implement something I've been asking for in Photoshop for years, Minimum and Maximum as one filter with a positive and negative slider.

The Missing Logical Matrix

When I started looking into matrix operations while building Joe's RGB Desaturate and Joe's Radial Desaturate, I learned there was another kind matrix process which used logic to generate the resulting pixel.

Matrix operations consider the eight pixels around the target pixel to generate their effects. Minimum and Maximum are Photoshop's names for processes called Erode and Dilate. Since I don't have a computer science degree, the names Minimum and Maximum were most familiar to me, and probably to most other Final Cut Pro users.

Erode/Minimum and Dilate/Maximum work by choosing either the lightest or darkest value of the target pixel and the eight pixels around it and then replacing the entire matrix with that color.

Clarity instead of Loops

This script could have used a nested loop, but instead places four directional offsets in sequence. It's longer, but easy to take apart. The script starts out by moving the source image into xbuffer2:

xbuffer2 = src1

Next the main loop starts. This loop repeats to whatever is set in the slider Shifter. Using abs(Shifter) ignores the positive/negative sign and returns the absolute value of the Shifter. That variable will be used later in the script to determine whether to darken or lighten the image.

repeat with j = 1 to abs(Shifter)

Below is the first iteration of the four directional offsets. The xoff and yoff variables will be used for the direction of the offset. These are something of a leftover from the first two-loop version of the script. The first line multiplies each variable by zero, setting them both to zero. Next the script assigns a direction by adding 1 to xoff.

xoff *= 0; yoff *= 0
xoff += 1 * zoomfactor; yoff += 0

Xoff and yoff are used in the offset command to determine direction and distance. The following command moves xbuffer2 into xbuffer with an offset of 1 pixel to the right:

offsetPixels(xbuffer2, xbuffer, 1, Xoff, Yoff, aspectOf(dest));

The 1 after the target and destination image buffers is a switch to turn edge repeating on or off. I didn't notice a difference but decided it was safer to generate pixels at the edges then to risk the possibility that Final Cut Pro might freak out on empty space.

The next step is to composite the nudged image. The script evaluates Shifter and composites the nudged image back onto the pre-nudged image using either Lighten or Darken.

if Shifter < 0; darken(xbuffer, xbuffer2, dest, 1, kalpha);end if;
if Shifter > 0; lighten(xbuffer, xbuffer2, dest, 1, kalpha); end if

This directional iteration ends by restoring xbuffer with the new contents of dest:

xbuffer2 = dest


The next three loops are identical with only the direction of the offset changing. My standard set of compositing controls finish the filter, allowing the complete effect to be composited back onto the original image.

While it would be more efficient and elegant for this filter to use a nested loop, a few extra lines of code are not what is slowing this down. If Apple would include a logical matrix operator in the FXScript syntax the same effect could be recreated in probably 1/100th the time this filter requires. From everything I've read, logical Matrix operations are simple and not processor-intensive, but for now, this is good enough. If you think it's slow on a G4, I wrote and tested it on a 350mhz G3 and a 233mhz G3 PowerBook, ugh.

The complete FXScript source code for Joe's Minimum Maximum 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