Update, new Beta-Version 3.2.2

EDIT: For some people the updated app crashes right after start (this is something that Intel users unfortunately always experienced). This is caused by renderscript and it is already fixed with the next update. I did not find the bug (it works on my phone ‘unfortunately’) but I had to deactivate the increased precision of some functions  😦

I just released a new beta version. The new big thing is the tutorial. You find instructions on how to use the app there. I will release the other articles also in the blog soon and hope that you provide some reviews on them.

Another maybe not so big thing (but much bigger for others) is the new Export/Import function in the favorites. First, you can select multiple items in the Favorites view.

Screenshot_20170803-103523.png

It works very similar to other android apps like Google Photo.

By selecting “Export” in the menu, a text file is shared to a location of your desire. Using “import” you can reimport this text file or import collections from others.

Technical stuff: The format I use is json (in detail I use the gson library), hence it is easy to edit. But careful, faulty entries are silently ignored during import (here is some space for improvement).

What else? I managed to increase the precision of the sqrt function and therefore also the rad function [EDIT: Due to crashes on some devices this feature was deactivated]. Other functions are still restricted to single precision but at least, exp and log now return valid results for double values. This is in particular important for exp. For a large bailout a function containing exp will soon create transparent artifacts that mean that the calculation returned an invalid value (like infinity). Now, exp works for much larger values (left is before, right is after):demo_new_exp.png

This is btw the fold preset with the function is “(exp z – E z) p” and a mandelinit of 1 (first derivation of function is “(exp z – E) p” and this is 0 for z = 1).

There are two/three new fractal types and simpler functions for the perpendicular types. The new “generic lambda” preset is the lambda fractal but it uses the “max_power” parameter in mandelinit and function. It is a lovely fractal. The other ones are the two Simonbrot variations that give rise to amazing julia sets. Thanks for the hint about them.

Internally, there were lots of changes, code polishing to increase the over-all stability, some dialogs changed a bit. For the final release there will be some corrections/improvements on the tutorial and also on the descriptions in the Demo/Preset pane but apart from that the app should be quite stable.

I hope you enjoy the update,

— Karl

P.S.: I just played around with this exp-fractal above and you should definitely take a look at its islands. The following image looks like it comes right out of Mandelbrot’s vegetable garden:exp zoom.png

And since the beta version has this lovely import function, you can test it and import this image.

Advertisements
Posted in Updates | Leave a comment

Tutorial: Part 1: Quickstart

I currently spend too much time programming and not enough time to explain all the possibilities of Fractview. Yet, I guess the bad reviews that point out that the app is not that easy to use have a point, so the next version, beta will be rolled out in hopefully around two weeks, will contain a tutorial. I am still a single person, so please don’t hope for a fancy interactive guide but rather a nice webview that explains some features. And since this is something that all 4446 users will see, I hope that you can help me: I will publish the tutorial here and ask you for comments whether you consider it appropriate, whether there are spelling mistakes or some sentence has a bad structure (years of teaching computer science in german leaves its traces).

So, let’s start with Part 1. It is complete apart from two screenshots of the Presets/Demo menu (I will rename it to “Demo” in the next version) and the Favorites menu – in both there is some polishing going on.

EDIT: Since it requires a lot of work to keep the tutorial up-to-date, please use the following link: Fractview Tutorial on Github

 

— Karl

Posted in Updates | Leave a comment

3.2.1… update!

Hello, just rolling out a minor update and although it only brings minor changes, it should be a major relief for most.

So, what changed? First, bug fixes.

Some small issues like the cancel button on the waiting dialog when you save the image. But there were some annoying crashes that should be fixed now:

Crash when image size is too large

The app does not restrict the image size, and I keep it this way. But there were two bugs that caused a crash either when the image was larger that what the UI can handle (around 8000×6000) or when there was not enough memory available.

Both should be gone. On my Nexus 5X, I now can try to set the image size to 90000×50000 and a friendly error message tells me that this is too much instead of a crash. The largest I get right after starting the app is 9000×5000 (no kidding) which slows down the UI a lot but it works. I’ll attach an image as a proof. Image sizes around 4000×3000 right after start should not be a problem. I guess that if the app runs for some time or you run memory consuming background apps, the app might not match these numbers, but at least, you now can try without risking a crash.

Crash when saving a palette with an empty name

Second, if you saved a palette with an empty name, the palette editor crashed when you wanted to load one. This should not happen anymore.

Crash when entering a too large integer

And third, another annoying bug, if you enter an integer with more than 8 digits, the app crashes. The crash is gone. There will be some further update on this topic in the future though.

