Using Ultra Fractal as a Drawing Tool

Images and text © 2001 Kerry Mitchell

What

Ultra Fractal is a very powerful program, and much of its power comes from the ability to write your own calculation and coloring formulas. Ultra Fractal is also a raster program, which means that each pixel is calculated and colored individually. While this is great for fractal art, it does present some challenges when you want to do something a bit non-traditional. Here I'll discuss some tips and techniques for using Ultra Fractal as a vector-based drawing program.

Typical fractal images are raster images. The image file (typically, jpg, bmp, gif, etc.) must store the color information for each pixel, and that's about it (except for possible compression information). The image only exists as an array of colored dots. While the image can easily be resized in a graphics program, this involves making up information to guess what would have been there for the new pixels.

Vector images are created using discrete shapes like lines, curves, solid fills, etc., and are not tied to a particular resolution (in concept, anyway). This makes them very scaleable--design the image small and then render it as large as necessary for the print job at hand. Any conversion to pixels is done at the final print resolution using the exact definitions of the shapes, so there are no "jaggies" or blurry areas from resizing a raster image.

Why

There are other types of fractals besides those generally created with Ultra Fractal. L-system fractals, like the Koch snowflake or Hilbert curve, are typically rendered as a series of lines. Iterated Function System fractals are usually visualized as a multitude of discrete points. Neither of these can be natively done with Ultra Fractal, but there are some ways around that. Or maybe you want to tinker with other geometric designs, like rose curves, Bezier splines, and polylines. Judicious use of formulas can not only make these adventures available to you, but can also allow you to go beyond black/white rendering to add color and even texture.

How

The first thing is to realize the practical difference between typical fractal images and vector drawings. With a typical fractal, Ultra Fractal runs the calculation and coloring formulas, assigns a color to the pixel, and moves on. The program doesn't have much knowledge of an "overall" image space, it just deals with pixels and layers one at a time. With a vector drawing, you might tell the computer, "Start at this point and draw that kind of curve." There's no way for Ultra Fractal to simply draw that curve, dealing only with those points on the curve and leaving everything else alone. Instead, it must ask every pixel how close it is to the curve, then color the pixel if it is close enough. Indeed, depending on the situation, there may not be any pixels in the image that are exactly on the curve. But that's ok--you now have the opportunity to make the curve thick enough to see, or thicker so you can color it, and so on. But let's not get ahead of ourselves.

Circles

black circle with red inside and blue outsideCircles are easily drawn due to their geometry: a circle is the set of all points in a plane that are a certain distance from a center point. That distance is the radius of the circle, and the distance between any two complex points is given by the cabs() function in Ultra Fractal. So we just need to check every pixel's distance from the center and compare that with the radius:
r = cabs(z - circlecenter)
error = r - radius
The "error" quantity is represents the location of the point relative to the circle. If it is negative, the point is inside, and positive means outside. Coloring by the absolute value of error (using the abs() function) will give a of color that is centered on the desired circle. This figure shows the black circle and the reddish inside and bluish outside regions.
 

Discrete points can be drawn by considering them as circles with a radius of 0:

r = cabs(z - zpoint)

Other curves

5-petal rose curveCurves such that y is expressed either implicitly or explicitly as a function of x can be drawn by finding a function such that f(x,y) = 0 when a point (x, y) is on the curve. For example, the standard parabola is described by y = x2. This can be rearranged to give y - x2 = 0, so f(x,y) would be y - x2 in this example. Once this function is found, then use a method similar to the circle method:
x = real(z)
y = imag(z)
f = (some function of x & y that is 0 for points on the curve)
The sign of f reflects which side of the curve the point is on. To show the curve, color by the absolute value of f with "Repeat Gradient" turned off. Crank up the "Color Density" to make the line thinner.

Polar curves, like rose curves, are expressed in terms of r and t, the magnitude and angle of the complex number, instead of x and y. For closed curves (the curve comes back and closes on itself), life is a bit easier. For example, here we have the rose curve, r = cos(5t). Since the cosine function is periodic, the true value of t is irrelevant. This is good, since the atan2() function, used to get the angle of a complex number, only returns values between -p and p. In fact, any routine for getting an angle from a complex number will only return a value in a range of width 2p. But once the angle is found, the desired magnitude is determined, and compared with the actual magnitude. In general:

t = atan2(z)
rdesired = (some function depending on the desired curve)
ractual = cabs(z)
error = ractual - rdesired
Open curves can be more difficult, since an exact value for t can't be determined. For example, consider the linear spiral r = t. The point corresponding to r = 3p and t = 3p is on the spiral. However, the atan2() function will never return a value of 3p, so this point can't be found that way. If the relationship between r and t can be inverted, then the search can be performed on r:
r = cabs(z)
t = (inverted function, t in terms of r)
x = r*cos(t)
y = r*sin(t)
zdesired = x + flip(y)
error = z - zdesired
The problem here (aside from solving the polar equation for r) is that this method won't work for negative r values. Strictly speaking, r is a polar coordinate, not a distance. As such, it can be negative. I'll leave it to you to figure out what to do then. :-)

