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}) {
|
||||
return (
|
||||
<SquareCanvas>
|
||||
<SquareCanvas name={"gasket"}>
|
||||
<Render f={f}/>
|
||||
</SquareCanvas>
|
||||
)
|
||||
|
@ -31,7 +31,7 @@ export default function GasketWeighted() {
|
||||
<>
|
||||
<div className={styles.inputElement}>
|
||||
<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))}/>
|
||||
</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
|
||||
|
||||
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 banner from '../banner.png'
|
||||
@ -22,14 +22,15 @@ import banner from '../banner.png'
|
||||
|
||||
<!-- 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
---
|
||||
@ -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),"
|
||||
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)
|
||||
@ -69,15 +70,15 @@ export const simpleData = [
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
(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
|
||||
known as an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)."
|
||||
While you could theoretically use any function, we'll start with a specific kind of function
|
||||
called an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)."
|
||||
|
||||
The general form of an affine transformation is:
|
||||
|
||||
@ -102,7 +103,7 @@ d &= 0 \\
|
||||
e &= 1 \\
|
||||
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) &= (x + 0.5, y + 0.5)
|
||||
F_{shift}(x, y) &= (x + 0.5, y + 1.5)
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
@ -139,7 +140,7 @@ $$
|
||||
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$,
|
||||
> 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$.
|
||||
|
||||
:::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
|
||||
- **...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,
|
||||
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.
|
||||
|
||||
However, this is all a bit vague, so let's work through an example.
|
||||
Thus, by applying the functions to fixed points of our system, we will find the other points we care about.
|
||||
|
||||
<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
|
||||
algorithm demonstrates, we only need functions to be contractive _on average_. At worst, the system will
|
||||
degenerate and produce a bad image.
|
||||
|
||||
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>
|
||||
|
||||
## 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) \\
|
||||
@ -200,7 +202,7 @@ $$
|
||||
### 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)"
|
||||
that will give us points in the solution set.
|
||||
that gives us points in the solution set:
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
@ -214,12 +216,13 @@ $$
|
||||
$$
|
||||
|
||||
:::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.
|
||||
|
||||
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'
|
||||
|
||||
@ -233,14 +236,12 @@ import randintSource from '!!raw-loader!../src/randomInteger'
|
||||
|
||||
### Plotting
|
||||
|
||||
Finally, implementing the `plot` function. This blog series
|
||||
is designed to be interactive, so everything shows
|
||||
real-time directly in the browser. As an alternative,
|
||||
software like `flam3` an Apophysis can also save an image.
|
||||
Finally, implementing the `plot` function. This blog series is designed to be interactive,
|
||||
so everything displays directly in the browser. As an alternative,
|
||||
software like `flam3` and Apophysis can "plot" by saving an image to disk.
|
||||
|
||||
To display 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,
|
||||
and display it on screen.
|
||||
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 in an image and display it on screen.
|
||||
|
||||
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
|
||||
@ -250,10 +251,9 @@ import cameraSource from "!!raw-loader!./cameraGasket"
|
||||
|
||||
<CodeBlock language="typescript">{cameraSource}</CodeBlock>
|
||||
|
||||
Next, we'll use an [`ImageData` object](https://developer.mozilla.org/en-US/docs/Web/API/ImageData)
|
||||
to store the pixel data.
|
||||
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.
|
||||
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'
|
||||
|
||||
@ -271,18 +271,16 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
|
||||
<hr/>
|
||||
|
||||
<small>
|
||||
The image here is slightly different than the one in the paper.
|
||||
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 image here is slightly different than in the paper.
|
||||
I think the paper has an error, so I'm choosing to plot the image
|
||||
the same way as the [reference implementation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441).
|
||||
</small>
|
||||
|
||||
### Weights
|
||||
|
||||
Finally, we'll introduce a "weight" ($w_i$) for each function that controls how often we choose
|
||||
that function during the chaos game relative to each other function.
|
||||
|
||||
For Sierpinski's Gasket, we start with equal weighting,
|
||||
but you can see how changing the function weights affects the image below:
|
||||
There's one last step before we finish the introduction. So far, each function $F_i$ has
|
||||
the same chance of being chosen. We can change that by introducing a "weight" ($w_i$)
|
||||
to each transform in the chaos game:
|
||||
|
||||
import randomChoiceSource from '!!raw-loader!../src/randomChoice'
|
||||
|
||||
@ -292,16 +290,23 @@ import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted";
|
||||
|
||||
<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 {SquareCanvas} from "../src/Canvas";
|
||||
|
||||
<SquareCanvas><GasketWeighted/></SquareCanvas>
|
||||
<SquareCanvas name={"gasket_weighted"}><GasketWeighted/></SquareCanvas>
|
||||
|
||||
## Summary
|
||||
|
||||
Studying the foundations of fractal flames is challenging,
|
||||
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
|
||||
bring: variations.
|
||||
In the next post, we'll study the first innovation of fractal flames: variations.
|
@ -12,9 +12,9 @@ shapes and patterns that fractal flames are known for.
|
||||
<!-- truncate -->
|
||||
|
||||
:::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/)
|
||||
can load that file and you can try tweaking things yourself!
|
||||
can load that file to play around with!
|
||||
:::
|
||||
|
||||
## 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
|
||||
algorithm is known for.
|
||||
|
||||
This leads us to the first big innovation of the Fractal Flame algorithm: applying non-linear functions
|
||||
after the affine transform has happened. These functions are called "variations":
|
||||
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":
|
||||
|
||||
$$
|
||||
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>
|
||||
|
||||
Just like transforms, variations ($V_j$) are functions that map $(x, y)$ coordinates to new coordinates.
|
||||
However, the sky is the limit for what we can do in between input and output.
|
||||
Just like transforms, variations ($V_j$) are functions that take in $(x, y)$ coordinates
|
||||
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,
|
||||
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
|
||||
and probability to produce interesting shapes:
|
||||
|
||||
TODO: Connection with the julia set?
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
r &= \sqrt{x^2 + y^2} \\
|
||||
@ -93,7 +92,7 @@ import juliaSrc from '!!raw-loader!../src/julia'
|
||||
|
||||
### 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$:
|
||||
|
||||
$$
|
||||
@ -106,7 +105,7 @@ import popcornSrc from '!!raw-loader!../src/popcorn'
|
||||
|
||||
### 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:
|
||||
|
||||
$$
|
||||
@ -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."
|
||||
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)
|
||||
@ -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 FlameBlend from "./FlameBlend";
|
||||
|
||||
<SquareCanvas><FlameBlend/></SquareCanvas>
|
||||
<SquareCanvas name={"flame_blend"}><FlameBlend/></SquareCanvas>
|
||||
|
||||
## 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:
|
||||
|
||||
$$
|
||||
@ -163,19 +163,18 @@ import postSource from '!!raw-loader!./post'
|
||||
<CodeBlock language="typescript">{postSource}</CodeBlock>
|
||||
|
||||
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>
|
||||
<summary>If you want to test your understanding...</summary>
|
||||
|
||||
Challenge 1: What post-transform coefficients will give us the previous image?
|
||||
|
||||
Challenge 2: What post-transform coefficients will give us a _mirrored_ image?
|
||||
- What post-transform coefficients will give us the previous image?
|
||||
- What post-transform coefficients will give us a _mirrored_ image?
|
||||
</details>
|
||||
|
||||
import FlamePost from "./FlamePost";
|
||||
|
||||
<SquareCanvas><FlamePost/></SquareCanvas>
|
||||
<SquareCanvas name={"flame_post"}><FlamePost/></SquareCanvas>
|
||||
|
||||
## Final transforms
|
||||
|
||||
@ -204,12 +203,12 @@ import chaosGameFinalSource from "!!raw-loader!./chaosGameFinal"
|
||||
|
||||
import FlameFinal from "./FlameFinal";
|
||||
|
||||
<SquareCanvas><FlameFinal/></SquareCanvas>
|
||||
<SquareCanvas name={"flame_final"}><FlameFinal/></SquareCanvas>
|
||||
|
||||
## Summary
|
||||
|
||||
Variations are the fractal flame algorithm's first major innovation over previous IFS's.
|
||||
Blending variation functions and post/final transforms allows us to generate interesting images.
|
||||
Variations are the fractal flame algorithm's first major innovation.
|
||||
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
|
||||
the image quality and add some color.
|
@ -6,9 +6,9 @@ authors: [bspeice]
|
||||
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),
|
||||
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.
|
||||
|
||||
@ -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
|
||||
:::
|
||||
|
||||
To start, it's worth demonstrating how much work is actually "wasted"
|
||||
when we treat pixels as a binary "on" (opaque) or "off" (transparent).
|
||||
One problem with the existing chaos game is that we waste work
|
||||
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
|
||||
we encounter a pixel during the chaos game. This gives us a kind of "histogram"
|
||||
of the image:
|
||||
To demonstrate how much work is wasted, we'll render the reference image again.
|
||||
However, we'll also count each time the chaos game encounters a pixel.
|
||||
This gives us a kind of image "histogram":
|
||||
|
||||
import chaosGameHistogramSource from "!!raw-loader!./chaosGameHistogram"
|
||||
|
||||
<CodeBlock language="typescript">{chaosGameHistogramSource}</CodeBlock>
|
||||
|
||||
When the chaos game finishes, find the pixel we encountered most frequently.
|
||||
Finally, "paint" the image by setting each pixel's alpha value (transparency)
|
||||
to the ratio of times encountered, divided by the maximum value:
|
||||
When the chaos game finishes, we find the pixel encountered most frequently.
|
||||
Finally, we "paint" the image by setting each pixel's alpha value (transparency)
|
||||
to the ratio of times encountered divided by the maximum:
|
||||
|
||||
import CodeBlock from "@theme/CodeBlock";
|
||||
|
||||
@ -49,27 +50,27 @@ import {paintLinear} from "./paintLinear";
|
||||
|
||||
## 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!
|
||||
|
||||
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
|
||||
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
|
||||
nice images of scenes with wide brightness ranges. 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.
|
||||
By taking multiple pictures using different exposure times, we can combine them to create
|
||||
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 lead to "hot spots" (sections that are pure white).
|
||||
By taking multiple pictures with different exposure times, we can combine them to create
|
||||
a final image where everything is visible.
|
||||
|
||||
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)
|
||||
will still be visible, and "bright spots" (pixels the chaos game visits frequently) won't wash out.
|
||||
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.
|
||||
|
||||
<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
|
||||
> if their densities are different enough because the lesser density is inconsequential in sum.
|
||||
@ -89,15 +90,17 @@ import {paintLogarithmic} from './paintLogarithmic'
|
||||
## 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
|
||||
responsible for each part of the image.
|
||||
By including a third coordinate ($c$) in the chaos game, we can illustrate the transforms
|
||||
responsible for the image.
|
||||
|
||||
### 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 allows us to swap in new color palettes easily
|
||||
- It helps blend colors together in the final image. Slight changes in the color value lead to
|
||||
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.
|
||||
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
|
||||
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.
|
||||
|
||||
### Palette
|
||||
|
||||
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.
|
||||
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
|
||||
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>
|
||||
|
||||
...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>
|
||||
|
||||
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 {PaletteBar} from "./FlameColor"
|
||||
@ -177,14 +180,15 @@ import {PaletteBar} from "./FlameColor"
|
||||
### Plotting
|
||||
|
||||
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"
|
||||
|
||||
<CodeBlock language="typescript">{chaosGameColorSource}</CodeBlock>
|
||||
|
||||
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"
|
||||
|
||||
@ -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
|
||||
color scale and color palette adds a splash of color to our transforms.
|
||||
|
||||
The Fractal Flame Algorithm paper goes on to describe more techniques
|
||||
not covered here. Image quality can be improved with density estimation
|
||||
The Fractal Flame Algorithm paper does go on to describe more techniques
|
||||
not covered here. For example, Image quality can be improved with density estimation
|
||||
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
|
||||
an introduction to the mathematics of fractal systems all the way to
|
||||
|
@ -8,18 +8,20 @@ type PainterProps = {
|
||||
}
|
||||
export const PainterContext = createContext<PainterProps>(null)
|
||||
|
||||
const downloadImage = (e: MouseEvent) => {
|
||||
const link = document.createElement("a");
|
||||
link.download = "flame.png";
|
||||
link.href = (e.target as HTMLCanvasElement).toDataURL("image/png");
|
||||
link.click();
|
||||
}
|
||||
const downloadImage = (name: string) =>
|
||||
(e: MouseEvent) => {
|
||||
const link = document.createElement("a");
|
||||
link.download = "flame.png";
|
||||
link.href = (e.target as HTMLCanvasElement).toDataURL("image/png");
|
||||
link.click();
|
||||
}
|
||||
|
||||
type CanvasProps = {
|
||||
name: string;
|
||||
style?: any;
|
||||
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 [width, setWidth] = useState(0);
|
||||
const [height, setHeight] = useState(0);
|
||||
@ -93,7 +95,7 @@ export const Canvas: React.FC<CanvasProps> = ({style, children}) => {
|
||||
<>
|
||||
<center>
|
||||
<div ref={sizingRef} style={style}>
|
||||
{width > 0 ? <canvas {...canvasProps} onDoubleClick={downloadImage}/> : null}
|
||||
{width > 0 ? <canvas {...canvasProps} onDoubleClick={downloadImage(name)}/> : null}
|
||||
</div>
|
||||
</center>
|
||||
<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}) => {
|
||||
return <center><Canvas style={{width: '75%', aspectRatio: '1/1', ...style}} children={children}/></center>
|
||||
export const SquareCanvas: React.FC<CanvasProps> = ({name, style, children}) => {
|
||||
return <center><Canvas name={name} style={{width: '75%', aspectRatio: '1/1', ...style}} children={children}/></center>
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
.inputGroup {
|
||||
padding: .2em;
|
||||
margin-top: .2em;
|
||||
margin-bottom: .2em;
|
||||
margin-top: .5em;
|
||||
margin-bottom: .5em;
|
||||
border: 1px solid;
|
||||
border-radius: var(--ifm-global-radius);
|
||||
border-color: var(--ifm-color-emphasis-500);
|
||||
@ -25,6 +25,10 @@
|
||||
|
||||
.inputElement > input[type=range] {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
appearance: none;
|
||||
background: var(--ifm-color-emphasis-400);
|
||||
border-radius: var(--ifm-global-radius);
|
||||
}
|
||||
|
||||
.inputReset {
|
||||
|
Loading…
Reference in New Issue
Block a user