diff --git a/blog/2024-11-15-playing-with-fire/.prettierrc b/blog/2024-11-15-playing-with-fire/.prettierrc new file mode 100644 index 0000000..b882b19 --- /dev/null +++ b/blog/2024-11-15-playing-with-fire/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "semi": true +} \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.tsx b/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.tsx index e1fdf7a..c3aa4d8 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.tsx +++ b/blog/2024-11-15-playing-with-fire/1-introduction/Gasket.tsx @@ -1,21 +1,21 @@ -import {SquareCanvas, PainterContext} from "../src/Canvas"; -import {useContext, useEffect} from "react"; +import { PainterContext, SquareCanvas } from "../src/Canvas"; +import { useContext, useEffect } from "react"; -export function Render({f}) { - const {width, height, setPainter} = useContext(PainterContext); - useEffect(() => { - if (width && height) { - const painter = f({width, height}); - setPainter(painter); - } - }, [width, height]); - return <>; +export function Render({ f }) { + const { width, height, setPainter } = useContext(PainterContext); + useEffect(() => { + if (width && height) { + const painter = f({ width, height }); + setPainter(painter); + } + }, [width, height]); + return <>; } -export default function Gasket({f}) { - return ( - - - - ) +export default function Gasket({ f }) { + return ( + + + + ); } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/GasketWeighted.tsx b/blog/2024-11-15-playing-with-fire/1-introduction/GasketWeighted.tsx index 3381c56..9d18454 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/GasketWeighted.tsx +++ b/blog/2024-11-15-playing-with-fire/1-introduction/GasketWeighted.tsx @@ -1,49 +1,49 @@ -import {useEffect, useState, useContext, useRef} from "react"; -import {PainterContext} from "../src/Canvas"; -import {chaosGameWeighted} from "./chaosGameWeighted"; -import TeX from '@matejmazur/react-katex'; +import { useContext, useEffect, useState } from "react"; +import { PainterContext } from "../src/Canvas"; +import { chaosGameWeighted } from "./chaosGameWeighted"; +import TeX from "@matejmazur/react-katex"; -import styles from "../src/css/styles.module.css" +import styles from "../src/css/styles.module.css"; type Transform = (x: number, y: number) => [number, number]; export default function GasketWeighted() { - const [f0Weight, setF0Weight] = useState(1); - const [f1Weight, setF1Weight] = useState(1); - const [f2Weight, setF2Weight] = useState(1); + const [f0Weight, setF0Weight] = useState(1); + const [f1Weight, setF1Weight] = useState(1); + const [f2Weight, setF2Weight] = useState(1); - const f0: Transform = (x, y) => [x / 2, y / 2]; - const f1: Transform = (x, y) => [(x + 1) / 2, y / 2]; - const f2: Transform = (x, y) => [x / 2, (y + 1) / 2]; + const f0: Transform = (x, y) => [x / 2, y / 2]; + const f1: Transform = (x, y) => [(x + 1) / 2, y / 2]; + const f2: Transform = (x, y) => [x / 2, (y + 1) / 2]; - const {width, height, setPainter} = useContext(PainterContext); + const { width, height, setPainter } = useContext(PainterContext); - useEffect(() => { - const transforms: [number, Transform][] = [ - [f0Weight, f0], - [f1Weight, f1], - [f2Weight, f2] - ]; - setPainter(chaosGameWeighted({width, height, transforms})); - }, [f0Weight, f1Weight, f2Weight]); + useEffect(() => { + const transforms: [number, Transform][] = [ + [f0Weight, f0], + [f1Weight, f1], + [f2Weight, f2] + ]; + setPainter(chaosGameWeighted({ width, height, transforms })); + }, [f0Weight, f1Weight, f2Weight]); - const weightInput = (title, weight, setWeight) => ( - <> -
-

{title}: {weight}

- setWeight(Number(e.currentTarget.value))}/> -
- - ) + const weightInput = (title, weight, setWeight) => ( + <> +
+

{title}: {weight}

+ setWeight(Number(e.currentTarget.value))} /> +
+ + ); - return ( - <> -
- {weightInput("F_0", f0Weight, setF0Weight)} - {weightInput("F_1", f1Weight, setF1Weight)} - {weightInput("F_2", f2Weight, setF2Weight)} -
- - ) + return ( + <> +
+ {weightInput("F_0", f0Weight, setF0Weight)} + {weightInput("F_1", f1Weight, setF1Weight)} + {weightInput("F_2", f2Weight, setF2Weight)} +
+ + ); } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/cameraGasket.ts b/blog/2024-11-15-playing-with-fire/1-introduction/cameraGasket.ts index 8f729cc..47d104c 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/cameraGasket.ts +++ b/blog/2024-11-15-playing-with-fire/1-introduction/cameraGasket.ts @@ -1,10 +1,10 @@ export function camera( - size: number, - x: number, - y: number + x: number, + y: number, + size: number ): [number, number] { - return [ - Math.floor(x * size), - Math.floor(y * size) - ]; + return [ + Math.floor(x * size), + Math.floor(y * size) + ]; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js index 17f6962..777ccfd 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js +++ b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGame.js @@ -6,28 +6,29 @@ const xforms = [ (x, y) => [x / 2, y / 2], (x, y) => [(x + 1) / 2, y / 2], (x, y) => [x / 2, (y + 1) / 2] -] +]; -function* chaosGame({width, height}) { - const step = 1000; - let img = new ImageData(width, height); +function* chaosGame({ width, height }) { + let img = + new ImageData(width, height); let [x, y] = [ randomBiUnit(), randomBiUnit() ]; - for (let c = 0; c < iterations; c++) { - const i = randomInteger(0, xforms.length); - [x, y] = xforms[i](x, y); + for (let i = 0; i < iterations; i++) { + const index = + randomInteger(0, xforms.length); + [x, y] = xforms[index](x, y); - if (c > 20) + if (i > 20) plot(x, y, img); - if (c % step === 0) + if (i % 1000 === 0) yield img; } yield img; } -render() +render(); diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGameWeighted.ts b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGameWeighted.ts index ef1966a..41a9922 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/chaosGameWeighted.ts +++ b/blog/2024-11-15-playing-with-fire/1-introduction/chaosGameWeighted.ts @@ -1,37 +1,41 @@ // hidden-start import { randomBiUnit } from "../src/randomBiUnit"; import { randomChoice } from "../src/randomChoice"; -import { plot } from "./plot" -import {Transform} from "../src/transform"; +import { plot } from "./plot"; +import { Transform } from "../src/transform"; const quality = 0.5; const step = 1000; // hidden-end export type Props = { - width: number, - height: number, - transforms: [number, Transform][] + width: number, + height: number, + transforms: [number, Transform][] } + export function* chaosGameWeighted( - {width, height, transforms}: Props + { width, height, transforms }: Props ) { - let img = new ImageData(width, height); + let img = + new ImageData(width, height); let [x, y] = [ - randomBiUnit(), - randomBiUnit() + randomBiUnit(), + randomBiUnit() ]; - const iterations = width * height * quality; - for (let c = 0; c < iterations; c++) { + const pixels = width * height; + const iterations = quality * pixels; + for (let i = 0; i < iterations; i++) { // highlight-start - const [_, xform] = randomChoice(transforms); + const [_, xform] = + randomChoice(transforms); // highlight-end [x, y] = xform(x, y); - if (c > 20) + if (i > 20) plot(x, y, img); - if (c % step === 0) + if (i % step === 0) yield img; } diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx index 60a7645..dd9dacc 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx +++ b/blog/2024-11-15-playing-with-fire/1-introduction/index.mdx @@ -83,7 +83,7 @@ called an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transfor The general form of an affine transformation is: $$ -F_i(a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i) +F_i(a_i x + b_i y + c_i, d_i x + e_i y + f_i) $$ import transformSource from "!!raw-loader!../src/transform" @@ -102,8 +102,7 @@ c &= 0.5 \\ 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 + 1.5) +F_{shift}(x, y) &= (1 \cdot x + 0.5, 1 \cdot y + 1.5) \end{align*} $$ @@ -155,7 +154,7 @@ 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 of the Fractal Flame paper +I've tweaked the conventions of that paper slightly to match the Fractal Flame paper ::: Before your eyes glaze over, let's unpack this: @@ -174,15 +173,15 @@ Thus, by applying the functions to fixed points of our system, we will find the ...then there are some extra details I've glossed over so far. 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 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 on $\mathbb{R}^2$ because we're generating images, but the Hutchinson paper allows for arbitrary dimensions; you could also have 3-dimensional fractal flames. Finally, there's a close relationship between fractal flames and [attractors](https://en.wikipedia.org/wiki/Attractor). - Specifically, the fixed points of $S$ act as attractors for the chaos game (explained in detail below). + Specifically, the fixed points of $S$ act as attractors for the chaos game (explained in more detail below). This is still a bit vague, so let's work through an example. @@ -272,8 +271,8 @@ import chaosGameSource from '!!raw-loader!./chaosGame' 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). + I think the paper has an error, so I'm plotting the image + like the [reference implementation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441). ### Weights @@ -292,7 +291,10 @@ import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted"; 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: +the chaos game "visits" parts of the image. If the chaos +were to run forever, we'd get the same image; +but because the iteration count is limited, +some parts may be missing below. :::tip Double-click the image if you want to save a copy! diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts b/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts index ef386f5..6db267d 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts +++ b/blog/2024-11-15-playing-with-fire/1-introduction/plot.ts @@ -1,10 +1,11 @@ // hidden-start -import {camera} from "./cameraGasket" +import { camera } from "./cameraGasket"; + // hidden-end function imageIndex( - width: number, x: number, - y: number + y: number, + width: number ) { return y * (width * 4) + x * 4; } @@ -14,24 +15,26 @@ export function plot( y: number, img: ImageData ) { - let [pixelX, pixelY] = camera(img.width, x, y); + let [pixelX, pixelY] = + camera(x, y, img.width); + + // Skip coordinates outside the display + if ( + pixelX < 0 || + pixelX >= img.width || + pixelY < 0 || + pixelY >= img.height + ) + return; const i = imageIndex( - img.width, pixelX, - pixelY + pixelY, + img.width ); - // Skip pixels outside the display range - if ( - i < 0 || - i > img.data.length - ) { - return; - } - - // Set the pixel to black by writing 0 - // to the first three elements at the index + // Set the pixel to black by setting + // the first three elements to 0 // (red, green, and blue, respectively), // and 255 to the last element (alpha) img.data[i] = 0; diff --git a/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx b/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx index dc05043..4adee1d 100644 --- a/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx +++ b/blog/2024-11-15-playing-with-fire/1-introduction/scope.tsx @@ -1,12 +1,12 @@ import Gasket from "./Gasket"; -import { plot } from './plot'; -import { randomBiUnit } from '../src/randomBiUnit'; -import { randomInteger } from '../src/randomInteger'; +import { plot } from "./plot"; +import { randomBiUnit } from "../src/randomBiUnit"; +import { randomInteger } from "../src/randomInteger"; const Scope = { - Gasket, - plot, - randomBiUnit, - randomInteger, -} + Gasket, + plot, + randomBiUnit, + randomInteger +}; export default Scope; \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx index fc4d782..77e0abd 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx +++ b/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx @@ -1,51 +1,52 @@ import TeX from "@matejmazur/react-katex"; -import {Coefs} from "../src/coefs"; +import { Coefs } from "../src/transform"; import styles from "../src/css/styles.module.css"; export interface Props { - title: String; - isPost: boolean; - coefs: Coefs; - setCoefs: (coefs: Coefs) => void; - resetCoefs: () => void; + title: String; + isPost: boolean; + coefs: Coefs; + setCoefs: (coefs: Coefs) => void; + resetCoefs: () => void; } -export const CoefEditor = ({title, isPost, coefs, setCoefs, resetCoefs}: Props) => { - const resetButton = - return ( -
-

{title} {resetButton}

-
-

{isPost ? \alpha : 'a'}: {coefs.a}

- setCoefs({...coefs, a: Number(e.currentTarget.value)})}/> -
-
-

{isPost ? \beta : 'b'}: {coefs.b}

- setCoefs({...coefs, b: Number(e.currentTarget.value)})}/> -
-
-

