From 2e8a6d1ce72205bc0e4057c417a589d7cd2ace31 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sun, 1 Dec 2024 21:57:10 -0500 Subject: [PATCH] Log density visualization --- .../1-introduction/GasketWeighted.tsx | 9 ++-- .../1-introduction/chaosGameWeighted.ts | 9 +++- .../2-transforms/FlameBlend.tsx | 33 +++++++------ .../2-transforms/FlameFinal.tsx | 41 ++++------------ .../2-transforms/FlamePost.tsx | 48 ++++++++----------- .../2-transforms/blend.ts | 17 ------- .../{buildTransform.ts => buildBlend.ts} | 13 +---- .../2-transforms/chaosGameFinal.ts | 13 +++-- .../2-transforms/index.mdx | 2 +- .../3-log-density/FlameHistogram.tsx | 43 ++++++++--------- .../3-log-density/chaosGameHistogram.ts | 38 +++++++-------- .../3-log-density/index.mdx | 20 +++++--- .../3-log-density/paintLinear.ts | 18 +++++++ .../3-log-density/paintLogarithmic.ts | 21 ++++++++ .../3-log-density/plotHistogram.ts | 23 --------- .../src/Canvas.tsx | 11 ++--- .../src/applyTransform.ts | 9 ++++ .../2024-11-15-playing-with-fire/src/blend.ts | 18 +++++++ .../src/params.ts | 12 ++++- .../src/variationBlend.ts | 2 - 20 files changed, 206 insertions(+), 194 deletions(-) delete mode 100644 blog/2024-11-15-playing-with-fire/2-transforms/blend.ts rename blog/2024-11-15-playing-with-fire/2-transforms/{buildTransform.ts => buildBlend.ts} (56%) create mode 100644 blog/2024-11-15-playing-with-fire/3-log-density/paintLinear.ts create mode 100644 blog/2024-11-15-playing-with-fire/3-log-density/paintLogarithmic.ts delete mode 100644 blog/2024-11-15-playing-with-fire/3-log-density/plotHistogram.ts create mode 100644 blog/2024-11-15-playing-with-fire/src/applyTransform.ts delete mode 100644 blog/2024-11-15-playing-with-fire/src/variationBlend.ts 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 c3f91f1..40d5a3b 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,6 +1,6 @@ import {useEffect, useState, useContext} from "react"; import {PainterContext} from "../src/Canvas"; -import {chaosGameWeighted } from "./chaosGameWeighted"; +import {chaosGameWeighted} from "./chaosGameWeighted"; import TeX from '@matejmazur/react-katex'; import styles from "../src/css/styles.module.css" @@ -16,14 +16,15 @@ export default function GasketWeighted() { const f1: Transform = (x, y) => [(x + 1) / 2, y / 2]; const f2: Transform = (x, y) => [x / 2, (y + 1) / 2]; - const {setPainter} = useContext(PainterContext); + const {width, height, setPainter} = useContext(PainterContext); useEffect(() => { - setPainter(chaosGameWeighted([ + const transforms: [number, Transform][] = [ [f0Weight, f0], [f1Weight, f1], [f2Weight, f2] - ])); + ]; + setPainter(chaosGameWeighted({width, height, transforms})); }, [f0Weight, f1Weight, f2Weight]); const weightInput = (title, weight, setWeight) => ( 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 fac18d1..35b00f5 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 @@ -6,8 +6,13 @@ import {Transform} from "../src/transform"; const iterations = 50_000; const step = 1000; // hidden-end -export function* chaosGameWeighted(transforms: [number, Transform][]) { - let image = new ImageData(500, 500); +export type ChaosGameWeightedProps = { + width: number, + height: number, + transforms: [number, Transform][] +} +export function* chaosGameWeighted({width, height, transforms}: ChaosGameWeightedProps) { + let image = new ImageData(width, height); var [x, y] = [randomBiUnit(), randomBiUnit()]; for (let i = 0; i < iterations; i++) { 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 f569844..b4792d4 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,17 +1,12 @@ import {useContext, useEffect, useState} from "react"; import {Transform} from "../src/transform"; -import { - xform1Coefs, - xform1Weight, - xform2Coefs, - xform2Weight, - xform3Coefs, - xform3Weight -} from "../src/params"; +import * as params from "../src/params" import {PainterContext} from "../src/Canvas" -import {buildBlend, buildTransform} from "./buildTransform" 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"; export default function FlameBlend() { const {width, height, setPainter} = useContext(PainterContext); @@ -47,11 +42,21 @@ export default function FlameBlend() { // and swap in identity components for each const identityXform: Transform = (x, y) => [x, y]; - useEffect(() => setPainter(chaosGameFinal(width, height, [ - [xform1Weight, buildTransform(xform1Coefs, buildBlend(xform1Coefs, xform1Variations))], - [xform2Weight, buildTransform(xform2Coefs, buildBlend(xform2Coefs, xform2Variations))], - [xform3Weight, buildTransform(xform3Coefs, buildBlend(xform3Coefs, xform3Variations))] - ], identityXform)), [xform1Variations, xform2Variations, 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]); return ( <> 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 b11d9d4..76b2ecb 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,40 +1,18 @@ import {useContext, useEffect, useState} from "react"; import {Coefs} from "../src/coefs" -import { - xform1Coefs, - xform1Weight, - xform1Variations, - xform1CoefsPost, - xform2Coefs, - xform2Weight, - xform2Variations, - xform2CoefsPost, - xform3Coefs, - xform3Weight, - xform3Variations, - xform3CoefsPost, - xformFinalCoefs as xformFinalCoefsDefault, - xformFinalCoefsPost as xformFinalCoefsPostDefault, -} from "../src/params"; +import * as params from "../src/params"; import {PainterContext} from "../src/Canvas" -import {buildBlend, buildTransform} from "./buildTransform"; -import {transformPost} from "./post"; +import {buildBlend} from "./buildBlend"; import {chaosGameFinal} from "./chaosGameFinal" import {VariationEditor, VariationProps} from "./VariationEditor"; import {CoefEditor} from "./CoefEditor"; -import {Transform} from "../src/transform"; - -export const transforms: [number, Transform][] = [ - [xform1Weight, transformPost(buildTransform(xform1Coefs, xform1Variations), xform1CoefsPost)], - [xform2Weight, transformPost(buildTransform(xform2Coefs, xform2Variations), xform2CoefsPost)], - [xform3Weight, transformPost(buildTransform(xform3Coefs, xform3Variations), xform3CoefsPost)] -]; +import {applyPost, applyTransform} from "../src/applyTransform"; export default function FlameFinal() { const {width, height, setPainter} = useContext(PainterContext); - const [xformFinalCoefs, setXformFinalCoefs] = useState(xformFinalCoefsDefault); - const resetXformFinalCoefs = () => setXformFinalCoefs(xformFinalCoefsDefault); + const [xformFinalCoefs, setXformFinalCoefs] = useState(params.xformFinalCoefs); + const resetXformFinalCoefs = () => setXformFinalCoefs(params.xformFinalCoefs); const xformFinalVariationsDefault: VariationProps = { linear: 0, @@ -45,15 +23,14 @@ export default function FlameFinal() { const [xformFinalVariations, setXformFinalVariations] = useState(xformFinalVariationsDefault); const resetXformFinalVariations = () => setXformFinalVariations(xformFinalVariationsDefault); - const [xformFinalCoefsPost, setXformFinalCoefsPost] = useState(xformFinalCoefsPostDefault); - const resetXformFinalCoefsPost = () => setXformFinalCoefsPost(xformFinalCoefsPostDefault); + const [xformFinalCoefsPost, setXformFinalCoefsPost] = useState(params.xformFinalCoefsPost); + const resetXformFinalCoefsPost = () => setXformFinalCoefsPost(params.xformFinalCoefsPost); useEffect(() => { const finalBlend = buildBlend(xformFinalCoefs, xformFinalVariations); - const finalTransform = buildTransform(xformFinalCoefs, finalBlend); - const finalPost = transformPost(finalTransform, xformFinalCoefsPost); + const finalXform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, finalBlend)); - setPainter(chaosGameFinal(width, height, transforms, finalPost)); + setPainter(chaosGameFinal({width, height, transforms: params.xforms, final: finalXform})); }, [xformFinalCoefs, xformFinalVariations, xformFinalCoefsPost]); return ( 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 8d1590c..33e0664 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,45 +1,37 @@ import {useContext, useEffect, useState} from "react"; import {Coefs} from "../src/coefs" import {Transform} from "../src/transform"; -import { - xform1Coefs, - xform1Weight, - xform1Variations, - xform1CoefsPost as xform1CoefsPostDefault, - xform2Coefs, - xform2Weight, - xform2Variations, - xform2CoefsPost as xform2CoefsPostDefault, - xform3Coefs, - xform3Weight, - xform3Variations, - xform3CoefsPost as xform3CoefsPostDefault -} from "../src/params"; +import * as params from "../src/params"; import {PainterContext} from "../src/Canvas" -import {chaosGameFinal} from "./chaosGameFinal" +import {chaosGameFinal, ChaosGameFinalProps} from "./chaosGameFinal" import {CoefEditor} from "./CoefEditor" -import {transformPost} from "./post"; -import {buildTransform} from "./buildTransform"; +import {applyPost, applyTransform} from "@site/blog/2024-11-15-playing-with-fire/src/applyTransform"; export default function FlamePost() { const {width, height, setPainter} = useContext(PainterContext); - const [xform1CoefsPost, setXform1CoefsPost] = useState(xform1CoefsPostDefault); - const resetXform1CoefsPost = () => setXform1CoefsPost(xform1CoefsPostDefault); + const [xform1CoefsPost, setXform1CoefsPost] = useState(params.xform1CoefsPost); + const resetXform1CoefsPost = () => setXform1CoefsPost(params.xform1CoefsPost); - const [xform2CoefsPost, setXform2CoefsPost] = useState(xform2CoefsPostDefault); - const resetXform2CoefsPost = () => setXform2CoefsPost(xform2CoefsPostDefault); + const [xform2CoefsPost, setXform2CoefsPost] = useState(params.xform2CoefsPost); + const resetXform2CoefsPost = () => setXform2CoefsPost(params.xform2CoefsPost); - const [xform3CoefsPost, setXform3CoefsPost] = useState(xform3CoefsPostDefault); - const resetXform3CoefsPost = () => setXform1CoefsPost(xform3CoefsPostDefault); + const [xform3CoefsPost, setXform3CoefsPost] = useState(params.xform3CoefsPost); + const resetXform3CoefsPost = () => setXform1CoefsPost(params.xform3CoefsPost); const identityXform: Transform = (x, y) => [x, y]; - useEffect(() => setPainter(chaosGameFinal(width, height, [ - [xform1Weight, transformPost(buildTransform(xform1Coefs, xform1Variations), xform1CoefsPost)], - [xform2Weight, transformPost(buildTransform(xform2Coefs, xform2Variations), xform2CoefsPost)], - [xform3Weight, transformPost(buildTransform(xform3Coefs, xform3Variations), xform3CoefsPost)] - ], identityXform)), [xform1CoefsPost, xform2CoefsPost, xform3CoefsPost]); + 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]); return ( <> diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/blend.ts b/blog/2024-11-15-playing-with-fire/2-transforms/blend.ts deleted file mode 100644 index 4f3b0da..0000000 --- a/blog/2024-11-15-playing-with-fire/2-transforms/blend.ts +++ /dev/null @@ -1,17 +0,0 @@ -// hidden-start -import {VariationBlend} from "../src/variationBlend"; -// hidden-end -export function blend( - x: number, - y: number, - variations: VariationBlend): [number, number] { - let [finalX, finalY] = [0, 0]; - - for (const [weight, variation] of variations) { - const [varX, varY] = variation(x, y); - finalX += weight * varX; - finalY += weight * varY; - } - - return [finalX, finalY]; -} \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/buildTransform.ts b/blog/2024-11-15-playing-with-fire/2-transforms/buildBlend.ts similarity index 56% rename from blog/2024-11-15-playing-with-fire/2-transforms/buildTransform.ts rename to blog/2024-11-15-playing-with-fire/2-transforms/buildBlend.ts index 57c1e07..c2398fd 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/buildTransform.ts +++ b/blog/2024-11-15-playing-with-fire/2-transforms/buildBlend.ts @@ -1,13 +1,11 @@ -import {applyCoefs, Coefs} from "../src/coefs"; +import {Coefs} from "../src/coefs"; import {VariationProps} from "./VariationEditor"; -import {Transform} from "../src/transform"; 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 "./blend"; -import {VariationBlend} from "../src/variationBlend"; +import {VariationBlend} from "../src/blend"; export function buildBlend(coefs: Coefs, variations: VariationProps): VariationBlend { return [ @@ -16,11 +14,4 @@ export function buildBlend(coefs: Coefs, variations: VariationProps): VariationB [variations.popcorn, popcorn(coefs)], [variations.pdj, pdj(pdjParams)] ] -} - -export function buildTransform(coefs: Coefs, variations: VariationBlend): Transform { - return (x: number, y: number) => { - [x, y] = applyCoefs(x, y, coefs); - return blend(x, y, variations); - } } \ 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 910eba6..d8b7b2a 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 @@ -3,13 +3,20 @@ import { randomBiUnit } from "../src/randomBiUnit"; import { randomChoice } from "../src/randomChoice"; import { plotBinary as plot } from "../src/plotBinary" import {Transform} from "../src/transform"; -const iterations = 500_000; -const step = 1000; +import {ChaosGameWeightedProps} from "../1-introduction/chaosGameWeighted"; // hidden-end -export function* chaosGameFinal(width: number, height: number, transforms: [number, Transform][], final: Transform) { +export type ChaosGameFinalProps = ChaosGameWeightedProps & { + final: Transform, + quality?: number, + step?: number, +} +export function* chaosGameFinal({width, height, transforms, final, quality, step}: ChaosGameFinalProps) { let image = new ImageData(width, height); let [x, y] = [randomBiUnit(), randomBiUnit()]; + const iterations = (quality ?? 0.5) * width * height; + step = step ?? 1000; + for (let i = 0; i < iterations; i++) { const [_, transform] = randomChoice(transforms); [x, y] = transform(x, y); 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 481e4b6..944243d 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 @@ -133,7 +133,7 @@ $$ The formula looks intimidating, but it's not hard to implement: -import blendSource from "!!raw-loader!./blend"; +import blendSource from "!!raw-loader!../src/blend"; {blendSource} 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 976f6fb..9ed60fa 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,30 +1,27 @@ -import {VictoryChart, VictoryLine, VictoryScatter, VictoryTheme} from "victory"; -import {useContext, useEffect, useState} from "react"; +import React, {useContext, useEffect} from "react"; +import * as params from "../src/params"; import {PainterContext} from "../src/Canvas"; -import {chaosGameHistogram} from "./chaosGameHistogram"; -import {PlotData, plotHistogram} from "./plotHistogram"; +import {chaosGameHistogram} from "@site/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameHistogram"; -function* plotChaosGame(width: number, height: number, setPdf: (data: PlotData) => void, setCdf: (data: PlotData) => void) { - const emptyImage = new ImageData(width, height); - for (let histogram of chaosGameHistogram(width, height)) { - const plotData = plotHistogram(histogram); - setPdf(plotData); - yield emptyImage; - } +type Props = { + quality?: number; + paintFn: (width: number, histogram: Uint32Array) => ImageData; + children?: React.ReactElement; } - -export default function FlameHistogram() { +export default function FlameHistogram({quality, paintFn, children}: Props) { const {width, height, setPainter} = useContext(PainterContext); - const [pdfData, setPdfData] = useState<{ x: number, y: number }[]>(null); - useEffect(() => setPainter(plotChaosGame(width, height, setPdfData, null)), []); + useEffect(() => { + const gameParams = { + width, + height, + transforms: params.xforms, + final: params.xformFinal, + quality, + painter: paintFn + } + setPainter(chaosGameHistogram(gameParams)); + }, []); - return ( - - - - ) + return children; } \ 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 c831353..bc1d8e6 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,34 +1,34 @@ -import {plot} from "./plotHistogram"; +// hidden-start import {randomBiUnit} from "../src/randomBiUnit"; import {randomChoice} from "../src/randomChoice"; -import {buildTransform} from "../2-transforms/buildTransform"; -import {transformPost} from "../2-transforms/post"; -import {transforms} from "../2-transforms/FlameFinal"; -import * as params from "../src/params"; +import {ChaosGameFinalProps} from "../2-transforms/chaosGameFinal"; +import {camera, histIndex} from "../src/camera"; +// hidden-end +export type ChaosGameHistogramProps = ChaosGameFinalProps & { + painter: (width: number, histogram: Uint32Array) => ImageData; +} +export function* chaosGameHistogram({width, height, transforms, final, quality, step, painter}: ChaosGameHistogramProps) { + let iterations = (quality ?? 10) * width * height; + step = step ?? 100_000; -const finalTransform = buildTransform(params.xformFinalCoefs, params.xformFinalVariations); -const finalTransformPost = transformPost(finalTransform, params.xformFinalCoefsPost); - -const step = 1000; -const quality = 1; - -export function* chaosGameHistogram(width: number, height: number) { - let iterations = quality * width * height; - let histogram = new Uint32Array(width * height); + const histogram = new Uint32Array(width * height); let [x, y] = [randomBiUnit(), randomBiUnit()]; for (let i = 0; i < iterations; i++) { const [_, transform] = randomChoice(transforms); [x, y] = transform(x, y); - [x, y] = finalTransformPost(x, y); + [x, y] = final(x, y); - if (i > 20) - plot(x, y, width, histogram); + if (i > 20) { + const [pixelX, pixelY] = camera(x, y, width); + const pixelIndex = histIndex(pixelX, pixelY, width, 1); + histogram[pixelIndex] += 1; + } if (i % step === 0) - yield histogram; + yield painter(width, histogram); } - yield histogram; + yield painter(width, histogram); } \ 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 ce2168c..7554ede 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 @@ -19,17 +19,23 @@ Can we do something more intelligent with that information? ## Image histograms To start with, it's worth demonstrating how much work is actually "wasted." -We'll render the reference image again, but this time, counting the times -we tried to turn on a pixel. +We'll render the reference image again, but this time, set each pixel's transparency +based on how many times we encounter it in the chaos game: import CodeBlock from "@theme/CodeBlock"; -import plotHistogramSource from "!!raw-loader!./plotHistogram"; -{plotHistogramSource} +import paintLinearSource from "!!raw-loader!./paintLinear" + +{paintLinearSource} import Canvas from "../src/Canvas"; import FlameHistogram from "./FlameHistogram"; +import {paintLinear} from "./paintLinear"; - \ No newline at end of file + + +## Log display + +import {paintLogarithmic} from './paintLogarithmic' + + \ 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 new file mode 100644 index 0000000..05d5c91 --- /dev/null +++ b/blog/2024-11-15-playing-with-fire/3-log-density/paintLinear.ts @@ -0,0 +1,18 @@ +export function paintLinear(width: number, histogram: Uint32Array): ImageData { + const image = new ImageData(width, histogram.length / width); + + let countMax = 0; + for (let value of histogram) { + countMax = Math.max(countMax, 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] = Number(histogram[i]) / countMax * 0xff; + } + + return image; +} \ 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 new file mode 100644 index 0000000..c79bce0 --- /dev/null +++ b/blog/2024-11-15-playing-with-fire/3-log-density/paintLogarithmic.ts @@ -0,0 +1,21 @@ +export function paintLogarithmic(width: number, histogram: Uint32Array): ImageData { + const image = new ImageData(width, histogram.length / width); + + const histogramLog = new Array(); + histogram.forEach(value => histogramLog.push(Math.log(value))); + + let histogramLogMax = -Infinity; + for (let value of histogramLog) { + histogramLogMax = Math.max(histogramLogMax, 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; + } + + return image; +} \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/plotHistogram.ts b/blog/2024-11-15-playing-with-fire/3-log-density/plotHistogram.ts deleted file mode 100644 index 479d0ae..0000000 --- a/blog/2024-11-15-playing-with-fire/3-log-density/plotHistogram.ts +++ /dev/null @@ -1,23 +0,0 @@ -// hidden-start -import {camera, histIndex} from "../src/camera"; -// hidden-end -export function plot(x: number, y: number, width: number, hitCount: Uint32Array) { - const [pixelX, pixelY] = camera(x, y, width); - const pixelIndex = histIndex(pixelX, pixelY, width, 1); - hitCount[pixelIndex] += 1; -} - -export type PlotData = {x: number, y: number}[]; -export function plotHistogram(hitCount: Uint32Array) { - const data = new Map(); - hitCount.forEach(value => { - const bucket = 32 - Math.clz32(value); - const currentCount = data.get(bucket) ?? 0; - data.set(bucket, currentCount + 1); - }) - - const output: PlotData = []; - data.forEach((value, bucket) => - output.push({x: Math.pow(2, bucket), y: value})); - return output; -} \ No newline at end of file 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 20e6bee..9a9c998 100644 --- a/blog/2024-11-15-playing-with-fire/src/Canvas.tsx +++ b/blog/2024-11-15-playing-with-fire/src/Canvas.tsx @@ -4,7 +4,7 @@ import BrowserOnly from "@docusaurus/BrowserOnly"; function invertImage(sourceImage: ImageData): ImageData { const image = new ImageData(sourceImage.width, sourceImage.height); - image.data.forEach((value, index) => + sourceImage.data.forEach((value, index) => image.data[index] = index % 4 === 3 ? value : 0xff - value) return image; @@ -13,7 +13,6 @@ function invertImage(sourceImage: ImageData): ImageData { type InvertibleCanvasProps = { width: number, height: number, - hidden?: boolean, // NOTE: Images are provided as a single-element array //so we can allow re-painting with the same (modified) ImageData reference. image?: [ImageData], @@ -27,7 +26,7 @@ type InvertibleCanvasProps = { * @param hidden Hide the canvas element * @param image Image data to draw on the canvas */ -const InvertibleCanvas: React.FC = ({width, height, hidden, image}) => { +const InvertibleCanvas: React.FC = ({width, height, image}) => { const [canvasCtx, setCanvasCtx] = useState(null); const canvasRef = useCallback(node => { if (node !== null) { @@ -54,7 +53,6 @@ const InvertibleCanvas: React.FC = ({width, height, hidde ref={canvasRef} width={width} height={height} - hidden={hidden ?? false} style={{ aspectRatio: width / height, width: '75%' @@ -73,7 +71,6 @@ export const PainterContext = createContext(null); interface CanvasProps { width?: number; height?: number; - hidden?: boolean; children?: React.ReactElement; } @@ -105,7 +102,7 @@ interface CanvasProps { * but we rely on contexts to manage the iterator, and I can't find * a good way to make those generic. */ -export default function Canvas({width, height, hidden, children}: CanvasProps) { +export default function Canvas({width, height, children}: CanvasProps) { const [image, setImage] = useState<[ImageData]>(null); const [painterHolder, setPainterHolder] = useState<[Iterator]>(null); useEffect(() => { @@ -135,7 +132,7 @@ export default function Canvas({width, height, hidden, children}: CanvasProps) { return ( <>
-
{() => children} diff --git a/blog/2024-11-15-playing-with-fire/src/applyTransform.ts b/blog/2024-11-15-playing-with-fire/src/applyTransform.ts new file mode 100644 index 0000000..0b28ae0 --- /dev/null +++ b/blog/2024-11-15-playing-with-fire/src/applyTransform.ts @@ -0,0 +1,9 @@ +import {Transform} from "./transform"; +import {applyCoefs, Coefs} from "./coefs"; +import {blend, VariationBlend} from "./blend"; + +export const applyTransform = (coefs: Coefs, variations: VariationBlend): 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 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 e69de29..a17e47b 100644 --- a/blog/2024-11-15-playing-with-fire/src/blend.ts +++ b/blog/2024-11-15-playing-with-fire/src/blend.ts @@ -0,0 +1,18 @@ +// hidden-start +import {Variation} from "./variation"; +// hidden-end +export type VariationBlend = [number, Variation][]; +export function blend( + x: number, + y: number, + variations: VariationBlend): [number, number] { + let [finalX, finalY] = [0, 0]; + + for (const [weight, variation] of variations) { + const [varX, varY] = variation(x, y); + finalX += weight * varX; + finalY += weight * varY; + } + + return [finalX, finalY]; +} \ 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 1688eec..4501dc2 100644 --- a/blog/2024-11-15-playing-with-fire/src/params.ts +++ b/blog/2024-11-15-playing-with-fire/src/params.ts @@ -4,11 +4,13 @@ */ import { Coefs } from './coefs'; -import {VariationBlend} from "./variationBlend"; +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"; export const identityCoefs: Coefs = { a: 1, b: 0, c: 0, @@ -66,6 +68,14 @@ export const xformFinalVariations: VariationBlend = [ ] 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))], +] + +export const xformFinal: Transform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, xformFinalVariations)); + export const palette = "7E3037762C45722B496E2A4E6A2950672853652754632656" + "5C265C5724595322574D2155482153462050451F4E441E4D" + diff --git a/blog/2024-11-15-playing-with-fire/src/variationBlend.ts b/blog/2024-11-15-playing-with-fire/src/variationBlend.ts deleted file mode 100644 index 37a24f6..0000000 --- a/blog/2024-11-15-playing-with-fire/src/variationBlend.ts +++ /dev/null @@ -1,2 +0,0 @@ -import {Variation} from "./variation"; -export type VariationBlend = [number, Variation][]; \ No newline at end of file