Lines

Lines turn out to be fairly simple to draw in Ultra Fractal, by using a parameter t:
t = (z - z0) /(z1 - z0).
In this formulation, t = 0 at one endpoint z0 and t = 1 at the other endpoint z1. Along the line between the endpoints, t varies smoothly between 0 and 1. Furthermore, even though z, z0, and z1 are all complex numbers, t will be a pure real number (imaginary part = 0) for any point on the line. This gives us an easy way to see if a point is on the line: form t as above for each pixel; check to see if the real part of t is between 0 and 1; use the imaginary part of t to determine how far away from the line the pixel is.

singe line on top, pieces to make it on bottomHere we see the pieces and how they come together. The top half of the image is the final single line, at twice the scale of the images in the bottom. In the bottom half, the image in the bottom right is a combination of three layers, each shown in the other three quadrants. The bottom right picture has two diagonal lines that run from top left to bottom right, one diagonal from bottom left to top right, and a series of concentric ovals. The single line drawn in the top is along the single line from bottom left to top right, between the other pair of lines. The real part of the parameter is shown in red in the top left quadrant. The black end corresponds to 0 and the white end to 1, so the desired line fits exactly in this band.

The top right of the bottom half shows the imaginary part of t, and is colored in diagonal bands from black to green to white. Our line lies on the boundary between the black and white areas. Moving away from the line and above it, the colors start at black and move to green, then to white. This indicates that the imaginary part of t is positive on this side and increases away from the line. On the other side, the imaginary part is negative. So the imaginary part of t can be used to tell how far away from the line the pixel is, and on which side.

The two parts are combined in the top layer, shown in blue in the bottom left quadrant. Just using the imaginary part of t when the real part is between 0 and 1 would lead to sharp breaks in color at the endpoints. While this may be desired in some applications, a better solution is to put round caps on the ends. These caps are made by looking at both the real and imaginary parts when the real part of t is outside of the range [0, 1]. This coloring makes a "glow" around the line, which can be adjusted through the Color Density setting. In the layer shown, white corresponds to being very close to the line, then blue further away, then black, even further away. This layer has multiple bands; limiting the coloring to one band is accomplished by clearing the "Repeat Gradient" box.

Splines

Splines are smooth curves through a set of points. A quadratic spline runs through a set of three control points. The curve begins and ends at two of the points and the third point is used to control the shape. Both the x and y coordinates of a point on the curve are parameterized by t:
x = Ax t2 + Bx t + Cx
y = Ay t2 + By t + Cy
where the coefficients are determined by the control points. They are set such that t = 0 at the first control point and t = 1 at the third point. Cubic splines are similar, except that they use four control points, and the x and y equations are cubic polynomials in t. The problem with these polynomial equations is that they can't be inverted; given x and y, t cannot be uniquely determined.

In situations where the equation of the curve can't be inverted, one can always do it the hard way. That is, choose many values of the parameter and determine the x and y values. Then, check the pixel against this (x, y) point. Move on to the next parameter choice, and color the pixel according to the closest pixel. If you use this method, use a relatively small number of parameter values while designing your image, then increase it for final rendering to close the gaps between points.

Solid regions

Solid regions can also be drawn. However, the difficulty in determining if a pixel is inside or outside of a shape depends greatly on the shape in question. For shapes where the magnitude can be expressed in terms of the angle, then the pixel is inside if the actual distance between it and the center is less than the distance to the curve. This works well with circles and rose curves. If you can (conceptually) draw a line from the center to the edge of the shape and have that line be entirely within the shape, this method will work.

4 solid trianglesFor polygonal shapes, I have used two different methods. The first is related to the above technique for polar curves. Imagine standing at point P, somewhere inside a square. The four corners of the square are labeled A, B, C, and D, in order. Then, you can make the angle from corner A to point P to corner B. Then angle BPC from corner B to point P to corner C. Then, angles CPD and DPA. Since you're standing inside the square, the sum of these four angles will be one complete turn around point P, or 360 degrees. This will be the case for any point inside the square, indeed, inside any convex shape (none of the sides are "caved in"). If P is outside, then the sum will be less than 360 degrees, and will decrease the further away from the shape P is.

The other method uses the line parameterization from above. For each line (which is a side of a polygon), the imaginary part of the parameter t measures how far away the point is from the line, and on what side (positive on one side, negative on the other). Now imagine walking around a dining room table. You can walk around it in either direction; in one direction, you can keep your left hand on the table all the way around. In the other direction, your right hand can stay on the table. So, if you set up the corner points of your polygon in a coherent fashion (all clockwise or all counter-clockwise), then you can simply check if the imaginary part of the parameter is the same sign for all the sides of the polygon. If you establish the four corner points of a convex quadrilateral in a counter-clockwise fashion, and all four parameters have a positive imaginary part for a given pixel, then that pixel is inside the quadrilateral.