New features: UI Settings and fractal types

There are two new options in UI-Settings: “Confirm zoom” means that you have to tap the image to confirm your scale setting, and “Deactivate zoom” deactivates all interactions with the fractal view.

Also, I added some perpendicular fractal types.

What’s next?

There are some things in progress: back up all favorites, allow to add a favorite when you save an image, and the underlying compiler for my little program language receives a lot of attention currently. Apart from that I have a lot of ideas, and I hope I will find the time to implement some of them. On my GitHub page you can find a list of issues that I want to fix. As usual, please tell me if you miss anything.

Btw some statistics: until October my app had around 300 users. Around Christmas the number skyrocketed beyond 2000. Now there are 4400 active users and great reviews. Thanks to all of you, also for your feedback and reviews.
Enjoy the update,

Karl

P.S. here is the 9000×5000 picture. Set image size right after start up, be patient with the UI and not too much interaction. It took me 2 minutes to enter the filename 😀

Btw, it is a zoom into the MinMax Orbit Trap with the trapfn “dist(z, znext)”.

a.png

Posted in Updates | 5 Comments

About syntax trees and dynamic binding/polymorphism

I am interested in Parsers, Syntax trees, term rewriting and all that. And whenever I implement
some syntax tree, I stumble across one programming problem: We all know of the advantages of polymorphism over instanceof in object oriented programming languages like Java, right? And we all know the numerous examples of double-dispatch to illustrate how to overcome limitations due to static binding of overloaded methods. Right?

But what do I do if I want to implement a syntax tree that automatically performs simple calculations? This was a concrete problem in Meelan. Since I am currently redesigning the compiler, I am thinking about this problem again for the millionth time, but this time I want to do it systematically.

Assume the expression x + (2 + 1). An abstract syntax tree (instance of some trait/interface ITree) would create the following tree:

branching[op[+], id[x], branching[op[+], int[2], int[1]]]

The numbers would be instances of some class IntLeaf and x of some IdLeaf.

(In the following I use a pseudo code mix of Scala, Java, Groovy, C++ etc…)

interface ITree {
    ITree add(ITree other) {
        return new Branch(this, other) 
        // create new node with left=this and right=other
    }
}

class Branch: ITree(ITree l, r) {}    

In order to get the desired behaviour, that two IntLeafs are implicitly added, I could create a new node as follows:

// in IntLeaf:

public IntLeaf add(ITree that) {
    if(that instanceof IntLeaf) {
        return new IntLeaf(this.value + ((IntLeaf) that).value);
    } else {
        return super.add(that);
    }
} 

But instanceof is ugly [citation needed], for the very least, as soon as I add a RealLeaf for real values (doubles or floats) things get extremely messy. To be honest, dynamic binding is great…

Dynamic binding

… because there I simply write things like this and it works out-of-the-box.

interface ITree {
    ITree add(ITree other)
        return new Branch(this, other)
}

class Leaf: ITree {} // for stuff like IDs.

class IntLeaf: ITree {
    int value

    IntLeaf add(IntLeaf other)
        return new IntLeaf(this.value + other.value)
}

But with static binding, things are more complex…

Static binding

For static binding (for overloaded methods), considering all the possibilities, this is the best I could come up with: Create a visitor interface. For IntLeaf use a special subclass of visitor, and the visitor interface is aware that there are IntLeafs around:

interface ITree {
    createVisitor() {
        return new Visitor(this)
    }

    ITree add(ITree other)
        other.visitor().visitAdd(this)

}

class Branch: ITree(ITree l, r) {}

class Leaf: ITree {}

class IntLeaf: ITree {
    int value

    createVisitor() {
        return new IntVisitor(this)
    }

    ITree add(ITree other)
        other.visitor().visitAddInt(this)
}

class Visitor(ITree tree) {
    visitAdd(ITree other) {
        return new Branch(tree, other)
    }

    visitAddInt(IntLeaf intLeaf) {
        return visit(intLeaf)
    }

}

class IntVisitor(IntLeaf tree) : Visitor(tree) {
    visitAddInt(IntLeaf intLeaf) {
        return new IntLeaf(intLeaf.value + tree.value);
    }
}

39 lines compared to 13 if we had dynamic binding. But not that bad I guess (or is it just my brain that is already used to such messy things?)

Make operations more generic

Let’s make things more generic. Instead of add I use a method eval that takes some operator and the arguments for this operator. How would things change? Not that much to be honest. It stays pretty much the same.

Dynamic binding

interface ITree {
    ITree eval(Op op, ITree other)
        return new Branch(op, this, other)
}

