mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
First pass proof-reading
This commit is contained in:
parent
a05acf6748
commit
1ce6137c17
@ -14,7 +14,7 @@ export function Render({f}) {
|
|||||||
|
|
||||||
export default function Gasket({f}) {
|
export default function Gasket({f}) {
|
||||||
return (
|
return (
|
||||||
<SquareCanvas>
|
<SquareCanvas name={"gasket"}>
|
||||||
<Render f={f}/>
|
<Render f={f}/>
|
||||||
</SquareCanvas>
|
</SquareCanvas>
|
||||||
)
|
)
|
||||||
|
@ -31,7 +31,7 @@ export default function GasketWeighted() {
|
|||||||
<>
|
<>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p><TeX>{title}</TeX>: {weight}</p>
|
<p><TeX>{title}</TeX>: {weight}</p>
|
||||||
<input type={'range'} min={0} max={1} step={.01} style={{width: '100%', background: 'transparent'}} value={weight}
|
<input type={'range'} min={0} max={1} step={.01} value={weight}
|
||||||
onInput={e => setWeight(Number(e.currentTarget.value))}/>
|
onInput={e => setWeight(Number(e.currentTarget.value))}/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -7,11 +7,11 @@ tags: []
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
Wikipedia describes fractal flames fractal flames as:
|
Wikipedia describes [fractal flames](https://en.wikipedia.org/wiki/Fractal_flame) as:
|
||||||
|
|
||||||
> a member of the iterated function system class of fractals
|
> a member of the iterated function system class of fractals
|
||||||
|
|
||||||
It's a bit tedious, but technically correct. I choose to think of them a different way: beauty in mathematics.
|
It's tedious, but technically correct. I choose to think of them a different way: beauty in mathematics.
|
||||||
|
|
||||||
import isDarkMode from '@site/src/isDarkMode'
|
import isDarkMode from '@site/src/isDarkMode'
|
||||||
import banner from '../banner.png'
|
import banner from '../banner.png'
|
||||||
@ -22,14 +22,15 @@ import banner from '../banner.png'
|
|||||||
|
|
||||||
<!-- truncate -->
|
<!-- truncate -->
|
||||||
|
|
||||||
I don't remember exactly when I first learned about fractal flames, but I do remember becoming entranced by the images they created.
|
I don't remember exactly when 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 original [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, maybe I can make some progress.
|
But the desire to understand it stuck around. Now, with a graduate degree under my belt, I want to revisit it
|
||||||
|
and try to make some progress.
|
||||||
|
|
||||||
This guide is my attempt to explain fractal flames in a way that younger me — and others interested in the art —
|
This guide is my attempt to explain how fractal flames work in a way that younger me — and others interested in the art —
|
||||||
can understand without too much prior knowledge.
|
can understand without too much prior knowledge.
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -41,7 +42,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 will take some time to unpack:
|
or IFS. The formula for an IFS is short, but takes some time to unpack:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
S = \bigcup_{i=0}^{n-1} F_i(S)
|
S = \bigcup_{i=0}^{n-1} F_i(S)
|
||||||
@ -69,15 +70,15 @@ export const simpleData = [
|
|||||||
However, this is a pretty boring image. With fractal flames, rather than listing individual points,
|
However, this is a pretty boring image. With fractal flames, rather than listing individual points,
|
||||||
we use functions to describe which points are part of the solution. This means there are an infinite
|
we use functions to describe which points are part of the solution. This means there are an infinite
|
||||||
number of points, but if we find _enough_ points to plot, we'll end up with a nice picture.
|
number of points, but if we find _enough_ points to plot, we'll end up with a nice picture.
|
||||||
And if we change the functions, our solution changes, and we'll get a new picture.
|
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
|
At their most basic, each $F_i$ takes in a 2-dimensional point and gives back a new point
|
||||||
(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 focus on a specific kind of function
|
While you could theoretically use any function, we'll start with a specific kind of function
|
||||||
known as an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)."
|
called an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)."
|
||||||
|
|
||||||
The general form of an affine transformation is:
|
The general form of an affine transformation is:
|
||||||
|
|
||||||
@ -102,7 +103,7 @@ d &= 0 \\
|
|||||||
e &= 1 \\
|
e &= 1 \\
|
||||||
f &= 1.5 \\
|
f &= 1.5 \\
|
||||||
F_{shift}(x,y) &= (1 \cdot x + 0 \cdot y + 0.5, 0 \cdot x + 1 \cdot y + 0.5) \\
|
F_{shift}(x,y) &= (1 \cdot x + 0 \cdot y + 0.5, 0 \cdot x + 1 \cdot y + 0.5) \\
|
||||||
F_{shift}(x, y) &= (x + 0.5, y + 0.5)
|
F_{shift}(x, y) &= (x + 0.5, y + 1.5)
|
||||||
\end{align*}
|
\end{align*}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
@ -139,7 +140,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 would get something like this:
|
Or, to put it 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.
|
||||||
@ -154,40 +155,41 @@ explaining the mathematics of iterated function systems:
|
|||||||
> 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
|
:::note
|
||||||
I've tweaked the wording slightly to match the conventions in the Fractal Flame paper
|
I've tweaked the wording slightly to match the conventions of the Fractal Flame paper
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Before your eyes glaze over, let's unpack this explanation:
|
Before your eyes glaze over, let's unpack this:
|
||||||
|
|
||||||
- **$S$ is [compact](https://en.wikipedia.org/wiki/Compact_space)...**: All points in our solution will be in a finite range
|
- **$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 these points does not change them
|
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 we care about
|
using the output of one function as input to the next function), we will arrive at the points in the solution
|
||||||
|
|
||||||
Thus, by applying the functions in our system to "fixed points," 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.
|
||||||
|
|
||||||
However, this is all a bit vague, so let's work through an example.
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>If you want a bit more math first...</summary>
|
<summary>If you want a bit more math...</summary>
|
||||||
|
|
||||||
...then there are some details worth mentioning that I've glossed over so far.
|
...then there are some extra details I've glossed over so far.
|
||||||
|
|
||||||
First, the Hutchinson paper requires that the functions $F_i$ be _contractive_ tor the solution set to exist.
|
First, the Hutchinson paper requires that the functions $F_i$ be _contractive_ for the solution set to exist.
|
||||||
That is, applying the function to a point must bring it closer to other points. However, as the Fractal Flame
|
That is, applying the function to a point must bring it closer to other points. However, as the Fractal Flame
|
||||||
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 $\mathbb{R}^2$ because we're generating images, but the Hutchinson paper
|
Second, we're focused $\mathbb{R}^2$ because we're generating images, but the Hutchinson paper
|
||||||
allows for arbitrary dimensions - which means you could also have 3-dimensional fractal flames.
|
allows for arbitrary dimensions; you could also have 3-dimensional fractal flames.
|
||||||
|
|
||||||
TODO: Mention attractors? https://en.wikipedia.org/wiki/Chaos_game
|
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 detail below).
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Sierpinski's gasket
|
This is still a bit vague, so let's work through an example.
|
||||||
|
|
||||||
The Fractal Flame paper gives us three functions we can use for our function system:
|
## [Sierpinski's gasket](https://www.britannica.com/biography/Waclaw-Sierpinski)
|
||||||
|
|
||||||
|
The Fractal Flame paper gives us three functions to use for our first IFS:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_0(x, y) = \left({x \over 2}, {y \over 2} \right) \\
|
F_0(x, y) = \left({x \over 2}, {y \over 2} \right) \\
|
||||||
@ -200,7 +202,7 @@ $$
|
|||||||
### The chaos game
|
### The chaos game
|
||||||
|
|
||||||
Next, how do we find the "fixed points" we mentioned earlier? The paper lays out an algorithm called the "[chaos game](https://en.wikipedia.org/wiki/Chaos_game)"
|
Next, how do we find the "fixed points" we mentioned earlier? The paper lays out an algorithm called the "[chaos game](https://en.wikipedia.org/wiki/Chaos_game)"
|
||||||
that will give us points in the solution set.
|
that gives us points in the solution set:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
\begin{align*}
|
\begin{align*}
|
||||||
@ -214,12 +216,13 @@ $$
|
|||||||
$$
|
$$
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
In effect, the chaos game algorithm implements the "finite compositions of $F_{i_1..i_p}$ mentioned earlier.
|
The chaos game algorithm is effectively the "finite compositions of $F_{i_1..i_p}$" mentioned earlier.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Now, let's turn this into code, one piece at a time.
|
Now, let's turn this into code, one piece at a time.
|
||||||
|
|
||||||
First, the "bi-unit square" is the range $[-1, 1]$. We can :
|
First, 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:
|
||||||
|
|
||||||
import biunitSource from '!!raw-loader!../src/randomBiUnit'
|
import biunitSource from '!!raw-loader!../src/randomBiUnit'
|
||||||
|
|
||||||
@ -233,14 +236,12 @@ import randintSource from '!!raw-loader!../src/randomInteger'
|
|||||||
|
|
||||||
### Plotting
|
### Plotting
|
||||||
|
|
||||||
Finally, implementing the `plot` function. This blog series
|
Finally, implementing the `plot` function. This blog series is designed to be interactive,
|
||||||
is designed to be interactive, so everything shows
|
so everything displays directly in the browser. As an alternative,
|
||||||
real-time directly in the browser. As an alternative,
|
software like `flam3` and Apophysis can "plot" by saving an image to disk.
|
||||||
software like `flam3` an Apophysis can also save an image.
|
|
||||||
|
|
||||||
To display the results, we'll use the [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API).
|
To show 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 an image,
|
This allows us to manipulate individual pixels in an image and display it on screen.
|
||||||
and display 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
|
||||||
@ -250,10 +251,9 @@ import cameraSource from "!!raw-loader!./cameraGasket"
|
|||||||
|
|
||||||
<CodeBlock language="typescript">{cameraSource}</CodeBlock>
|
<CodeBlock language="typescript">{cameraSource}</CodeBlock>
|
||||||
|
|
||||||
Next, we'll use 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).
|
||||||
to store the pixel data.
|
|
||||||
Each pixel in the image on screen has a corresponding index in the `data` array.
|
Each pixel in the image on screen has a corresponding index in the `data` array.
|
||||||
To plot our image, 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'
|
||||||
|
|
||||||
@ -271,18 +271,16 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
|
|||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
<small>
|
<small>
|
||||||
The image here is slightly different than the one in the paper.
|
The image here is slightly different than in the paper.
|
||||||
I think the paper has an error, so I'm choosing to plot the image
|
I think the paper has an error, so I'm choosing to plot the image
|
||||||
in a way that's consistent with [`flam3` itself](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441).
|
the same way as the [reference implementation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441).
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
### Weights
|
### Weights
|
||||||
|
|
||||||
Finally, we'll introduce a "weight" ($w_i$) for each function that controls how often we choose
|
There's one last step before we finish the introduction. So far, each function $F_i$ has
|
||||||
that function during the chaos game relative to each other function.
|
the same chance of being chosen. We can change that by introducing a "weight" ($w_i$)
|
||||||
|
to each transform in the chaos game:
|
||||||
For Sierpinski's Gasket, we start with equal weighting,
|
|
||||||
but you can see how changing the function weights affects the image below:
|
|
||||||
|
|
||||||
import randomChoiceSource from '!!raw-loader!../src/randomChoice'
|
import randomChoiceSource from '!!raw-loader!../src/randomChoice'
|
||||||
|
|
||||||
@ -292,16 +290,23 @@ 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:
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Double-click the image if you want to save a copy!
|
||||||
|
:::
|
||||||
|
|
||||||
import GasketWeighted from "./GasketWeighted";
|
import GasketWeighted from "./GasketWeighted";
|
||||||
import {SquareCanvas} from "../src/Canvas";
|
import {SquareCanvas} from "../src/Canvas";
|
||||||
|
|
||||||
<SquareCanvas><GasketWeighted/></SquareCanvas>
|
<SquareCanvas name={"gasket_weighted"}><GasketWeighted/></SquareCanvas>
|
||||||
|
|
||||||
## 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 both the mathematics
|
||||||
and implementation of iterated function systems.
|
and implementations of iterated function systems.
|
||||||
|
|
||||||
In the next post, we'll study the first innovation that fractal flames
|
In the next post, we'll study the first innovation of fractal flames: variations.
|
||||||
bring: variations.
|
|
@ -12,9 +12,9 @@ shapes and patterns that fractal flames are known for.
|
|||||||
<!-- truncate -->
|
<!-- truncate -->
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
This post uses a set of [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 generating your own art, [Apophysis](https://sourceforge.net/projects/apophysis/)
|
||||||
can load that file and you can try tweaking things yourself!
|
can load that file to play around with!
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## Variations
|
## Variations
|
||||||
@ -30,8 +30,8 @@ playing the chaos game leads to an image of Sierpinski's Gasket. Even though we
|
|||||||
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 exciting. But it's still not nearly as exciting as the images the Fractal Flame
|
||||||
algorithm is known for.
|
algorithm is known for.
|
||||||
|
|
||||||
This leads us to the first big innovation of the Fractal Flame algorithm: applying non-linear functions
|
This leads us to the first big innovation of the Fractal Flame algorithm: adding non-linear functions
|
||||||
after the affine transform has happened. These functions are called "variations":
|
after the affine transform. These functions are called "variations":
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_i(x, y) = V_j(a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i)
|
F_i(x, y) = V_j(a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i)
|
||||||
@ -41,8 +41,9 @@ import variationSource from '!!raw-loader!../src/variation'
|
|||||||
|
|
||||||
<CodeBlock language="typescript">{variationSource}</CodeBlock>
|
<CodeBlock language="typescript">{variationSource}</CodeBlock>
|
||||||
|
|
||||||
Just like transforms, variations ($V_j$) are functions that map $(x, y)$ coordinates to new coordinates.
|
Just like transforms, variations ($V_j$) are functions that take in $(x, y)$ coordinates
|
||||||
However, the sky is the limit for what we can do in between input and output.
|
and give back new $(x, y)$ coordinates.
|
||||||
|
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 different 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).
|
||||||
|
|
||||||
@ -70,8 +71,6 @@ apply the affine coefficients to the input point, and use that as the output poi
|
|||||||
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 the non-linear functions we can use. It uses both trigonometry
|
||||||
and probability to produce interesting shapes:
|
and probability to produce interesting shapes:
|
||||||
|
|
||||||
TODO: Connection with the julia set?
|
|
||||||
|
|
||||||
$$
|
$$
|
||||||
\begin{align*}
|
\begin{align*}
|
||||||
r &= \sqrt{x^2 + y^2} \\
|
r &= \sqrt{x^2 + y^2} \\
|
||||||
@ -93,7 +92,7 @@ import juliaSrc from '!!raw-loader!../src/julia'
|
|||||||
|
|
||||||
### Popcorn (variation 17)
|
### Popcorn (variation 17)
|
||||||
|
|
||||||
Some variations rely on knowing their transform's affine coefficients; they're known as "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 the popcorn variation, we use $c$ and $f$:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
@ -106,7 +105,7 @@ import popcornSrc from '!!raw-loader!../src/popcorn'
|
|||||||
|
|
||||||
### PDJ (variation 24)
|
### PDJ (variation 24)
|
||||||
|
|
||||||
Some variations have extra parameters we can choose; these are known as "parametric variations."
|
Some variations have extra parameters; 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 we can choose:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
@ -122,7 +121,7 @@ import pdjSrc from '!!raw-loader!../src/pdj'
|
|||||||
|
|
||||||
Now, one variation is fun, but we can also combine variations in a process called "blending."
|
Now, one variation is fun, but we can also combine variations in a process called "blending."
|
||||||
Each variation receives the same $x$ and $y$ inputs, and we add together each variation's $x$ and $y$ outputs.
|
Each variation receives the same $x$ and $y$ inputs, and we add together each variation's $x$ and $y$ outputs.
|
||||||
We'll also give each variation a weight (called $v_{ij}$) that changes how much it contributes to the transform:
|
We'll also give each variation a weight ($v_{ij}$) that changes how much it contributes to the result:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_i(x,y) = \sum_{j} v_{ij} V_j(a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
|
F_i(x,y) = \sum_{j} v_{ij} V_j(a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
|
||||||
@ -144,11 +143,12 @@ Try using the variation weight sliders to figure out which parts of the image ea
|
|||||||
import {SquareCanvas} from "../src/Canvas";
|
import {SquareCanvas} from "../src/Canvas";
|
||||||
import FlameBlend from "./FlameBlend";
|
import FlameBlend from "./FlameBlend";
|
||||||
|
|
||||||
<SquareCanvas><FlameBlend/></SquareCanvas>
|
<SquareCanvas name={"flame_blend"}><FlameBlend/></SquareCanvas>
|
||||||
|
|
||||||
## Post transforms
|
## Post transforms
|
||||||
|
|
||||||
Next, we'll introduce a second affine transform, known as a post transform, that is applied _after_ variation blending.
|
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 function should look familiar:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
@ -163,19 +163,18 @@ 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 modifying the post-transform coefficients:
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>If you want to test your understanding...</summary>
|
<summary>If you want to test your understanding...</summary>
|
||||||
|
|
||||||
Challenge 1: What post-transform coefficients will give us the previous image?
|
- What post-transform coefficients will give us the previous image?
|
||||||
|
- What post-transform coefficients will give us a _mirrored_ image?
|
||||||
Challenge 2: What post-transform coefficients will give us a _mirrored_ image?
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
import FlamePost from "./FlamePost";
|
import FlamePost from "./FlamePost";
|
||||||
|
|
||||||
<SquareCanvas><FlamePost/></SquareCanvas>
|
<SquareCanvas name={"flame_post"}><FlamePost/></SquareCanvas>
|
||||||
|
|
||||||
## Final transforms
|
## Final transforms
|
||||||
|
|
||||||
@ -204,12 +203,12 @@ import chaosGameFinalSource from "!!raw-loader!./chaosGameFinal"
|
|||||||
|
|
||||||
import FlameFinal from "./FlameFinal";
|
import FlameFinal from "./FlameFinal";
|
||||||
|
|
||||||
<SquareCanvas><FlameFinal/></SquareCanvas>
|
<SquareCanvas name={"flame_final"}><FlameFinal/></SquareCanvas>
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Variations are the fractal flame algorithm's first major innovation over previous IFS's.
|
Variations are the fractal flame algorithm's first major innovation.
|
||||||
Blending variation functions and post/final transforms allows us to generate interesting 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, the images themselves 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,9 +6,9 @@ authors: [bspeice]
|
|||||||
tags: []
|
tags: []
|
||||||
---
|
---
|
||||||
|
|
||||||
So far, our `plot()` function has been fairly simple; map an input 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 our reference parameters) produce grainy images.
|
but more complex systems (like the reference parameters) produce grainy images.
|
||||||
|
|
||||||
In this post, we'll refine the image quality and add color to really make things shine.
|
In this post, we'll refine the image quality and add color to really make things shine.
|
||||||
|
|
||||||
@ -20,20 +20,21 @@ 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
|
||||||
:::
|
:::
|
||||||
|
|
||||||
To start, it's worth demonstrating how much work is actually "wasted"
|
One problem with the existing chaos game is that we waste work
|
||||||
when we treat pixels as a binary "on" (opaque) or "off" (transparent).
|
by treating pixels as a binary "on" (opaque) or "off" (transparent).
|
||||||
|
If the chaos game encounters the same location twice, nothing actually changes.
|
||||||
|
|
||||||
We'll render the reference image again, but this time, count each time
|
To demonstrate how much work is wasted, we'll render the reference image again.
|
||||||
we encounter a pixel during the chaos game. This gives us a kind of "histogram"
|
However, we'll also count each time the chaos game encounters a pixel.
|
||||||
of the image:
|
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, find the pixel we encountered most frequently.
|
When the chaos game finishes, we find the pixel encountered most frequently.
|
||||||
Finally, "paint" the image by setting each pixel's alpha value (transparency)
|
Finally, we "paint" the image by setting each pixel's alpha value (transparency)
|
||||||
to the ratio of times encountered, divided by the maximum value:
|
to the ratio of times encountered divided by the maximum:
|
||||||
|
|
||||||
import CodeBlock from "@theme/CodeBlock";
|
import CodeBlock from "@theme/CodeBlock";
|
||||||
|
|
||||||
@ -49,27 +50,27 @@ import {paintLinear} from "./paintLinear";
|
|||||||
|
|
||||||
## Tone mapping
|
## Tone mapping
|
||||||
|
|
||||||
While using a histogram to paint the image improves the quality, it also leads to some parts vanishing entirely.
|
While using a histogram reduces the "graininess" of the image, 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 preserved, but the interior appears to be missing!
|
||||||
|
|
||||||
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 color, and how people see color.
|
computers represent brightness, and how people 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 wide brightness ranges. To take a picture of something dark,
|
nice images of scenes with a wide range of brightnesses. To take a picture of something dark,
|
||||||
you need a long exposure time. However, long exposures can lead to "hot spots" in images 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 using 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, "dark 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 "bright spots" (pixels the chaos game visits frequently) won't wash out.
|
will still be visible, and "hot spots" (pixels the chaos game visits frequently) won't wash out.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Log-scale vibrancy is also why fractal flames appear to be 3D...</summary>
|
<summary>Log-scale vibrancy also explains fractal flames appear to be 3D...</summary>
|
||||||
|
|
||||||
As explained in the Fractal Flame paper:
|
As mentioned in the paper:
|
||||||
|
|
||||||
> Where one branch of the fractal crosses another, one may appear to occlude the other
|
> Where one branch of the fractal crosses another, one may appear to occlude the other
|
||||||
> if their densities are different enough because the lesser density is inconsequential in sum.
|
> if their densities are different enough because the lesser density is inconsequential in sum.
|
||||||
@ -89,15 +90,17 @@ import {paintLogarithmic} from './paintLogarithmic'
|
|||||||
## Color
|
## Color
|
||||||
|
|
||||||
Finally, we'll introduce the last innovation of the fractal flame algorithm: color.
|
Finally, we'll introduce the last innovation of the fractal flame algorithm: color.
|
||||||
By including a color 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 each part of the image.
|
responsible for the image.
|
||||||
|
|
||||||
### Color coordinate
|
### Color coordinate
|
||||||
|
|
||||||
Color in a fractal flame uses a range of $[0, 1]$. This is important for two reasons:
|
Color in a fractal flame is continuous on the range $[0, 1]$. This is important for two reasons:
|
||||||
|
|
||||||
- It helps blend colors together in the final image
|
- It helps blend colors together in the final image. Slight changes in the color value lead to
|
||||||
- It allows us to swap in new color palettes easily
|
slight changes in the actual color
|
||||||
|
- It allows us to swap in new color palettes easily. We're free to choose what actual colors
|
||||||
|
each color 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.
|
||||||
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
|
||||||
@ -139,14 +142,14 @@ import mixColorSource from "!!raw-loader!./mixColor"
|
|||||||
|
|
||||||
Color speed values work just like transform weights. A value of 1
|
Color speed values work just like transform weights. A value of 1
|
||||||
means we take the transform color and ignore the previous color state.
|
means we take the transform color and ignore the previous color state.
|
||||||
Similarly, a value of 0 means we keep the current color state and ignore the
|
A value of 0 means we keep the current color state and ignore the
|
||||||
transform color.
|
transform color.
|
||||||
|
|
||||||
### Palette
|
### Palette
|
||||||
|
|
||||||
Now, we need to map the color coordinate to a pixel color. Fractal flames typically use
|
Now, we need to map the color coordinate to a pixel color. Fractal flames typically use
|
||||||
256 colors (each color has 3 values - red, green, blue) to define a palette.
|
256 colors (each color has 3 values - red, green, blue) to define a palette.
|
||||||
Then, the color coordinate becomes an index into the palette.
|
The color coordinate then becomes an index into the palette.
|
||||||
|
|
||||||
There's one small complication: the color coordinate is continuous, but the palette
|
There's one small complication: the color coordinate is continuous, but the palette
|
||||||
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
|
||||||
@ -163,11 +166,11 @@ import colorFromPaletteSource from "!!raw-loader!./colorFromPalette";
|
|||||||
<summary>As an alternative...</summary>
|
<summary>As an alternative...</summary>
|
||||||
|
|
||||||
...you could also interpolate between colors in the palette.
|
...you could also interpolate between colors in the palette.
|
||||||
For example: [`flam3` code](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 our palette is plotted on a small vertical strip.
|
||||||
Putting the strips side by side shows the palette used by our reference image:
|
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"
|
||||||
import {PaletteBar} from "./FlameColor"
|
import {PaletteBar} from "./FlameColor"
|
||||||
@ -177,14 +180,15 @@ 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. After translating from color coordinate ($c_f$)
|
||||||
to RGB value, add that value to the image histogram:
|
to RGB value, add that to the image histogram:
|
||||||
|
|
||||||
import chaosGameColorSource from "!!raw-loader!./chaosGameColor"
|
import chaosGameColorSource from "!!raw-loader!./chaosGameColor"
|
||||||
|
|
||||||
<CodeBlock language="typescript">{chaosGameColorSource}</CodeBlock>
|
<CodeBlock language="typescript">{chaosGameColorSource}</CodeBlock>
|
||||||
|
|
||||||
Finally, painting the image. With tone mapping, logarithms scale the image brightness to match
|
Finally, painting the image. With tone mapping, logarithms scale the image brightness to match
|
||||||
how it is perceived. When using color, we scale each color channel by the alpha channel:
|
how it is perceived. With color, we use a similar method, but scale each color channel
|
||||||
|
by the alpha channel:
|
||||||
|
|
||||||
import paintColorSource from "!!raw-loader!./paintColor"
|
import paintColorSource from "!!raw-loader!./paintColor"
|
||||||
|
|
||||||
@ -206,10 +210,10 @@ Next, introducing a third coordinate to the chaos game makes color images possib
|
|||||||
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 color to our transforms.
|
||||||
|
|
||||||
The Fractal Flame Algorithm paper goes on to describe more techniques
|
The Fractal Flame Algorithm paper does go on to describe more techniques
|
||||||
not covered here. 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. 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 were able to go 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
|
||||||
|
@ -8,7 +8,8 @@ type PainterProps = {
|
|||||||
}
|
}
|
||||||
export const PainterContext = createContext<PainterProps>(null)
|
export const PainterContext = createContext<PainterProps>(null)
|
||||||
|
|
||||||
const downloadImage = (e: MouseEvent) => {
|
const downloadImage = (name: string) =>
|
||||||
|
(e: MouseEvent) => {
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.download = "flame.png";
|
link.download = "flame.png";
|
||||||
link.href = (e.target as HTMLCanvasElement).toDataURL("image/png");
|
link.href = (e.target as HTMLCanvasElement).toDataURL("image/png");
|
||||||
@ -16,10 +17,11 @@ const downloadImage = (e: MouseEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CanvasProps = {
|
type CanvasProps = {
|
||||||
|
name: string;
|
||||||
style?: any;
|
style?: any;
|
||||||
children?: React.ReactElement
|
children?: React.ReactElement
|
||||||
}
|
}
|
||||||
export const Canvas: React.FC<CanvasProps> = ({style, children}) => {
|
export const Canvas: React.FC<CanvasProps> = ({name, style, children}) => {
|
||||||
const sizingRef = useRef<HTMLDivElement>(null);
|
const sizingRef = useRef<HTMLDivElement>(null);
|
||||||
const [width, setWidth] = useState(0);
|
const [width, setWidth] = useState(0);
|
||||||
const [height, setHeight] = useState(0);
|
const [height, setHeight] = useState(0);
|
||||||
@ -93,7 +95,7 @@ export const Canvas: React.FC<CanvasProps> = ({style, children}) => {
|
|||||||
<>
|
<>
|
||||||
<center>
|
<center>
|
||||||
<div ref={sizingRef} style={style}>
|
<div ref={sizingRef} style={style}>
|
||||||
{width > 0 ? <canvas {...canvasProps} onDoubleClick={downloadImage}/> : null}
|
{width > 0 ? <canvas {...canvasProps} onDoubleClick={downloadImage(name)}/> : null}
|
||||||
</div>
|
</div>
|
||||||
</center>
|
</center>
|
||||||
<PainterContext.Provider value={{width, height, setPainter}}>
|
<PainterContext.Provider value={{width, height, setPainter}}>
|
||||||
@ -103,6 +105,6 @@ export const Canvas: React.FC<CanvasProps> = ({style, children}) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SquareCanvas: React.FC<CanvasProps> = ({style, children}) => {
|
export const SquareCanvas: React.FC<CanvasProps> = ({name, style, children}) => {
|
||||||
return <center><Canvas style={{width: '75%', aspectRatio: '1/1', ...style}} children={children}/></center>
|
return <center><Canvas name={name} style={{width: '75%', aspectRatio: '1/1', ...style}} children={children}/></center>
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
.inputGroup {
|
.inputGroup {
|
||||||
padding: .2em;
|
padding: .2em;
|
||||||
margin-top: .2em;
|
margin-top: .5em;
|
||||||
margin-bottom: .2em;
|
margin-bottom: .5em;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-radius: var(--ifm-global-radius);
|
border-radius: var(--ifm-global-radius);
|
||||||
border-color: var(--ifm-color-emphasis-500);
|
border-color: var(--ifm-color-emphasis-500);
|
||||||
@ -25,6 +25,10 @@
|
|||||||
|
|
||||||
.inputElement > input[type=range] {
|
.inputElement > input[type=range] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
appearance: none;
|
||||||
|
background: var(--ifm-color-emphasis-400);
|
||||||
|
border-radius: var(--ifm-global-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputReset {
|
.inputReset {
|
||||||
|
Loading…
Reference in New Issue
Block a user