{isPost ? \gamma : 'c'}: {coefs.c}

- setCoefs({...coefs, c: Number(e.currentTarget.value)})}/> -
-
-

{isPost ? \delta : 'd'}: {coefs.d}

- setCoefs({...coefs, d: Number(e.currentTarget.value)})}/> -
-
-

{isPost ? \epsilon : 'e'}: {coefs.e}

- setCoefs({...coefs, e: Number(e.currentTarget.value)})}/> -
-
-

{isPost ? \zeta : 'f'}: {coefs.f}

- setCoefs({...coefs, f: Number(e.currentTarget.value)})}/> -
-
- ) -} \ No newline at end of file +export const CoefEditor = ({ title, isPost, coefs, setCoefs, resetCoefs }: Props) => { + const resetButton = ; + + return ( +
+

{title} {resetButton}

+
+

{isPost ? \alpha : "a"}: {coefs.a}

+ setCoefs({ ...coefs, a: Number(e.currentTarget.value) })} /> +
+
+

{isPost ? \beta : "b"}: {coefs.b}

+ setCoefs({ ...coefs, b: Number(e.currentTarget.value) })} /> +
+
+

{isPost ? \gamma : "c"}: {coefs.c}

+ setCoefs({ ...coefs, c: Number(e.currentTarget.value) })} /> +
+
+

{isPost ? \delta : "d"}: {coefs.d}

+ setCoefs({ ...coefs, d: Number(e.currentTarget.value) })} /> +
+
+

{isPost ? \epsilon : "e"}: {coefs.e}

+ setCoefs({ ...coefs, e: Number(e.currentTarget.value) })} /> +
+
+

{isPost ? \zeta : "f"}: {coefs.f}