class IntLeaf: ITree {
    int value

    IntLeaf eval(Op op, IntLeaf other) {
        // disclaimer: this is how it would work if java/C++ etc.. was fully dynamic
        return op(this.value + other.value)
    }
}

Static binding

In fact, this part is remarkably painless.

interface ITree {
    createVisitor() {
        return new Visitor(this)
    }

    ITree eval(Op op, ITree other)
        other.visitor().visit(op, this)
}

class Branch: ITree(ITree l, r) {}

class Leaf: ITree {}

class IntLeaf: ITree {
    int value

    createVisitor() {
        return new IntVisitor(this)
    }

    ITree eval(Op op, ITree other)
        other.visitor().visitInt(Op op, this)
}

class Visitor(ITree tree) {
    visit(Op op, ITree other) {
        return new Branch(op, tree, other)
    }

    visitInt(Op op, IntLeaf intLeaf) {
        return visit(op, intLeaf)
    }
}

class IntVisitor(IntLeaf tree) : Visitor(tree) {
    visitInt(Op op, IntLeaf intLeaf) {
        return op(intLeaf.value + tree.value);
    }
}

Add another type Real

Now, let’s make things messy. In fractview there are further types, Real and Cplx. Let’s add Real for starters. Observe that we can easily add integers and reals (doubles/floats) and obtain a real. I could use some interface like RealLike to indicate that IntLeaf could also return a real value, maybe I will, but I rather want to focus on polymorphism here.

Dynamic binding

I guess people who claim that duck typing has too much drawbacks should reconsider…

interface ITree {
    ITree eval(Op op, ITree other) {
        return new Branch(op, this, other)
    }
}

class IntLeaf: ITree {
    int value

    IntLeaf eval(Op op, IntLeaf other) {
        return op(this.value + other.value)
    }

    RealLeaf eval(Op op, RealLeaf other) {
        return op(this.value + other.value)
    }
}

class RealLeaf: ITree {
    double value

    RealLeaf eval(Op op, RealLeaf other) {
        return op(this.value + other.value)
    }

    RealLeaf eval(Op op, IntLeaf other) {
        return op(this.value + other.value)
    }
}

28 lines.

Static binding, using instanceof

How to do things in a programming language with static binding? First, back to instanceof: I have to modify the eval method in IntLeaf. Easy in Scala with pattern matching, yet I somehow think that once I’ve implemented a method I want to be
done with it and keep it the way it is.

// in IntLeaf:

public IntLeaf eval(Op op, ITree that) {
    if(that instanceof IntLeaf) {
        return op(this.value, ((IntLeaf) that).value);
    } else if(that instanceof RealLeaf) {
        return op(this.value, ((RealLeaf) that).value);
    } else {
        return super.add(that);
    }
} 

And RealLeaf will look similar. Not that nice…

Maybe things get better if I use some RealLike interface for everything that can be converted to a real. But this is not on what I want to focus here now.

Static binding

Adding a real type is remarkably painless. The modifications to be done are clean and straight forward.

interface ITree {
    createVisitor() {
        return new Visitor(this)
    }

    ITree eval(Op op, ITree other) {
        other.visitor().visit(op, this)
    }
}

class Branch: ITree(ITree l, r) {}

class Leaf: ITree {}

class IntLeaf: ITree {
    int value

    createVisitor() {
        return new IntVisitor(this)
    }

    ITree eval(Op op, ITree other) {
        other.visitor().visitInt(Op op, this)
    }
}

class RealLeaf: ITree {
    double value

    createVisitor() {
        return new RealVisitor(this)
    }

    ITree eval(Op op, ITree other)
        other.visitor().visitReal(Op op, this)
}

class Visitor(ITree tree) {
    visit(Op op, ITree other) {
        return new Branch(op, tree, other)
    }

    visitInt(Op op, IntLeaf leaf) {
        return visit(op, leaf)
    }

    visitReal(Op op, RealLeaf leaf) {
        return visit(op, leaf)
    }
}

class IntVisitor(IntLeaf tree) : Visitor(tree) {
    visitInt(Op op, IntLeaf leaf) {
        return op(leaf.value + tree.value);
    }

    visitReal(Op op, RealLeaf leaf) {
        return op(leaf.value + tree.value);
    }
}

class RealVisitor(RealLeaf tree) : Visitor(tree) {
    visitInt(Op op, IntLeaf intLeaf) {
        return op(leaf.value + tree.value);
    }

    visitReal(Op op, RealLeaf leaf) {
        return op(leaf.value + tree.value);
    }
}

