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 {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) => (
|
||||
|
@ -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++) {
|
||||
|
@ -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 (
|
||||
<>
|
||||
|
@ -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<Coefs>(xformFinalCoefsDefault);
|
||||
const resetXformFinalCoefs = () => setXformFinalCoefs(xformFinalCoefsDefault);
|
||||
const [xformFinalCoefs, setXformFinalCoefs] = useState<Coefs>(params.xformFinalCoefs);
|
||||
const resetXformFinalCoefs = () => setXformFinalCoefs(params.xformFinalCoefs);
|
||||
|
||||
const xformFinalVariationsDefault: VariationProps = {
|
||||
linear: 0,
|
||||
@ -45,15 +23,14 @@ export default function FlameFinal() {
|
||||
const [xformFinalVariations, setXformFinalVariations] = useState<VariationProps>(xformFinalVariationsDefault);
|
||||
const resetXformFinalVariations = () => setXformFinalVariations(xformFinalVariationsDefault);
|
||||
|
||||
const [xformFinalCoefsPost, setXformFinalCoefsPost] = useState<Coefs>(xformFinalCoefsPostDefault);
|
||||
const resetXformFinalCoefsPost = () => setXformFinalCoefsPost(xformFinalCoefsPostDefault);
|
||||
const [xformFinalCoefsPost, setXformFinalCoefsPost] = useState<Coefs>(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 (
|
||||
|
@ -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<Coefs>(xform1CoefsPostDefault);
|
||||
const resetXform1CoefsPost = () => setXform1CoefsPost(xform1CoefsPostDefault);
|
||||
const [xform1CoefsPost, setXform1CoefsPost] = useState<Coefs>(params.xform1CoefsPost);
|
||||
const resetXform1CoefsPost = () => setXform1CoefsPost(params.xform1CoefsPost);
|
||||
|
||||
const [xform2CoefsPost, setXform2CoefsPost] = useState<Coefs>(xform2CoefsPostDefault);
|
||||
const resetXform2CoefsPost = () => setXform2CoefsPost(xform2CoefsPostDefault);
|
||||
const [xform2CoefsPost, setXform2CoefsPost] = useState<Coefs>(params.xform2CoefsPost);
|
||||
const resetXform2CoefsPost = () => setXform2CoefsPost(params.xform2CoefsPost);
|
||||
|
||||
const [xform3CoefsPost, setXform3CoefsPost] = useState<Coefs>(xform3CoefsPostDefault);
|
||||
const resetXform3CoefsPost = () => setXform1CoefsPost(xform3CoefsPostDefault);
|
||||
const [xform3CoefsPost, setXform3CoefsPost] = useState<Coefs>(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 (
|
||||
<>
|
||||
|
@ -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 {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 [
|
||||
@ -17,10 +15,3 @@ export function buildBlend(coefs: Coefs, variations: VariationProps): VariationB
|
||||
[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 { 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);
|
||||
|
@ -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";
|
||||
|
||||
<CodeBlock language={'typescript'}>{blendSource}</CodeBlock>
|
||||
|
||||
|
@ -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 (
|
||||
<VictoryChart theme={VictoryTheme.clean}>
|
||||
<VictoryLine
|
||||
data={pdfData}
|
||||
interpolation='natural'
|
||||
/>
|
||||
</VictoryChart>
|
||||
)
|
||||
return children;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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";
|
||||
|
||||
<CodeBlock language="typescript">{plotHistogramSource}</CodeBlock>
|
||||
import paintLinearSource from "!!raw-loader!./paintLinear"
|
||||
|
||||
<CodeBlock language="typescript">{paintLinearSource}</CodeBlock>
|
||||
|
||||
import Canvas from "../src/Canvas";
|
||||
import FlameHistogram from "./FlameHistogram";
|
||||
import {paintLinear} from "./paintLinear";
|
||||
|
||||
<Canvas width={400} height={400} hidden={true}>
|
||||
<FlameHistogram/>
|
||||
</Canvas>
|
||||
<Canvas><FlameHistogram quality={5} paintFn={paintLinear}/></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 {
|
||||
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<InvertibleCanvasProps> = ({width, height, hidden, image}) => {
|
||||
const InvertibleCanvas: React.FC<InvertibleCanvasProps> = ({width, height, image}) => {
|
||||
const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D>(null);
|
||||
const canvasRef = useCallback(node => {
|
||||
if (node !== null) {
|
||||
@ -54,7 +53,6 @@ const InvertibleCanvas: React.FC<InvertibleCanvasProps> = ({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<PainterProps>(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<ImageData>]>(null);
|
||||
useEffect(() => {
|
||||
@ -135,7 +132,7 @@ export default function Canvas({width, height, hidden, children}: CanvasProps) {
|
||||
return (
|
||||
<>
|
||||
<center>
|
||||
<InvertibleCanvas width={width} height={height} hidden={hidden} image={image}/>
|
||||
<InvertibleCanvas width={width} height={height} image={image}/>
|
||||
</center>
|
||||
<PainterContext.Provider value={{width, height, setPainter}}>
|
||||
<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 {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" +
|
||||
|
@ -1,2 +0,0 @@
|
||||
import {Variation} from "./variation";
|
||||
export type VariationBlend = [number, Variation][];
|
Loading…
Reference in New Issue
Block a user