+ setCoefs({ ...coefs, f: Number(e.currentTarget.value) })} /> +
+
+ ); +}; \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx index 03d83a2..6d2a7d0 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx +++ b/blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx @@ -1,68 +1,68 @@ -import {useContext, useEffect, useState} from "react"; -import {Transform} from "../src/transform"; -import * as params from "../src/params" -import {PainterContext} from "../src/Canvas" -import {chaosGameFinal} from "./chaosGameFinal" -import {VariationEditor, VariationProps} from "./VariationEditor" -import {applyTransform} from "../src/applyTransform"; -import {buildBlend} from "./buildBlend"; +import { useContext, useEffect, useState } from "react"; +import { Transform } from "../src/transform"; +import * as params from "../src/params"; +import { PainterContext } from "../src/Canvas"; +import { chaosGameFinal } from "./chaosGameFinal"; +import { VariationEditor, VariationProps } from "./VariationEditor"; +import { applyTransform } from "../src/applyTransform"; +import { buildBlend } from "./buildBlend"; export default function FlameBlend() { - const {width, height, setPainter} = useContext(PainterContext); + const { width, height, setPainter } = useContext(PainterContext); - const xform1VariationsDefault: VariationProps = { - linear: 0, - julia: 1, - popcorn: 0, - pdj: 0, - } - const [xform1Variations, setXform1Variations] = useState(xform1VariationsDefault) - const resetXform1Variations = () => setXform1Variations(xform1VariationsDefault); + const xform1VariationsDefault: VariationProps = { + linear: 0, + julia: 1, + popcorn: 0, + pdj: 0 + }; + const [xform1Variations, setXform1Variations] = useState(xform1VariationsDefault); + const resetXform1Variations = () => setXform1Variations(xform1VariationsDefault); - const xform2VariationsDefault: VariationProps = { - linear: 1, - julia: 0, - popcorn: 1, - pdj: 0 - } - const [xform2Variations, setXform2Variations] = useState(xform2VariationsDefault) - const resetXform2Variations = () => setXform2Variations(xform2VariationsDefault); + const xform2VariationsDefault: VariationProps = { + linear: 1, + julia: 0, + popcorn: 1, + pdj: 0 + }; + const [xform2Variations, setXform2Variations] = useState(xform2VariationsDefault); + const resetXform2Variations = () => setXform2Variations(xform2VariationsDefault); - const xform3VariationsDefault: VariationProps = { - linear: 0, - julia: 0, - popcorn: 0, - pdj: 1 - } - const [xform3Variations, setXform3Variations] = useState(xform3VariationsDefault) - const resetXform3Variations = () => setXform3Variations(xform3VariationsDefault); + const xform3VariationsDefault: VariationProps = { + linear: 0, + julia: 0, + popcorn: 0, + pdj: 1 + }; + const [xform3Variations, setXform3Variations] = useState(xform3VariationsDefault); + const resetXform3Variations = () => setXform3Variations(xform3VariationsDefault); - const identityXform: Transform = (x, y) => [x, y]; + const identityXform: Transform = (x, y) => [x, y]; - useEffect(() => { - const transforms: [number, Transform][] = [ - [params.xform1Weight, applyTransform(params.xform1Coefs, buildBlend(params.xform1Coefs, xform1Variations))], - [params.xform2Weight, applyTransform(params.xform2Coefs, buildBlend(params.xform2Coefs, xform2Variations))], - [params.xform3Weight, applyTransform(params.xform3Coefs, buildBlend(params.xform3Coefs, xform3Variations))] - ] + useEffect(() => { + const transforms: [number, Transform][] = [ + [params.xform1Weight, applyTransform(params.xform1Coefs, buildBlend(params.xform1Coefs, xform1Variations))], + [params.xform2Weight, applyTransform(params.xform2Coefs, buildBlend(params.xform2Coefs, xform2Variations))], + [params.xform3Weight, applyTransform(params.xform3Coefs, buildBlend(params.xform3Coefs, xform3Variations))] + ]; - const gameParams = { - width, - height, - transforms, - final: identityXform - } - setPainter(chaosGameFinal(gameParams)); - }, [xform1Variations, xform2Variations, xform3Variations]); + const gameParams = { + width, + height, + transforms, + final: identityXform + }; + setPainter(chaosGameFinal(gameParams)); + }, [xform1Variations, xform2Variations, xform3Variations]); - return ( - <> - - - - - ) + return ( + <> + + + + + ); } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/FlameFinal.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/FlameFinal.tsx index 76b2ecb..530243b 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/FlameFinal.tsx +++ b/blog/2024-11-15-playing-with-fire/2-transforms/FlameFinal.tsx @@ -1,46 +1,46 @@ -import {useContext, useEffect, useState} from "react"; -import {Coefs} from "../src/coefs" +import { useContext, useEffect, useState } from "react"; +import { Coefs } from "../src/transform"; import * as params from "../src/params"; -import {PainterContext} from "../src/Canvas" -import {buildBlend} from "./buildBlend"; -import {chaosGameFinal} from "./chaosGameFinal" -import {VariationEditor, VariationProps} from "./VariationEditor"; -import {CoefEditor} from "./CoefEditor"; -import {applyPost, applyTransform} from "../src/applyTransform"; +import { PainterContext } from "../src/Canvas"; +import { buildBlend } from "./buildBlend"; +import { chaosGameFinal } from "./chaosGameFinal"; +import { VariationEditor, VariationProps } from "./VariationEditor"; +import { CoefEditor } from "./CoefEditor"; +import { applyPost, applyTransform } from "../src/applyTransform"; export default function FlameFinal() { - const {width, height, setPainter} = useContext(PainterContext); + const { width, height, setPainter } = useContext(PainterContext); - const [xformFinalCoefs, setXformFinalCoefs] = useState(params.xformFinalCoefs); - const resetXformFinalCoefs = () => setXformFinalCoefs(params.xformFinalCoefs); + const [xformFinalCoefs, setXformFinalCoefs] = useState(params.xformFinalCoefs); + const resetXformFinalCoefs = () => setXformFinalCoefs(params.xformFinalCoefs); - const xformFinalVariationsDefault: VariationProps = { - linear: 0, - julia: 1, - popcorn: 0, - pdj: 0 - } - const [xformFinalVariations, setXformFinalVariations] = useState(xformFinalVariationsDefault); - const resetXformFinalVariations = () => setXformFinalVariations(xformFinalVariationsDefault); + const xformFinalVariationsDefault: VariationProps = { + linear: 0, + julia: 1, + popcorn: 0, + pdj: 0 + }; + const [xformFinalVariations, setXformFinalVariations] = useState(xformFinalVariationsDefault); + const resetXformFinalVariations = () => setXformFinalVariations(xformFinalVariationsDefault); - const [xformFinalCoefsPost, setXformFinalCoefsPost] = useState(params.xformFinalCoefsPost); - const resetXformFinalCoefsPost = () => setXformFinalCoefsPost(params.xformFinalCoefsPost); + const [xformFinalCoefsPost, setXformFinalCoefsPost] = useState(params.xformFinalCoefsPost); + const resetXformFinalCoefsPost = () => setXformFinalCoefsPost(params.xformFinalCoefsPost); - useEffect(() => { - const finalBlend = buildBlend(xformFinalCoefs, xformFinalVariations); - const finalXform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, finalBlend)); + useEffect(() => { + const finalBlend = buildBlend(xformFinalCoefs, xformFinalVariations); + const finalXform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, finalBlend)); - setPainter(chaosGameFinal({width, height, transforms: params.xforms, final: finalXform})); - }, [xformFinalCoefs, xformFinalVariations, xformFinalCoefsPost]); + setPainter(chaosGameFinal({ width, height, transforms: params.xforms, final: finalXform })); + }, [xformFinalCoefs, xformFinalVariations, xformFinalCoefsPost]); - return ( - <> - - - - - ) + return ( + <> + + + + + ); } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx index 7402383..ef767f9 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx +++ b/blog/2024-11-15-playing-with-fire/2-transforms/FlamePost.tsx @@ -1,46 +1,46 @@ -import {useContext, useEffect, useState} from "react"; -import {Coefs} from "../src/coefs" -import {Transform} from "../src/transform"; +import { useContext, useEffect, useState } from "react"; +import { applyTransform } from "../src/applyTransform"; +import { Coefs, Transform } from "../src/transform"; import * as params from "../src/params"; -import {PainterContext} from "../src/Canvas" -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"; +import { PainterContext } from "../src/Canvas"; +import { chaosGameFinal, Props as ChaosGameFinalProps } from "./chaosGameFinal"; +import { CoefEditor } from "./CoefEditor"; +import { transformPost } from "./post"; export default function FlamePost() { - const {width, height, setPainter} = useContext(PainterContext); + const { width, height, setPainter } = useContext(PainterContext); - const [xform1CoefsPost, setXform1CoefsPost] = useState(params.xform1CoefsPost); - const resetXform1CoefsPost = () => setXform1CoefsPost(params.xform1CoefsPost); + const [xform1CoefsPost, setXform1CoefsPost] = useState(params.xform1CoefsPost); + const resetXform1CoefsPost = () => setXform1CoefsPost(params.xform1CoefsPost); - const [xform2CoefsPost, setXform2CoefsPost] = useState(params.xform2CoefsPost); - const resetXform2CoefsPost = () => setXform2CoefsPost(params.xform2CoefsPost); + const [xform2CoefsPost, setXform2CoefsPost] = useState(params.xform2CoefsPost); + const resetXform2CoefsPost = () => setXform2CoefsPost(params.xform2CoefsPost); - const [xform3CoefsPost, setXform3CoefsPost] = useState(params.xform3CoefsPost); - const resetXform3CoefsPost = () => setXform3CoefsPost(params.xform3CoefsPost); + const [xform3CoefsPost, setXform3CoefsPost] = useState(params.xform3CoefsPost); + const resetXform3CoefsPost = () => setXform3CoefsPost(params.xform3CoefsPost); - const identityXform: Transform = (x, y) => [x, y]; + const identityXform: Transform = (x, y) => [x, y]; - const gameParams: ChaosGameFinalProps = { - width, - height, - transforms: [ - [params.xform1Weight, applyPost(xform1CoefsPost, applyTransform(params.xform1Coefs, params.xform1Variations))], - [params.xform2Weight, applyPost(xform2CoefsPost, applyTransform(params.xform2Coefs, params.xform2Variations))], - [params.xform3Weight, applyPost(xform3CoefsPost, applyTransform(params.xform3Coefs, params.xform3Variations))], - ], - final: identityXform - } - useEffect(() => setPainter(chaosGameFinal(gameParams)), [xform1CoefsPost, xform2CoefsPost, xform3CoefsPost]); + const gameParams: ChaosGameFinalProps = { + width, + height, + transforms: [ + [params.xform1Weight, transformPost(applyTransform(params.xform1Coefs, params.xform1Variations), xform1CoefsPost)], + [params.xform2Weight, transformPost(applyTransform(params.xform2Coefs, params.xform2Variations), xform2CoefsPost)], + [params.xform3Weight, transformPost(applyTransform(params.xform3Coefs, params.xform3Variations), xform3CoefsPost)] + ], + final: identityXform + }; + useEffect(() => setPainter(chaosGameFinal(gameParams)), [xform1CoefsPost, xform2CoefsPost, xform3CoefsPost]); - return ( - <> - - - - - ) + return ( + <> + + + + + ); } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/VariationEditor.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/VariationEditor.tsx index 57a3159..62289b3 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/VariationEditor.tsx +++ b/blog/2024-11-15-playing-with-fire/2-transforms/VariationEditor.tsx @@ -1,45 +1,45 @@ -import styles from "../src/css/styles.module.css" +import styles from "../src/css/styles.module.css"; export interface VariationProps { - linear: number; - julia: number; - popcorn: number; - pdj: number; + linear: number; + julia: number; + popcorn: number; + pdj: number; } export interface Props { - title: String; - variations: VariationProps; - setVariations: (variations: VariationProps) => void; - resetVariations: () => void; + title: String; + variations: VariationProps; + setVariations: (variations: VariationProps) => void; + resetVariations: () => void; } -export const VariationEditor = ({title, variations, setVariations, resetVariations}: Props) => { - const resetButton = +export const VariationEditor = ({ title, variations, setVariations, resetVariations }: Props) => { + const resetButton = ; - return ( -
-

{title} {resetButton}