Squares and rectangles with sides parallel to the real and imaginary axes are particularly easy. If you had a rectangle with corners at (1, 2), (1, 3), (4, 3), and (4, 2), then you can check to see if the real part of a pixel's coordinates are between 1 and 4, and the imaginary part of the pixel's coordinates are between 2 and 3. If both of these conditions hold, then the pixel is inside the rectangle. If either condition is violated, then the pixel is outside.

Hints and tips

While drawing individual shapes isn't difficult, creating interesting images with them can be challenging. One approach is to have each layer represent one shape. This general method is very flexible, but it may require approximately 173 zillion layers to make an image. Another approach is to draw shapes that can be calculated inside the formula. This can reduce the layer load, but severely increase the calculation time as the complexity of the shape increases. Each pixel must be compared against every element of the shape. And if coloring is actually used in conjunction with a fractal formula, the comparisons must be done every iteration. The calculation time can rise quickly.

One way to speed up complex shape calculations is to use a bounding shape. For example, if you're doing a 37-segment curve that completely lives inside a circle, then see if the pixel is inside or outside of the circle. If it is outside, then you can go on to the next point without having to run through the entire calculation.

In your quest for speed, don't be tempted to use the "Guessing" drawing mode. Your image will probably have scads of thin lines or small points in it, and the "Guessing" mode can pass over miss parts, leaving gaps and blank spots. Be sure to use one of the linear modes, either "One-pass" or "Multi-pass."

Examples

Here are some examples to demonstrate the ideas discussed above. All the images were created in Ultra Fractal. Click on the thumbnail to see a larger version.
 
Apollonian packing
This is an Apollonian packing, in which the triangular gap between three touching circles is filled with another circle. That leaves three more gaps, which are filled by three smaller circles, etc. It was rendered using transforms; each circle was created from its own transform. I wrote a separate program to determine the locations of the circles in the image and to create the upr.
  I call this image, "Harmony." It is composed completely of overlapping black and white solid circles. Each circle is slightly smaller than the previous, the opposite color, and tangent to it at one point. To avoid having to place a zillion transforms, this is all accomplished in the calculation formula.
Harmony
Gaussian prime distribution
An exercise in rendering discrete points. This one shows the location of the Gaussian primes in the complex plane. Each dot is actually a small diamond. The blue diamond in the middle represents the point (0, 0).
A basket created with 19 layers of rose curves. The layers used the "Rose Range Lite" coloring with the "Pixel" formula. Each layer merged using the "Darken" mode at 100% opacity so that each curve would show through equally.
Rose curve basket
Astroid string art
This demonstration of drawing lines uses 8 layers of "Astroid String Art" coloring. This formula offers the option of coloring by the magnitude or angle of z when it approached the line. The "Lighten" merge mode gives the effect of depth; some lines appear to be in front of or behind others.
This Hilbert curve variant is made of four layers. In each layer, the curve is drawn as a series of lines, where the lines were computed in the formula, rather than laid down with transforms. The color comes from having a different stage of the curve and different gradient for each layer, and merging them all with the "Difference" mode.
Hilbert curve variation
Metagon variation
Here is a variation on the "metagon" shape. The image has three layers. The bottom layer colors by the pixel's membership in a polygonal area. That gives rise to the overall orange and cyan sweeps. The second layer, merged with the "Difference" mode, colors by the angle between the pixel and a side of the polygon. That creates the angular gradation in colors within the sweeps. The top layer is simply a mask to cut off the main design from some residual smudge, now tastefully colored black.
Pentominoes are puzzle pieces made up of five squares. Even though they are concave shapes, they are easily drawn by drawing each of the five squares separately. These were drawn using the "Pentomino Color" transform. Pentominoes also have the interesting feature that a large version of any of the twelve shapes can be made from nine of the remaining pieces. This was the basis for my "Pentominoes" image.
Pentominoes study
Kites and darts
Penrose tiles are shapes that can also lead to fractal tilings. Here, I illustrate the "deflation" property of the "kite and dart" tiles. The overall image is broken into five kite shapes. Each kite is then broken into two smaller kites and a dart, and so on for five iterations. Each iteration is a separate layer; each kite on a layer is its own transform.
To finish, here's a sample using a cubic (Bezier) spline. Each letter was made of two spline curves (and two layers). Each curve, in turn, was made of hundreds of individual points. Each point had to be checked against every pixel in the image. As a cheat, I made the lines thick and one color so I could use the "Guessing" drawing mode on each layer.
Signature written with cubic splines
Back to the articles page
Up to my home page