mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Log density visualization
This commit is contained in:
parent
79b66337e8
commit
2e8a6d1ce7
@ -1,6 +1,6 @@
|
|||||||
import {useEffect, useState, useContext} from "react";
|
import {useEffect, useState, useContext} from "react";
|
||||||
import {PainterContext} from "../src/Canvas";
|
import {PainterContext} from "../src/Canvas";
|
||||||
import {chaosGameWeighted } from "./chaosGameWeighted";
|
import {chaosGameWeighted} from "./chaosGameWeighted";
|
||||||
import TeX from '@matejmazur/react-katex';
|
import TeX from '@matejmazur/react-katex';
|
||||||
|
|
||||||
import styles from "../src/css/styles.module.css"
|
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 f1: Transform = (x, y) => [(x + 1) / 2, y / 2];
|
||||||
const f2: Transform = (x, y) => [x / 2, (y + 1) / 2];
|
const f2: Transform = (x, y) => [x / 2, (y + 1) / 2];
|
||||||
|
|
||||||
const {setPainter} = useContext(PainterContext);
|
const {width, height, setPainter} = useContext(PainterContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPainter(chaosGameWeighted([
|
const transforms: [number, Transform][] = [
|
||||||
[f0Weight, f0],
|
[f0Weight, f0],
|
||||||
[f1Weight, f1],
|
[f1Weight, f1],
|
||||||
[f2Weight, f2]
|
[f2Weight, f2]
|
||||||
]));
|
];
|
||||||
|
setPainter(chaosGameWeighted({width, height, transforms}));
|
||||||
}, [f0Weight, f1Weight, f2Weight]);
|
}, [f0Weight, f1Weight, f2Weight]);
|
||||||
|
|
||||||
const weightInput = (title, weight, setWeight) => (
|
const weightInput = (title, weight, setWeight) => (
|
||||||
|
@ -6,8 +6,13 @@ import {Transform} from "../src/transform";
|
|||||||
const iterations = 50_000;
|
const iterations = 50_000;
|
||||||
const step = 1000;
|
const step = 1000;
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export function* chaosGameWeighted(transforms: [number, Transform][]) {
|
export type ChaosGameWeightedProps = {
|
||||||
let image = new ImageData(500, 500);
|
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()];
|
var [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
import {useContext, useEffect, useState} from "react";
|
import {useContext, useEffect, useState} from "react";
|
||||||
import {Transform} from "../src/transform";
|
import {Transform} from "../src/transform";
|
||||||
import {
|
import * as params from "../src/params"
|
||||||
xform1Coefs,
|
|
||||||
xform1Weight,
|
|
||||||
xform2Coefs,
|
|
||||||
xform2Weight,
|
|
||||||
xform3Coefs,
|
|
||||||
xform3Weight
|
|
||||||
} from "../src/params";
|
|
||||||
import {PainterContext} from "../src/Canvas"
|
import {PainterContext} from "../src/Canvas"
|
||||||
import {buildBlend, buildTransform} from "./buildTransform"
|
|
||||||
import {chaosGameFinal} from "./chaosGameFinal"
|
import {chaosGameFinal} from "./chaosGameFinal"
|
||||||
import {VariationEditor, VariationProps} from "./VariationEditor"
|
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() {
|
export default function FlameBlend() {
|
||||||
const {width, height, setPainter} = useContext(PainterContext);
|
const {width, height, setPainter} = useContext(PainterContext);
|
||||||
@ -47,11 +42,21 @@ export default function FlameBlend() {
|
|||||||
// and swap in identity components for each
|
// and swap in identity components for each
|
||||||
const identityXform: Transform = (x, y) => [x, y];
|
const identityXform: Transform = (x, y) => [x, y];
|
||||||
|
|
||||||
useEffect(() => setPainter(chaosGameFinal(width, height, [
|
useEffect(() => {
|
||||||
[xform1Weight, buildTransform(xform1Coefs, buildBlend(xform1Coefs, xform1Variations))],
|
const transforms: [number, Transform][] = [
|
||||||
[xform2Weight, buildTransform(xform2Coefs, buildBlend(xform2Coefs, xform2Variations))],
|
[params.xform1Weight, applyTransform(params.xform1Coefs, buildBlend(params.xform1Coefs, xform1Variations))],
|
||||||
[xform3Weight, buildTransform(xform3Coefs, buildBlend(xform3Coefs, xform3Variations))]
|
[params.xform2Weight, applyTransform(params.xform2Coefs, buildBlend(params.xform2Coefs, xform2Variations))],
|
||||||
], identityXform)), [xform1Variations, xform2Variations, xform3Variations]);
|
[params.xform3Weight, applyTransform(params.xform3Coefs, buildBlend(params.xform3Coefs, xform3Variations))]
|
||||||
|
]
|
||||||
|
|
||||||
|
const gameParams = {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
transforms,
|
||||||
|
final: identityXform
|
||||||
|
}
|
||||||
|
setPainter(chaosGameFinal(gameParams));
|
||||||
|
}, [xform1Variations, xform2Variations, xform3Variations]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,40 +1,18 @@
|
|||||||
import {useContext, useEffect, useState} from "react";
|
import {useContext, useEffect, useState} from "react";
|
||||||
import {Coefs} from "../src/coefs"
|
import {Coefs} from "../src/coefs"
|
||||||
import {
|
import * as params from "../src/params";
|
||||||
xform1Coefs,
|
|
||||||
xform1Weight,
|
|
||||||
xform1Variations,
|
|
||||||
xform1CoefsPost,
|
|
||||||
xform2Coefs,
|
|
||||||
xform2Weight,
|
|
||||||
xform2Variations,
|
|
||||||
xform2CoefsPost,
|
|
||||||
xform3Coefs,
|
|
||||||
xform3Weight,
|
|
||||||
xform3Variations,
|
|
||||||
xform3CoefsPost,
|
|
||||||
xformFinalCoefs as xformFinalCoefsDefault,
|
|
||||||
xformFinalCoefsPost as xformFinalCoefsPostDefault,
|
|
||||||
} from "../src/params";
|
|
||||||
import {PainterContext} from "../src/Canvas"
|
import {PainterContext} from "../src/Canvas"
|
||||||
import {buildBlend, buildTransform} from "./buildTransform";
|
import {buildBlend} from "./buildBlend";
|
||||||
import {transformPost} from "./post";
|
|
||||||
import {chaosGameFinal} from "./chaosGameFinal"
|
import {chaosGameFinal} from "./chaosGameFinal"
|
||||||
import {VariationEditor, VariationProps} from "./VariationEditor";
|
import {VariationEditor, VariationProps} from "./VariationEditor";
|
||||||
import {CoefEditor} from "./CoefEditor";
|
import {CoefEditor} from "./CoefEditor";
|
||||||
import {Transform} from "../src/transform";
|
import {applyPost, applyTransform} from "../src/applyTransform";
|
||||||
|
|
||||||
export const transforms: [number, Transform][] = [
|
|
||||||
[xform1Weight, transformPost(buildTransform(xform1Coefs, xform1Variations), xform1CoefsPost)],
|
|
||||||
[xform2Weight, transformPost(buildTransform(xform2Coefs, xform2Variations), xform2CoefsPost)],
|
|
||||||
[xform3Weight, transformPost(buildTransform(xform3Coefs, xform3Variations), xform3CoefsPost)]
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function FlameFinal() {
|
export default function FlameFinal() {
|
||||||
const {width, height, setPainter} = useContext(PainterContext);
|
const {width, height, setPainter} = useContext(PainterContext);
|
||||||
|
|
||||||
const [xformFinalCoefs, setXformFinalCoefs] = useState<Coefs>(xformFinalCoefsDefault);
|
const [xformFinalCoefs, setXformFinalCoefs] = useState<Coefs>(params.xformFinalCoefs);
|
||||||
const resetXformFinalCoefs = () => setXformFinalCoefs(xformFinalCoefsDefault);
|
const resetXformFinalCoefs = () => setXformFinalCoefs(params.xformFinalCoefs);
|
||||||
|
|
||||||
const xformFinalVariationsDefault: VariationProps = {
|
const xformFinalVariationsDefault: VariationProps = {
|
||||||
linear: 0,
|
linear: 0,
|
||||||
@ -45,15 +23,14 @@ export default function FlameFinal() {
|
|||||||
const [xformFinalVariations, setXformFinalVariations] = useState<VariationProps>(xformFinalVariationsDefault);
|
const [xformFinalVariations, setXformFinalVariations] = useState<VariationProps>(xformFinalVariationsDefault);
|
||||||
const resetXformFinalVariations = () => setXformFinalVariations(xformFinalVariationsDefault);
|
const resetXformFinalVariations = () => setXformFinalVariations(xformFinalVariationsDefault);
|
||||||
|
|
||||||
const [xformFinalCoefsPost, setXformFinalCoefsPost] = useState<Coefs>(xformFinalCoefsPostDefault);
|
const [xformFinalCoefsPost, setXformFinalCoefsPost] = useState<Coefs>(params.xformFinalCoefsPost);
|
||||||
const resetXformFinalCoefsPost = () => setXformFinalCoefsPost(xformFinalCoefsPostDefault);
|
const resetXformFinalCoefsPost = () => setXformFinalCoefsPost(params.xformFinalCoefsPost);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const finalBlend = buildBlend(xformFinalCoefs, xformFinalVariations);
|
const finalBlend = buildBlend(xformFinalCoefs, xformFinalVariations);
|
||||||
const finalTransform = buildTransform(xformFinalCoefs, finalBlend);
|
const finalXform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, finalBlend));
|
||||||
const finalPost = transformPost(finalTransform, xformFinalCoefsPost);
|
|
||||||
|
|
||||||
setPainter(chaosGameFinal(width, height, transforms, finalPost));
|
setPainter(chaosGameFinal({width, height, transforms: params.xforms, final: finalXform}));
|
||||||
}, [xformFinalCoefs, xformFinalVariations, xformFinalCoefsPost]);
|
}, [xformFinalCoefs, xformFinalVariations, xformFinalCoefsPost]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,45 +1,37 @@
|
|||||||
import {useContext, useEffect, useState} from "react";
|
import {useContext, useEffect, useState} from "react";
|
||||||
import {Coefs} from "../src/coefs"
|
import {Coefs} from "../src/coefs"
|
||||||
import {Transform} from "../src/transform";
|
import {Transform} from "../src/transform";
|
||||||
import {
|
import * as params from "../src/params";
|
||||||
xform1Coefs,
|
|
||||||
xform1Weight,
|
|
||||||
xform1Variations,
|
|
||||||
xform1CoefsPost as xform1CoefsPostDefault,
|
|
||||||
xform2Coefs,
|
|
||||||
xform2Weight,
|
|
||||||
xform2Variations,
|
|
||||||
xform2CoefsPost as xform2CoefsPostDefault,
|
|
||||||
xform3Coefs,
|
|
||||||
xform3Weight,
|
|
||||||
xform3Variations,
|
|
||||||
xform3CoefsPost as xform3CoefsPostDefault
|
|
||||||
} from "../src/params";
|
|
||||||
import {PainterContext} from "../src/Canvas"
|
import {PainterContext} from "../src/Canvas"
|
||||||
import {chaosGameFinal} from "./chaosGameFinal"
|
import {chaosGameFinal, ChaosGameFinalProps} from "./chaosGameFinal"
|
||||||
import {CoefEditor} from "./CoefEditor"
|
import {CoefEditor} from "./CoefEditor"
|
||||||
import {transformPost} from "./post";
|
import {applyPost, applyTransform} from "@site/blog/2024-11-15-playing-with-fire/src/applyTransform";
|
||||||
import {buildTransform} from "./buildTransform";
|
|
||||||
|
|
||||||
export default function FlamePost() {
|
export default function FlamePost() {
|
||||||
const {width, height, setPainter} = useContext(PainterContext);
|
const {width, height, setPainter} = useContext(PainterContext);
|
||||||
|
|
||||||
const [xform1CoefsPost, setXform1CoefsPost] = useState<Coefs>(xform1CoefsPostDefault);
|
const [xform1CoefsPost, setXform1CoefsPost] = useState<Coefs>(params.xform1CoefsPost);
|
||||||
const resetXform1CoefsPost = () => setXform1CoefsPost(xform1CoefsPostDefault);
|
const resetXform1CoefsPost = () => setXform1CoefsPost(params.xform1CoefsPost);
|
||||||
|
|
||||||
const [xform2CoefsPost, setXform2CoefsPost] = useState<Coefs>(xform2CoefsPostDefault);
|
const [xform2CoefsPost, setXform2CoefsPost] = useState<Coefs>(params.xform2CoefsPost);
|
||||||
const resetXform2CoefsPost = () => setXform2CoefsPost(xform2CoefsPostDefault);
|
const resetXform2CoefsPost = () => setXform2CoefsPost(params.xform2CoefsPost);
|
||||||
|
|
||||||
const [xform3CoefsPost, setXform3CoefsPost] = useState<Coefs>(xform3CoefsPostDefault);
|
const [xform3CoefsPost, setXform3CoefsPost] = useState<Coefs>(params.xform3CoefsPost);
|
||||||
const resetXform3CoefsPost = () => setXform1CoefsPost(xform3CoefsPostDefault);
|
const resetXform3CoefsPost = () => setXform1CoefsPost(params.xform3CoefsPost);
|
||||||
|
|
||||||
const identityXform: Transform = (x, y) => [x, y];
|
const identityXform: Transform = (x, y) => [x, y];
|
||||||
|
|
||||||
useEffect(() => setPainter(chaosGameFinal(width, height, [
|
const gameParams: ChaosGameFinalProps = {
|
||||||
[xform1Weight, transformPost(buildTransform(xform1Coefs, xform1Variations), xform1CoefsPost)],
|
width,
|
||||||
[xform2Weight, transformPost(buildTransform(xform2Coefs, xform2Variations), xform2CoefsPost)],
|
height,
|
||||||
[xform3Weight, transformPost(buildTransform(xform3Coefs, xform3Variations), xform3CoefsPost)]
|
transforms: [
|
||||||
], identityXform)), [xform1CoefsPost, xform2CoefsPost, xform3CoefsPost]);
|
[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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -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];
|
|
||||||
}
|
|
@ -1,13 +1,11 @@
|
|||||||
import {applyCoefs, Coefs} from "../src/coefs";
|
import {Coefs} from "../src/coefs";
|
||||||
import {VariationProps} from "./VariationEditor";
|
import {VariationProps} from "./VariationEditor";
|
||||||
import {Transform} from "../src/transform";
|
|
||||||
import {linear} from "../src/linear";
|
import {linear} from "../src/linear";
|
||||||
import {julia} from "../src/julia";
|
import {julia} from "../src/julia";
|
||||||
import {popcorn} from "../src/popcorn";
|
import {popcorn} from "../src/popcorn";
|
||||||
import {pdj} from "../src/pdj";
|
import {pdj} from "../src/pdj";
|
||||||
import {pdjParams} from "../src/params";
|
import {pdjParams} from "../src/params";
|
||||||
import {blend} from "./blend";
|
import {VariationBlend} from "../src/blend";
|
||||||
import {VariationBlend} from "../src/variationBlend";
|
|
||||||
|
|
||||||
export function buildBlend(coefs: Coefs, variations: VariationProps): VariationBlend {
|
export function buildBlend(coefs: Coefs, variations: VariationProps): VariationBlend {
|
||||||
return [
|
return [
|
||||||
@ -16,11 +14,4 @@ export function buildBlend(coefs: Coefs, variations: VariationProps): VariationB
|
|||||||
[variations.popcorn, popcorn(coefs)],
|
[variations.popcorn, popcorn(coefs)],
|
||||||
[variations.pdj, pdj(pdjParams)]
|
[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);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -3,13 +3,20 @@ import { randomBiUnit } from "../src/randomBiUnit";
|
|||||||
import { randomChoice } from "../src/randomChoice";
|
import { randomChoice } from "../src/randomChoice";
|
||||||
import { plotBinary as plot } from "../src/plotBinary"
|
import { plotBinary as plot } from "../src/plotBinary"
|
||||||
import {Transform} from "../src/transform";
|
import {Transform} from "../src/transform";
|
||||||
const iterations = 500_000;
|
import {ChaosGameWeightedProps} from "../1-introduction/chaosGameWeighted";
|
||||||
const step = 1000;
|
|
||||||
// hidden-end
|
// 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 image = new ImageData(width, height);
|
||||||
let [x, y] = [randomBiUnit(), randomBiUnit()];
|
let [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
|
|
||||||
|
const iterations = (quality ?? 0.5) * width * height;
|
||||||
|
step = step ?? 1000;
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
const [_, transform] = randomChoice(transforms);
|
const [_, transform] = randomChoice(transforms);
|
||||||
[x, y] = transform(x, y);
|
[x, y] = transform(x, y);
|
||||||
|
@ -133,7 +133,7 @@ $$
|
|||||||
|
|
||||||
The formula looks intimidating, but it's not hard to implement:
|
The formula looks intimidating, but it's not hard to implement:
|
||||||
|
|
||||||
import blendSource from "!!raw-loader!./blend";
|
import blendSource from "!!raw-loader!../src/blend";
|
||||||
|
|
||||||
<CodeBlock language={'typescript'}>{blendSource}</CodeBlock>
|
<CodeBlock language={'typescript'}>{blendSource}</CodeBlock>
|
||||||
|
|
||||||
|
@ -1,30 +1,27 @@
|
|||||||
import {VictoryChart, VictoryLine, VictoryScatter, VictoryTheme} from "victory";
|
import React, {useContext, useEffect} from "react";
|
||||||
import {useContext, useEffect, useState} from "react";
|
import * as params from "../src/params";
|
||||||
import {PainterContext} from "../src/Canvas";
|
import {PainterContext} from "../src/Canvas";
|
||||||
import {chaosGameHistogram} from "./chaosGameHistogram";
|
import {chaosGameHistogram} from "@site/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameHistogram";
|
||||||
import {PlotData, plotHistogram} from "./plotHistogram";
|
|
||||||
|
|
||||||
function* plotChaosGame(width: number, height: number, setPdf: (data: PlotData) => void, setCdf: (data: PlotData) => void) {
|
type Props = {
|
||||||
const emptyImage = new ImageData(width, height);
|
quality?: number;
|
||||||
for (let histogram of chaosGameHistogram(width, height)) {
|
paintFn: (width: number, histogram: Uint32Array) => ImageData;
|
||||||
const plotData = plotHistogram(histogram);
|
children?: React.ReactElement;
|
||||||
setPdf(plotData);
|
|
||||||
yield emptyImage;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
export default function FlameHistogram({quality, paintFn, children}: Props) {
|
||||||
export default function FlameHistogram() {
|
|
||||||
const {width, height, setPainter} = useContext(PainterContext);
|
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;
|
||||||
<VictoryChart theme={VictoryTheme.clean}>
|
|
||||||
<VictoryLine
|
|
||||||
data={pdfData}
|
|
||||||
interpolation='natural'
|
|
||||||
/>
|
|
||||||
</VictoryChart>
|
|
||||||
)
|
|
||||||
}
|
}
|
@ -1,34 +1,34 @@
|
|||||||
import {plot} from "./plotHistogram";
|
// hidden-start
|
||||||
import {randomBiUnit} from "../src/randomBiUnit";
|
import {randomBiUnit} from "../src/randomBiUnit";
|
||||||
import {randomChoice} from "../src/randomChoice";
|
import {randomChoice} from "../src/randomChoice";
|
||||||
import {buildTransform} from "../2-transforms/buildTransform";
|
import {ChaosGameFinalProps} from "../2-transforms/chaosGameFinal";
|
||||||
import {transformPost} from "../2-transforms/post";
|
import {camera, histIndex} from "../src/camera";
|
||||||
import {transforms} from "../2-transforms/FlameFinal";
|
// hidden-end
|
||||||
import * as params from "../src/params";
|
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 histogram = new Uint32Array(width * height);
|
||||||
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);
|
|
||||||
|
|
||||||
let [x, y] = [randomBiUnit(), randomBiUnit()];
|
let [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
const [_, transform] = randomChoice(transforms);
|
const [_, transform] = randomChoice(transforms);
|
||||||
[x, y] = transform(x, y);
|
[x, y] = transform(x, y);
|
||||||
[x, y] = finalTransformPost(x, y);
|
[x, y] = final(x, y);
|
||||||
|
|
||||||
if (i > 20)
|
if (i > 20) {
|
||||||
plot(x, y, width, histogram);
|
const [pixelX, pixelY] = camera(x, y, width);
|
||||||
|
const pixelIndex = histIndex(pixelX, pixelY, width, 1);
|
||||||
|
histogram[pixelIndex] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (i % step === 0)
|
if (i % step === 0)
|
||||||
yield histogram;
|
yield painter(width, histogram);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield histogram;
|
yield painter(width, histogram);
|
||||||
}
|
}
|
@ -19,17 +19,23 @@ Can we do something more intelligent with that information?
|
|||||||
## Image histograms
|
## Image histograms
|
||||||
|
|
||||||
To start with, it's worth demonstrating how much work is actually "wasted."
|
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'll render the reference image again, but this time, set each pixel's transparency
|
||||||
we tried to turn on a pixel.
|
based on how many times we encounter it in the chaos game:
|
||||||
|
|
||||||
import CodeBlock from "@theme/CodeBlock";
|
import CodeBlock from "@theme/CodeBlock";
|
||||||
import plotHistogramSource from "!!raw-loader!./plotHistogram";
|
|
||||||
|
|
||||||
<CodeBlock language="typescript">{plotHistogramSource}</CodeBlock>
|
import paintLinearSource from "!!raw-loader!./paintLinear"
|
||||||
|
|
||||||
|
<CodeBlock language="typescript">{paintLinearSource}</CodeBlock>
|
||||||
|
|
||||||
import Canvas from "../src/Canvas";
|
import Canvas from "../src/Canvas";
|
||||||
import FlameHistogram from "./FlameHistogram";
|
import FlameHistogram from "./FlameHistogram";
|
||||||
|
import {paintLinear} from "./paintLinear";
|
||||||
|
|
||||||
<Canvas width={400} height={400} hidden={true}>
|
<Canvas><FlameHistogram quality={5} paintFn={paintLinear}/></Canvas>
|
||||||
<FlameHistogram/>
|
|
||||||
</Canvas>
|
## Log display
|
||||||
|
|
||||||
|
import {paintLogarithmic} from './paintLogarithmic'
|
||||||
|
|
||||||
|
<Canvas><FlameHistogram quality={10} paintFn={paintLogarithmic}/></Canvas>
|
@ -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;
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
export function paintLogarithmic(width: number, histogram: Uint32Array): ImageData {
|
||||||
|
const image = new ImageData(width, histogram.length / width);
|
||||||
|
|
||||||
|
const histogramLog = new Array<number>();
|
||||||
|
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;
|
||||||
|
}
|
@ -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<number, number>();
|
|
||||||
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;
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import BrowserOnly from "@docusaurus/BrowserOnly";
|
|||||||
|
|
||||||
function invertImage(sourceImage: ImageData): ImageData {
|
function invertImage(sourceImage: ImageData): ImageData {
|
||||||
const image = new ImageData(sourceImage.width, sourceImage.height);
|
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)
|
image.data[index] = index % 4 === 3 ? value : 0xff - value)
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
@ -13,7 +13,6 @@ function invertImage(sourceImage: ImageData): ImageData {
|
|||||||
type InvertibleCanvasProps = {
|
type InvertibleCanvasProps = {
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
hidden?: boolean,
|
|
||||||
// NOTE: Images are provided as a single-element array
|
// NOTE: Images are provided as a single-element array
|
||||||
//so we can allow re-painting with the same (modified) ImageData reference.
|
//so we can allow re-painting with the same (modified) ImageData reference.
|
||||||
image?: [ImageData],
|
image?: [ImageData],
|
||||||
@ -27,7 +26,7 @@ type InvertibleCanvasProps = {
|
|||||||
* @param hidden Hide the canvas element
|
* @param hidden Hide the canvas element
|
||||||
* @param image Image data to draw on the canvas
|
* @param image Image data to draw on the canvas
|
||||||
*/
|
*/
|
||||||
const InvertibleCanvas: React.FC<InvertibleCanvasProps> = ({width, height, hidden, image}) => {
|
const InvertibleCanvas: React.FC<InvertibleCanvasProps> = ({width, height, image}) => {
|
||||||
const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D>(null);
|
const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D>(null);
|
||||||
const canvasRef = useCallback(node => {
|
const canvasRef = useCallback(node => {
|
||||||
if (node !== null) {
|
if (node !== null) {
|
||||||
@ -54,7 +53,6 @@ const InvertibleCanvas: React.FC<InvertibleCanvasProps> = ({width, height, hidde
|
|||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
hidden={hidden ?? false}
|
|
||||||
style={{
|
style={{
|
||||||
aspectRatio: width / height,
|
aspectRatio: width / height,
|
||||||
width: '75%'
|
width: '75%'
|
||||||
@ -73,7 +71,6 @@ export const PainterContext = createContext<PainterProps>(null);
|
|||||||
interface CanvasProps {
|
interface CanvasProps {
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
hidden?: boolean;
|
|
||||||
children?: React.ReactElement;
|
children?: React.ReactElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +102,7 @@ interface CanvasProps {
|
|||||||
* but we rely on contexts to manage the iterator, and I can't find
|
* but we rely on contexts to manage the iterator, and I can't find
|
||||||
* a good way to make those generic.
|
* 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 [image, setImage] = useState<[ImageData]>(null);
|
||||||
const [painterHolder, setPainterHolder] = useState<[Iterator<ImageData>]>(null);
|
const [painterHolder, setPainterHolder] = useState<[Iterator<ImageData>]>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -135,7 +132,7 @@ export default function Canvas({width, height, hidden, children}: CanvasProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<center>
|
<center>
|
||||||
<InvertibleCanvas width={width} height={height} hidden={hidden} image={image}/>
|
<InvertibleCanvas width={width} height={height} image={image}/>
|
||||||
</center>
|
</center>
|
||||||
<PainterContext.Provider value={{width, height, setPainter}}>
|
<PainterContext.Provider value={{width, height, setPainter}}>
|
||||||
<BrowserOnly>{() => children}</BrowserOnly>
|
<BrowserOnly>{() => children}</BrowserOnly>
|
||||||
|
9
blog/2024-11-15-playing-with-fire/src/applyTransform.ts
Normal file
9
blog/2024-11-15-playing-with-fire/src/applyTransform.ts
Normal file
@ -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);
|
@ -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];
|
||||||
|
}
|
@ -4,11 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Coefs } from './coefs';
|
import { Coefs } from './coefs';
|
||||||
import {VariationBlend} from "./variationBlend";
|
import {VariationBlend} from "./blend";
|
||||||
import { linear } from './linear'
|
import { linear } from './linear'
|
||||||
import { julia } from './julia'
|
import { julia } from './julia'
|
||||||
import { popcorn } from './popcorn'
|
import { popcorn } from './popcorn'
|
||||||
import {pdj, PdjParams} from './pdj'
|
import {pdj, PdjParams} from './pdj'
|
||||||
|
import {Transform} from "./transform";
|
||||||
|
import {applyPost, applyTransform} from "./applyTransform";
|
||||||
|
|
||||||
export const identityCoefs: Coefs = {
|
export const identityCoefs: Coefs = {
|
||||||
a: 1, b: 0, c: 0,
|
a: 1, b: 0, c: 0,
|
||||||
@ -66,6 +68,14 @@ export const xformFinalVariations: VariationBlend = [
|
|||||||
]
|
]
|
||||||
export const xformFinalColor = 0;
|
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 =
|
export const palette =
|
||||||
"7E3037762C45722B496E2A4E6A2950672853652754632656" +
|
"7E3037762C45722B496E2A4E6A2950672853652754632656" +
|
||||||
"5C265C5724595322574D2155482153462050451F4E441E4D" +
|
"5C265C5724595322574D2155482153462050451F4E441E4D" +
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
import {Variation} from "./variation";
|
|
||||||
export type VariationBlend = [number, Variation][];
|
|
Loading…
Reference in New Issue
Block a user