mirror of
				https://github.com/bspeice/speice.io
				synced 2025-11-03 18:10:32 -05:00 
			
		
		
		
	Actually doing some writing
This commit is contained in:
		@ -9,6 +9,7 @@ const xforms = [
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
function* chaosGame({width, height}) {
 | 
			
		||||
  const step = 1000;
 | 
			
		||||
  let img = new ImageData(width, height);
 | 
			
		||||
  let [x, y] = [
 | 
			
		||||
    randomBiUnit(),
 | 
			
		||||
@ -22,7 +23,7 @@ function* chaosGame({width, height}) {
 | 
			
		||||
    if (c > 20)
 | 
			
		||||
      plot(x, y, img);
 | 
			
		||||
 | 
			
		||||
    if (c % 1000 === 0)
 | 
			
		||||
    if (c % step === 0)
 | 
			
		||||
      yield img;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ import {Transform} from "../src/transform";
 | 
			
		||||
const iterations = 50_000;
 | 
			
		||||
const step = 1000;
 | 
			
		||||
// hidden-end
 | 
			
		||||
type Props = {
 | 
			
		||||
export type Props = {
 | 
			
		||||
    width: number,
 | 
			
		||||
    height: number,
 | 
			
		||||
    transforms: [number, Transform][]
 | 
			
		||||
@ -20,6 +20,7 @@ export function* chaosGameWeighted(
 | 
			
		||||
      randomBiUnit()
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  // TODO: Explain quality
 | 
			
		||||
  const iterations = width * height * 0.5;
 | 
			
		||||
  for (let c = 0; c < iterations; c++) {
 | 
			
		||||
    // highlight-start
 | 
			
		||||
 | 
			
		||||
@ -6,11 +6,11 @@ authors: [bspeice]
 | 
			
		||||
tags: []
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
Wikipedia [describes](https://en.wikipedia.org/wiki/Fractal_flame) fractal flames as:
 | 
			
		||||
Wikipedia describes fractal flames fractal flames as:
 | 
			
		||||
 | 
			
		||||
> a member of the iterated function system class of fractals
 | 
			
		||||
 | 
			
		||||
I think of them a different way: beauty in mathematics.
 | 
			
		||||
It's a bit 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'
 | 
			
		||||
@ -21,37 +21,38 @@ import banner from '../banner.png'
 | 
			
		||||
 | 
			
		||||
<!-- truncate -->
 | 
			
		||||
 | 
			
		||||
I don't remember exactly when or how I originally came across fractal flames, but I do remember becoming entranced by the images they created.
 | 
			
		||||
I also remember their unique appeal to my young engineering mind; this was an art form I could actively participate in.
 | 
			
		||||
I don't remember exactly when I first learned about fractal flames, but I do remember becoming 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 [paper](https://flam3.com/flame_draves.pdf) describing their mathematical structure was too much
 | 
			
		||||
for me to handle at the time (I was ~12 years old), and I was content to play around and enjoy the pictures.
 | 
			
		||||
But the desire to understand it stuck with me, so I wanted to try again. With a graduate degree in Financial Engineering under my belt,
 | 
			
		||||
maybe it would be easier this time.
 | 
			
		||||
The original [Fractal Flame](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.
 | 
			
		||||
 | 
			
		||||
This guide is my attempt to explain fractal flames in a way that younger me — and others interested in the art —
 | 
			
		||||
can understand without too much prior knowledge.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Iterated function systems
 | 
			
		||||
 | 
			
		||||
Let's begin by defining an "[iterated function system](https://en.wikipedia.org/wiki/Iterated_function_system)" (IFS).
 | 
			
		||||
We'll start at the end and work backwards to build a practical understanding. In mathematical notation, an IFS is:
 | 
			
		||||
As mentioned above, fractal flames are a type of "[iterated function system](https://en.wikipedia.org/wiki/Iterated_function_system),"
 | 
			
		||||
or IFS. Their mathematical foundations come from a paper written by [John E. Hutchinson](https://maths-people.anu.edu.au/~john/Assets/Research%20Papers/fractals_self-similarity.pdf),
 | 
			
		||||
but reading that paper isn't critical for our purposes. Instead, we'll focus on building a practical understanding
 | 
			
		||||
of how they work. The formula for an IFS is short, but will take some time to unpack:
 | 
			
		||||
 | 
			
		||||
$$
 | 
			
		||||
S = \bigcup_{i=0}^{n-1} F_i(S) \\[0.6cm]
 | 
			
		||||
S \in \mathbb{R}^2 \\
 | 
			
		||||
F_i(S) \in \mathbb{R}^2 \rightarrow \mathbb{R}^2
 | 
			
		||||
S = \bigcup_{i=0}^{n-1} F_i(S)
 | 
			
		||||
$$
 | 
			
		||||
 | 
			
		||||
### Stationary point
 | 
			
		||||
### Fixed set
 | 
			
		||||
 | 
			
		||||
First, $S$. We're generating images, so everything is in two dimensions: $S \in \mathbb{R}^2$. The set $S$ is
 | 
			
		||||
all points that are "in the system." To generate our final image, we just plot every point in the system
 | 
			
		||||
like a coordinate chart.
 | 
			
		||||
 | 
			
		||||
TODO: What is a stationary point? How does it relate to the chaos game? Why does the chaos game work?
 | 
			
		||||
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. Our goal is to find all points in the set $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:
 | 
			
		||||
 | 
			
		||||
<!-- TODO: What is a stationary point? How does it relate to the chaos game? Why does the chaos game work? -->
 | 
			
		||||
 | 
			
		||||
import {VictoryChart, VictoryTheme, VictoryScatter, VictoryLegend} from "victory";
 | 
			
		||||
export const simpleData = [
 | 
			
		||||
    {x: 0, y: 0},
 | 
			
		||||
@ -63,8 +64,18 @@ export const simpleData = [
 | 
			
		||||
    <VictoryScatter data={simpleData} size={5} style={{data: {fill: "blue"}}}/>
 | 
			
		||||
</VictoryChart>
 | 
			
		||||
 | 
			
		||||
For fractal flames, we just need to figure out which points are in $S$ and plot them. While there are
 | 
			
		||||
technically an infinite number of points, if we find _enough_ points and plot them, we end up with a nice picture.
 | 
			
		||||
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 choose different functions to start with, our solution set changes, and we'll end up
 | 
			
		||||
with a new picture.
 | 
			
		||||
 | 
			
		||||
However, it's not clear which points belong in the solution just by staring at the functions.
 | 
			
		||||
We'll need a computer to figure it out.
 | 
			
		||||
 | 
			
		||||
TODO: Other topics worth covering in this section? Maybe in a `details` block?:
 | 
			
		||||
- Fixed sets: https://en.wiktionary.org/wiki/fixed_set
 | 
			
		||||
- Compact sets
 | 
			
		||||
 | 
			
		||||
### Transformation functions
 | 
			
		||||
 | 
			
		||||
@ -182,7 +193,8 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
 | 
			
		||||
<hr/>
 | 
			
		||||
 | 
			
		||||
<small>
 | 
			
		||||
Note: The image here is different than the fractal flame paper, but I think the paper has an error.
 | 
			
		||||
Note: The image here is slightly different than the fractal flame 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).
 | 
			
		||||
</small>
 | 
			
		||||
 | 
			
		||||
## Weights
 | 
			
		||||
 | 
			
		||||
@ -29,17 +29,17 @@ export function plot(
 | 
			
		||||
  let pixelY = Math.floor(y * img.height);
 | 
			
		||||
 | 
			
		||||
  const index = imageIndex(
 | 
			
		||||
      img.width,
 | 
			
		||||
      pixelX,
 | 
			
		||||
      pixelY
 | 
			
		||||
    img.width,
 | 
			
		||||
    pixelX,
 | 
			
		||||
    pixelY
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Skip pixels outside the display range
 | 
			
		||||
  if (
 | 
			
		||||
      index < 0 ||
 | 
			
		||||
      index > img.data.length
 | 
			
		||||
    index < 0 ||
 | 
			
		||||
    index > img.data.length
 | 
			
		||||
  ) {
 | 
			
		||||
      return;
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set the pixel to black by writing 0
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ export const CoefEditor = ({title, isPost, coefs, setCoefs, resetCoefs}: Props)
 | 
			
		||||
    const resetButton = <button className={styles.inputReset} onClick={resetCoefs}>Reset</button>
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: 'auto auto auto'}}>
 | 
			
		||||
        <div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr'}}>
 | 
			
		||||
            <p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>{title} {resetButton}</p>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <p>{isPost ? <TeX>\alpha</TeX> : 'a'}: {coefs.a}</p>
 | 
			
		||||
 | 
			
		||||
@ -4,9 +4,8 @@ import * as params from "../src/params"
 | 
			
		||||
import {PainterContext} from "../src/Canvas"
 | 
			
		||||
import {chaosGameFinal} from "./chaosGameFinal"
 | 
			
		||||
import {VariationEditor, VariationProps} from "./VariationEditor"
 | 
			
		||||
import {xform1Weight} from "../src/params";
 | 
			
		||||
import {applyTransform} from "@site/blog/2024-11-15-playing-with-fire/src/applyTransform";
 | 
			
		||||
import {buildBlend} from "@site/blog/2024-11-15-playing-with-fire/2-transforms/buildBlend";
 | 
			
		||||
import {applyTransform} from "../src/applyTransform";
 | 
			
		||||
import {buildBlend} from "./buildBlend";
 | 
			
		||||
 | 
			
		||||
export default function FlameBlend() {
 | 
			
		||||
    const {width, height, setPainter} = useContext(PainterContext);
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import {Coefs} from "../src/coefs"
 | 
			
		||||
import {Transform} from "../src/transform";
 | 
			
		||||
import * as params from "../src/params";
 | 
			
		||||
import {PainterContext} from "../src/Canvas"
 | 
			
		||||
import {chaosGameFinal, ChaosGameFinalProps} from "./chaosGameFinal"
 | 
			
		||||
import {chaosGameFinal, Props as ChaosGameFinalProps} from "./chaosGameFinal"
 | 
			
		||||
import {CoefEditor} from "./CoefEditor"
 | 
			
		||||
import {applyPost, applyTransform} from "@site/blog/2024-11-15-playing-with-fire/src/applyTransform";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ export const VariationEditor = ({title, variations, setVariations, resetVariatio
 | 
			
		||||
    const resetButton = <button className={styles.inputReset} onClick={resetVariations}>Reset</button>
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: 'auto auto auto auto'}}>
 | 
			
		||||
        <div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: '1fr 1fr'}}>
 | 
			
		||||
            <p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>{title} {resetButton}</p>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <span>Linear: {variations.linear}</span>
 | 
			
		||||
 | 
			
		||||
@ -3,20 +3,19 @@ import { randomBiUnit } from "../src/randomBiUnit";
 | 
			
		||||
import { randomChoice } from "../src/randomChoice";
 | 
			
		||||
import { plotBinary as plot } from "../src/plotBinary"
 | 
			
		||||
import {Transform} from "../src/transform";
 | 
			
		||||
import {ChaosGameWeightedProps} from "../1-introduction/chaosGameWeighted";
 | 
			
		||||
import {Props as ChaosGameWeightedProps} from "../1-introduction/chaosGameWeighted";
 | 
			
		||||
 | 
			
		||||
const quality = 0.5;
 | 
			
		||||
const step = 1000;
 | 
			
		||||
// hidden-end
 | 
			
		||||
export type ChaosGameFinalProps = ChaosGameWeightedProps & {
 | 
			
		||||
export type Props = ChaosGameWeightedProps & {
 | 
			
		||||
    final: Transform,
 | 
			
		||||
    quality?: number,
 | 
			
		||||
    step?: number,
 | 
			
		||||
}
 | 
			
		||||
export function* chaosGameFinal({width, height, transforms, final, quality, step}: ChaosGameFinalProps) {
 | 
			
		||||
export function* chaosGameFinal({width, height, transforms, final}: Props) {
 | 
			
		||||
    let image = new ImageData(width, height);
 | 
			
		||||
    let [x, y] = [randomBiUnit(), randomBiUnit()];
 | 
			
		||||
 | 
			
		||||
    const iterations = (quality ?? 0.5) * width * height;
 | 
			
		||||
    step = step ?? 1000;
 | 
			
		||||
 | 
			
		||||
    const iterations = width * height * quality;
 | 
			
		||||
    for (let i = 0; i < iterations; i++) {
 | 
			
		||||
        const [_, transform] = randomChoice(transforms);
 | 
			
		||||
        [x, y] = transform(x, y);
 | 
			
		||||
 | 
			
		||||
@ -142,10 +142,10 @@ The sliders below change the variation weights for each transform (the $v_{ij}$
 | 
			
		||||
try changing them around to see which parts of the image are controlled by
 | 
			
		||||
each transform.
 | 
			
		||||
 | 
			
		||||
import Canvas from "../src/Canvas";
 | 
			
		||||
import {SquareCanvas} from "../src/Canvas";
 | 
			
		||||
import FlameBlend from "./FlameBlend";
 | 
			
		||||
 | 
			
		||||
<!-- <Canvas><FlameBlend/></Canvas> -->
 | 
			
		||||
<SquareCanvas><FlameBlend/></SquareCanvas>
 | 
			
		||||
 | 
			
		||||
## Post transforms
 | 
			
		||||
 | 
			
		||||
@ -160,10 +160,10 @@ $$
 | 
			
		||||
 | 
			
		||||
import FlamePost from "./FlamePost";
 | 
			
		||||
 | 
			
		||||
<!-- <Canvas><FlamePost/></Canvas> -->
 | 
			
		||||
<SquareCanvas><FlamePost/></SquareCanvas>
 | 
			
		||||
 | 
			
		||||
## Final transform
 | 
			
		||||
 | 
			
		||||
import FlameFinal from "./FlameFinal";
 | 
			
		||||
 | 
			
		||||
<!-- <Canvas><FlameFinal/></Canvas> -->
 | 
			
		||||
<SquareCanvas><FlameFinal/></SquareCanvas>
 | 
			
		||||
@ -1,35 +1,12 @@
 | 
			
		||||
import React, {useContext, useEffect, useMemo, useRef, useState} from "react";
 | 
			
		||||
import * as params from "../src/params";
 | 
			
		||||
import {InvertibleCanvas, PainterContext} from "../src/Canvas";
 | 
			
		||||
import {PainterContext} from "../src/Canvas";
 | 
			
		||||
import {colorFromPalette} from "./paintColor";
 | 
			
		||||
import {chaosGameColor, ChaosGameColorProps, TransformColor} from "./chaosGameColor";
 | 
			
		||||
 | 
			
		||||
import styles from "../src/css/styles.module.css";
 | 
			
		||||
import {histIndex} from "../src/camera";
 | 
			
		||||
 | 
			
		||||
type AutoSizingCanvasProps = {
 | 
			
		||||
    painter: (width: number, height: number) => ImageData;
 | 
			
		||||
}
 | 
			
		||||
const AutoSizingCanvas: React.FC<AutoSizingCanvasProps> = ({painter}) => {
 | 
			
		||||
    const sizingRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
    const [width, setWidth] = useState<number>(0);
 | 
			
		||||
    const [height, setHeight] = useState<number>(0);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (sizingRef) {
 | 
			
		||||
            setWidth(sizingRef.current.offsetWidth);
 | 
			
		||||
            setHeight(sizingRef.current.offsetHeight)
 | 
			
		||||
        }
 | 
			
		||||
    }, [sizingRef]);
 | 
			
		||||
 | 
			
		||||
    const image: [ImageData] = useMemo(() => (width && height) ? [painter(width, height)] : null, [painter, width, height]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div ref={sizingRef} style={{width: '100%', height: '100%'}}>
 | 
			
		||||
            <InvertibleCanvas width={width} height={height} image={image}/>
 | 
			
		||||
        </div>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
import {useColorMode} from "@docusaurus/theme-common";
 | 
			
		||||
 | 
			
		||||
const paletteBarPainter = (palette: number[]) =>
 | 
			
		||||
    (width: number, height: number) => {
 | 
			
		||||
@ -60,7 +37,7 @@ const PaletteBar: React.FC<PaletteBarProps> = ({height, palette, children}) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div style={{width: '100%', height, marginTop: '1em', marginBottom: '1em'}}>
 | 
			
		||||
                <AutoSizingCanvas painter={painter}/>
 | 
			
		||||
                {/*<AutoSizingCanvas painter={painter}/>*/}
 | 
			
		||||
            </div>
 | 
			
		||||
            {children}
 | 
			
		||||
        </>
 | 
			
		||||
@ -90,9 +67,13 @@ type ColorEditorProps = {
 | 
			
		||||
    children?: React.ReactNode;
 | 
			
		||||
}
 | 
			
		||||
const ColorEditor: React.FC<ColorEditorProps> = ({title, palette, transformColor, setTransformColor, resetTransformColor, children}) => {
 | 
			
		||||
    const painter = useMemo(() => colorSwatchPainter(palette, transformColor.color), [palette, transformColor]);
 | 
			
		||||
    const resetButton = <button className={styles.inputReset} onClick={resetTransformColor}>Reset</button>
 | 
			
		||||
 | 
			
		||||
    const [r, g, b] = colorFromPalette(palette, transformColor.color);
 | 
			
		||||
    const colorCss = `rgb(${Math.floor(r * 0xff)},${Math.floor(g * 0xff)},${Math.floor(b * 0xff)})`;
 | 
			
		||||
 | 
			
		||||
    const {colorMode} = useColorMode();
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: '2fr 2fr 1fr'}}>
 | 
			
		||||
@ -107,9 +88,12 @@ const ColorEditor: React.FC<ColorEditorProps> = ({title, palette, transformColor
 | 
			
		||||
                    <input type={'range'} min={0} max={1} step={.001} value={transformColor.colorSpeed}
 | 
			
		||||
                           onInput={e => setTransformColor({...transformColor, colorSpeed: Number(e.currentTarget.value)})}/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className={styles.inputElement}>
 | 
			
		||||
                    <AutoSizingCanvas painter={painter}/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className={styles.inputElement} style={{
 | 
			
		||||
                    width: '100%',
 | 
			
		||||
                    height: '100%',
 | 
			
		||||
                    backgroundColor: colorCss,
 | 
			
		||||
                    filter: colorMode === 'dark' ? 'invert(1)' : ''
 | 
			
		||||
                }}/>
 | 
			
		||||
            </div>
 | 
			
		||||
            {children}
 | 
			
		||||
        </>
 | 
			
		||||
 | 
			
		||||
@ -28,11 +28,11 @@ import paintLinearSource from "!!raw-loader!./paintLinear"
 | 
			
		||||
 | 
			
		||||
<CodeBlock language="typescript">{paintLinearSource}</CodeBlock>
 | 
			
		||||
 | 
			
		||||
import Canvas from "../src/Canvas";
 | 
			
		||||
import {SquareCanvas} from "../src/Canvas";
 | 
			
		||||
import FlameHistogram from "./FlameHistogram";
 | 
			
		||||
import {paintLinear} from "./paintLinear";
 | 
			
		||||
 | 
			
		||||
<!-- <Canvas><FlameHistogram quality={5} paint={paintLinear}/></Canvas> -->
 | 
			
		||||
<SquareCanvas><FlameHistogram quality={5} paint={paintLinear}/></SquareCanvas>
 | 
			
		||||
 | 
			
		||||
## Log display
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ import paintLogarithmicSource from "!!raw-loader!./paintLogarithmic"
 | 
			
		||||
 | 
			
		||||
import {paintLogarithmic} from './paintLogarithmic'
 | 
			
		||||
 | 
			
		||||
<!-- <Canvas><FlameHistogram quality={5} paint={paintLogarithmic}/></Canvas> -->
 | 
			
		||||
<SquareCanvas><FlameHistogram quality={10} paint={paintLogarithmic}/></SquareCanvas>
 | 
			
		||||
 | 
			
		||||
## Color
 | 
			
		||||
 | 
			
		||||
@ -52,4 +52,4 @@ import paintColorSource from "!!raw-loader!./paintColor"
 | 
			
		||||
 | 
			
		||||
import FlameColor from "./FlameColor";
 | 
			
		||||
 | 
			
		||||
<!-- <Canvas><FlameColor quality={15}/></Canvas> -->
 | 
			
		||||
<SquareCanvas><FlameColor quality={15}/></SquareCanvas>
 | 
			
		||||
		Reference in New Issue
	
	Block a user