Log density visualization

This commit is contained in:
Bradlee Speice 2024-12-01 21:57:10 -05:00
parent 79b66337e8
commit 2e8a6d1ce7
20 changed files with 206 additions and 194 deletions

View File

@ -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) => (

View File

@ -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++) {

View File

@ -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 (
<> <>

View File

@ -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 (

View File

@ -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 (
<> <>

View File

@ -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];
}

View File

@ -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 [
@ -17,10 +15,3 @@ export function buildBlend(coefs: Coefs, variations: VariationProps): VariationB
[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);
}
}

View File

@ -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);

View File

@ -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>

View File

@ -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>
)
} }

View File

@ -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);
} }

View File

@ -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>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>

View 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);

View File

@ -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];
}

View File

@ -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" +

View File

@ -1,2 +0,0 @@
import {Variation} from "./variation";
export type VariationBlend = [number, Variation][];