mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Proofreading
This commit is contained in:
parent
a6194763d1
commit
37e2992865
@ -22,15 +22,14 @@ import banner from '../banner.png'
|
|||||||
|
|
||||||
<!-- truncate -->
|
<!-- truncate -->
|
||||||
|
|
||||||
I don't remember exactly when I first learned about fractal flames, but I do remember being entranced by the images they created.
|
I don't remember when exactly I first learned about fractal flames, but I do remember being entranced by the images they created.
|
||||||
I also remember their unique appeal to my young engineering mind; this was an art form I could participate in.
|
I also remember their unique appeal to my young engineering mind; this was an art form I could participate in.
|
||||||
|
|
||||||
The original [Fractal Flame Algorithm paper](https://flam3.com/flame_draves.pdf) describing their structure was too much
|
The [Fractal Flame Algorithm paper](https://flam3.com/flame_draves.pdf) describing their structure was too much
|
||||||
for me to handle at the time (I was ~12 years old), so I was content to play around and enjoy the pictures.
|
for me to handle at the time (I was ~12 years old), so I was content to play around and enjoy the pictures.
|
||||||
But the desire to understand it stuck around. Now, with a graduate degree under my belt, I want to revisit it
|
But the desire to understand it stuck around. Now, with a graduate degree under my belt, I wanted to revisit it.
|
||||||
and try to make some progress.
|
|
||||||
|
|
||||||
This guide is my attempt to explain how fractal flames work in a way that younger me — and others interested in the art —
|
This guide is my attempt to explain how fractal flames work so that younger me — and others interested in the art —
|
||||||
can understand without too much prior knowledge.
|
can understand without too much prior knowledge.
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -42,7 +41,7 @@ This post covers section 2 of the Fractal Flame Algorithm paper
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
As mentioned, fractal flames are a type of "[iterated function system](https://en.wikipedia.org/wiki/Iterated_function_system),"
|
As mentioned, fractal flames are a type of "[iterated function system](https://en.wikipedia.org/wiki/Iterated_function_system),"
|
||||||
or IFS. The formula for an IFS is short, but takes some time to unpack:
|
or IFS. The formula for an IFS is short, but takes some time to work through:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
S = \bigcup_{i=0}^{n-1} F_i(S)
|
S = \bigcup_{i=0}^{n-1} F_i(S)
|
||||||
@ -52,7 +51,7 @@ $$
|
|||||||
|
|
||||||
First, $S$. $S$ is the set of points in two dimensions (in math terms, $S \in \mathbb{R}^2$)
|
First, $S$. $S$ is the set of points in two dimensions (in math terms, $S \in \mathbb{R}^2$)
|
||||||
that represent a "solution" of some kind to our equation.
|
that represent a "solution" of some kind to our equation.
|
||||||
Our goal is to find all points in $S$, plot them, and display that image.
|
Our goal is to find all the points in $S$, plot them, and display that image.
|
||||||
|
|
||||||
For example, if we say $S = \{(0,0), (1, 1), (2, 2)\}$, there are three points to plot:
|
For example, if we say $S = \{(0,0), (1, 1), (2, 2)\}$, there are three points to plot:
|
||||||
|
|
||||||
@ -67,20 +66,17 @@ export const simpleData = [
|
|||||||
<VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
|
<VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
|
||||||
</VictoryChart>
|
</VictoryChart>
|
||||||
|
|
||||||
However, this is a pretty boring image. With fractal flames, rather than listing individual points,
|
With fractal flames, rather than listing individual points, we use functions to describe the solution.
|
||||||
we use functions to describe which points are part of the solution. This means there are an infinite
|
This means there are an infinite number of points, but if we find _enough_ points to plot, we get a nice picture.
|
||||||
number of points, but if we find _enough_ points to plot, we'll end up with a nice picture.
|
And if the functions change, the solution also changes, and we get something new.
|
||||||
And if the functions change, the solution also changes, and we get a new picture.
|
|
||||||
|
|
||||||
### Transform functions
|
### Transform functions
|
||||||
|
|
||||||
Second, the $F_i(S)$ functions, also known as "transforms."
|
Second, the $F_i(S)$ functions, also known as "transforms."
|
||||||
At their most basic, each $F_i$ takes in a 2-dimensional point and gives back a new point
|
Each transform takes in a 2-dimensional point and gives a new point back
|
||||||
(in math terms, $F_i \in \mathbb{R}^2 \rightarrow \mathbb{R}^2$).
|
(in math terms, $F_i \in \mathbb{R}^2 \rightarrow \mathbb{R}^2$).
|
||||||
While you could theoretically use any function, we'll start with a specific kind of function
|
While you could theoretically use any function, we'll focus on a specific kind of function
|
||||||
called an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)."
|
called an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)." Every transform uses the same formula:
|
||||||
|
|
||||||
The general form of an affine transformation is:
|
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_i(a_i x + b_i y + c_i, d_i x + e_i y + f_i)
|
F_i(a_i x + b_i y + c_i, d_i x + e_i y + f_i)
|
||||||
@ -91,8 +87,8 @@ import CodeBlock from '@theme/CodeBlock'
|
|||||||
|
|
||||||
<CodeBlock language="typescript">{transformSource}</CodeBlock>
|
<CodeBlock language="typescript">{transformSource}</CodeBlock>
|
||||||
|
|
||||||
The parameters ($a_i$, $b_i$, etc.) are values we get to choose.
|
The parameters ($a_i$, $b_i$, etc.) are values we choose.
|
||||||
For example, we can represent a "shift" function like this:
|
For example, we can define a "shift" function like this:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
\begin{align*}
|
\begin{align*}
|
||||||
@ -106,7 +102,7 @@ F_{shift}(x, y) &= (1 \cdot x + 0.5, 1 \cdot y + 1.5)
|
|||||||
\end{align*}
|
\end{align*}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
Applying this function to our original points will give us a new set of points:
|
Applying this transform to the original points gives us a new set of points:
|
||||||
|
|
||||||
import {applyCoefs} from "../src/transform"
|
import {applyCoefs} from "../src/transform"
|
||||||
|
|
||||||
@ -139,7 +135,7 @@ $$
|
|||||||
S = \bigcup_{i=0}^{n-1} F_i(S)
|
S = \bigcup_{i=0}^{n-1} F_i(S)
|
||||||
$$
|
$$
|
||||||
|
|
||||||
Or, to put it in English, we might say:
|
Or, in English, we might say:
|
||||||
|
|
||||||
> Our solution, $S$, is the union of all sets produced by applying each function, $F_i$,
|
> Our solution, $S$, is the union of all sets produced by applying each function, $F_i$,
|
||||||
> to points in the solution.
|
> to points in the solution.
|
||||||
@ -153,17 +149,13 @@ defining the mathematics of iterated function systems:
|
|||||||
> Furthermore, $S$ is compact and is the closure of the set of fixed points $s_{i_1...i_p}$
|
> Furthermore, $S$ is compact and is the closure of the set of fixed points $s_{i_1...i_p}$
|
||||||
> of finite compositions $F_{i_1...i_p}$ of members of $F$.
|
> of finite compositions $F_{i_1...i_p}$ of members of $F$.
|
||||||
|
|
||||||
:::note
|
|
||||||
I've tweaked the conventions of that paper slightly to match the Fractal Flame paper
|
|
||||||
:::
|
|
||||||
|
|
||||||
Before your eyes glaze over, let's unpack this:
|
Before your eyes glaze over, let's unpack this:
|
||||||
|
|
||||||
- **Furthermore, $S$ is [compact](https://en.wikipedia.org/wiki/Compact_space)...**: All points in our solution will be in a finite range
|
- **Furthermore, $S$ is [compact](https://en.wikipedia.org/wiki/Compact_space)...**: All points in our solution will be in a finite range
|
||||||
- **...and is the [closure](https://en.wikipedia.org/wiki/Closure_(mathematics)) of the set of [fixed points](https://en.wikipedia.org/wiki/Fixed_point_(mathematics))**:
|
- **...and is the [closure](https://en.wikipedia.org/wiki/Closure_(mathematics)) of the set of [fixed points](https://en.wikipedia.org/wiki/Fixed_point_(mathematics))**:
|
||||||
Applying our functions to points in the solution will give us other points that are in the solution
|
Applying our functions to points in the solution will give us other points that are in the solution
|
||||||
- **...of finite compositions $F_{i_1...i_p}$ of members of $F$**: By composing our functions (that is,
|
- **...of finite compositions $F_{i_1...i_p}$ of members of $F$**: By composing our functions (that is,
|
||||||
using the output of one function as input to the next function), we will arrive at the points in the solution
|
using the output of one function as input to the next), we will arrive at the points in the solution
|
||||||
|
|
||||||
Thus, by applying the functions to fixed points of our system, we will find the other points we care about.
|
Thus, by applying the functions to fixed points of our system, we will find the other points we care about.
|
||||||
|
|
||||||
@ -177,11 +169,11 @@ Thus, by applying the functions to fixed points of our system, we will find the
|
|||||||
algorithm demonstrates, we only need functions to be contractive _on average_. At worst, the system will
|
algorithm demonstrates, we only need functions to be contractive _on average_. At worst, the system will
|
||||||
degenerate and produce a bad image.
|
degenerate and produce a bad image.
|
||||||
|
|
||||||
Second, we're focused on $\mathbb{R}^2$ because we're generating images, but the Hutchinson paper
|
Second, we're focused on $\mathbb{R}^2$ because we're generating images, but the math
|
||||||
allows for arbitrary dimensions; you could also have 3-dimensional fractal flames.
|
allows for arbitrary dimensions; you could also have 3-dimensional fractal flames.
|
||||||
|
|
||||||
Finally, there's a close relationship between fractal flames and [attractors](https://en.wikipedia.org/wiki/Attractor).
|
Finally, there's a close relationship between fractal flames and [attractors](https://en.wikipedia.org/wiki/Attractor).
|
||||||
Specifically, the fixed points of $S$ act as attractors for the chaos game (explained in more detail below).
|
Specifically, the fixed points of $S$ act as attractors for the chaos game (explained below).
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
This is still a bit vague, so let's work through an example.
|
This is still a bit vague, so let's work through an example.
|
||||||
@ -200,7 +192,7 @@ $$
|
|||||||
|
|
||||||
### The chaos game
|
### The chaos game
|
||||||
|
|
||||||
Next, how do we find the "fixed points" mentioned earlier? The paper lays out an algorithm called the "[chaos game](https://en.wikipedia.org/wiki/Chaos_game)"
|
Now, how do we find the "fixed points" mentioned earlier? The paper lays out an algorithm called the "[chaos game](https://en.wikipedia.org/wiki/Chaos_game)"
|
||||||
that gives us points in the solution:
|
that gives us points in the solution:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
@ -220,8 +212,8 @@ The chaos game algorithm is effectively the "finite compositions of $F_{i_1..i_p
|
|||||||
|
|
||||||
Let's turn this into code, one piece at a time.
|
Let's turn this into code, one piece at a time.
|
||||||
|
|
||||||
First, we need to generate some random numbers. The "bi-unit square" is the range $[-1, 1]$,
|
To start, we need to generate some random numbers. The "bi-unit square" is the range $[-1, 1]$,
|
||||||
so we generate a random point using an existing API:
|
and we can do this using an existing API:
|
||||||
|
|
||||||
import biunitSource from '!!raw-loader!../src/randomBiUnit'
|
import biunitSource from '!!raw-loader!../src/randomBiUnit'
|
||||||
|
|
||||||
@ -235,14 +227,14 @@ import randintSource from '!!raw-loader!../src/randomInteger'
|
|||||||
|
|
||||||
### Plotting
|
### Plotting
|
||||||
|
|
||||||
Finally, implementing the `plot` function. This blog series is designed to be interactive,
|
Finally, implementing the `plot` function. This blog series is interactive,
|
||||||
so everything displays directly in the browser. As an alternative,
|
so everything displays directly in the browser. As an alternative,
|
||||||
software like `flam3` and Apophysis can "plot" by saving an image to disk.
|
software like `flam3` and Apophysis can "plot" by saving an image to disk.
|
||||||
|
|
||||||
To show the results, we'll use the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).
|
To see the results, we'll use the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).
|
||||||
This allows us to manipulate individual pixels in an image and display it on screen.
|
This allows us to manipulate individual pixels in an image and show it on screen.
|
||||||
|
|
||||||
First, we need to convert from Fractal Flame coordinates to pixel coordinates.
|
First, we need to convert from fractal flame coordinates to pixel coordinates.
|
||||||
To simplify things, we'll assume that we're plotting a square image
|
To simplify things, we'll assume that we're plotting a square image
|
||||||
with range $[0, 1]$ for both $x$ and $y$:
|
with range $[0, 1]$ for both $x$ and $y$:
|
||||||
|
|
||||||
@ -251,7 +243,7 @@ import cameraSource from "!!raw-loader!./cameraGasket"
|
|||||||
<CodeBlock language="typescript">{cameraSource}</CodeBlock>
|
<CodeBlock language="typescript">{cameraSource}</CodeBlock>
|
||||||
|
|
||||||
Next, we'll store the pixel data in an [`ImageData` object](https://developer.mozilla.org/en-US/docs/Web/API/ImageData).
|
Next, we'll store the pixel data in an [`ImageData` object](https://developer.mozilla.org/en-US/docs/Web/API/ImageData).
|
||||||
Each pixel in the image on screen has a corresponding index in the `data` array.
|
Each pixel on screen has a corresponding index in the `data` array.
|
||||||
To plot a point, we set that pixel to be black:
|
To plot a point, we set that pixel to be black:
|
||||||
|
|
||||||
import plotSource from '!!raw-loader!./plot'
|
import plotSource from '!!raw-loader!./plot'
|
||||||
@ -277,25 +269,22 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
|
|||||||
|
|
||||||
### Weights
|
### Weights
|
||||||
|
|
||||||
There's one last step before we finish the introduction. So far, each function $F_i$ has
|
There's one last step before we finish the introduction. So far, each transform has
|
||||||
the same chance of being chosen. We can change that by introducing a "weight" ($w_i$)
|
the same chance of being picked in the chaos game.
|
||||||
to each transform in the chaos game:
|
We can change that by giving them a "weight" ($w_i$) instead:
|
||||||
|
|
||||||
import randomChoiceSource from '!!raw-loader!../src/randomChoice'
|
import randomChoiceSource from '!!raw-loader!../src/randomChoice'
|
||||||
|
|
||||||
<CodeBlock language={'typescript'}>{randomChoiceSource}</CodeBlock>
|
<CodeBlock language={'typescript'}>{randomChoiceSource}</CodeBlock>
|
||||||
|
|
||||||
|
If we let the chaos game run forever, these weights wouldn't matter.
|
||||||
|
But because the iteration count is limited, changing the weights
|
||||||
|
means we don't plot some parts of the image:
|
||||||
|
|
||||||
import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted";
|
import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted";
|
||||||
|
|
||||||
<CodeBlock language={'typescript'}>{chaosGameWeightedSource}</CodeBlock>
|
<CodeBlock language={'typescript'}>{chaosGameWeightedSource}</CodeBlock>
|
||||||
|
|
||||||
For Sierpinski's Gasket, we start with equal weighting,
|
|
||||||
but changing the transform weights affects how often
|
|
||||||
the chaos game "visits" parts of the image. If the chaos
|
|
||||||
were to run forever, we'd get the same image;
|
|
||||||
but because the iteration count is limited,
|
|
||||||
some parts may be missing below.
|
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
Double-click the image if you want to save a copy!
|
Double-click the image if you want to save a copy!
|
||||||
:::
|
:::
|
||||||
@ -308,7 +297,7 @@ import {SquareCanvas} from "../src/Canvas";
|
|||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Studying the foundations of fractal flames is challenging,
|
Studying the foundations of fractal flames is challenging,
|
||||||
but we now have an understanding of both the mathematics
|
but we now have an understanding of the mathematics
|
||||||
and implementations of iterated function systems.
|
and the implementation of iterated function systems.
|
||||||
|
|
||||||
In the next post, we'll study the first innovation of fractal flames: variations.
|
In the next post, we'll look at the first innovation of fractal flame algorithm: variations.
|
@ -6,31 +6,30 @@ authors: [bspeice]
|
|||||||
tags: []
|
tags: []
|
||||||
---
|
---
|
||||||
|
|
||||||
Now that we have a basic chaos game in place, it's time to spice things up. Variations create the
|
Now that we've learned about the chaos game, it's time to spice things up. Variations create the
|
||||||
shapes and patterns that fractal flames are known for.
|
shapes and patterns that fractal flames are known for.
|
||||||
|
|
||||||
<!-- truncate -->
|
<!-- truncate -->
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
This post uses [reference parameters](../params.flame) to demonstrate the fractal flame algorithm.
|
This post uses [reference parameters](../params.flame) to demonstrate the fractal flame algorithm.
|
||||||
If you're interested in tweaking the parameters, or generating your own art, [Apophysis](https://sourceforge.net/projects/apophysis/)
|
If you're interested in tweaking the parameters, or creating your own, [Apophysis](https://sourceforge.net/projects/apophysis/)
|
||||||
can load that file to play around with!
|
can load that file.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Variations
|
## Variations
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
This post covers section 3 for the Fractal Flame Algorithm paper
|
This post covers section 3 of the Fractal Flame Algorithm paper
|
||||||
:::
|
:::
|
||||||
|
|
||||||
import CodeBlock from '@theme/CodeBlock'
|
import CodeBlock from '@theme/CodeBlock'
|
||||||
|
|
||||||
We previously introduced transforms as the "functions" of an "iterated function system," and showed how
|
We previously introduced transforms as the "functions" of an "iterated function system," and showed how
|
||||||
playing the chaos game leads to an image of Sierpinski's Gasket. Even though we used simple functions,
|
playing the chaos game gives us an image of Sierpinski's Gasket. Even though we used simple functions,
|
||||||
the image it generates is exciting. But it's still not nearly as exciting as the images the Fractal Flame
|
the image it generates is intriguing. But what would happen if we used something more complex?
|
||||||
algorithm is known for.
|
|
||||||
|
|
||||||
This leads us to the first big innovation of the Fractal Flame algorithm: adding non-linear functions
|
This leads us to the first big innovation of the fractal flame algorithm: adding non-linear functions
|
||||||
after the affine transform. These functions are called "variations":
|
after the affine transform. These functions are called "variations":
|
||||||
|
|
||||||
$$
|
$$
|
||||||
@ -44,14 +43,14 @@ import variationSource from '!!raw-loader!../src/variation'
|
|||||||
Just like transforms, variations ($V_j$) are functions that take in $(x, y)$ coordinates
|
Just like transforms, variations ($V_j$) are functions that take in $(x, y)$ coordinates
|
||||||
and give back new $(x, y)$ coordinates.
|
and give back new $(x, y)$ coordinates.
|
||||||
However, the sky is the limit for what happens between input and output.
|
However, the sky is the limit for what happens between input and output.
|
||||||
The Fractal Flame paper lists 49 different variation functions,
|
The Fractal Flame paper lists 49 variation functions,
|
||||||
and the official `flam3` implementation supports [98 different variations](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/variations.c).
|
and the official `flam3` implementation supports [98 different variations](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/variations.c).
|
||||||
|
|
||||||
To draw our reference image, we'll focus on four variations:
|
To draw our reference image, we'll focus on just four:
|
||||||
|
|
||||||
### Linear (variation 0)
|
### Linear (variation 0)
|
||||||
|
|
||||||
This variation is dead simple: just return the $x$ and $y$ coordinates as-is.
|
This variation is dead simple: return the $x$ and $y$ coordinates as-is.
|
||||||
|
|
||||||
$$
|
$$
|
||||||
V_0(x,y) = (x,y)
|
V_0(x,y) = (x,y)
|
||||||
@ -63,12 +62,12 @@ import linearSrc from '!!raw-loader!../src/linear'
|
|||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
In a way, we've already been using this variation! The transforms that define Sierpinski's Gasket
|
In a way, we've already been using this variation! The transforms that define Sierpinski's Gasket
|
||||||
apply the affine coefficients to the input point, and use that as the output point.
|
apply the affine coefficients to the input point and use that as the output.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
### Julia (variation 13)
|
### Julia (variation 13)
|
||||||
|
|
||||||
This variation is a good example of the non-linear functions we can use. It uses both trigonometry
|
This variation is a good example of a non-linear function. It uses both trigonometry
|
||||||
and probability to produce interesting shapes:
|
and probability to produce interesting shapes:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
@ -93,7 +92,7 @@ import juliaSrc from '!!raw-loader!../src/julia'
|
|||||||
### Popcorn (variation 17)
|
### Popcorn (variation 17)
|
||||||
|
|
||||||
Some variations rely on knowing the transform's affine coefficients; they're called "dependent variations."
|
Some variations rely on knowing the transform's affine coefficients; they're called "dependent variations."
|
||||||
For the popcorn variation, we use $c$ and $f$:
|
For this variation, we use $c$ and $f$:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
V_{17}(x,y) = (x + c\ \text{sin}(\text{tan }3y), y + f\ \text{sin}(\text{tan }3x))
|
V_{17}(x,y) = (x + c\ \text{sin}(\text{tan }3y), y + f\ \text{sin}(\text{tan }3x))
|
||||||
@ -105,8 +104,8 @@ import popcornSrc from '!!raw-loader!../src/popcorn'
|
|||||||
|
|
||||||
### PDJ (variation 24)
|
### PDJ (variation 24)
|
||||||
|
|
||||||
Some variations have extra parameters; they're called "parametric variations."
|
Some variations have extra parameters we can choose; they're called "parametric variations."
|
||||||
For the PDJ variation, there are four extra parameters we can choose:
|
For the PDJ variation, there are four extra parameters:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
p_1 = \text{pdj.a} \hspace{0.1cm} p_2 = \text{pdj.b} \hspace{0.1cm} p_3 = \text{pdj.c} \hspace{0.1cm} p_4 = \text{pdj.d} \\
|
p_1 = \text{pdj.a} \hspace{0.1cm} p_2 = \text{pdj.b} \hspace{0.1cm} p_3 = \text{pdj.c} \hspace{0.1cm} p_4 = \text{pdj.d} \\
|
||||||
@ -133,11 +132,11 @@ import blendSource from "!!raw-loader!../src/blend";
|
|||||||
|
|
||||||
<CodeBlock language={'typescript'}>{blendSource}</CodeBlock>
|
<CodeBlock language={'typescript'}>{blendSource}</CodeBlock>
|
||||||
|
|
||||||
With that in place, we have enough to render a first fractal flame. We'll use the same
|
With that in place, we have enough to render a fractal flame. We'll use the same
|
||||||
chaos game as before, but our new transforms and variations produce a dramatically different image:
|
chaos game as before, but the new transforms and variations produce a dramatically different image:
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
Try using the variation weight sliders to figure out which parts of the image each transform controls.
|
Try using the variation weights to figure out which parts of the image each transform controls.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
import {SquareCanvas} from "../src/Canvas";
|
import {SquareCanvas} from "../src/Canvas";
|
||||||
@ -147,9 +146,9 @@ import FlameBlend from "./FlameBlend";
|
|||||||
|
|
||||||
## Post transforms
|
## Post transforms
|
||||||
|
|
||||||
Next, we'll introduce a second affine transform, applied _after_ variation blending. This is called a "post transform."
|
Next, we'll introduce a second affine transform applied _after_ variation blending. This is called a "post transform."
|
||||||
|
|
||||||
We'll use some new variables, but the post transform function should look familiar:
|
We'll use some new variables, but the post transform should look familiar:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
\begin{align*}
|
\begin{align*}
|
||||||
@ -163,7 +162,7 @@ import postSource from '!!raw-loader!./post'
|
|||||||
<CodeBlock language="typescript">{postSource}</CodeBlock>
|
<CodeBlock language="typescript">{postSource}</CodeBlock>
|
||||||
|
|
||||||
The image below uses the same transforms/variations as the previous fractal flame,
|
The image below uses the same transforms/variations as the previous fractal flame,
|
||||||
but allows modifying the post-transform coefficients:
|
but allows changing the post-transform coefficients:
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>If you want to test your understanding...</summary>
|
<summary>If you want to test your understanding...</summary>
|
||||||
@ -178,12 +177,12 @@ import FlamePost from "./FlamePost";
|
|||||||
|
|
||||||
## Final transforms
|
## Final transforms
|
||||||
|
|
||||||
Our last step is to introduce a "final transform" ($F_{final}$) that is applied
|
The last step is to introduce a "final transform" ($F_{final}$) that is applied
|
||||||
regardless of which transform the chaos game selects. It works just like a normal transform
|
regardless of which regular transform ($F_i$) the chaos game selects.
|
||||||
(composition of affine transform, variation blend, and post transform),
|
It's just like a normal transform (composition of affine transform, variation blend, and post transform),
|
||||||
but it doesn't change the chaos game state.
|
but it doesn't affect the chaos game state.
|
||||||
|
|
||||||
With that in place, our chaos game algorithm changes slightly:
|
After adding the final transform, our chaos game algorithm looks like this:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
\begin{align*}
|
\begin{align*}
|
||||||
@ -201,6 +200,9 @@ import chaosGameFinalSource from "!!raw-loader!./chaosGameFinal"
|
|||||||
|
|
||||||
<CodeBlock language="typescript">{chaosGameFinalSource}</CodeBlock>
|
<CodeBlock language="typescript">{chaosGameFinalSource}</CodeBlock>
|
||||||
|
|
||||||
|
This image uses the same normal/post transforms as above, but allows modifying
|
||||||
|
the coefficients and variations of the final transform:
|
||||||
|
|
||||||
import FlameFinal from "./FlameFinal";
|
import FlameFinal from "./FlameFinal";
|
||||||
|
|
||||||
<SquareCanvas name={"flame_final"}><FlameFinal/></SquareCanvas>
|
<SquareCanvas name={"flame_final"}><FlameFinal/></SquareCanvas>
|
||||||
@ -208,7 +210,7 @@ import FlameFinal from "./FlameFinal";
|
|||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Variations are the fractal flame algorithm's first major innovation.
|
Variations are the fractal flame algorithm's first major innovation.
|
||||||
By blending variation functions, and post/final transforms, we generate unique images.
|
By blending variation functions and post/final transforms, we generate unique images.
|
||||||
|
|
||||||
However, the images themselves are grainy and unappealing. In the next post, we'll clean up
|
However, these images are grainy and unappealing. In the next post, we'll clean up
|
||||||
the image quality and add some color.
|
the image quality and add some color.
|
@ -6,7 +6,7 @@ authors: [bspeice]
|
|||||||
tags: []
|
tags: []
|
||||||
---
|
---
|
||||||
|
|
||||||
So far, our `plot()` function has been fairly simple; map a fractal flame coordinate to a specific pixel,
|
So far, our `plot()` function has been fairly simple: map a fractal flame coordinate to a specific pixel,
|
||||||
and color in that pixel. This works well for simple function systems (like Sierpinski's Gasket),
|
and color in that pixel. This works well for simple function systems (like Sierpinski's Gasket),
|
||||||
but more complex systems (like the reference parameters) produce grainy images.
|
but more complex systems (like the reference parameters) produce grainy images.
|
||||||
|
|
||||||
@ -20,21 +20,20 @@ In this post, we'll refine the image quality and add color to really make things
|
|||||||
This post covers sections 4 and 5 of the Fractal Flame Algorithm paper
|
This post covers sections 4 and 5 of the Fractal Flame Algorithm paper
|
||||||
:::
|
:::
|
||||||
|
|
||||||
One problem with the existing chaos game is that we waste work
|
One problem with the current chaos game algorithm is that we waste work
|
||||||
by treating pixels as a binary "on" (opaque) or "off" (transparent).
|
because pixels are either "on" (opaque) or "off" (transparent).
|
||||||
If the chaos game encounters the same location twice, nothing actually changes.
|
If the chaos game encounters the same pixel twice, nothing changes.
|
||||||
|
|
||||||
To demonstrate how much work is wasted, we'll render the reference image again.
|
To demonstrate how much work is wasted, we'll count each time the chaos game
|
||||||
However, we'll also count each time the chaos game encounters a pixel.
|
visits a pixel while iterating. This gives us a kind of image "histogram":
|
||||||
This gives us a kind of image "histogram":
|
|
||||||
|
|
||||||
import chaosGameHistogramSource from "!!raw-loader!./chaosGameHistogram"
|
import chaosGameHistogramSource from "!!raw-loader!./chaosGameHistogram"
|
||||||
|
|
||||||
<CodeBlock language="typescript">{chaosGameHistogramSource}</CodeBlock>
|
<CodeBlock language="typescript">{chaosGameHistogramSource}</CodeBlock>
|
||||||
|
|
||||||
When the chaos game finishes, we find the pixel encountered most frequently.
|
When the chaos game finishes, we find the pixel encountered most often.
|
||||||
Finally, we "paint" the image by setting each pixel's alpha value (transparency)
|
Finally, we "paint" the image by setting each pixel's alpha (transparency) value
|
||||||
to the ratio of times encountered divided by the maximum:
|
to the ratio of times visited divided by the maximum:
|
||||||
|
|
||||||
import CodeBlock from "@theme/CodeBlock";
|
import CodeBlock from "@theme/CodeBlock";
|
||||||
|
|
||||||
@ -50,22 +49,22 @@ import {paintLinear} from "./paintLinear";
|
|||||||
|
|
||||||
## Tone mapping
|
## Tone mapping
|
||||||
|
|
||||||
While using a histogram reduces the "graininess" of the image, it also leads to some parts vanishing entirely.
|
While using a histogram reduces the "graining," it also leads to some parts vanishing entirely.
|
||||||
In the reference parameters, the outer circle is preserved, but the interior appears to be missing!
|
In the reference parameters, the outer circle is still there, but the interior is gone!
|
||||||
|
|
||||||
To fix this, we'll introduce the second major innovation of the fractal flame algorithm: [tone mapping](https://en.wikipedia.org/wiki/Tone_mapping).
|
To fix this, we'll introduce the second major innovation of the fractal flame algorithm: [tone mapping](https://en.wikipedia.org/wiki/Tone_mapping).
|
||||||
This is a technique used in computer graphics to compensate for differences in how
|
This is a technique used in computer graphics to compensate for differences in how
|
||||||
computers represent brightness, and how people see brightness.
|
computers represent brightness, and how people actually see brightness.
|
||||||
|
|
||||||
As a concrete example, high dynamic range (HDR) photography uses this technique to capture
|
As a concrete example, high-dynamic-range (HDR) photography uses this technique to capture
|
||||||
nice images of scenes with a wide range of brightnesses. To take a picture of something dark,
|
scenes with a wide range of brightnesses. To take a picture of something dark,
|
||||||
you need a long exposure time. However, long exposures lead to "hot spots" (sections that are pure white).
|
you need a long exposure time. However, long exposures lead to "hot spots" (sections that are pure white).
|
||||||
By taking multiple pictures with different exposure times, we can combine them to create
|
By taking multiple pictures with different exposure times, we can combine them to create
|
||||||
a final image where everything is visible.
|
a final image where everything is visible.
|
||||||
|
|
||||||
In fractal flames, this "tone map" is accomplished by scaling brightness according to the _logarithm_
|
In fractal flames, this "tone map" is accomplished by scaling brightness according to the _logarithm_
|
||||||
of how many times we encounter a pixel. This way, "cold spots" (pixels the chaos game visits infrequently)
|
of how many times we encounter a pixel. This way, "cold spots" (pixels the chaos game visits infrequently)
|
||||||
will still be visible, and "hot spots" (pixels the chaos game visits frequently) won't wash out.
|
are still visible, and "hot spots" (pixels the chaos game visits frequently) won't wash out.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Log-scale vibrancy also explains fractal flames appear to be 3D...</summary>
|
<summary>Log-scale vibrancy also explains fractal flames appear to be 3D...</summary>
|
||||||
@ -89,7 +88,7 @@ import {paintLogarithmic} from './paintLogarithmic'
|
|||||||
|
|
||||||
## Color
|
## Color
|
||||||
|
|
||||||
Finally, we'll introduce the last innovation of the fractal flame algorithm: color.
|
Now we'll introduce the last innovation of the fractal flame algorithm: color.
|
||||||
By including a third coordinate ($c$) in the chaos game, we can illustrate the transforms
|
By including a third coordinate ($c$) in the chaos game, we can illustrate the transforms
|
||||||
responsible for the image.
|
responsible for the image.
|
||||||
|
|
||||||
@ -100,11 +99,12 @@ Color in a fractal flame is continuous on the range $[0, 1]$. This is important
|
|||||||
- It helps blend colors together in the final image. Slight changes in the color value lead to
|
- It helps blend colors together in the final image. Slight changes in the color value lead to
|
||||||
slight changes in the actual color
|
slight changes in the actual color
|
||||||
- It allows us to swap in new color palettes easily. We're free to choose what actual colors
|
- It allows us to swap in new color palettes easily. We're free to choose what actual colors
|
||||||
each color value represents
|
each value represents
|
||||||
|
|
||||||
We'll give each transform a color value ($c_i$) in the $[0, 1]$ range.
|
We'll give each transform a color value ($c_i$) in the $[0, 1]$ range.
|
||||||
|
The final transform gets a value too ($c_f$).
|
||||||
Then, at each step in the chaos game, we'll set the current color
|
Then, at each step in the chaos game, we'll set the current color
|
||||||
by blending it with the previous color and the current transform:
|
by blending it with the previous color:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
\begin{align*}
|
\begin{align*}
|
||||||
@ -123,13 +123,13 @@ $$
|
|||||||
### Color speed
|
### Color speed
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
Color speed as a concept isn't introduced in the Fractal Flame Algorithm paper.
|
Color speed isn't introduced in the Fractal Flame Algorithm paper.
|
||||||
|
|
||||||
It is included here because [`flam3` implements it](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/variations.c#L2140),
|
It is included here because [`flam3` implements it](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/variations.c#L2140),
|
||||||
and because it's fun to play with.
|
and because it's fun to play with.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Next, we'll add a parameter to each transform that controls how much it affects the current color.
|
Next, we'll add a parameter to each transform that controls how much it changes the current color.
|
||||||
This is known as the "color speed" ($s_i$):
|
This is known as the "color speed" ($s_i$):
|
||||||
|
|
||||||
$$
|
$$
|
||||||
@ -155,7 +155,7 @@ There's one small complication: the color coordinate is continuous, but the pale
|
|||||||
uses discrete colors. How do we handle situations where the color coordinate is
|
uses discrete colors. How do we handle situations where the color coordinate is
|
||||||
"in between" the colors of our palette?
|
"in between" the colors of our palette?
|
||||||
|
|
||||||
One way is to use a step function. In the code below, we multiply the color coordinate
|
One way to handle this is a step function. In the code below, we multiply the color coordinate
|
||||||
by the number of colors in the palette, then truncate that value. This gives us a discrete index:
|
by the number of colors in the palette, then truncate that value. This gives us a discrete index:
|
||||||
|
|
||||||
import colorFromPaletteSource from "!!raw-loader!./colorFromPalette";
|
import colorFromPaletteSource from "!!raw-loader!./colorFromPalette";
|
||||||
@ -169,7 +169,7 @@ import colorFromPaletteSource from "!!raw-loader!./colorFromPalette";
|
|||||||
For example, `flam3` uses [linear interpolation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L483-L486)
|
For example, `flam3` uses [linear interpolation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L483-L486)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
In the diagram below, each color in our palette is plotted on a small vertical strip.
|
In the diagram below, each color in the palette is plotted on a small vertical strip.
|
||||||
Putting the strips side by side shows the full palette used by the reference parameters:
|
Putting the strips side by side shows the full palette used by the reference parameters:
|
||||||
|
|
||||||
import * as params from "../src/params"
|
import * as params from "../src/params"
|
||||||
@ -179,8 +179,9 @@ import {PaletteBar} from "./FlameColor"
|
|||||||
|
|
||||||
### Plotting
|
### Plotting
|
||||||
|
|
||||||
We're now ready to plot our $(x_f,y_f,c_f)$ coordinates. After translating from color coordinate ($c_f$)
|
We're now ready to plot our $(x_f,y_f,c_f)$ coordinates. This time, we'll use a histogram
|
||||||
to RGB value, add that to the image histogram:
|
for each color channel (red, green, blue, alpha). After translating from color coordinate ($c_f$)
|
||||||
|
to RGB value, add that to the histogram:
|
||||||
|
|
||||||
import chaosGameColorSource from "!!raw-loader!./chaosGameColor"
|
import chaosGameColorSource from "!!raw-loader!./chaosGameColor"
|
||||||
|
|
||||||
@ -208,14 +209,14 @@ brightness/transparency to reduce the visual "graining" of previous images.
|
|||||||
|
|
||||||
Next, introducing a third coordinate to the chaos game makes color images possible,
|
Next, introducing a third coordinate to the chaos game makes color images possible,
|
||||||
the third major innovation of the fractal flame algorithm. Using a continuous
|
the third major innovation of the fractal flame algorithm. Using a continuous
|
||||||
color scale and color palette adds a splash of color to our transforms.
|
color scale and color palette adds a splash of excitement to the image.
|
||||||
|
|
||||||
The Fractal Flame Algorithm paper does go on to describe more techniques
|
The Fractal Flame Algorithm paper goes on to describe more techniques
|
||||||
not covered here. For example, Image quality can be improved with density estimation
|
not covered here. For example, image quality can be improved with density estimation
|
||||||
and filtering. New parameters can be generated by "mutating" existing
|
and filtering. New parameters can be generated by "mutating" existing
|
||||||
fractal flames. And fractal flames can even be animated to produce videos!
|
fractal flames. And fractal flames can even be animated to produce videos!
|
||||||
|
|
||||||
That said, I think this is a good place to wrap up. We were able to go from
|
That said, I think this is a good place to wrap up. We went from
|
||||||
an introduction to the mathematics of fractal systems all the way to
|
an introduction to the mathematics of fractal systems all the way to
|
||||||
generating full-color images. Fractal flames are a challenging topic,
|
generating full-color images. Fractal flames are a challenging topic,
|
||||||
but it's extremely rewarding to learn more about how they work.
|
but it's extremely rewarding to learn about how they work.
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 557 KiB After Width: | Height: | Size: 418 KiB |
Loading…
Reference in New Issue
Block a user