-
- Linear: {variations.linear} - setVariations({...variations, linear: Number(e.currentTarget.value)})}/> -
-
- Julia: {variations.julia} - setVariations({...variations, julia: Number(e.currentTarget.value)})}/> -
-
- Popcorn: {variations.popcorn} - setVariations({...variations, popcorn: Number(e.currentTarget.value)})}/> -
-
- PDJ: {variations.pdj} - setVariations({...variations, pdj: Number(e.currentTarget.value)})}/> -
-
- ) -} \ No newline at end of file + return ( +
+

{title} {resetButton}

+
+ Linear: {variations.linear} + setVariations({ ...variations, linear: Number(e.currentTarget.value) })} /> +
+
+ Julia: {variations.julia} + setVariations({ ...variations, julia: Number(e.currentTarget.value) })} /> +
+
+ Popcorn: {variations.popcorn} + setVariations({ ...variations, popcorn: Number(e.currentTarget.value) })} /> +
+
+ PDJ: {variations.pdj} + setVariations({ ...variations, pdj: Number(e.currentTarget.value) })} /> +
+
+ ); +}; \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/buildBlend.ts b/blog/2024-11-15-playing-with-fire/2-transforms/buildBlend.ts index c2398fd..155ddfb 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/buildBlend.ts +++ b/blog/2024-11-15-playing-with-fire/2-transforms/buildBlend.ts @@ -1,17 +1,17 @@ -import {Coefs} from "../src/coefs"; -import {VariationProps} from "./VariationEditor"; -import {linear} from "../src/linear"; -import {julia} from "../src/julia"; -import {popcorn} from "../src/popcorn"; -import {pdj} from "../src/pdj"; -import {pdjParams} from "../src/params"; -import {VariationBlend} from "../src/blend"; +import { Coefs } from "../src/transform"; +import { VariationProps } from "./VariationEditor"; +import { linear } from "../src/linear"; +import { julia } from "../src/julia"; +import { popcorn } from "../src/popcorn"; +import { pdj } from "../src/pdj"; +import { pdjParams } from "../src/params"; +import { Blend } from "../src/blend"; -export function buildBlend(coefs: Coefs, variations: VariationProps): VariationBlend { - return [ - [variations.linear, linear], - [variations.julia, julia], - [variations.popcorn, popcorn(coefs)], - [variations.pdj, pdj(pdjParams)] - ] +export function buildBlend(coefs: Coefs, variations: VariationProps): Blend { + return [ + [variations.linear, linear], + [variations.julia, julia], + [variations.popcorn, popcorn(coefs)], + [variations.pdj, pdj(pdjParams)] + ]; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/chaosGameFinal.ts b/blog/2024-11-15-playing-with-fire/2-transforms/chaosGameFinal.ts index caacae1..c653dee 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/chaosGameFinal.ts +++ b/blog/2024-11-15-playing-with-fire/2-transforms/chaosGameFinal.ts @@ -1,37 +1,51 @@ // hidden-start import { randomBiUnit } from "../src/randomBiUnit"; import { randomChoice } from "../src/randomChoice"; -import { plotBinary as plot } from "../src/plotBinary" -import {Transform} from "../src/transform"; -import {Props as ChaosGameWeightedProps} from "../1-introduction/chaosGameWeighted"; +import { plotBinary as plot } from "../src/plotBinary"; +import { Transform } from "../src/transform"; +import { Props as WeightedProps } from "../1-introduction/chaosGameWeighted"; const quality = 0.5; const step = 1000; // hidden-end -export type Props = ChaosGameWeightedProps & { - final: Transform, +export type Props = WeightedProps & { + final: Transform, } -export function* chaosGameFinal({width, height, transforms, final}: Props) { - let image = new ImageData(width, height); - let [x, y] = [randomBiUnit(), randomBiUnit()]; - const iterations = width * height * quality; - for (let i = 0; i < iterations; i++) { - const [_, transform] = randomChoice(transforms); - [x, y] = transform(x, y); +export function* chaosGameFinal( + { + width, + height, + transforms, + final + }: Props +) { + let img = + new ImageData(width, height); + let [x, y] = [ + randomBiUnit(), + randomBiUnit() + ]; - // highlight-start - const [finalX, finalY] = final(x, y); - // highlight-end + const pixels = width * height; + const iterations = quality * pixels; + for (let i = 0; i < iterations; i++) { + const [_, transform] = + randomChoice(transforms); + [x, y] = transform(x, y); - if (i > 20) - // highlight-start - plot(finalX, finalY, image); - // highlight-end + // highlight-start + const [finalX, finalY] = final(x, y); + // highlight-end - if (i % step === 0) - yield image; - } + if (i > 20) + // highlight-start + plot(finalX, finalY, img); + // highlight-end - yield image; + if (i % step === 0) + yield img; + } + + yield img; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx b/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx index d01750e..8c64c7a 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx +++ b/blog/2024-11-15-playing-with-fire/2-transforms/index.mdx @@ -34,7 +34,7 @@ This leads us to the first big innovation of the Fractal Flame algorithm: adding 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 x + b_i y + c_i, d_i x + e_i y + f_i) $$ import variationSource from '!!raw-loader!../src/variation' @@ -96,7 +96,7 @@ Some variations rely on knowing the transform's affine coefficients; they're cal For the popcorn variation, we use $c$ and $f$: $$ -V_{17}(x,y) = (x + c \cdot \text{sin}(\text{tan }3y), y + f \cdot \text{sin}(\text{tan }3x)) +V_{17}(x,y) = (x + c\ \text{sin}(\text{tan }3y), y + f\ \text{sin}(\text{tan }3x)) $$ import popcornSrc from '!!raw-loader!../src/popcorn' @@ -109,8 +109,8 @@ Some variations have extra parameters; they're called "parametric variations." For the PDJ variation, there are four extra parameters we can choose: $$ -p_1 = \text{pdj.a} \hspace{0.2cm} p_2 = \text{pdj.b} \hspace{0.2cm} p_3 = \text{pdj.c} \hspace{0.2cm} p_4 = \text{pdj.d} \\ -V_{24} = (\text{sin}(p_1 \cdot y) - \text{cos}(p_2 \cdot x), \text{sin}(p_3 \cdot x) - \text{cos}(p_4 \cdot y)) +p_1 = \text{pdj.a} \hspace{0.1cm} p_2 = \text{pdj.b} \hspace{0.1cm} p_3 = \text{pdj.c} \hspace{0.1cm} p_4 = \text{pdj.d} \\ +V_{24} = (\text{sin}(p_1 y) - \text{cos}(p_2 x), \text{sin}(p_3 x) - \text{cos}(p_4 y)) $$ import pdjSrc from '!!raw-loader!../src/pdj' @@ -124,7 +124,7 @@ Each variation receives the same $x$ and $y$ inputs, and we add together each va 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(x, y) $$ The formula looks intimidating, but it's not hard to implement: diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/post.ts b/blog/2024-11-15-playing-with-fire/2-transforms/post.ts index b79ded1..c70c3d6 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/post.ts +++ b/blog/2024-11-15-playing-with-fire/2-transforms/post.ts @@ -1,7 +1,11 @@ // hidden-start -import {Coefs} from "../src/coefs"; -import {Transform} from "../src/transform"; -import {applyCoefs} from "../src/coefs"; +import { applyCoefs, Coefs, Transform } from "../src/transform"; // hidden-end -export const transformPost = (transform: Transform, coefs: Coefs): Transform => - (x, y) => applyCoefs(...transform(x, y), coefs) \ No newline at end of file +export const transformPost = ( + transform: Transform, + coefs: Coefs +): Transform => + (x, y) => { + [x, y] = transform(x, y); + return applyCoefs(x, y, coefs); + } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx b/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx index f6cc860..2040b0b 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx +++ b/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx @@ -1,174 +1,181 @@ -import React, {useContext, useEffect, useMemo, useRef, useState} from "react"; +import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import * as params from "../src/params"; -import {PainterContext} from "../src/Canvas"; -import {colorFromPalette} from "./colorFromPalette"; -import {chaosGameColor, Props as ChaosGameColorProps, TransformColor} from "./chaosGameColor"; +import { PainterContext } from "../src/Canvas"; +import { colorFromPalette } from "./colorFromPalette"; +import { chaosGameColor, Props as ChaosGameColorProps, TransformColor } from "./chaosGameColor"; import styles from "../src/css/styles.module.css"; -import {histIndex} from "../src/camera"; -import {useColorMode} from "@docusaurus/theme-common"; +import { histIndex } from "../src/camera"; +import { useColorMode } from "@docusaurus/theme-common"; type PaletteBarProps = { - height: number; - palette: number[]; - children?: React.ReactNode; + height: number; + palette: number[]; + children?: React.ReactNode; } -export const PaletteBar: React.FC = ({height, palette, children}) => { - const sizingRef = useRef(null); - const [width, setWidth] = useState(0); - useEffect(() => { - if (sizingRef) { - setWidth(sizingRef.current.offsetWidth); - } - }, [sizingRef]); +export const PaletteBar: React.FC = ({ height, palette, children }) => { + const sizingRef = useRef(null); + const [width, setWidth] = useState(0); + useEffect(() => { + if (sizingRef) { + setWidth(sizingRef.current.offsetWidth); + } + }, [sizingRef]); - const canvasRef = useRef(null); - const paletteImage = useMemo(() => { - if (width === 0) { - return; - } + const canvasRef = useRef(null); + const paletteImage = useMemo(() => { + if (width === 0) { + return; + } - const image = new ImageData(width, height); - for (let x = 0; x < width; x++) { - const colorIndex = x / width; - const [r, g, b] = colorFromPalette(palette, colorIndex); + const image = new ImageData(width, height); + for (let x = 0; x < width; x++) { + const colorIndex = x / width; + const [r, g, b] = colorFromPalette(palette, colorIndex); - for (let y = 0; y < height; y++) { - const pixelIndex = histIndex(x, y, width, 4); - image.data[pixelIndex] = r * 0xff; - image.data[pixelIndex + 1] = g * 0xff; - image.data[pixelIndex + 2] = b * 0xff; - image.data[pixelIndex + 3] = 0xff; - } - } + for (let y = 0; y < height; y++) { + const pixelIndex = histIndex(x, y, width, 4); + image.data[pixelIndex] = r * 0xff; + image.data[pixelIndex + 1] = g * 0xff; + image.data[pixelIndex + 2] = b * 0xff; + image.data[pixelIndex + 3] = 0xff; + } + } - return image; - }, [width, height, palette]); + return image; + }, [width, height, palette]); - useEffect(() => { - if (canvasRef && paletteImage) { - canvasRef.current.getContext("2d").putImageData(paletteImage, 0, 0); - } - }, [canvasRef, paletteImage]); + useEffect(() => { + if (canvasRef && paletteImage) { + canvasRef.current.getContext("2d").putImageData(paletteImage, 0, 0); + } + }, [canvasRef, paletteImage]); - const canvasStyle = {filter: useColorMode().colorMode === 'dark' ? 'invert(1)' : ''}; + const canvasStyle = { filter: useColorMode().colorMode === "dark" ? "invert(1)" : "" }; - return ( - <> -
- {width > 0 ? : null} -
- {children} - - ) -} + return ( + <> +
+ {width > 0 ? : null} +
+ {children} + + ); +}; type ColorEditorProps = { - title: string; - palette: number[]; - transformColor: TransformColor; - setTransformColor: (transformColor: TransformColor) => void; - resetTransformColor: () => void; - children?: React.ReactNode; + title: string; + palette: number[]; + transformColor: TransformColor; + setTransformColor: (transformColor: TransformColor) => void; + resetTransformColor: () => void; + children?: React.ReactNode; } -const ColorEditor: React.FC = ({title, palette, transformColor, setTransformColor, resetTransformColor, children}) => { - const resetButton = +const ColorEditor: React.FC = ( + { + title, + palette, + transformColor, + setTransformColor, + resetTransformColor, + children + }) => { + const resetButton = ; - const [r, g, b] = colorFromPalette(palette, transformColor.color); - const colorCss = `rgb(${Math.floor(r * 0xff)},${Math.floor(g * 0xff)},${Math.floor(b * 0xff)})`; + 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(); + const { colorMode } = useColorMode(); - return ( - <> -
-

{title} {resetButton}

-
-

Color: {transformColor.color}

- setTransformColor({...transformColor, color: Number(e.currentTarget.value)})}/> -
-
-

Speed: {transformColor.colorSpeed}

- setTransformColor({...transformColor, colorSpeed: Number(e.currentTarget.value)})}/> -
-
-
- {children} - - ) -} + return ( + <> +
+

{title} {resetButton}

+
+

Color: {transformColor.color}

+ setTransformColor({ ...transformColor, color: Number(e.currentTarget.value) })} /> +
+
+

Speed: {transformColor.colorSpeed}