70 lines. Before the ratio was 3/1, now it is a bit less.

(this article will be extended and edited during development of Meelan)

Posted in Meelan, Programming | Leave a comment

On Fold: Part 3 (Lake)

So far, in this series only dealt with points for which the fractal diverges, i.e. the values of the orbit are unbounded. But this does not capture the lake of fractals like the Mandelbrot Set, or Newton and Nova fractals. Hence, this part contains a closer look onto the fold preset and convergent fractals.

In the Newton fractal, the formula can be quite complex and long since it involves the first derivation. In general it is “z – f / f'” where f is some function. One very common function is “z^3 – 1”. Using this function, the orbit approximates one complex third root of 1 where the starting point is the point on the complex plane. For some starting points, the sequence approaches its limit faster and for others slower. 

Usually, such fractals are colorized using the argument (angle) of the last point in the orbit or using the number of iterations until “znext – z” drops below a certain value (epsilon). In the following image, both colorizations are combined:

Newton set of z^3 – 1, lake transfer is “arcnorm znext : log(1 + i)”

Similar to the Mandelbrot Set, using the iterations creates a non-continuous color gradient. Since “znext-z” will converge to 0, we can sum up “rad(znext -z)” using fold instead. Hence, let’s use for foldfn “rad(znext – z) + foldvalue”, and we create a nice 3d effect by choosing “foldvalue.x” for lake value (yes, definitely gonna rename it to lake height or lake depth) and lake transfer should simply be “arcnorm znext”.

I will write more on value and transfer in a separate article.

Now, it would be nice to have some branching feature, some smooth stripes. In the previous parts this was done my multiplying the addend in foldfn by some value that depends on the angle of znext. Here we can do the same, though we have to use “arc(znext – z)”. Try “arcnorm(znext – z) * rad(znext – z) + foldvalue”. It creates lovely images. In the following image I used “arcnorm(znext – z) / (6 + / rad(znext – z)) + foldvalue” (this way I smoothened fast changes in the orbit) and for transfer “arcnorm znext : foldvalue.x”:

I sometimes really think that random color palette generation is the best feature of fractview…. 

Ifthe stripes have too sharp edges for your taste, you can smoothen them using cosine. In the next one, I used “cos 6 arc(znext – z) / (12 + / rad(znext – z)) + foldvalue” for foldfn:

Don’t forget to experiment with 3d effects.

Finally, another recommendation: Experiment with the complex logarithm. Its components are the logarithm of the absolute value, and the argument. Combining both creates amazing images like the following:

foldfn is “log(znext – z) / (12 + / rad(znext – z)) + foldvalue”, lakevalue is “foldvalue.x” and lake transfer is simply “foldvalue”.

Happy fractaling,

Karl

Posted in Uncategorized | Leave a comment

On Fold (Part 2: Geometric functions)

I left off with two nice pictures that used a bit more complex fold functions. So, a natural question is, what other functions can we use? In the backing programming language I added some functions that represent graph primitives. These functions are “circle(center, radius, point)”, “line(p1, p2, point)” and “box(lu, rl, point)”. “line” is a line through p1 and p2, “box” ist the rectangle with left upper corner lu and right lower corner rl. The functions return the distance of “point” from these graphic primitives. You can use the complex-function preset to visualize them. For “min(circle(1:0, 1, c), box(-1.5:1, -0.5:0, c), line(-1:-1, 0:-1.5, c))” you obtain the following function plot:

What happens if we use these primitives in the fold preset? First, remember that the function that we sum up in fold should converge to 0 if z increases to obtation a continuous gradient. So, let’s use the reciprocate of the distance from a primitive, and let’s start our experiment with a box: foldfn is “/box(-2:-1.5, 2:1.5, znext) + foldvalue” and the result is the following:

Simple and astonishing results. 

In the next one I used a simple line. It is a zoom into the seahorse valley.

So, there is a lot of room for experiments to combine boxes with circle and lines. For the green antenna image on DeviantArt I used one simple circle btw, apart from that, no magic. 

In the third part I will show you how to make even more use of this by using different bailoutvalues (I will definitely rename that to bailoutheigth in some future release) than bailouttransfers. Good night and finally, the code of this final picture.

