This document is Copyright (c) 1996-1997 by Sylvie Gallet. I encourage you to copy and distribute it, so long as you leave it unchanged. It may NOT be used for commercial purposes without my explicit prior permission.
Any comments, questions, corrections... are welcome. My addresses are:
CompuServe: Sylvie_Gallet [101324,3444] Internet: sylvie_gallet@compuserve.com
Pseudo-HiColor (PHC) was co-discovered by Jim Deutch who wrote the very first PHC formula and Lee Skinner who realized the enormous possibilities that this technique offers. Given the great interest aroused by PHC, the Fractint developers decided to implement three new variables that make formulas much simpler; a big thank you to Tim Wegner, Jonathan Osuch and George Martin for doing this!
And a MEGA thanks for the IF..ELSE instruction they implemented in Fractint 19.6. This very powerful instruction considerably improves the readability of the formulas that use conditional statements and makes them generally faster.
In this second version of the PHC and PTC tutorial, I have rewritten both the explanatory material and the formulas to take advantage of the express branching instructions available now with Fractint 19.6. Some historical perspective may be helpful. Branching instructions have evolved from the technique described by Bradley Beacham in his formula tutorial, to the more direct conditional assignments commonly seen now, to the new IF..ELSE instructions. PHC formulas originally contained rather complex calculations to achieve the checkerboard effect required, but were much simplified when the "whitesq", "scrnpix", and "scrnmax" predefined variables were added to the formula parser.
Because of these developments, the same PHC formula may be found in different formula files with much different instructions, depending on the "state of the art" when the particular version of the formula was written. I have included samples of formulas written in the previous style in the accompanying phctutor.frm. This should help the Fractint user to understand how the old formulas worked and make easier the conversion of existing formulas written in the prior old formats.
Pseudo-TrueColor (PTC) is an extension of the PHC concept. All the PHC and PTC formulas included in this document are by the author of these lines. Thanks to Lee Skinner and George Martin for their help, suggestions and encouragements.
The text-only version of this tutorial is also available for downloading
It is possible to combine two fractals (say fractal_0 and fractal_1) in one image. Imagine that alternate screen pixels form a checkerboard pattern (represented by 0's and 1's) as follows:
0 1 0 1 0 . . 1 0 1 0 1 . . 0 1 0 1 0 . . 1 0 1 0 1 . . . . . . . . .
If one fractal is drawn on the "white squares" (the 1's) and the other on the black squares (the 0's), the separate fractals will be visible, and at higher screen resolutions you will not be able to see the way the individual pixels intermesh with the others. The effect is as if the two fractals were drawn on separate transparent sheets and overlaid.
... + =
Fractint v. 19.5 provides a predefined variable "whitesq", which is automatically set to 1 prior to the calculation of a white square pixel, and to 0 prior to calculation of a black square pixel. Let's see how to use this variable in a formula.
Suppose that fractal_0 and fractal_1 have the following assignment statements:
fractal_0 var = something fractal_1 var = somethingelseTo overlay the two fractals in PHC fashion, you can use the following IF..ELSE instruction in a formula:
IF (whitesq == 0) ; "whitesq == 0" is TRUE var = something ELSE ; "whitesq == 0" is FALSE var = somethingelse ENDIFor, even more simple:
IF (whitesq) ; "whitesq == 1" is TRUE var = somethingelse ELSE ; "whitesq == 1" is FALSE var = something ENDIF
To assign the appropriate value to var, our PHC formula will have to simulate an IF...THEN...ELSE... construct:
if (whitesq == 0) /* if "whitesq == 0" is TRUE */ var = something ; else /* if "whitesq == 0" is FALSE */ /* or "whitesq == 1" is TRUE */ var = somethingelse ;This can be done with the following line:
var = something * (whitesq == 0) + somethingelse * (whitesq == 1)
which results in "something" being assigned to var for the black squares, and "somethingelse" being assigned to var for the white squares.
To understand how it works, it's important to know that Fractint represents TRUE with a one, and FALSE with a zero, therefore:
* if whitesq is equal to 0: - "whitesq == 0" evaluates to 1 - "whitesq == 1" evaluates to 0 and var = something * 1 + somethingelse * 0 = something * if whitesq is equal to 1: - "whitesq == 0" evaluates to 0 - "whitesq == 1" evaluates to 1 and var = something * 0 + somethingelse * 1 = somethingelse
You may notice that the result of the test "whitesq == 1" is equal to the value of the variable "whitesq", so we can use the following statement:
var = something * (whitesq == 0) + somethingelse * whitesq
Suppose that fractal_0 and fractal_1 use the bailout tests bailout_0 and bailout_1. What will be the PHC bailout test?
Remember that if the answer of a bailout test is "TRUE" (the real portion of the complex number is nonzero), the loop must be performed again; otherwise, it is time to quit iterating.
The bailout test of the PHC formula must be the translation in the parser language of the following rule:
PHC_bailout is TRUE only in two cases: when (whitesq == 0) and (bailout_0 == TRUE) or when (whitesq == 1) and (bailout_1 == TRUE)Under Fractint 19.6, this expression becomes:
IF (whitesq) PHC_bailout = bailout_1 ELSE PHC_bailout = bailout_0 ENDIF
You'll notice that the IF block is followed by the name of a variable. Omitting the last line will produce an error message from Fractint.
This is the easiest case: both fractal use the same iteration instruction and the same bailout test.
mandel { ; Mandel set of z^2 + c z = c = pixel : z = z*z + c |z| <= 4 } julia { ; Julia set of z^2 + (-0.75,0.1234) z = pixel , c = (-0.75,0.1234) : z = z*z + c |z| <= 4 }
Since the only difference is the initial value of c, the PHC formula will use whitesq only in the init section:
phc_mj { ; overlay the Mandel set of z^2 + c with ; the Julia set of z^2 + (-0.75,0.1234) ; Modified for if..else logic, April 1997 z = pixel IF (whitesq) c = (-0.75,0.1234) ELSE c = pixel ENDIF : z = z*z + c |z| <= 4 }
and the Julia set will be drawn on the white squares of the checkerboard, and the Mandelbrot set on the black squares.
Here, except "z = pixel", everything is different.
mandel { ; Mandel set of z^2 + c z = c = pixel : z = z*z + c |z| <= 4 }
newton { ; Julia set of Newton's method ; applied to z^3 - 1 = 0 z = pixel : n = z^3 - 1 , d = 3*z^2 z = z - n/d |n| >= 0.000001 }
The resulting PHC formula is:
phc_mn_A { ; overlay the Mandel set of z^2 + c with the Julia ; set of Newton's method applied to z^3 - 1 = 0 ; Modified for if..else logic, April 1997 z = pixel : IF (whitesq) n = z^3 - 1 , d = 3*z^2 , z = z - n/d PHC_bailout = |n| >= 0.000001 ELSE z = z*z + pixel , PHC_bailout = |z| <= 4 ENDIF PHC_bailout }
You'll notice that c is initialized to pixel even when whitesq == 1 but it is not used by the Newton algorithm.
Overlaying three fractals can be done with the following pattern:
0 1 2 0 1 2 . . 1 2 0 1 2 0 . . 2 0 1 2 0 1 . . 0 1 2 0 1 2 . . . . . . . . . .
Fractint v. 19.5 provides a predefined variable "scrnpix", which is set to (column, row) prior to calculation of each pixel. The upper left hand corner of the screen is (0,0); at resolution 1024x768, the lower right hand corner is therefore (1023,767).
Here, we'll use scrnpix to assign the value 0, 1 or 2 to a variable r (as you can see, I choose a very explicit name!).
With col = real(scrnpix) and row = imag(scrnpix), the value of r should be:
r = (col + row) modulo 3or, using the parser language:
cr = real(scrnpix) + imag(scrnpix) r = cr - 3 * trunc(cr / 3)
But this instruction doesn't work (see "what's wrong").
The following instruction does work:
r = cr - 3 * trunc(cr / real(3))
Now, let's see an example:
Suppose you want to overlay the three following fractals:
mandel { ; Mandel set of z^2 + c z = c = pixel : z = z*z + c |z| <= 4 }
julia { ; Julia set of z^2 + (-0.75,0.1234) z = pixel , c = (-0.75,0.1234) : z = z*z + c |z| <= 4 }
newton { ; Julia set of Newton's method ; applied to z^3 - 1 = 0 z = pixel : n = z^3 - 1 , d = 3*z^2 z = z - n/d |n| >= 0.000001 }
We can merge them in the following way:
ptc_mjn_A { ; overlay the Mandel set of z^2 + c with the Julia set ; of z^2 + (-0.75,0.1234) and the Julia set of Newton's ; method applied to z^3 - 1 = 0 ; Modified for if..else logic, April 1997 cr = real(scrnpix) + imag(scrnpix) r = cr - 3 * trunc(cr / real(3)) z = pixel IF (r == 0) c = pixel ELSEIF (r == 1) c = (-0.75,0.1234) ENDIF : IF (r == 2) n = z^3 - 1 , d = 3*z^2 , z = z - n/d PTC_bailout = |n| >= 0.000001 ELSE z = z*z + c PTC_bailout = |z| <= 4 ENDIF PTC_bailout }
The best dithering is produced by the following pattern:
0 1 2 3 0 1 . . 2 3 0 1 2 3 . . 0 1 2 3 0 1 . . 2 3 0 1 2 3 . . . . . . . . . . and r is given by the formula: r = (col + 2*row) modulo 4 or, using the parser language: cr = real(scrnpix) + 2 * imag(scrnpix) r = cr - 4 * trunc(cr / 4)
and r can then be used as in the previous examples, to combine four fractals in one image.
Here is an example:
mand_0 { mand_1 { z = c = sin(pixel) : z = c = pixel : z = z*z + c z = z*z + c |real(z)| <= 4 |z| <= 4 } } mand_2 { mand_3 { z = c = 1/pixel : z = c = -pixel : z = z*z + c z = z*z + c |imag(z)| < 4 |real(z)+imag(z)| < 4 } } ptc_4m_A { ; overlay four Mandels with different initializations ; and bailout tests ; Isn't it terrific??? ; Modified for if..else logic, April 1997 cr = real(scrnpix) + 2 * imag(scrnpix) r = cr - 4 * trunc(cr / 4) IF (r == 0) z = c = sin(pixel) ELSEIF (r == 1) z = c = pixel ELSEIF (r == 2) z = c = 1/pixel ELSE z = c = -pixel ENDIF : z = z*z + c IF (r == 0) PTC_bailout = |real(z)| <= 4 ELSEIF (r == 1) PTC_bailout = |z| <= 4 ELSEIF (r == 2) PTC_bailout = |imag(z)| <= 4 ELSE PTC_bailout = |real(z)+imag(z)| <= 4 ENDIF PTC_bailout }
Let's look at this formula in detail.
The best way to make your formulas run a little faster would be to read or reread Bradley Beacham's FRMTUTOR.TXT (that'll save me having to plagiarize his work <g>).
In the newton formula, we can replace
n = z^3 - 1 , d = 3*z^2with
z2 = z*z , n = z2*z - 1 , d = 3*z2This gives:
newton_B { ; Julia set of Newton's method ; applied to z^3 - 1 = 0 z = pixel : z2 = z*z , n = z2*z - 1 , d = 3*z2 z = z - n/d |n| >= 0.000001 }
Look at this formula:
mandelvariation { z = c = pixel: z = z*z + sin(c) - 0.2 |z| < 4 }
Once you notice that the expression sin(c) - .2 is the same value every time it is calculated for a pixel (which could be many thousands of times) you will get in the habit of writing formulas like this one as follows:
mandelvariation { z = pixel c = sin(pixel) - 0.2: z = z*z + c |z| < 4 }
By moving the constant expression into the initialization section, the calculation is made only once per pixel.
An expression such as
z = (3.5 * a + 3.5 * b) / (10.5 * c)
is reduced to the simpler
z = (a + b) / (3 * c)
While this example seems trivial, cases where such speedups are possible abound in existing formulas; in more complicated formulas, the algebraic reductions may not be as obvious.
As we've seen earlier, the PHC or PTC dithering is based on the value of a variable initialized by Fractint (whitesq) or in the formula (r).
To write a PHC formula, you just have to use "whitesq" at least once. A PTC formula will start with these lines:
cr = real(scrnpix) + imag(scrnpix) r = cr - 3 * trunc(cr / real(3)) or these ones: cr = real(scrnpix) + 2 * imag(scrnpix) r = cr - 4 * trunc(cr / 4)
For a good dithering, it's essential to leave the variables "whitesq", "cr" and "r" intact.
For example, if you change:
r = cr - 3 * trunc(cr / real(3)) to: r = cr - 3 * trunc(cr / real(3.5)) the values of r will be: 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 4, ...
and the result will be quite disappointing. Also, it will be helpful if you use the templates (including the variable names "r" and "cr") in exactly the form shown above. This will make it easier to identify and update formulas using PHC and PTC if future changes to Fractint's formula parser would make such updating desirable.
The following examples show typical old style PHC statements and their translation for Fractint 19.6.
z = z*z + sin(z)*whitesq + pixel IF (whitesq) z = z*z + sin(z) + pixel ELSE z = z*z + pixel ENDIF
z = z^(z + whitesq - (whitesq == 0)) IF (whitesq) z = z^(z + 1) ELSE z = z^(z - 1) ENDIF
z = (z*z + pixel)*whitesq + (exp(z) + c)*(whitesq == 0) + p1 IF (whitesq) z = z*z + pixel + p1 ELSE z = exp(z) + c + p1 ENDIF
The best way to translate a bailout test that uses whitesq is to assign its value to a variable and to end the formula with this variable. For example:
(|z| <= 4 && whitesq == 0) || (|n| >= 0.000001 && whitesq)
will read as follow:
IF (whitesq) PHC_bailout = |n| >= 0.000001 ELSE PHC_bailout = |z| <= 4 ENDIF PHC_bailout
Of course, if the iterated section already has an IF block controlled by whitesq, we can put the "PHC_bailout =" statements in this block as in the example below:
phc_mn_A { ; overlay the Mandel set of z^2 + c with the Julia ; set of Newton's method applied to z^3 - 1 = 0 z = c = pixel : n = z^3 - 1 , d = 3*z^2 z = (z*z + c) * (whitesq == 0) + (z - n/d) * whitesq (|z| <= 4 && whitesq == 0) || (|n| >= 0.000001 && whitesq) } phc_mn_A { ; overlay the Mandel set of z^2 + c with the Julia ; set of Newton's method applied to z^3 - 1 = 0 ; Modified for if..else logic, April 1997 z = pixel : IF (whitesq) n = z^3 - 1 , d = 3*z^2 , z = z - n/d PHC_bailout = |n| >= 0.000001 ELSE z = z*z + pixel , PHC_bailout = |z| <= 4 ENDIF PHC_bailout }
Fractint provides two calculation modes: integer math and floating point math.
Though it often produces very nice images, integer math has an important limitation: you can't use numbers greater than 255.999... In integer mode, when you *think* you're using say 372, Fractint uses 116 (372-256=116). For this reason, the Fractint developers decided to force the floating point mode when the formula parser detects the use of one of the predefined variables "scrnpix", "scrnmax" and "maxit".
When you run a formula in floating point mode, the formula parser uses some tricks to optimize the code. Trunc(cr / 3) is the victim of one of these tricks.
The problem is this: trunc(cr/3) is replaced by trunc(cr*(1/3)), but since 1/3 is represented internally as .33333..., cr*(1/3) is slightly less than cr/3. If cr is a multiple of 3, trunc(cr/3) therefore returns one less than you would expect. For example trunc(6/3) calculates as trunc(6*.3333333...) or trunc(1.9999...), which in turn evaluates to 1 instead of the expected result of 2.
Replacing 3 with real(3) circumvents this problem and yields the desired result.
Precedence is a way to make mathematical expressions more readable by using less parentheses. For a better comprehension of this section, you might want to look at the table of precedence in the Fractint documentation.
The following expressions are mathematically equivalent because division and multiplication have a higher precedence than addition and subtraction (this means that divisions and multiplications will be performed before additions and subtraction):
2+(3*((5*3)+(4/5))*(7-5)) 2 + 3 * (5*3 + 4/5) * (7 - 5)
(they both evaluate to 96.8) I must confess that I tend to prefer the second one... (OK, I'm cheating, I just added a few white spaces... <g>).
Now, let's see another example:
In this expression:
(|z| < 4) && (whitesq == 0)
the parentheses mean that the comparisons must be performed before the logical AND. Comparisons have a higher precedence than logical operators thus, we can remove the parentheses:
|z| < 4 && whitesq == 0
However, with
(|z| <= 4 && whitesq == 0) || (|n| >= 0.000001 && whitesq)
Can we remove the parentheses? The answer is: no! Without parentheses,
and since we know that comparisons are performed first, this expression has
the following format:
A && B || C && D
"&&" and "||" have the same precedence and in such a case, the expression
must be calculated from the left to the right. Just look at this:
(1 && 1) || (1 && 0) = 1 || 0 = 1 1 && 1 || 1 && 0 = 1 || 1 && 0 = 1 && 0 = 0
With the new variables introduced in Fractint 19.5, PHC and PTC formulas are now resolution independent and the image can be interrupted, saved and restored. Panning an even number of pixels for PHC images or multiples of 3 for "24-bit PTC's" and multiples of 4 for "32-bit PTC's " is possible without artifacts.
All PHC and PTC formulas require passes=1.
The use of symmetry in PHC or PTC formulas or par files is not recommended since symmetry alters the pattern along the axes and results in horizontal or vertical lines.
That's all for now! I hope you found this text interesting and useful. I'm planning an update of Bradley Beacham's Formula Tutorial which could include this text and any subject you'd like to see treated.
Back to
The Fractint Home Page.
or back to
The Fractint Index Page.