+ setTransformColor({ ...transformColor, colorSpeed: Number(e.currentTarget.value) })} /> +
+
+
+ {children} + + ); +}; type Props = { - quality?: number; - children?: React.ReactElement; + children?: React.ReactElement; } -export default function FlameColor({quality, children}: Props) { - const {width, height, setPainter} = useContext(PainterContext); +export default function FlameColor({ children }: Props) { + const { width, height, setPainter } = useContext(PainterContext); - const xform1ColorDefault: TransformColor = {color: params.xform1Color, colorSpeed: 0.5}; - const [xform1Color, setXform1Color] = useState(xform1ColorDefault); - const resetXform1Color = () => setXform1Color(xform1ColorDefault); + const xform1ColorDefault: TransformColor = { color: params.xform1Color, colorSpeed: 0.5 }; + const [xform1Color, setXform1Color] = useState(xform1ColorDefault); + const resetXform1Color = () => setXform1Color(xform1ColorDefault); - const xform2ColorDefault: TransformColor = {color: params.xform2Color, colorSpeed: 0.5}; - const [xform2Color, setXform2Color] = useState(xform2ColorDefault); - const resetXform2Color = () => setXform2Color(xform2ColorDefault); + const xform2ColorDefault: TransformColor = { color: params.xform2Color, colorSpeed: 0.5 }; + const [xform2Color, setXform2Color] = useState(xform2ColorDefault); + const resetXform2Color = () => setXform2Color(xform2ColorDefault); - const xform3ColorDefault: TransformColor = {color: params.xform3Color, colorSpeed: 0.5}; - const [xform3Color, setXform3Color] = useState(xform3ColorDefault); - const resetXform3Color = () => setXform3Color(xform3ColorDefault); + const xform3ColorDefault: TransformColor = { color: params.xform3Color, colorSpeed: 0.5 }; + const [xform3Color, setXform3Color] = useState(xform3ColorDefault); + const resetXform3Color = () => setXform3Color(xform3ColorDefault); - const xformFinalColorDefault: TransformColor = {color: params.xformFinalColor, colorSpeed: 0}; - const [xformFinalColor, setXformFinalColor] = useState(xformFinalColorDefault); - const resetXformFinalColor = () => setXformFinalColor(xformFinalColorDefault); + const xformFinalColorDefault: TransformColor = { color: params.xformFinalColor, colorSpeed: 0 }; + const [xformFinalColor, setXformFinalColor] = useState(xformFinalColorDefault); + const resetXformFinalColor = () => setXformFinalColor(xformFinalColorDefault); - useEffect(() => { - const gameParams: ChaosGameColorProps = { - width, - height, - transforms: params.xforms, - final: params.xformFinal, - palette: params.palette, - colors: [xform1Color, xform2Color, xform3Color], - finalColor: xformFinalColor - } - setPainter(chaosGameColor(gameParams)); - }, [xform1Color, xform2Color, xform3Color, xformFinalColor]); + useEffect(() => { + const gameParams: ChaosGameColorProps = { + width, + height, + transforms: params.xforms, + final: params.xformFinal, + palette: params.palette, + colors: [xform1Color, xform2Color, xform3Color], + finalColor: xformFinalColor + }; + setPainter(chaosGameColor(gameParams)); + }, [xform1Color, xform2Color, xform3Color, xformFinalColor]); - return ( - <> - - - - - - {children} - - ); + return ( + <> + + + + + + {children} + + ); } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/FlameHistogram.tsx b/blog/2024-11-15-playing-with-fire/3-log-density/FlameHistogram.tsx index 3ac3a86..c7c854c 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/FlameHistogram.tsx +++ b/blog/2024-11-15-playing-with-fire/3-log-density/FlameHistogram.tsx @@ -1,25 +1,25 @@ -import React, {useContext, useEffect} from "react"; -import {xforms as transforms, xformFinal as final} from "../src/params"; -import {PainterContext} from "../src/Canvas"; -import {chaosGameHistogram} from "./chaosGameHistogram"; +import React, { useContext, useEffect } from "react"; +import { xformFinal as final, xforms as transforms } from "../src/params"; +import { PainterContext } from "../src/Canvas"; +import { chaosGameHistogram } from "./chaosGameHistogram"; type Props = { - paint: (width: number, height: number, histogram: number[]) => ImageData; - children?: React.ReactElement; + paint: (width: number, height: number, histogram: number[]) => ImageData; + children?: React.ReactElement; } -export default function FlameHistogram({paint, children}: Props) { - const {width, height, setPainter} = useContext(PainterContext); +export default function FlameHistogram({ paint, children }: Props) { + const { width, height, setPainter } = useContext(PainterContext); - useEffect(() => { - const gameParams = { - width, - height, - transforms, - final, - paint - } - setPainter(chaosGameHistogram(gameParams)); - }, [width, height]); + useEffect(() => { + const gameParams = { + width, + height, + transforms, + final, + paint + }; + setPainter(chaosGameHistogram(gameParams)); + }, [width, height]); - return children; + return children; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameColor.ts b/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameColor.ts index 4b75409..76f6f5c 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameColor.ts +++ b/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameColor.ts @@ -1,64 +1,135 @@ // hidden-start -import {Props as ChaosGameFinalProps} from "../2-transforms/chaosGameFinal"; -import {randomBiUnit} from "../src/randomBiUnit"; -import {randomChoice} from "../src/randomChoice"; -import {camera, histIndex} from "../src/camera"; -import {colorFromPalette} from "./colorFromPalette"; -import {mixColor} from "./mixColor"; -import {paintColor} from "./paintColor"; +import { Props as ChaosGameFinalProps } from "../2-transforms/chaosGameFinal"; +import { randomBiUnit } from "../src/randomBiUnit"; +import { randomChoice } from "../src/randomChoice"; +import { camera, histIndex } from "../src/camera"; +import { colorFromPalette } from "./colorFromPalette"; +import { mixColor } from "./mixColor"; +import { paintColor } from "./paintColor"; const quality = 15; const step = 100_000; // hidden-end export type TransformColor = { - color: number; - colorSpeed: number; + color: number; + colorSpeed: number; } export type Props = ChaosGameFinalProps & { - palette: number[]; - colors: TransformColor[]; - finalColor: TransformColor; + palette: number[]; + colors: TransformColor[]; + finalColor: TransformColor; } -export function* chaosGameColor({width, height, transforms, final, palette, colors, finalColor}: Props) { - const imgRed = Array(width * height).fill(0); - const imgGreen = Array(width * height).fill(0); - const imgBlue = Array(width * height).fill(0); - const imgAlpha = Array(width * height).fill(0); - let [x, y] = [randomBiUnit(), randomBiUnit()]; - let c = Math.random(); +export function* chaosGameColor( + { + width, + height, + transforms, + final, + palette, + colors, + finalColor + }: Props +) { + const pixels = width * height; - const iterations = width * height * quality; - for (let i = 0; i < iterations; i++) { - const [transformIndex, transform] = randomChoice(transforms); - [x, y] = transform(x, y); + // highlight-start + const imgRed = Array(pixels) + .fill(0); + const imgGreen = Array(pixels) + .fill(0); + const imgBlue = Array(pixels) + .fill(0); + const imgAlpha = Array(pixels) + .fill(0); - // highlight-start - const transformColor = colors[transformIndex]; - c = mixColor(c, transformColor.color, transformColor.colorSpeed); - // highlight-end + const plotColor = ( + x: number, + y: number, + c: number + ) => { + const [pixelX, pixelY] = + camera(x, y, width); - const [finalX, finalY] = final(x, y); + if ( + pixelX < 0 || + pixelX >= width || + pixelY < 0 || + pixelY >= width + ) + return; - if (i > 20) { - const [pixelX, pixelY] = camera(finalX, finalY, width); - const pixelIndex = histIndex(pixelX, pixelY, width, 1); + const hIndex = + histIndex(pixelX, pixelY, width, 1); - if (pixelIndex < 0 || pixelIndex >= imgAlpha.length) - continue; + const [r, g, b] = + colorFromPalette(palette, c); - const colorFinal = mixColor(c, finalColor.color, finalColor.colorSpeed); - const [r, g, b] = colorFromPalette(palette, colorFinal); - imgRed[pixelIndex] += r; - imgGreen[pixelIndex] += g; - imgBlue[pixelIndex] += b; - imgAlpha[pixelIndex] += 1; - } + imgRed[hIndex] += r; + imgGreen[hIndex] += g; + imgBlue[hIndex] += b; + imgAlpha[hIndex] += 1; + } + // highlight-end - if (i % step === 0) - yield paintColor(width, height, imgRed, imgGreen, imgBlue, imgAlpha); - } + let [x, y] = [ + randomBiUnit(), + randomBiUnit() + ]; + let c = Math.random(); - yield paintColor(width, height, imgRed, imgGreen, imgBlue, imgAlpha); + const iterations = quality * pixels; + for (let i = 0; i < iterations; i++) { + const [transformIndex, transform] = + randomChoice(transforms); + [x, y] = transform(x, y); + + // highlight-start + const transformColor = + colors[transformIndex]; + + c = mixColor( + c, + transformColor.color, + transformColor.colorSpeed + ); + // highlight-end + + const [finalX, finalY] = final(x, y); + + // highlight-start + const finalC = mixColor( + c, + finalColor.color, + finalColor.colorSpeed + ); + // highlight-end + + if (i > 20) + plotColor( + finalX, + finalY, + finalC + ) + + if (i % step === 0) + yield paintColor( + width, + height, + imgRed, + imgGreen, + imgBlue, + imgAlpha + ); + } + + yield paintColor( + width, + height, + imgRed, + imgGreen, + imgBlue, + imgAlpha + ); } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameHistogram.ts b/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameHistogram.ts index 0f122a4..66253c0 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameHistogram.ts +++ b/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameHistogram.ts @@ -1,45 +1,78 @@ // hidden-start -import {randomBiUnit} from "../src/randomBiUnit"; -import {randomChoice} from "../src/randomChoice"; -import {Props as ChaosGameFinalProps} from "../2-transforms/chaosGameFinal"; -import {camera, histIndex} from "../src/camera"; +import { randomBiUnit } from "../src/randomBiUnit"; +import { randomChoice } from "../src/randomChoice"; +import { Props as ChaosGameFinalProps } from "../2-transforms/chaosGameFinal"; +import { camera, histIndex } from "../src/camera"; const quality = 10; const step = 100_000; // hidden-end -export type Props = ChaosGameFinalProps & { - paint: (width: number, height: number, histogram: number[]) => ImageData; +type Props = ChaosGameFinalProps & { + paint: ( + width: number, + height: number, + histogram: number[] + ) => ImageData; } -export function* chaosGameHistogram({width, height, transforms, final, paint}: Props) { - let iterations = quality * width * height; - // highlight-start - const histogram = Array(width * height).fill(0); - // highlight-end +export function* chaosGameHistogram( + { + width, + height, + transforms, + final, + paint + }: Props +) { + const pixels = width * height; + const iterations = quality * pixels; - let [x, y] = [randomBiUnit(), randomBiUnit()]; + // highlight-start + const hist = Array(pixels) + .fill(0); - for (let i = 0; i < iterations; i++) { - const [_, transform] = randomChoice(transforms); - [x, y] = transform(x, y); - const [finalX, finalY] = final(x, y); + const plotHist = ( + x: number, + y: number + ) => { + const [pixelX, pixelY] = + camera(x, y, width); - if (i > 20) { - // highlight-start - const [pixelX, pixelY] = camera(finalX, finalY, width); - const hIndex = histIndex(pixelX, pixelY, width, 1); + if ( + pixelX < 0 || + pixelX >= width || + pixelY < 0 || + pixelY >= height + ) + return; - if (hIndex < 0 || hIndex >= histogram.length) { - continue; - } + const hIndex = + histIndex(pixelX, pixelY, width, 1); - histogram[hIndex] += 1; - // highlight-end - } + hist[hIndex] += 1; + }; + // highlight-end - if (i % step === 0) - yield paint(width, height, histogram); + let [x, y] = [ + randomBiUnit(), + randomBiUnit() + ]; + + for (let i = 0; i < iterations; i++) { + const [_, transform] = + randomChoice(transforms); + [x, y] = transform(x, y); + const [finalX, finalY] = final(x, y); + + if (i > 20) { + // highlight-start + plotHist(finalX, finalY); + // highlight-end } - yield paint(width, height, histogram); + if (i % step === 0) + yield paint(width, height, hist); + } + + yield paint(width, height, hist); } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/colorFromPalette.ts b/blog/2024-11-15-playing-with-fire/3-log-density/colorFromPalette.ts index a039313..9dc66dc 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/colorFromPalette.ts +++ b/blog/2024-11-15-playing-with-fire/3-log-density/colorFromPalette.ts @@ -1,4 +1,14 @@ -export function colorFromPalette(palette: number[], colorIndex: number): [number, number, number] { - const paletteIndex = Math.floor(colorIndex * (palette.length / 3)) * 3; - return [palette[paletteIndex], palette[paletteIndex + 1], palette[paletteIndex + 2]]; +export function colorFromPalette( + palette: number[], + colorIndex: number +): [number, number, number] { + const numColors = palette.length / 3; + const paletteIndex = Math.floor( + colorIndex * (numColors) + ) * 3; + return [ + palette[paletteIndex], // red + palette[paletteIndex + 1], // green + palette[paletteIndex + 2] // blue + ]; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx b/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx index a9356ea..0254a4f 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx +++ b/blog/2024-11-15-playing-with-fire/3-log-density/index.mdx @@ -165,7 +165,7 @@ import colorFromPaletteSource from "!!raw-loader!./colorFromPalette";
As an alternative... - ...you could also interpolate between colors in the palette. + ...you could interpolate between colors in the palette. For example, `flam3` uses [linear interpolation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L483-L486)
diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/mixColor.ts b/blog/2024-11-15-playing-with-fire/3-log-density/mixColor.ts index f27b175..d065103 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/mixColor.ts +++ b/blog/2024-11-15-playing-with-fire/3-log-density/mixColor.ts @@ -1,3 +1,8 @@ -export function mixColor(color1: number, color2: number, colorSpeed: number) { - return color1 * (1 - colorSpeed) + color2 * colorSpeed; +export function mixColor( + color1: number, + color2: number, + colorSpeed: number +) { + return color1 * (1 - colorSpeed) + + color2 * colorSpeed; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/paintColor.ts b/blog/2024-11-15-playing-with-fire/3-log-density/paintColor.ts index 33b0470..650057e 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/paintColor.ts +++ b/blog/2024-11-15-playing-with-fire/3-log-density/paintColor.ts @@ -1,25 +1,34 @@ -// hidden-start -import {colorFromPalette} from "./colorFromPalette"; -// hidden-end export function paintColor( - width: number, - height: number, - red: number[], - green: number[], - blue: number[], - alpha: number[] + width: number, + height: number, + red: number[], + green: number[], + blue: number[], + alpha: number[] ): ImageData { - const image = new ImageData(width, height); + const pixels = width * height; + const img = + new ImageData(width, height); - for (let i = 0; i < width * height; i++) { - const alphaScale = Math.log10(alpha[i]) / (alpha[i] * 1.5); + for (let i = 0; i < pixels; i++) { + const scale = + Math.log10(alpha[i]) / + (alpha[i] * 1.5); - const pixelIndex = i * 4; - image.data[pixelIndex] = red[i] * alphaScale * 0xff; - image.data[pixelIndex + 1] = green[i] * alphaScale * 0xff; - image.data[pixelIndex + 2] = blue[i] * alphaScale * 0xff; - image.data[pixelIndex + 3] = alpha[i] * alphaScale * 0xff; - } + const pixelIndex = i * 4; - return image; + const rVal = red[i] * scale * 0xff; + img.data[pixelIndex] = rVal; + + const gVal = green[i] * scale * 0xff; + img.data[pixelIndex + 1] = gVal; + + const bVal = blue[i] * scale * 0xff; + img.data[pixelIndex + 2] = bVal; + + const aVal = alpha[i] * scale * 0xff; + img.data[pixelIndex + 3] = aVal; + } + + return img; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/paintLinear.ts b/blog/2024-11-15-playing-with-fire/3-log-density/paintLinear.ts index 077b3a6..cd2527b 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/paintLinear.ts +++ b/blog/2024-11-15-playing-with-fire/3-log-density/paintLinear.ts @@ -1,18 +1,26 @@ -export function paintLinear(width: number, height: number, histogram: number[]): ImageData { - const image = new ImageData(width, height); +export function paintLinear( + width: number, + height: number, + hist: number[] +) { + const img = + new ImageData(width, height); - let valueMax = 0; - for (let value of histogram) { - valueMax = Math.max(valueMax, value); - } + let hMax = 0; + for (let value of hist) { + hMax = Math.max(hMax, value); + } - for (let i = 0; i < histogram.length; i++) { - const pixelIndex = i * 4; - image.data[pixelIndex] = 0; - image.data[pixelIndex + 1] = 0; - image.data[pixelIndex + 2] = 0; - image.data[pixelIndex + 3] = histogram[i] / valueMax * 0xff; - } + for (let i = 0; i < hist.length; i++) { + const pixelIndex = i * 4; - return image; + img.data[pixelIndex] = 0; + img.data[pixelIndex + 1] = 0; + img.data[pixelIndex + 2] = 0; + + const alpha = hist[i] / hMax * 0xff; + img.data[pixelIndex + 3] = alpha; + } + + return img; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/paintLogarithmic.ts b/blog/2024-11-15-playing-with-fire/3-log-density/paintLogarithmic.ts index eacad34..93bf83f 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/paintLogarithmic.ts +++ b/blog/2024-11-15-playing-with-fire/3-log-density/paintLogarithmic.ts @@ -1,21 +1,29 @@ -export function paintLogarithmic(width: number, height: number, histogram: number[]): ImageData { - const image = new ImageData(width, height); +export function paintLogarithmic( + width: number, + height: number, + hist: number[] +) { + const img = + new ImageData(width, height); - const histogramLog: number[] = []; - histogram.forEach(value => histogramLog.push(Math.log(value))); + const histLog = hist.map(Math.log); - let histogramLogMax = -Infinity; - for (let value of histogramLog) { - histogramLogMax = Math.max(histogramLogMax, value); - } + let hLogMax = -Infinity; + for (let value of histLog) { + hLogMax = Math.max(hLogMax, value); + } - for (let i = 0; i < histogram.length; i++) { - const pixelIndex = i * 4; - image.data[pixelIndex] = 0; // red - image.data[pixelIndex + 1] = 0; // green - image.data[pixelIndex + 2] = 0; // blue - image.data[pixelIndex + 3] = histogramLog[i] / histogramLogMax * 0xff; - } + for (let i = 0; i < hist.length; i++) { + const pixelIndex = i * 4; - return image; + img.data[pixelIndex] = 0; // red + img.data[pixelIndex + 1] = 0; // green + img.data[pixelIndex + 2] = 0; // blue + + const alpha = + histLog[i] / hLogMax * 0xff; + img.data[pixelIndex + 3] = alpha; + } + + return img; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/params.flame b/blog/2024-11-15-playing-with-fire/params.flame index 6be89c4..61dac5a 100644 --- a/blog/2024-11-15-playing-with-fire/params.flame +++ b/blog/2024-11-15-playing-with-fire/params.flame @@ -1,8 +1,8 @@ - - + + 3130323635383B3A3D403F424644484B494D504E52565358 5B585D605D626562686B676D706C737571787B767D807B83 @@ -39,9 +39,9 @@ - + 3130323635383B3A3D403F424644484B494D504E52565358 5B585D605D626562686B676D706C737571787B767D807B83 @@ -78,9 +78,9 @@ - + 3130323635383B3A3D403F424644484B494D504E52565358 diff --git a/blog/2024-11-15-playing-with-fire/src/Canvas.tsx b/blog/2024-11-15-playing-with-fire/src/Canvas.tsx index b7ea30c..155b982 100644 --- a/blog/2024-11-15-playing-with-fire/src/Canvas.tsx +++ b/blog/2024-11-15-playing-with-fire/src/Canvas.tsx @@ -11,7 +11,7 @@ export const PainterContext = createContext(null) const downloadImage = (name: string) => (e: MouseEvent) => { const link = document.createElement("a"); - link.download = "flame.png"; + link.download = `${name}.png`; link.href = (e.target as HTMLCanvasElement).toDataURL("image/png"); link.click(); } diff --git a/blog/2024-11-15-playing-with-fire/src/applyTransform.ts b/blog/2024-11-15-playing-with-fire/src/applyTransform.ts index 92b2165..a307d59 100644 --- a/blog/2024-11-15-playing-with-fire/src/applyTransform.ts +++ b/blog/2024-11-15-playing-with-fire/src/applyTransform.ts @@ -1,8 +1,8 @@ -import {Transform, Coefs, applyCoefs} from "./transform"; -import {blend, VariationBlend} from "./blend"; +import { applyCoefs, Coefs, Transform } from "./transform"; +import { blend, Blend } from "./blend"; -export const applyTransform = (coefs: Coefs, variations: VariationBlend): Transform => - (x, y) => blend(...applyCoefs(x, y, coefs), variations); +export const applyTransform = (coefs: Coefs, variations: Blend): Transform => + (x, y) => blend(...applyCoefs(x, y, coefs), variations) export const applyPost = (coefsPost: Coefs, transform: Transform): Transform => - (x, y) => applyCoefs(...transform(x, y), coefsPost); \ No newline at end of file + (x, y) => applyCoefs(...transform(x, y), coefsPost); \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/blend.ts b/blog/2024-11-15-playing-with-fire/src/blend.ts index d47f2cc..13692df 100644 --- a/blog/2024-11-15-playing-with-fire/src/blend.ts +++ b/blog/2024-11-15-playing-with-fire/src/blend.ts @@ -1,19 +1,20 @@ // hidden-start -import {Variation} from "./variation"; +import { Variation } from "./variation"; // hidden-end -export type VariationBlend = [number, Variation][]; +export type Blend = [number, Variation][]; + export function blend( - x: number, - y: number, - variations: VariationBlend + x: number, + y: number, + varFns: Blend ): [number, number] { - let [outX, outY] = [0, 0]; + let [outX, outY] = [0, 0]; - for (const [weight, variation] of variations) { - const [varX, varY] = variation(x, y); - outX += weight * varX; - outY += weight * varY; - } + for (const [weight, varFn] of varFns) { + const [varX, varY] = varFn(x, y); + outX += weight * varX; + outY += weight * varY; + } - return [outX, outY]; + return [outX, outY]; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/julia.ts b/blog/2024-11-15-playing-with-fire/src/julia.ts index cd0ede6..3d2508b 100644 --- a/blog/2024-11-15-playing-with-fire/src/julia.ts +++ b/blog/2024-11-15-playing-with-fire/src/julia.ts @@ -1,16 +1,21 @@ // hidden-start -import { Variation } from './variation' +import { Variation } from "./variation"; // hidden-end -export const julia: Variation = (x, y) => { - const r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); +const omega = + () => Math.random() > 0.5 ? 0 : Math.PI; + +export const julia: Variation = + (x, y) => { + const x2 = Math.pow(x, 2); + const y2 = Math.pow(y, 2); + const r = Math.sqrt(x2 + y2); const theta = Math.atan2(x, y); - const omega = Math.random() > 0.5 ? 0 : Math.PI; const sqrtR = Math.sqrt(r); - const thetaVal = theta / 2 + omega; + const thetaVal = theta / 2 + omega(); return [ - sqrtR * Math.cos(thetaVal), - sqrtR * Math.sin(thetaVal) - ] -} \ No newline at end of file + sqrtR * Math.cos(thetaVal), + sqrtR * Math.sin(thetaVal) + ]; + }; \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/linear.ts b/blog/2024-11-15-playing-with-fire/src/linear.ts index 36b234a..3733d98 100644 --- a/blog/2024-11-15-playing-with-fire/src/linear.ts +++ b/blog/2024-11-15-playing-with-fire/src/linear.ts @@ -1,4 +1,5 @@ // hidden-start import {Variation} from "./variation" //hidden-end -export const linear: Variation = (x, y) => [x, y]; \ No newline at end of file +export const linear: Variation = + (x, y) => [x, y]; \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/params.ts b/blog/2024-11-15-playing-with-fire/src/params.ts index 66098af..9f53aef 100644 --- a/blog/2024-11-15-playing-with-fire/src/params.ts +++ b/blog/2024-11-15-playing-with-fire/src/params.ts @@ -3,119 +3,118 @@ * translated into something that's easier to work with. */ -import { Coefs } from './coefs'; -import {VariationBlend} from "./blend"; -import { linear } from './linear' -import { julia } from './julia' -import { popcorn } from './popcorn' -import {pdj, PdjParams} from './pdj' -import {Transform} from "./transform"; -import {applyPost, applyTransform} from "./applyTransform"; +import { Blend } from "./blend"; +import { linear } from "./linear"; +import { julia } from "./julia"; +import { popcorn } from "./popcorn"; +import { pdj, PdjParams } from "./pdj"; +import { Coefs, Transform } from "./transform"; +import { applyPost, applyTransform } from "./applyTransform"; export const identityCoefs: Coefs = { - a: 1, b: 0, c: 0, - d: 0, e: 1, f: 0, -} + a: 1, b: 0, c: 0, + d: 0, e: 1, f: 0 +}; export const pdjParams: PdjParams = { - a: 1.09358, b: 2.13048, c: 2.54127, d: 2.37267 -} + a: 1.09358, b: 2.13048, c: 2.54127, d: 2.37267 +}; export const xform1Weight = 0.56453495; export const xform1Coefs = { - a: -1.381068, b: -1.381068, c: 0, - d: 1.381068, e: -1.381068, f: 0, -} + a: -1.381068, b: -1.381068, c: 0, + d: 1.381068, e: -1.381068, f: 0 +}; export const xform1CoefsPost = identityCoefs; -export const xform1Variations: VariationBlend = [ - [1, julia] -] +export const xform1Variations: Blend = [ + [1, julia] +]; export const xform1Color = 0; export const xform2Weight = 0.013135; export const xform2Coefs = { - a: 0.031393, b: 0.031367, c: 0, - d: -0.031367, e: 0.031393, f: 0, -} + a: 0.031393, b: 0.031367, c: 0, + d: -0.031367, e: 0.031393, f: 0 +}; export const xform2CoefsPost = { - a: 1, b: 0, c: 0.24, - d: 0, e: 1, f: 0.27, -} -export const xform2Variations: VariationBlend = [ - [1, linear], - [1, popcorn(xform2Coefs)] -] + a: 1, b: 0, c: 0.24, + d: 0, e: 1, f: 0.27 +}; +export const xform2Variations: Blend = [ + [1, linear], + [1, popcorn(xform2Coefs)] +]; export const xform2Color = 0.844; export const xform3Weight = 0.42233; export const xform3Coefs = { - a: 1.51523, b: -3.048677, c: 0.724135, - d: 0.740356, e: -1.455964, f: -0.362059, -} + a: 1.51523, b: -3.048677, c: 0.724135, + d: 0.740356, e: -1.455964, f: -0.362059 +}; export const xform3CoefsPost = identityCoefs; -export const xform3Variations: VariationBlend = [ - [1, pdj(pdjParams)] +export const xform3Variations: Blend = [ + [1, pdj(pdjParams)] ]; export const xform3Color = 0.349; export const xformFinalCoefs = { - a: 2, b: 0, c: 0, - d: 0, e: 2, f: 0 -} + a: 2, b: 0, c: 0, + d: 0, e: 2, f: 0 +}; export const xformFinalCoefsPost = identityCoefs; -export const xformFinalVariations: VariationBlend = [ - [1, julia] -] +export const xformFinalVariations: Blend = [ + [1, julia] +]; export const xformFinalColor = 0; export const xforms: [number, Transform][] = [ - [xform1Weight, applyPost(xform1CoefsPost, applyTransform(xform1Coefs, xform1Variations))], - [xform2Weight, applyPost(xform2CoefsPost, applyTransform(xform2Coefs, xform2Variations))], - [xform3Weight, applyPost(xform3CoefsPost, applyTransform(xform3Coefs, xform3Variations))], -] + [xform1Weight, applyPost(xform1CoefsPost, applyTransform(xform1Coefs, xform1Variations))], + [xform2Weight, applyPost(xform2CoefsPost, applyTransform(xform2Coefs, xform2Variations))], + [xform3Weight, applyPost(xform3CoefsPost, applyTransform(xform3Coefs, xform3Variations))] +]; export const xformFinal: Transform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, xformFinalVariations)); export const paletteString = - "3130323635383B3A3D403F424644484B494D504E52565358" + - "5B585D605D626562686B676D706C737571787B767D807B83" + - "8580888A858D908A93958F989A949DA099A3A59EA8AAA3AD" + - "AFA8B3B5ADB8BAB2BEBFB7C3C5BCC8CAC1CECFC6D3D4CBD8" + - "DAD0DEDFD5E3DFD2E0DFCEDDE0CBDAE0C8D7E0C4D3E0C1D0" + - "E1BECDE1BBCAE1B7C7E1B4C4E1B1C1E2ADBEE2AABAE2A7B7" + - "E2A3B4E2A0B1E39DAEE399ABE396A8E393A5E490A1E48C9E" + - "E4899BE48698E48295E57F92E57C8FE5788CE57589E57285" + - "E66E82E66B7FE6687CE66479E76176E75E73E75B70E7576C" + - "E75469E85166E84D63E84A60E4495EE0485CDC475BD84659" + - "D44557D04455CB4353C74252C34150BF404EBB3F4CB73E4B" + - "B33D49AF3C47AB3B45A73A43A339429F38409B373E97363C" + - "92353A8E34398A33378632358231337E30327A2F30762E2E" + - "722D2C6E2C2A6A2B29662A276229255E2823592721552620" + - "51251E4D241C49231A4522194121173D20153C1F153A1F14" + - "391E14381E14361D14351C13341C13321B13311B132F1A12" + - "2E19122D19122B18122A1811291711271611261611251510" + - "23151022141021140F1F130F1E120F1C120F1B110E1A110E" + - "18100E170F0E160F0D140E0D130E0D120D0D100C0C0F0C0C" + - "0E0B0C0C0B0C0B0A0B09090B08090B07080B05080A04070A" + - "0606090804090A03088C46728A457087446D85436B824369" + - "8042667D41647B4061793F5F763E5D743D5A713D586F3C56" + - "6C3B536A3A5168394F65384C63374A6037485E36455B3543" + - "59344057333E54323C5231394F31374D30354A2F32482E30" + - "462D2E432C2B412B293E2B273C2A2439292237281F35271D" + - "32261B3025182D25162B241428231126220F25210F24210E" + - "23200E221F0E221E0D211E0D201D0D1F1C0D1E1B0C1D1B0C" + - "1C1A0C1B190B1B180B1A180B19170A18160A17150A161509" + - "1514091413091413081312081211081110081010070F0F07" + - "0E0E070D0D060C0D060C0C060B0B050A0A05090A05080904" + - "070804060704050704050603040503030403020402010302" + - "0608070C0D0D1112121617171B1C1D2121222626272B2B2D" + "3130323635383B3A3D403F424644484B494D504E52565358" + + "5B585D605D626562686B676D706C737571787B767D807B83" + + "8580888A858D908A93958F989A949DA099A3A59EA8AAA3AD" + + "AFA8B3B5ADB8BAB2BEBFB7C3C5BCC8CAC1CECFC6D3D4CBD8" + + "DAD0DEDFD5E3DFD2E0DFCEDDE0CBDAE0C8D7E0C4D3E0C1D0" + + "E1BECDE1BBCAE1B7C7E1B4C4E1B1C1E2ADBEE2AABAE2A7B7" + + "E2A3B4E2A0B1E39DAEE399ABE396A8E393A5E490A1E48C9E" + + "E4899BE48698E48295E57F92E57C8FE5788CE57589E57285" + + "E66E82E66B7FE6687CE66479E76176E75E73E75B70E7576C" + + "E75469E85166E84D63E84A60E4495EE0485CDC475BD84659" + + "D44557D04455CB4353C74252C34150BF404EBB3F4CB73E4B" + + "B33D49AF3C47AB3B45A73A43A339429F38409B373E97363C" + + "92353A8E34398A33378632358231337E30327A2F30762E2E" + + "722D2C6E2C2A6A2B29662A276229255E2823592721552620" + + "51251E4D241C49231A4522194121173D20153C1F153A1F14" + + "391E14381E14361D14351C13341C13321B13311B132F1A12" + + "2E19122D19122B18122A1811291711271611261611251510" + + "23151022141021140F1F130F1E120F1C120F1B110E1A110E" + + "18100E170F0E160F0D140E0D130E0D120D0D100C0C0F0C0C" + + "0E0B0C0C0B0C0B0A0B09090B08090B07080B05080A04070A" + + "0606090804090A03088C46728A457087446D85436B824369" + + "8042667D41647B4061793F5F763E5D743D5A713D586F3C56" + + "6C3B536A3A5168394F65384C63374A6037485E36455B3543" + + "59344057333E54323C5231394F31374D30354A2F32482E30" + + "462D2E432C2B412B293E2B273C2A2439292237281F35271D" + + "32261B3025182D25162B241428231126220F25210F24210E" + + "23200E221F0E221E0D211E0D201D0D1F1C0D1E1B0C1D1B0C" + + "1C1A0C1B190B1B180B1A180B19170A18160A17150A161509" + + "1514091413091413081312081211081110081010070F0F07" + + "0E0E070D0D060C0D060C0C060B0B050A0A05090A05080904" + + "070804060704050704050603040503030403020402010302" + + "0608070C0D0D1112121617171B1C1D2121222626272B2B2D"; function hexToBytes(hex: string) { - let bytes: number[] = []; - for (let i = 0; i < hex.length; i += 2) { - bytes.push(parseInt(hex.substring(i, i + 2), 16)); - } - return bytes; + let bytes: number[] = []; + for (let i = 0; i < hex.length; i += 2) { + bytes.push(parseInt(hex.substring(i, i + 2), 16)); + } + return bytes; } export const palette = hexToBytes(paletteString).map(value => value / 0xff); \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/pdj.ts b/blog/2024-11-15-playing-with-fire/src/pdj.ts index 5afd470..bec8e8d 100644 --- a/blog/2024-11-15-playing-with-fire/src/pdj.ts +++ b/blog/2024-11-15-playing-with-fire/src/pdj.ts @@ -1,10 +1,15 @@ // hidden-start import { Variation } from './variation' //hidden-end -export type PdjParams = {a: number, b: number, c: number, d: number}; -export function pdj({a, b, c, d}: PdjParams): Variation { - return (x, y) => [ +export type PdjParams = { + a: number, + b: number, + c: number, + d: number +}; +export const pdj = + ({a, b, c, d}: PdjParams): Variation => + (x, y) => [ Math.sin(a * y) - Math.cos(b * x), Math.sin(c * x) - Math.cos(d * y) - ] -} \ No newline at end of file + ] \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/plotBinary.ts b/blog/2024-11-15-playing-with-fire/src/plotBinary.ts index fd5b9a8..1beb080 100644 --- a/blog/2024-11-15-playing-with-fire/src/plotBinary.ts +++ b/blog/2024-11-15-playing-with-fire/src/plotBinary.ts @@ -1,14 +1,14 @@ -import { camera, histIndex } from "./camera" +import { camera, histIndex } from "./camera"; export function plotBinary(x: number, y: number, image: ImageData) { - const [pixelX, pixelY] = camera(x, y, image.width); - const pixelIndex = histIndex(pixelX, pixelY, image.width, 4); - if (pixelIndex < 0 || pixelIndex > image.data.length) { - return; - } + const [pixelX, pixelY] = camera(x, y, image.width); + if (pixelX < 0 || pixelX >= image.width || pixelY < 0 || pixelY >= image.height) + return; - image.data[pixelIndex] = 0; - image.data[pixelIndex + 1] = 0; - image.data[pixelIndex + 2] = 0; - image.data[pixelIndex + 3] = 0xff; + const pixelIndex = histIndex(pixelX, pixelY, image.width, 4); + + image.data[pixelIndex] = 0; + image.data[pixelIndex + 1] = 0; + image.data[pixelIndex + 2] = 0; + image.data[pixelIndex + 3] = 0xff; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/popcorn.ts b/blog/2024-11-15-playing-with-fire/src/popcorn.ts index bcb2741..168070f 100644 --- a/blog/2024-11-15-playing-with-fire/src/popcorn.ts +++ b/blog/2024-11-15-playing-with-fire/src/popcorn.ts @@ -1,10 +1,10 @@ // hidden-start -import {Coefs} from './coefs' -import {Variation} from './variation' +import { Coefs } from "./transform"; +import { Variation } from "./variation"; // hidden-end -export function popcorn({c, f}: Coefs): Variation { - return (x, y) => [ - x + c * Math.sin(Math.tan(3 * y)), - y + f * Math.sin(Math.tan(3 * x)) - ]; -} \ No newline at end of file +export const popcorn = + ({ c, f }: Coefs): Variation => + (x, y) => [ + x + c * Math.sin(Math.tan(3 * y)), + y + f * Math.sin(Math.tan(3 * x)) + ]; \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/randomChoice.ts b/blog/2024-11-15-playing-with-fire/src/randomChoice.ts index 58cf17e..9d72fd2 100644 --- a/blog/2024-11-15-playing-with-fire/src/randomChoice.ts +++ b/blog/2024-11-15-playing-with-fire/src/randomChoice.ts @@ -7,7 +7,8 @@ export function randomChoice( ); let choice = Math.random() * weightSum; - for (const [idx, elem] of choices.entries()) { + for (const entry of choices.entries()) { + const [idx, elem] = entry; const [weight, t] = elem; if (choice < weight) { return [idx, t]; diff --git a/blog/2024-11-15-playing-with-fire/src/transform.ts b/blog/2024-11-15-playing-with-fire/src/transform.ts index 9b7e783..ab2c364 100644 --- a/blog/2024-11-15-playing-with-fire/src/transform.ts +++ b/blog/2024-11-15-playing-with-fire/src/transform.ts @@ -1,13 +1,23 @@ -export type Transform = (x: number, y: number) => [number, number]; +export type Transform = + (x: number, y: number) => + [number, number]; export interface Coefs { - a: number, b: number, c: number, - d: number, e: number, f: number + a: number, + b: number, + c: number, + d: number, + e: number, + f: number } -export function applyCoefs(x: number, y: number, coefs: Coefs): [number, number] { - return [ - (x * coefs.a + y * coefs.b + coefs.c), - (x * coefs.d + y * coefs.e + coefs.f) - ] +export function applyCoefs( + x: number, + y: number, + coefs: Coefs +): [number, number] { + return [ + (x * coefs.a + y * coefs.b + coefs.c), + (x * coefs.d + y * coefs.e + coefs.f) + ]; } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/src/variation.ts b/blog/2024-11-15-playing-with-fire/src/variation.ts index 3b90d18..eec745c 100644 --- a/blog/2024-11-15-playing-with-fire/src/variation.ts +++ b/blog/2024-11-15-playing-with-fire/src/variation.ts @@ -1 +1,4 @@ -export type Variation = (x: number, y: number) => [number, number]; \ No newline at end of file +export type Variation = ( + x: number, + y: number +) => [number, number]; \ No newline at end of file