{
  "scale": [
    1.5967631071452336E-5,
    4.3266139225037626E-4,
    -4.3266139225037626E-4,
    1.5967631071452336E-5,
    -0.7429302714898326,
    -0.1277124882391435
  ],
  "source": [
    "\/\/ Fold",
    "var x int, y int, color int;",
    "",
    "",
    "extern maxdepth int = 120;",
    "",
    "\/\/ some further arguments",
    "extern juliaset bool = false;",
    "extern juliapoint cplx =


-0.8:0.16;", "", "\/\/ c: coordinates, breakcondition: a function whether we should stop,", "\/\/ value: a real variable to return some kind of value", "\/\/ used in 3d-types for the height.", "\/\/ returns a quat representing the color", "func escapetime(c, breakcondition) {", " var i int = 0,", " p cplx = juliapoint if juliaset else c,", " zlast cplx = 0,", " z cplx,", " znext cplx = 0;", "", " extern mandelinit expr = \"0\";", "", " z = c if juliaset else mandelinit;", "", " extern function expr = \"mandelbrot(z, p)\";", "", " var color quat;", "", " while {", " znext = function;", " not breakcondition(i, znext, z, zlast, c, p, color)", " } do {", " \/\/ advance to next values", " zlast = z;", " z = znext;", " }", "", " \/\/ return color", " color", "}", "", "\/\/ everything that is drawn must have a get_color-function.", "", "\/\/ c = coordinates (scaled)", "\/\/ value is a real variable for z-information in 3D", "\/\/ but also otherwise convenient to separate drawing", "\/\/ algorithm from transfer", "\/\/ returns color.", "func get_color(c, value) {", "", " \/\/ if the fractal accumulates some values", " \/\/ like in traps or addends, here is a got place to do it.", " extern foldinit expr = \"0\";", " var foldvalue cplx = foldinit;", "", " func breakcondition(i, znext, z, zlast, c, p, color) {", "", " extern foldfn expr = \"\/cosh rad znext + foldvalue\";", "", " func bailoutcolor() {", " extern bailout real = 65536;", " extern max_power real = 2; \/\/ just for compatibility", "", " \/\/ the next ones are only used in 3d-fractals", " extern bailoutvalue expr = \"log(1 + foldvalue.x)\";", " value = bailoutvalue ;", "", " extern bailouttransfer expr = \"value\";", "", " extern bailoutpalette palette = [", " [#0f8, #080, #ff8, #f80, #f20, #008]];", "", " color = bailoutpalette bailouttransfer", " }", "", " func lakecolor() {", " extern epsilon real = 1e-9;", "", " \/\/ the next ones are only used in 3d-fractals", " extern lakevalue expr = \"log(1 + rad znext)\";", " value = lakevalue;", "", " extern laketransfer expr = \"arcnorm znext : value\";", "", " extern lakepalette palette = [", " [#000, #000, #000, #000, #000, #000],", " [#0f8, #080, #ff8, #f80, #f20, #008],", " [#4fa, #3a3, #ffa, #fa3, #f63, #33a],", " [#fff, #fff, #fff, #fff, #fff, #fff]];", "", " color = lakepalette laketransfer", " }", "", " { lakecolor() ; true } if not next(i, maxdepth) else", " true if radrange(znext, z, bailout, epsilon, bailoutcolor(), lakecolor()) else", " { foldvalue = foldfn; false }", " }", "", " escapetime(c, breakcondition)", "}", "", "", "\/\/ ******************************************", "\/\/ * Next are just drawing procedures. They *", "\/\/ * should be the same for all drawings. *", "\/\/ ******************************************", "", "extern supersampling bool = false;", "extern light bool = false;", "", "\/\/ drawpixel for 2D", "func drawpixel_2d(x, y) {", " var c cplx = map(x, y);", " var value real;", " get_color(c, value) \/\/ value is not used", "}", "", "\/\/ drawpixel for 3D", "func drawpixel_3d(x, y) {", " var c00 cplx = map(x, y),", " c10 cplx = map(x + 1, y + 0.5),", " c01 cplx = map(x + 0.5, y + 1);", "", " var h00 real, h10 real, h01 real; \/\/ heights", "", " \/\/ color is already kinda super-sampled", " var color = (get_color(c00, h00) + get_color(c10, h10) + get_color(c01, h01)) \/ 3;", "", " \/\/ get height out of value", " func height(value) {", " extern valuetransfer expr = \"value\";", " valuetransfer", " }", "", " h00 = height h00; h01 = height h01; h10 = height h10;", "", " \/\/ get the normal vector (cross product)", " var xp = c10 - c00, xz = h10 - h00;", " var yp = c01 - c00, yz = h01 - h00;", "", " var np cplx = (xp.y yz - xz yp.y) : (xz yp.x - xp.x yz);", " var nz real = xp.x yp.y - xp.y yp.x;", "", " \/\/ normalize np and nz", " var nlen = sqrt(rad2 np + sqr nz);", " np = np \/ nlen; nz = nz \/ nlen;", "", " \/\/ get light direction", " extern lightvector cplx = -0.667 : -0.667; \/\/ direction from which the light is coming", " def lz = sqrt(1 - sqr re lightvector - sqr im lightvector); \/\/ this is inlined", "", " \/\/ Lambert's law.", " var cos_a real = dot(lightvector, np) + lz nz;", "", " \/\/ diffuse reflexion with ambient factor", " extern lightintensity real = 1;", " extern ambientlight real = 0.5;", "", " \/\/ if lumen is negative it is behind,", " \/\/ but I tweak it a bit for the sake of the looks:", " \/\/ cos_a = -1 (which is super-behind) ==> 0", " \/\/ cos_a = 0 ==> ambientlight", " \/\/ cos_a = 1 ==> lightintensity", "", " \/\/ for a mathematically correct look use the following:", " \/\/ if cos_a 0 then", " color.a = color.a + 100 * specularintensity * spec_refl ^ shininess;", "", " color", "}", "", "func do_pixel(x, y) {", " \/\/ two or three dimensions?", " def drawpixel = drawpixel_3d if light else drawpixel_2d;", "", " func drawaapixel(x, y) {", " 0.25 (", " drawpixel(x - 0.375, y - 0.125) +", " drawpixel(x + 0.125, y - 0.375) +", " drawpixel(x + 0.375, y + 0.125) +", " drawpixel(x - 0.125, y + 0.375)", " );", " }", "", " \/\/ which function to apply?", " def fn = drawpixel if not supersampling else drawaapixel;", "", " color = lab2int fn(x, y)", "}", "", "\/\/ and finally call the draing procedure", "do_pixel(x, y)" ], "arguments": { "ints": { "maxdepth": 1200 }, "cplxs": { "lightvector": [ 0.667, -0.667 ] }, "exprs": { "valuetransfer": "value\/999", "lakevalue": "0", "foldfn": "\/line(1:1, 0:0, znext) + foldvalue", "bailoutvalue": "log(5 + foldvalue.x)" }, "bools": { "light": true }, "palettes": { "bailoutpalette": { "height": 1, "width": 8, "colors": [ -16711621, -15, -1, -7783841, -4426, -12713896, -7143390, -1074 ] } } } }
Posted in Uncategorized | 1 Comment

On Fold (Part 1: Basics)

Some of you might know that I publish some of my fractals on DeviantArt​. I made a lovely picture recently:

And another one (originally there was another picture, but it was created with the branching preset, sorry for the mix-up):

I created both images with the Fold-preset which is so powerful that I decided to dedicate a few articles to it (okay, I admit, it was Matthew’s suggestion who hosts a lovely collection on DeviantArt).

So, here we go:

Basics

You might know how fractals like the Mandelbrot Set are created: There is a function (sqr z + c for the Mandelbrot Set) that generates a sequence of numbers (called the orbit) using some point on the complex plane. Based on some property of this orbit you pick a color for the current point. In the most common image of the mandelbrot set, the color is determined by the first index in the orbit that exceeds a certain threshold, commonly called bailout:

There are some methods to get rid of these stripes (Wikipedia offers more information) and the most interesting one IMHO is to calculate a value based on each element in the orbit and sum up the results. This resembles the fold function in functional languages, also sometimes called reduction or aggregate. In fractview, this is exactly the fold preset.

By picking a function that converges to 0 for larger values and setting the threshold high we obtain a smooth (continuous) colorization. Let’s pick the fold preset. For each point in the orbit let’s use 1/|z|. So: Fold preset, let’s use 256 for bailout, and as foldfn we use /rad znext + foldvalue, ie the aggregated values so far (foldvalue) plus 1/rad of the current value in the orbit. The result is lovely, also for deeper zooms:

Don’t forget to try the 3D effects.

We can use any function we want. For instance, instead of 1/|z| you can use e^-z. This is called “exponential smoothing” in the literature and also in fractview (with some more tweaks).  You also obtain lovely results by using the angle of the current value in the orbit. In the following image I used “arcnorm znext / rad znext + foldvalue”. Since the final value of foldvalue might be negative, you should use the absolute value in the logarithm in bailout value, so bailout value should be “log(1 + abs foldvalue.x)”. As you can see fold uses complex values while bailoutvalue (I definitely gonna rename it to bailoutheigth) must be real. Hence we only use the real part of foldvalue which is “foldvalue.x”. You may also sum up complex values in foldfn though.

Now look at the result (with 3D):

Lovely, right? You find the source for this last image at the bottom of this post. But finally, I want to show you how branching works in connection with fold: we are actually close. Just multiply arc znext by some number and use the cosine of the result. Eg “cos 12 arc znext / rad znext + foldvalue”. This is the whole magic behind the lovely fold-branch preset and the following image (a zoom into the Phoenix set):

I think this is a nice final fractal for today. 

Good night,

Karl

P.S.: I wrote most of this on my phone in the subway and I hardly proofread. If you find mistakes, just tell me, I won’t get mad at you 😀

P.P.S.: Paste the following into Fractview to reproduce the Mandelbrot set:

{
"scale": [
1.2621248853931866,
0,
0,
1.2621248853931866,
-0.75,
0
],
"source": [
"\/\/ Fold",
"var x int, y int, color int;",
"",
"",
"extern maxdepth int = 120;",
"",
"\/\/ some further arguments",
"extern juliaset bool = false;",
"extern juliapoint cplx = -0.8:0.16;",
"",
"\/\/ c: coordinates, breakcondition: a function whether we should stop,",
"\/\/ value: a real variable to return some kind of value",
"\/\/ used in 3d-types for the height.",
"\/\/ returns a quat representing the color",
"func escapetime(c, breakcondition) {",
" var i int = 0,",
" p cplx = juliapoint if juliaset else c,",
" zlast cplx = 0,",
" z cplx,",
" znext cplx = 0;",
"",
" extern mandelinit expr = \"0\";",
"",
" z = c if juliaset else mandelinit;",
"",
" extern function expr = \"mandelbrot(z, p)\";",
"",
" var color quat;",
"",
" while {",
" znext = function;",
" not breakcondition(i, znext, z, zlast, c, p, color)",
" } do {",
" \/\/ advance to next values",
" zlast = z;",
" z = znext;",
" }",
"",
" \/\/ return color",
" color",
"}",
"",
"\/\/ everything that is drawn must have a get_color-function.",
"",
"\/\/ c = coordinates (scaled)",
"\/\/ value is a real variable for z-information in 3D",
"\/\/ but also otherwise convenient to separate drawing",
"\/\/ algorithm from transfer",
"\/\/ returns color.",
"func get_color(c, value) {",
"",
" \/\/ if the fractal accumulates some values",
" \/\/ like in traps or addends, here is a got place to do it.",
" extern foldinit expr = \"0\";",
" var foldvalue cplx = foldinit;",
"",
" func breakcondition(i, znext, z, zlast, c, p, color) {",
"",
" extern foldfn expr = \"\/cosh rad znext + foldvalue\";",
"",
" func bailoutcolor() {",
" extern bailout real = 65536;",
" extern max_power real = 2; \/\/ just for compatibility",
"",
" \/\/ the next ones are only used in 3d-fractals",
" extern bailoutvalue expr = \"log(1 + foldvalue.x)\";",
" value = bailoutvalue ;",
"",
" extern bailouttransfer expr = \"value\";",
"",
" extern bailoutpalette palette = [",
" [#0f8, #080, #ff8, #f80, #f20, #008]];",
"",
" color = bailoutpalette bailouttransfer",
" }",
"",
" func lakecolor() {",
" extern epsilon real = 1e-9;",
"",
" \/\/ the next ones are only used in 3d-fractals",
" extern lakevalue expr = \"log(1 + rad znext)\";",
" value = lakevalue;",
"",
" extern laketransfer expr = \"arcnorm znext : value\";",
"",
" extern lakepalette palette = [",
" [#000, #000, #000, #000, #000, #000],",
" [#0f8, #080, #ff8, #f80, #f20, #008],",
" [#4fa, #3a3, #ffa, #fa3, #f63, #33a],",
" [#fff, #fff, #fff, #fff, #fff, #fff]];",
"",
" color = lakepalette laketransfer",
" }",
"",
" { lakecolor() ; true } if not next(i, maxdepth) else",
" true if radrange(znext, z, bailout, epsilon, bailoutcolor(), lakecolor()) else",
" { foldvalue = foldfn; false }",
" }",
"",
" escapetime(c, breakcondition)",
"}",
"",
"",
"\/\/ ******************************************",
"\/\/ * Next are just drawing procedures. They *",
"\/\/ * should be the same for all drawings. *",
"\/\/ ******************************************",
"",
"extern supersampling bool = false;",
"extern light bool = false;",
"",
"\/\/ drawpixel for 2D",
"func drawpixel_2d(x, y) {",
" var c cplx = map(x, y);",
" var value real;",
" get_color(c, value) \/\/ value is not used",
"}",
"",
"\/\/ drawpixel for 3D",
"func drawpixel_3d(x, y) {",
" var c00 cplx = map(x, y),",
" c10 cplx = map(x + 1, y + 0.5),",
" c01 cplx = map(x + 0.5, y + 1);",
"",
" var h00 real, h10 real, h01 real; \/\/ heights",
"",
" \/\/ color is already kinda super-sampled",
" var color = (get_color(c00, h00) + get_color(c10, h10) + get_color(c01, h01)) \/ 3;",
"",
" \/\/ get height out of value",
" func height(value) {",
" extern valuetransfer expr = \"value\";",
" valuetransfer",
" }",
"",
" h00 = height h00; h01 = height h01; h10 = height h10;",
"",
" \/\/ get the normal vector (cross product)",
" var xp = c10 - c00, xz = h10 - h00;",
" var yp = c01 - c00, yz = h01 - h00;",
"",
" var np cplx = (xp.y yz - xz yp.y) : (xz yp.x - xp.x yz);",
" var nz real = xp.x yp.y - xp.y yp.x;",
"",
" \/\/ normalize np and nz",
" var nlen = sqrt(rad2 np + sqr nz);",
" np = np \/ nlen; nz = nz \/ nlen;",
"",
" \/\/ get light direction",
" extern lightvector cplx = -0.667 : -0.667; \/\/ direction from which the light is coming",
" def lz = sqrt(1 - sqr re lightvector - sqr im lightvector); \/\/ this is inlined",
"",
" \/\/ Lambert's law.",
" var cos_a real = dot(lightvector, np) + lz nz;",
"",
" \/\/ diffuse reflexion with ambient factor",
" extern lightintensity real = 1;",
" extern ambientlight real = 0.5;",
"",
" \/\/ if lumen is negative it is behind,",
" \/\/ but I tweak it a bit for the sake of the looks:",
" \/\/ cos_a = -1 (which is super-behind) ==> 0",
" \/\/ cos_a = 0 ==> ambientlight",
" \/\/ cos_a = 1 ==> lightintensity",
"",
" \/\/ for a mathematically correct look use the following:",
" \/\/ if cos_a < 0 then cos_a = 0;",
" \/\/ color.a = color.a * (ambientlight + lightintensity lumen);",
"",
" def d = lightintensity \/ 2; \/\/ will be inlined later",
"",
" \/\/ Change L in Lab-Color",
" color.a = color.a (((d - ambientlight) cos_a + d) cos_a + ambientlight);",
"",
" \/\/ Next, specular reflection. Viewer is always assumed to be in direction (0,0,1)",
" extern specularintensity real = 1;",
"",
" extern shininess real = 8;",
"",
" \/\/ r = 2 n l - l; v = 0:0:1",
" var spec_refl = 2 cos_a nz - lz;",
"",
" \/\/ 100 because L in the Lab-Model is between 0 and 100",
" if spec_refl > 0 then",
" color.a = color.a + 100 * specularintensity * spec_refl ^ shininess;",
"",
" color",
"}",
"",
"func do_pixel(x, y) {",
" \/\/ two or three dimensions?",
" def drawpixel = drawpixel_3d if light else drawpixel_2d;",
"",
" func drawaapixel(x, y) {",
" 0.25 (",
" drawpixel(x - 0.375, y - 0.125) +",
" drawpixel(x + 0.125, y - 0.375) +",
" drawpixel(x + 0.375, y + 0.125) +",
" drawpixel(x - 0.125, y + 0.375)",
" );",
" }",
"",
" \/\/ which function to apply?",
" def fn = drawpixel if not supersampling else drawaapixel;",
"",
" color = lab2int fn(x, y)",
"}",
"",
"\/\/ and finally call the draing procedure",
"do_pixel(x, y)"
],
"arguments": {
"ints": {
"maxdepth": 1200
},
"reals": {
"bailout": 1024
},
"cplxs": {
"lightvector": [
0.667,
-0.667
]
},
"exprs": {
"valuetransfer": "value",
"foldinit": "0",
"lakevalue": "0",
"foldfn": "arcnorm znext \/ rad znext + foldvalue",
"bailoutvalue": "log(1 + abs foldvalue.x)"
},
"bools": {
"light": true
},
"palettes": {
"bailoutpalette": {
"height": 1,
"width": 5,
"colors": [
-16777216,
-65536,
-256,
-1,
-16776961
]
}
}
}
}
Posted in Fractals | Tagged , | Leave a comment