mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Mass formatting, fix mobile display, fix issues with image wrap-around
This commit is contained in:
parent
1ce6137c17
commit
456c3a66e5
4
blog/2024-11-15-playing-with-fire/.prettierrc
Normal file
4
blog/2024-11-15-playing-with-fire/.prettierrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true
|
||||||
|
}
|
@ -1,21 +1,21 @@
|
|||||||
import {SquareCanvas, PainterContext} from "../src/Canvas";
|
import { PainterContext, SquareCanvas } from "../src/Canvas";
|
||||||
import {useContext, useEffect} from "react";
|
import { useContext, useEffect } from "react";
|
||||||
|
|
||||||
export function Render({f}) {
|
export function Render({ f }) {
|
||||||
const {width, height, setPainter} = useContext(PainterContext);
|
const { width, height, setPainter } = useContext(PainterContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (width && height) {
|
if (width && height) {
|
||||||
const painter = f({width, height});
|
const painter = f({ width, height });
|
||||||
setPainter(painter);
|
setPainter(painter);
|
||||||
}
|
}
|
||||||
}, [width, height]);
|
}, [width, height]);
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Gasket({f}) {
|
export default function Gasket({ f }) {
|
||||||
return (
|
return (
|
||||||
<SquareCanvas name={"gasket"}>
|
<SquareCanvas name={"gasket"}>
|
||||||
<Render f={f}/>
|
<Render f={f} />
|
||||||
</SquareCanvas>
|
</SquareCanvas>
|
||||||
)
|
);
|
||||||
}
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import {useEffect, useState, useContext, useRef} from "react";
|
import { useContext, useEffect, useState } 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";
|
||||||
|
|
||||||
type Transform = (x: number, y: number) => [number, number];
|
type Transform = (x: number, y: number) => [number, number];
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ 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 {width, height, setPainter} = useContext(PainterContext);
|
const { width, height, setPainter } = useContext(PainterContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const transforms: [number, Transform][] = [
|
const transforms: [number, Transform][] = [
|
||||||
@ -24,26 +24,26 @@ export default function GasketWeighted() {
|
|||||||
[f1Weight, f1],
|
[f1Weight, f1],
|
||||||
[f2Weight, f2]
|
[f2Weight, f2]
|
||||||
];
|
];
|
||||||
setPainter(chaosGameWeighted({width, height, transforms}));
|
setPainter(chaosGameWeighted({ width, height, transforms }));
|
||||||
}, [f0Weight, f1Weight, f2Weight]);
|
}, [f0Weight, f1Weight, f2Weight]);
|
||||||
|
|
||||||
const weightInput = (title, weight, setWeight) => (
|
const weightInput = (title, weight, setWeight) => (
|
||||||
<>
|
<>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p><TeX>{title}</TeX>: {weight}</p>
|
<p><TeX>{title}</TeX>: {weight}</p>
|
||||||
<input type={'range'} min={0} max={1} step={.01} value={weight}
|
<input type={"range"} min={0} max={1} step={.01} value={weight}
|
||||||
onInput={e => setWeight(Number(e.currentTarget.value))}/>
|
onInput={e => setWeight(Number(e.currentTarget.value))} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr'}}>
|
<div className={styles.inputGroup} style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr" }}>
|
||||||
{weightInput("F_0", f0Weight, setF0Weight)}
|
{weightInput("F_0", f0Weight, setF0Weight)}
|
||||||
{weightInput("F_1", f1Weight, setF1Weight)}
|
{weightInput("F_1", f1Weight, setF1Weight)}
|
||||||
{weightInput("F_2", f2Weight, setF2Weight)}
|
{weightInput("F_2", f2Weight, setF2Weight)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
export function camera(
|
export function camera(
|
||||||
size: number,
|
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number,
|
||||||
|
size: number
|
||||||
): [number, number] {
|
): [number, number] {
|
||||||
return [
|
return [
|
||||||
Math.floor(x * size),
|
Math.floor(x * size),
|
||||||
|
@ -6,28 +6,29 @@ const xforms = [
|
|||||||
(x, y) => [x / 2, y / 2],
|
(x, y) => [x / 2, y / 2],
|
||||||
(x, y) => [(x + 1) / 2, y / 2],
|
(x, y) => [(x + 1) / 2, y / 2],
|
||||||
(x, y) => [x / 2, (y + 1) / 2]
|
(x, y) => [x / 2, (y + 1) / 2]
|
||||||
]
|
];
|
||||||
|
|
||||||
function* chaosGame({width, height}) {
|
function* chaosGame({ width, height }) {
|
||||||
const step = 1000;
|
let img =
|
||||||
let img = new ImageData(width, height);
|
new ImageData(width, height);
|
||||||
let [x, y] = [
|
let [x, y] = [
|
||||||
randomBiUnit(),
|
randomBiUnit(),
|
||||||
randomBiUnit()
|
randomBiUnit()
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let c = 0; c < iterations; c++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
const i = randomInteger(0, xforms.length);
|
const index =
|
||||||
[x, y] = xforms[i](x, y);
|
randomInteger(0, xforms.length);
|
||||||
|
[x, y] = xforms[index](x, y);
|
||||||
|
|
||||||
if (c > 20)
|
if (i > 20)
|
||||||
plot(x, y, img);
|
plot(x, y, img);
|
||||||
|
|
||||||
if (c % step === 0)
|
if (i % 1000 === 0)
|
||||||
yield img;
|
yield img;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield img;
|
yield img;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(<Gasket f={chaosGame}/>)
|
render(<Gasket f={chaosGame} />);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import { randomBiUnit } from "../src/randomBiUnit";
|
import { randomBiUnit } from "../src/randomBiUnit";
|
||||||
import { randomChoice } from "../src/randomChoice";
|
import { randomChoice } from "../src/randomChoice";
|
||||||
import { plot } from "./plot"
|
import { plot } from "./plot";
|
||||||
import {Transform} from "../src/transform";
|
import { Transform } from "../src/transform";
|
||||||
|
|
||||||
const quality = 0.5;
|
const quality = 0.5;
|
||||||
const step = 1000;
|
const step = 1000;
|
||||||
@ -12,26 +12,30 @@ export type Props = {
|
|||||||
height: number,
|
height: number,
|
||||||
transforms: [number, Transform][]
|
transforms: [number, Transform][]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* chaosGameWeighted(
|
export function* chaosGameWeighted(
|
||||||
{width, height, transforms}: Props
|
{ width, height, transforms }: Props
|
||||||
) {
|
) {
|
||||||
let img = new ImageData(width, height);
|
let img =
|
||||||
|
new ImageData(width, height);
|
||||||
let [x, y] = [
|
let [x, y] = [
|
||||||
randomBiUnit(),
|
randomBiUnit(),
|
||||||
randomBiUnit()
|
randomBiUnit()
|
||||||
];
|
];
|
||||||
|
|
||||||
const iterations = width * height * quality;
|
const pixels = width * height;
|
||||||
for (let c = 0; c < iterations; c++) {
|
const iterations = quality * pixels;
|
||||||
|
for (let i = 0; i < iterations; i++) {
|
||||||
// highlight-start
|
// highlight-start
|
||||||
const [_, xform] = randomChoice(transforms);
|
const [_, xform] =
|
||||||
|
randomChoice(transforms);
|
||||||
// highlight-end
|
// highlight-end
|
||||||
[x, y] = xform(x, y);
|
[x, y] = xform(x, y);
|
||||||
|
|
||||||
if (c > 20)
|
if (i > 20)
|
||||||
plot(x, y, img);
|
plot(x, y, img);
|
||||||
|
|
||||||
if (c % step === 0)
|
if (i % step === 0)
|
||||||
yield img;
|
yield img;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ called an "[affine transformation](https://en.wikipedia.org/wiki/Affine_transfor
|
|||||||
The general form of an affine transformation is:
|
The general form of an affine transformation is:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_i(a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i)
|
F_i(a_i x + b_i y + c_i, d_i x + e_i y + f_i)
|
||||||
$$
|
$$
|
||||||
|
|
||||||
import transformSource from "!!raw-loader!../src/transform"
|
import transformSource from "!!raw-loader!../src/transform"
|
||||||
@ -102,8 +102,7 @@ c &= 0.5 \\
|
|||||||
d &= 0 \\
|
d &= 0 \\
|
||||||
e &= 1 \\
|
e &= 1 \\
|
||||||
f &= 1.5 \\
|
f &= 1.5 \\
|
||||||
F_{shift}(x,y) &= (1 \cdot x + 0 \cdot y + 0.5, 0 \cdot x + 1 \cdot y + 0.5) \\
|
F_{shift}(x, y) &= (1 \cdot x + 0.5, 1 \cdot y + 1.5)
|
||||||
F_{shift}(x, y) &= (x + 0.5, y + 1.5)
|
|
||||||
\end{align*}
|
\end{align*}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
@ -155,7 +154,7 @@ explaining the mathematics of iterated function systems:
|
|||||||
> of finite compositions $F_{i_1...i_p}$ of members of $F$.
|
> of finite compositions $F_{i_1...i_p}$ of members of $F$.
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
I've tweaked the wording slightly to match the conventions of the Fractal Flame paper
|
I've tweaked the conventions of that paper slightly to match the Fractal Flame paper
|
||||||
:::
|
:::
|
||||||
|
|
||||||
Before your eyes glaze over, let's unpack this:
|
Before your eyes glaze over, let's unpack this:
|
||||||
@ -174,15 +173,15 @@ Thus, by applying the functions to fixed points of our system, we will find the
|
|||||||
...then there are some extra details I've glossed over so far.
|
...then there are some extra details I've glossed over so far.
|
||||||
|
|
||||||
First, the Hutchinson paper requires that the functions $F_i$ be _contractive_ for the solution set to exist.
|
First, the Hutchinson paper requires that the functions $F_i$ be _contractive_ for the solution set to exist.
|
||||||
That is, applying the function to a point must bring it closer to other points. However, as the Fractal Flame
|
That is, applying the function to a point must bring it closer to other points. However, as the fractal flame
|
||||||
algorithm demonstrates, we only need functions to be contractive _on average_. At worst, the system will
|
algorithm demonstrates, we only need functions to be contractive _on average_. At worst, the system will
|
||||||
degenerate and produce a bad image.
|
degenerate and produce a bad image.
|
||||||
|
|
||||||
Second, we're focused $\mathbb{R}^2$ because we're generating images, but the Hutchinson paper
|
Second, we're focused on $\mathbb{R}^2$ because we're generating images, but the Hutchinson paper
|
||||||
allows for arbitrary dimensions; you could also have 3-dimensional fractal flames.
|
allows for arbitrary dimensions; you could also have 3-dimensional fractal flames.
|
||||||
|
|
||||||
Finally, there's a close relationship between fractal flames and [attractors](https://en.wikipedia.org/wiki/Attractor).
|
Finally, there's a close relationship between fractal flames and [attractors](https://en.wikipedia.org/wiki/Attractor).
|
||||||
Specifically, the fixed points of $S$ act as attractors for the chaos game (explained in detail below).
|
Specifically, the fixed points of $S$ act as attractors for the chaos game (explained in more detail below).
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
This is still a bit vague, so let's work through an example.
|
This is still a bit vague, so let's work through an example.
|
||||||
@ -272,8 +271,8 @@ import chaosGameSource from '!!raw-loader!./chaosGame'
|
|||||||
|
|
||||||
<small>
|
<small>
|
||||||
The image here is slightly different than in the paper.
|
The image here is slightly different than in the paper.
|
||||||
I think the paper has an error, so I'm choosing to plot the image
|
I think the paper has an error, so I'm plotting the image
|
||||||
the same way as the [reference implementation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441).
|
like the [reference implementation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L440-L441).
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
### Weights
|
### Weights
|
||||||
@ -292,7 +291,10 @@ import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted";
|
|||||||
|
|
||||||
For Sierpinski's Gasket, we start with equal weighting,
|
For Sierpinski's Gasket, we start with equal weighting,
|
||||||
but changing the transform weights affects how often
|
but changing the transform weights affects how often
|
||||||
the chaos game "visits" parts of the image:
|
the chaos game "visits" parts of the image. If the chaos
|
||||||
|
were to run forever, we'd get the same image;
|
||||||
|
but because the iteration count is limited,
|
||||||
|
some parts may be missing below.
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
Double-click the image if you want to save a copy!
|
Double-click the image if you want to save a copy!
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import {camera} from "./cameraGasket"
|
import { camera } from "./cameraGasket";
|
||||||
|
|
||||||
// hidden-end
|
// hidden-end
|
||||||
function imageIndex(
|
function imageIndex(
|
||||||
width: number,
|
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number,
|
||||||
|
width: number
|
||||||
) {
|
) {
|
||||||
return y * (width * 4) + x * 4;
|
return y * (width * 4) + x * 4;
|
||||||
}
|
}
|
||||||
@ -14,24 +15,26 @@ export function plot(
|
|||||||
y: number,
|
y: number,
|
||||||
img: ImageData
|
img: ImageData
|
||||||
) {
|
) {
|
||||||
let [pixelX, pixelY] = camera(img.width, x, y);
|
let [pixelX, pixelY] =
|
||||||
|
camera(x, y, img.width);
|
||||||
|
|
||||||
|
// Skip coordinates outside the display
|
||||||
|
if (
|
||||||
|
pixelX < 0 ||
|
||||||
|
pixelX >= img.width ||
|
||||||
|
pixelY < 0 ||
|
||||||
|
pixelY >= img.height
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
const i = imageIndex(
|
const i = imageIndex(
|
||||||
img.width,
|
|
||||||
pixelX,
|
pixelX,
|
||||||
pixelY
|
pixelY,
|
||||||
|
img.width
|
||||||
);
|
);
|
||||||
|
|
||||||
// Skip pixels outside the display range
|
// Set the pixel to black by setting
|
||||||
if (
|
// the first three elements to 0
|
||||||
i < 0 ||
|
|
||||||
i > img.data.length
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the pixel to black by writing 0
|
|
||||||
// to the first three elements at the index
|
|
||||||
// (red, green, and blue, respectively),
|
// (red, green, and blue, respectively),
|
||||||
// and 255 to the last element (alpha)
|
// and 255 to the last element (alpha)
|
||||||
img.data[i] = 0;
|
img.data[i] = 0;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import Gasket from "./Gasket";
|
import Gasket from "./Gasket";
|
||||||
import { plot } from './plot';
|
import { plot } from "./plot";
|
||||||
import { randomBiUnit } from '../src/randomBiUnit';
|
import { randomBiUnit } from "../src/randomBiUnit";
|
||||||
import { randomInteger } from '../src/randomInteger';
|
import { randomInteger } from "../src/randomInteger";
|
||||||
|
|
||||||
const Scope = {
|
const Scope = {
|
||||||
Gasket,
|
Gasket,
|
||||||
plot,
|
plot,
|
||||||
randomBiUnit,
|
randomBiUnit,
|
||||||
randomInteger,
|
randomInteger
|
||||||
}
|
};
|
||||||
export default Scope;
|
export default Scope;
|
@ -1,5 +1,5 @@
|
|||||||
import TeX from "@matejmazur/react-katex";
|
import TeX from "@matejmazur/react-katex";
|
||||||
import {Coefs} from "../src/coefs";
|
import { Coefs } from "../src/transform";
|
||||||
|
|
||||||
import styles from "../src/css/styles.module.css";
|
import styles from "../src/css/styles.module.css";
|
||||||
|
|
||||||
@ -10,42 +10,43 @@ export interface Props {
|
|||||||
setCoefs: (coefs: Coefs) => void;
|
setCoefs: (coefs: Coefs) => void;
|
||||||
resetCoefs: () => void;
|
resetCoefs: () => void;
|
||||||
}
|
}
|
||||||
export const CoefEditor = ({title, isPost, coefs, setCoefs, resetCoefs}: Props) => {
|
|
||||||
const resetButton = <button className={styles.inputReset} onClick={resetCoefs}>Reset</button>
|
export const CoefEditor = ({ title, isPost, coefs, setCoefs, resetCoefs }: Props) => {
|
||||||
|
const resetButton = <button className={styles.inputReset} onClick={resetCoefs}>Reset</button>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr'}}>
|
<div className={styles.inputGroup} style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr" }}>
|
||||||
<p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>{title} {resetButton}</p>
|
<p className={styles.inputTitle} style={{ gridColumn: "1/-1" }}>{title} {resetButton}</p>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p>{isPost ? <TeX>\alpha</TeX> : 'a'}: {coefs.a}</p>
|
<p>{isPost ? <TeX>\alpha</TeX> : "a"}: {coefs.a}</p>
|
||||||
<input type={'range'} min={-2} max={2} step={0.01} value={coefs.a}
|
<input type={"range"} min={-2} max={2} step={0.01} value={coefs.a}
|
||||||
onInput={e => setCoefs({...coefs, a: Number(e.currentTarget.value)})}/>
|
onInput={e => setCoefs({ ...coefs, a: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p>{isPost ? <TeX>\beta</TeX> : 'b'}: {coefs.b}</p>
|
<p>{isPost ? <TeX>\beta</TeX> : "b"}: {coefs.b}</p>
|
||||||
<input type={'range'} min={-2} max={2} step={0.01} value={coefs.b}
|
<input type={"range"} min={-2} max={2} step={0.01} value={coefs.b}
|
||||||
onInput={e => setCoefs({...coefs, b: Number(e.currentTarget.value)})}/>
|
onInput={e => setCoefs({ ...coefs, b: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p>{isPost ? <TeX>\gamma</TeX> : 'c'}: {coefs.c}</p>
|
<p>{isPost ? <TeX>\gamma</TeX> : "c"}: {coefs.c}</p>
|
||||||
<input type={'range'} min={-2} max={2} step={0.01} value={coefs.c}
|
<input type={"range"} min={-2} max={2} step={0.01} value={coefs.c}
|
||||||
onInput={e => setCoefs({...coefs, c: Number(e.currentTarget.value)})}/>
|
onInput={e => setCoefs({ ...coefs, c: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p>{isPost ? <TeX>\delta</TeX> : 'd'}: {coefs.d}</p>
|
<p>{isPost ? <TeX>\delta</TeX> : "d"}: {coefs.d}</p>
|
||||||
<input type={'range'} min={-2} max={2} step={0.01} value={coefs.d}
|
<input type={"range"} min={-2} max={2} step={0.01} value={coefs.d}
|
||||||
onInput={e => setCoefs({...coefs, d: Number(e.currentTarget.value)})}/>
|
onInput={e => setCoefs({ ...coefs, d: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p>{isPost ? <TeX>\epsilon</TeX> : 'e'}: {coefs.e}</p>
|
<p>{isPost ? <TeX>\epsilon</TeX> : "e"}: {coefs.e}</p>
|
||||||
<input type={'range'} min={-2} max={2} step={0.01} value={coefs.e}
|
<input type={"range"} min={-2} max={2} step={0.01} value={coefs.e}
|
||||||
onInput={e => setCoefs({...coefs, e: Number(e.currentTarget.value)})}/>
|
onInput={e => setCoefs({ ...coefs, e: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p>{isPost ? <TeX>\zeta</TeX> : 'f'}: {coefs.f}</p>
|
<p>{isPost ? <TeX>\zeta</TeX> : "f"}: {coefs.f}</p>
|
||||||
<input type={'range'} min={-2} max={2} step={0.01} value={coefs.f}
|
<input type={"range"} min={-2} max={2} step={0.01} value={coefs.f}
|
||||||
onInput={e => setCoefs({...coefs, f: Number(e.currentTarget.value)})}/>
|
onInput={e => setCoefs({ ...coefs, f: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
@ -1,22 +1,22 @@
|
|||||||
import {useContext, useEffect, useState} from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import {Transform} from "../src/transform";
|
import { Transform } from "../src/transform";
|
||||||
import * as params from "../src/params"
|
import * as params from "../src/params";
|
||||||
import {PainterContext} from "../src/Canvas"
|
import { PainterContext } from "../src/Canvas";
|
||||||
import {chaosGameFinal} from "./chaosGameFinal"
|
import { chaosGameFinal } from "./chaosGameFinal";
|
||||||
import {VariationEditor, VariationProps} from "./VariationEditor"
|
import { VariationEditor, VariationProps } from "./VariationEditor";
|
||||||
import {applyTransform} from "../src/applyTransform";
|
import { applyTransform } from "../src/applyTransform";
|
||||||
import {buildBlend} from "./buildBlend";
|
import { buildBlend } from "./buildBlend";
|
||||||
|
|
||||||
export default function FlameBlend() {
|
export default function FlameBlend() {
|
||||||
const {width, height, setPainter} = useContext(PainterContext);
|
const { width, height, setPainter } = useContext(PainterContext);
|
||||||
|
|
||||||
const xform1VariationsDefault: VariationProps = {
|
const xform1VariationsDefault: VariationProps = {
|
||||||
linear: 0,
|
linear: 0,
|
||||||
julia: 1,
|
julia: 1,
|
||||||
popcorn: 0,
|
popcorn: 0,
|
||||||
pdj: 0,
|
pdj: 0
|
||||||
}
|
};
|
||||||
const [xform1Variations, setXform1Variations] = useState(xform1VariationsDefault)
|
const [xform1Variations, setXform1Variations] = useState(xform1VariationsDefault);
|
||||||
const resetXform1Variations = () => setXform1Variations(xform1VariationsDefault);
|
const resetXform1Variations = () => setXform1Variations(xform1VariationsDefault);
|
||||||
|
|
||||||
const xform2VariationsDefault: VariationProps = {
|
const xform2VariationsDefault: VariationProps = {
|
||||||
@ -24,8 +24,8 @@ export default function FlameBlend() {
|
|||||||
julia: 0,
|
julia: 0,
|
||||||
popcorn: 1,
|
popcorn: 1,
|
||||||
pdj: 0
|
pdj: 0
|
||||||
}
|
};
|
||||||
const [xform2Variations, setXform2Variations] = useState(xform2VariationsDefault)
|
const [xform2Variations, setXform2Variations] = useState(xform2VariationsDefault);
|
||||||
const resetXform2Variations = () => setXform2Variations(xform2VariationsDefault);
|
const resetXform2Variations = () => setXform2Variations(xform2VariationsDefault);
|
||||||
|
|
||||||
const xform3VariationsDefault: VariationProps = {
|
const xform3VariationsDefault: VariationProps = {
|
||||||
@ -33,8 +33,8 @@ export default function FlameBlend() {
|
|||||||
julia: 0,
|
julia: 0,
|
||||||
popcorn: 0,
|
popcorn: 0,
|
||||||
pdj: 1
|
pdj: 1
|
||||||
}
|
};
|
||||||
const [xform3Variations, setXform3Variations] = useState(xform3VariationsDefault)
|
const [xform3Variations, setXform3Variations] = useState(xform3VariationsDefault);
|
||||||
const resetXform3Variations = () => setXform3Variations(xform3VariationsDefault);
|
const resetXform3Variations = () => setXform3Variations(xform3VariationsDefault);
|
||||||
|
|
||||||
const identityXform: Transform = (x, y) => [x, y];
|
const identityXform: Transform = (x, y) => [x, y];
|
||||||
@ -44,25 +44,25 @@ export default function FlameBlend() {
|
|||||||
[params.xform1Weight, applyTransform(params.xform1Coefs, buildBlend(params.xform1Coefs, xform1Variations))],
|
[params.xform1Weight, applyTransform(params.xform1Coefs, buildBlend(params.xform1Coefs, xform1Variations))],
|
||||||
[params.xform2Weight, applyTransform(params.xform2Coefs, buildBlend(params.xform2Coefs, xform2Variations))],
|
[params.xform2Weight, applyTransform(params.xform2Coefs, buildBlend(params.xform2Coefs, xform2Variations))],
|
||||||
[params.xform3Weight, applyTransform(params.xform3Coefs, buildBlend(params.xform3Coefs, xform3Variations))]
|
[params.xform3Weight, applyTransform(params.xform3Coefs, buildBlend(params.xform3Coefs, xform3Variations))]
|
||||||
]
|
];
|
||||||
|
|
||||||
const gameParams = {
|
const gameParams = {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
transforms,
|
transforms,
|
||||||
final: identityXform
|
final: identityXform
|
||||||
}
|
};
|
||||||
setPainter(chaosGameFinal(gameParams));
|
setPainter(chaosGameFinal(gameParams));
|
||||||
}, [xform1Variations, xform2Variations, xform3Variations]);
|
}, [xform1Variations, xform2Variations, xform3Variations]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<VariationEditor title={"Transform 1"} variations={xform1Variations} setVariations={setXform1Variations}
|
<VariationEditor title={"Transform 1"} variations={xform1Variations} setVariations={setXform1Variations}
|
||||||
resetVariations={resetXform1Variations}/>
|
resetVariations={resetXform1Variations} />
|
||||||
<VariationEditor title={"Transform 2"} variations={xform2Variations} setVariations={setXform2Variations}
|
<VariationEditor title={"Transform 2"} variations={xform2Variations} setVariations={setXform2Variations}
|
||||||
resetVariations={resetXform2Variations}/>
|
resetVariations={resetXform2Variations} />
|
||||||
<VariationEditor title={"Transform 3"} variations={xform3Variations} setVariations={setXform3Variations}
|
<VariationEditor title={"Transform 3"} variations={xform3Variations} setVariations={setXform3Variations}
|
||||||
resetVariations={resetXform3Variations}/>
|
resetVariations={resetXform3Variations} />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
@ -1,15 +1,15 @@
|
|||||||
import {useContext, useEffect, useState} from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import {Coefs} from "../src/coefs"
|
import { Coefs } from "../src/transform";
|
||||||
import * as params from "../src/params";
|
import * as params from "../src/params";
|
||||||
import {PainterContext} from "../src/Canvas"
|
import { PainterContext } from "../src/Canvas";
|
||||||
import {buildBlend} from "./buildBlend";
|
import { buildBlend } from "./buildBlend";
|
||||||
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 {applyPost, applyTransform} from "../src/applyTransform";
|
import { applyPost, applyTransform } from "../src/applyTransform";
|
||||||
|
|
||||||
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>(params.xformFinalCoefs);
|
const [xformFinalCoefs, setXformFinalCoefs] = useState<Coefs>(params.xformFinalCoefs);
|
||||||
const resetXformFinalCoefs = () => setXformFinalCoefs(params.xformFinalCoefs);
|
const resetXformFinalCoefs = () => setXformFinalCoefs(params.xformFinalCoefs);
|
||||||
@ -19,7 +19,7 @@ export default function FlameFinal() {
|
|||||||
julia: 1,
|
julia: 1,
|
||||||
popcorn: 0,
|
popcorn: 0,
|
||||||
pdj: 0
|
pdj: 0
|
||||||
}
|
};
|
||||||
const [xformFinalVariations, setXformFinalVariations] = useState<VariationProps>(xformFinalVariationsDefault);
|
const [xformFinalVariations, setXformFinalVariations] = useState<VariationProps>(xformFinalVariationsDefault);
|
||||||
const resetXformFinalVariations = () => setXformFinalVariations(xformFinalVariationsDefault);
|
const resetXformFinalVariations = () => setXformFinalVariations(xformFinalVariationsDefault);
|
||||||
|
|
||||||
@ -30,17 +30,17 @@ export default function FlameFinal() {
|
|||||||
const finalBlend = buildBlend(xformFinalCoefs, xformFinalVariations);
|
const finalBlend = buildBlend(xformFinalCoefs, xformFinalVariations);
|
||||||
const finalXform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, finalBlend));
|
const finalXform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, finalBlend));
|
||||||
|
|
||||||
setPainter(chaosGameFinal({width, height, transforms: params.xforms, final: finalXform}));
|
setPainter(chaosGameFinal({ width, height, transforms: params.xforms, final: finalXform }));
|
||||||
}, [xformFinalCoefs, xformFinalVariations, xformFinalCoefsPost]);
|
}, [xformFinalCoefs, xformFinalVariations, xformFinalCoefsPost]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CoefEditor title={"Final Transform"} isPost={false} coefs={xformFinalCoefs} setCoefs={setXformFinalCoefs}
|
<CoefEditor title={"Final Transform"} isPost={false} coefs={xformFinalCoefs} setCoefs={setXformFinalCoefs}
|
||||||
resetCoefs={resetXformFinalCoefs}/>
|
resetCoefs={resetXformFinalCoefs} />
|
||||||
<VariationEditor title={"Final Transform Variations"} variations={xformFinalVariations}
|
<VariationEditor title={"Final Transform Variations"} variations={xformFinalVariations}
|
||||||
setVariations={setXformFinalVariations} resetVariations={resetXformFinalVariations}/>
|
setVariations={setXformFinalVariations} resetVariations={resetXformFinalVariations} />
|
||||||
<CoefEditor title={"Final Transform Post"} isPost={true} coefs={xformFinalCoefsPost}
|
<CoefEditor title={"Final Transform Post"} isPost={true} coefs={xformFinalCoefsPost}
|
||||||
setCoefs={setXformFinalCoefsPost} resetCoefs={resetXformFinalCoefsPost}/>
|
setCoefs={setXformFinalCoefsPost} resetCoefs={resetXformFinalCoefsPost} />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
@ -1,14 +1,14 @@
|
|||||||
import {useContext, useEffect, useState} from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import {Coefs} from "../src/coefs"
|
import { applyTransform } from "../src/applyTransform";
|
||||||
import {Transform} from "../src/transform";
|
import { Coefs, Transform } from "../src/transform";
|
||||||
import * as params from "../src/params";
|
import * as params from "../src/params";
|
||||||
import {PainterContext} from "../src/Canvas"
|
import { PainterContext } from "../src/Canvas";
|
||||||
import {chaosGameFinal, Props as ChaosGameFinalProps} from "./chaosGameFinal"
|
import { chaosGameFinal, Props as ChaosGameFinalProps } from "./chaosGameFinal";
|
||||||
import {CoefEditor} from "./CoefEditor"
|
import { CoefEditor } from "./CoefEditor";
|
||||||
import {applyPost, applyTransform} from "@site/blog/2024-11-15-playing-with-fire/src/applyTransform";
|
import { transformPost } from "./post";
|
||||||
|
|
||||||
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>(params.xform1CoefsPost);
|
const [xform1CoefsPost, setXform1CoefsPost] = useState<Coefs>(params.xform1CoefsPost);
|
||||||
const resetXform1CoefsPost = () => setXform1CoefsPost(params.xform1CoefsPost);
|
const resetXform1CoefsPost = () => setXform1CoefsPost(params.xform1CoefsPost);
|
||||||
@ -25,22 +25,22 @@ export default function FlamePost() {
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
transforms: [
|
transforms: [
|
||||||
[params.xform1Weight, applyPost(xform1CoefsPost, applyTransform(params.xform1Coefs, params.xform1Variations))],
|
[params.xform1Weight, transformPost(applyTransform(params.xform1Coefs, params.xform1Variations), xform1CoefsPost)],
|
||||||
[params.xform2Weight, applyPost(xform2CoefsPost, applyTransform(params.xform2Coefs, params.xform2Variations))],
|
[params.xform2Weight, transformPost(applyTransform(params.xform2Coefs, params.xform2Variations), xform2CoefsPost)],
|
||||||
[params.xform3Weight, applyPost(xform3CoefsPost, applyTransform(params.xform3Coefs, params.xform3Variations))],
|
[params.xform3Weight, transformPost(applyTransform(params.xform3Coefs, params.xform3Variations), xform3CoefsPost)]
|
||||||
],
|
],
|
||||||
final: identityXform
|
final: identityXform
|
||||||
}
|
};
|
||||||
useEffect(() => setPainter(chaosGameFinal(gameParams)), [xform1CoefsPost, xform2CoefsPost, xform3CoefsPost]);
|
useEffect(() => setPainter(chaosGameFinal(gameParams)), [xform1CoefsPost, xform2CoefsPost, xform3CoefsPost]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CoefEditor title={"Transform 1 Post"} isPost={true} coefs={xform1CoefsPost} setCoefs={setXform1CoefsPost}
|
<CoefEditor title={"Transform 1 Post"} isPost={true} coefs={xform1CoefsPost} setCoefs={setXform1CoefsPost}
|
||||||
resetCoefs={resetXform1CoefsPost}/>
|
resetCoefs={resetXform1CoefsPost} />
|
||||||
<CoefEditor title={"Transform 2 Post"} isPost={true} coefs={xform2CoefsPost} setCoefs={setXform2CoefsPost}
|
<CoefEditor title={"Transform 2 Post"} isPost={true} coefs={xform2CoefsPost} setCoefs={setXform2CoefsPost}
|
||||||
resetCoefs={resetXform2CoefsPost}/>
|
resetCoefs={resetXform2CoefsPost} />
|
||||||
<CoefEditor title={"Transform 3 Post"} isPost={true} coefs={xform3CoefsPost} setCoefs={setXform3CoefsPost}
|
<CoefEditor title={"Transform 3 Post"} isPost={true} coefs={xform3CoefsPost} setCoefs={setXform3CoefsPost}
|
||||||
resetCoefs={resetXform3CoefsPost}/>
|
resetCoefs={resetXform3CoefsPost} />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import styles from "../src/css/styles.module.css"
|
import styles from "../src/css/styles.module.css";
|
||||||
|
|
||||||
export interface VariationProps {
|
export interface VariationProps {
|
||||||
linear: number;
|
linear: number;
|
||||||
@ -14,32 +14,32 @@ export interface Props {
|
|||||||
resetVariations: () => void;
|
resetVariations: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VariationEditor = ({title, variations, setVariations, resetVariations}: Props) => {
|
export const VariationEditor = ({ title, variations, setVariations, resetVariations }: Props) => {
|
||||||
const resetButton = <button className={styles.inputReset} onClick={resetVariations}>Reset</button>
|
const resetButton = <button className={styles.inputReset} onClick={resetVariations}>Reset</button>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: '1fr 1fr'}}>
|
<div className={styles.inputGroup} style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
|
||||||
<p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>{title} {resetButton}</p>
|
<p className={styles.inputTitle} style={{ gridColumn: "1/-1" }}>{title} {resetButton}</p>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<span>Linear: {variations.linear}</span>
|
<span>Linear: {variations.linear}</span>
|
||||||
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.linear}
|
<input type={"range"} min={0} max={1} step={0.01} style={{ width: "100%" }} value={variations.linear}
|
||||||
onInput={e => setVariations({...variations, linear: Number(e.currentTarget.value)})}/>
|
onInput={e => setVariations({ ...variations, linear: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<span>Julia: {variations.julia}</span>
|
<span>Julia: {variations.julia}</span>
|
||||||
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.julia}
|
<input type={"range"} min={0} max={1} step={0.01} style={{ width: "100%" }} value={variations.julia}
|
||||||
onInput={e => setVariations({...variations, julia: Number(e.currentTarget.value)})}/>
|
onInput={e => setVariations({ ...variations, julia: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<span>Popcorn: {variations.popcorn}</span>
|
<span>Popcorn: {variations.popcorn}</span>
|
||||||
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.popcorn}
|
<input type={"range"} min={0} max={1} step={0.01} style={{ width: "100%" }} value={variations.popcorn}
|
||||||
onInput={e => setVariations({...variations, popcorn: Number(e.currentTarget.value)})}/>
|
onInput={e => setVariations({ ...variations, popcorn: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<span>PDJ: {variations.pdj}</span>
|
<span>PDJ: {variations.pdj}</span>
|
||||||
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.pdj}
|
<input type={"range"} min={0} max={1} step={0.01} style={{ width: "100%" }} value={variations.pdj}
|
||||||
onInput={e => setVariations({...variations, pdj: Number(e.currentTarget.value)})}/>
|
onInput={e => setVariations({ ...variations, pdj: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
@ -1,17 +1,17 @@
|
|||||||
import {Coefs} from "../src/coefs";
|
import { Coefs } from "../src/transform";
|
||||||
import {VariationProps} from "./VariationEditor";
|
import { VariationProps } from "./VariationEditor";
|
||||||
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 {VariationBlend} from "../src/blend";
|
import { Blend } from "../src/blend";
|
||||||
|
|
||||||
export function buildBlend(coefs: Coefs, variations: VariationProps): VariationBlend {
|
export function buildBlend(coefs: Coefs, variations: VariationProps): Blend {
|
||||||
return [
|
return [
|
||||||
[variations.linear, linear],
|
[variations.linear, linear],
|
||||||
[variations.julia, julia],
|
[variations.julia, julia],
|
||||||
[variations.popcorn, popcorn(coefs)],
|
[variations.popcorn, popcorn(coefs)],
|
||||||
[variations.pdj, pdj(pdjParams)]
|
[variations.pdj, pdj(pdjParams)]
|
||||||
]
|
];
|
||||||
}
|
}
|
@ -1,23 +1,37 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import { randomBiUnit } from "../src/randomBiUnit";
|
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";
|
||||||
import {Props as ChaosGameWeightedProps} from "../1-introduction/chaosGameWeighted";
|
import { Props as WeightedProps } from "../1-introduction/chaosGameWeighted";
|
||||||
|
|
||||||
const quality = 0.5;
|
const quality = 0.5;
|
||||||
const step = 1000;
|
const step = 1000;
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export type Props = ChaosGameWeightedProps & {
|
export type Props = WeightedProps & {
|
||||||
final: Transform,
|
final: Transform,
|
||||||
}
|
}
|
||||||
export function* chaosGameFinal({width, height, transforms, final}: Props) {
|
|
||||||
let image = new ImageData(width, height);
|
|
||||||
let [x, y] = [randomBiUnit(), randomBiUnit()];
|
|
||||||
|
|
||||||
const iterations = width * height * quality;
|
export function* chaosGameFinal(
|
||||||
|
{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
transforms,
|
||||||
|
final
|
||||||
|
}: Props
|
||||||
|
) {
|
||||||
|
let img =
|
||||||
|
new ImageData(width, height);
|
||||||
|
let [x, y] = [
|
||||||
|
randomBiUnit(),
|
||||||
|
randomBiUnit()
|
||||||
|
];
|
||||||
|
|
||||||
|
const pixels = width * height;
|
||||||
|
const iterations = quality * pixels;
|
||||||
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);
|
||||||
|
|
||||||
// highlight-start
|
// highlight-start
|
||||||
@ -26,12 +40,12 @@ export function* chaosGameFinal({width, height, transforms, final}: Props) {
|
|||||||
|
|
||||||
if (i > 20)
|
if (i > 20)
|
||||||
// highlight-start
|
// highlight-start
|
||||||
plot(finalX, finalY, image);
|
plot(finalX, finalY, img);
|
||||||
// highlight-end
|
// highlight-end
|
||||||
|
|
||||||
if (i % step === 0)
|
if (i % step === 0)
|
||||||
yield image;
|
yield img;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield image;
|
yield img;
|
||||||
}
|
}
|
@ -34,7 +34,7 @@ This leads us to the first big innovation of the Fractal Flame algorithm: adding
|
|||||||
after the affine transform. These functions are called "variations":
|
after the affine transform. These functions are called "variations":
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_i(x, y) = V_j(a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i)
|
F_i(x, y) = V_j(a_i x + b_i y + c_i, d_i x + e_i y + f_i)
|
||||||
$$
|
$$
|
||||||
|
|
||||||
import variationSource from '!!raw-loader!../src/variation'
|
import variationSource from '!!raw-loader!../src/variation'
|
||||||
@ -96,7 +96,7 @@ Some variations rely on knowing the transform's affine coefficients; they're cal
|
|||||||
For the popcorn variation, we use $c$ and $f$:
|
For the popcorn variation, we use $c$ and $f$:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
V_{17}(x,y) = (x + c \cdot \text{sin}(\text{tan }3y), y + f \cdot \text{sin}(\text{tan }3x))
|
V_{17}(x,y) = (x + c\ \text{sin}(\text{tan }3y), y + f\ \text{sin}(\text{tan }3x))
|
||||||
$$
|
$$
|
||||||
|
|
||||||
import popcornSrc from '!!raw-loader!../src/popcorn'
|
import popcornSrc from '!!raw-loader!../src/popcorn'
|
||||||
@ -109,8 +109,8 @@ Some variations have extra parameters; they're called "parametric variations."
|
|||||||
For the PDJ variation, there are four extra parameters we can choose:
|
For the PDJ variation, there are four extra parameters we can choose:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
p_1 = \text{pdj.a} \hspace{0.2cm} p_2 = \text{pdj.b} \hspace{0.2cm} p_3 = \text{pdj.c} \hspace{0.2cm} p_4 = \text{pdj.d} \\
|
p_1 = \text{pdj.a} \hspace{0.1cm} p_2 = \text{pdj.b} \hspace{0.1cm} p_3 = \text{pdj.c} \hspace{0.1cm} p_4 = \text{pdj.d} \\
|
||||||
V_{24} = (\text{sin}(p_1 \cdot y) - \text{cos}(p_2 \cdot x), \text{sin}(p_3 \cdot x) - \text{cos}(p_4 \cdot y))
|
V_{24} = (\text{sin}(p_1 y) - \text{cos}(p_2 x), \text{sin}(p_3 x) - \text{cos}(p_4 y))
|
||||||
$$
|
$$
|
||||||
|
|
||||||
import pdjSrc from '!!raw-loader!../src/pdj'
|
import pdjSrc from '!!raw-loader!../src/pdj'
|
||||||
@ -124,7 +124,7 @@ Each variation receives the same $x$ and $y$ inputs, and we add together each va
|
|||||||
We'll also give each variation a weight ($v_{ij}$) that changes how much it contributes to the result:
|
We'll also give each variation a weight ($v_{ij}$) that changes how much it contributes to the result:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_i(x,y) = \sum_{j} v_{ij} V_j(a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
|
F_i(x,y) = \sum_{j} v_{ij} V_j(x, y)
|
||||||
$$
|
$$
|
||||||
|
|
||||||
The formula looks intimidating, but it's not hard to implement:
|
The formula looks intimidating, but it's not hard to implement:
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import {Coefs} from "../src/coefs";
|
import { applyCoefs, Coefs, Transform } from "../src/transform";
|
||||||
import {Transform} from "../src/transform";
|
|
||||||
import {applyCoefs} from "../src/coefs";
|
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export const transformPost = (transform: Transform, coefs: Coefs): Transform =>
|
export const transformPost = (
|
||||||
(x, y) => applyCoefs(...transform(x, y), coefs)
|
transform: Transform,
|
||||||
|
coefs: Coefs
|
||||||
|
): Transform =>
|
||||||
|
(x, y) => {
|
||||||
|
[x, y] = transform(x, y);
|
||||||
|
return applyCoefs(x, y, coefs);
|
||||||
|
}
|
@ -1,19 +1,19 @@
|
|||||||
import React, {useContext, useEffect, useMemo, useRef, useState} from "react";
|
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import * as params from "../src/params";
|
import * as params from "../src/params";
|
||||||
import {PainterContext} from "../src/Canvas";
|
import { PainterContext } from "../src/Canvas";
|
||||||
import {colorFromPalette} from "./colorFromPalette";
|
import { colorFromPalette } from "./colorFromPalette";
|
||||||
import {chaosGameColor, Props as ChaosGameColorProps, TransformColor} from "./chaosGameColor";
|
import { chaosGameColor, Props as ChaosGameColorProps, TransformColor } from "./chaosGameColor";
|
||||||
|
|
||||||
import styles from "../src/css/styles.module.css";
|
import styles from "../src/css/styles.module.css";
|
||||||
import {histIndex} from "../src/camera";
|
import { histIndex } from "../src/camera";
|
||||||
import {useColorMode} from "@docusaurus/theme-common";
|
import { useColorMode } from "@docusaurus/theme-common";
|
||||||
|
|
||||||
type PaletteBarProps = {
|
type PaletteBarProps = {
|
||||||
height: number;
|
height: number;
|
||||||
palette: number[];
|
palette: number[];
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
export const PaletteBar: React.FC<PaletteBarProps> = ({height, palette, children}) => {
|
export const PaletteBar: React.FC<PaletteBarProps> = ({ height, palette, children }) => {
|
||||||
const sizingRef = useRef<HTMLDivElement>(null);
|
const sizingRef = useRef<HTMLDivElement>(null);
|
||||||
const [width, setWidth] = useState(0);
|
const [width, setWidth] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -51,17 +51,17 @@ export const PaletteBar: React.FC<PaletteBarProps> = ({height, palette, children
|
|||||||
}
|
}
|
||||||
}, [canvasRef, paletteImage]);
|
}, [canvasRef, paletteImage]);
|
||||||
|
|
||||||
const canvasStyle = {filter: useColorMode().colorMode === 'dark' ? 'invert(1)' : ''};
|
const canvasStyle = { filter: useColorMode().colorMode === "dark" ? "invert(1)" : "" };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={sizingRef} style={{width: '100%', height}}>
|
<div ref={sizingRef} style={{ width: "100%", height }}>
|
||||||
{width > 0 ? <canvas ref={canvasRef} width={width} height={height} style={canvasStyle}/> : null}
|
{width > 0 ? <canvas ref={canvasRef} width={width} height={height} style={canvasStyle} /> : null}
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
type ColorEditorProps = {
|
type ColorEditorProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -71,60 +71,67 @@ type ColorEditorProps = {
|
|||||||
resetTransformColor: () => void;
|
resetTransformColor: () => void;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
const ColorEditor: React.FC<ColorEditorProps> = ({title, palette, transformColor, setTransformColor, resetTransformColor, children}) => {
|
const ColorEditor: React.FC<ColorEditorProps> = (
|
||||||
const resetButton = <button className={styles.inputReset} onClick={resetTransformColor}>Reset</button>
|
{
|
||||||
|
title,
|
||||||
|
palette,
|
||||||
|
transformColor,
|
||||||
|
setTransformColor,
|
||||||
|
resetTransformColor,
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
|
const resetButton = <button className={styles.inputReset} onClick={resetTransformColor}>Reset</button>;
|
||||||
|
|
||||||
const [r, g, b] = colorFromPalette(palette, transformColor.color);
|
const [r, g, b] = colorFromPalette(palette, transformColor.color);
|
||||||
const colorCss = `rgb(${Math.floor(r * 0xff)},${Math.floor(g * 0xff)},${Math.floor(b * 0xff)})`;
|
const colorCss = `rgb(${Math.floor(r * 0xff)},${Math.floor(g * 0xff)},${Math.floor(b * 0xff)})`;
|
||||||
|
|
||||||
const {colorMode} = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: '2fr 2fr 1fr'}}>
|
<div className={styles.inputGroup} style={{ display: "grid", gridTemplateColumns: "2fr 2fr 1fr" }}>
|
||||||
<p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>{title} {resetButton}</p>
|
<p className={styles.inputTitle} style={{ gridColumn: "1/-1" }}>{title} {resetButton}</p>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p>Color: {transformColor.color}</p>
|
<p>Color: {transformColor.color}</p>
|
||||||
<input type={'range'} min={0} max={1} step={.001} value={transformColor.color}
|
<input type={"range"} min={0} max={1} step={.001} value={transformColor.color}
|
||||||
onInput={e => setTransformColor({...transformColor, color: Number(e.currentTarget.value)})}/>
|
onInput={e => setTransformColor({ ...transformColor, color: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement}>
|
<div className={styles.inputElement}>
|
||||||
<p>Speed: {transformColor.colorSpeed}</p>
|
<p>Speed: {transformColor.colorSpeed}</p>
|
||||||
<input type={'range'} min={0} max={1} step={.001} value={transformColor.colorSpeed}
|
<input type={"range"} min={0} max={1} step={.001} value={transformColor.colorSpeed}
|
||||||
onInput={e => setTransformColor({...transformColor, colorSpeed: Number(e.currentTarget.value)})}/>
|
onInput={e => setTransformColor({ ...transformColor, colorSpeed: Number(e.currentTarget.value) })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.inputElement} style={{
|
<div className={styles.inputElement} style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
backgroundColor: colorCss,
|
backgroundColor: colorCss,
|
||||||
filter: colorMode === 'dark' ? 'invert(1)' : ''
|
filter: colorMode === "dark" ? "invert(1)" : ""
|
||||||
}}/>
|
}} />
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
quality?: number;
|
|
||||||
children?: React.ReactElement;
|
children?: React.ReactElement;
|
||||||
}
|
}
|
||||||
export default function FlameColor({quality, children}: Props) {
|
export default function FlameColor({ children }: Props) {
|
||||||
const {width, height, setPainter} = useContext(PainterContext);
|
const { width, height, setPainter } = useContext(PainterContext);
|
||||||
|
|
||||||
const xform1ColorDefault: TransformColor = {color: params.xform1Color, colorSpeed: 0.5};
|
const xform1ColorDefault: TransformColor = { color: params.xform1Color, colorSpeed: 0.5 };
|
||||||
const [xform1Color, setXform1Color] = useState(xform1ColorDefault);
|
const [xform1Color, setXform1Color] = useState(xform1ColorDefault);
|
||||||
const resetXform1Color = () => setXform1Color(xform1ColorDefault);
|
const resetXform1Color = () => setXform1Color(xform1ColorDefault);
|
||||||
|
|
||||||
const xform2ColorDefault: TransformColor = {color: params.xform2Color, colorSpeed: 0.5};
|
const xform2ColorDefault: TransformColor = { color: params.xform2Color, colorSpeed: 0.5 };
|
||||||
const [xform2Color, setXform2Color] = useState(xform2ColorDefault);
|
const [xform2Color, setXform2Color] = useState(xform2ColorDefault);
|
||||||
const resetXform2Color = () => setXform2Color(xform2ColorDefault);
|
const resetXform2Color = () => setXform2Color(xform2ColorDefault);
|
||||||
|
|
||||||
const xform3ColorDefault: TransformColor = {color: params.xform3Color, colorSpeed: 0.5};
|
const xform3ColorDefault: TransformColor = { color: params.xform3Color, colorSpeed: 0.5 };
|
||||||
const [xform3Color, setXform3Color] = useState(xform3ColorDefault);
|
const [xform3Color, setXform3Color] = useState(xform3ColorDefault);
|
||||||
const resetXform3Color = () => setXform3Color(xform3ColorDefault);
|
const resetXform3Color = () => setXform3Color(xform3ColorDefault);
|
||||||
|
|
||||||
const xformFinalColorDefault: TransformColor = {color: params.xformFinalColor, colorSpeed: 0};
|
const xformFinalColorDefault: TransformColor = { color: params.xformFinalColor, colorSpeed: 0 };
|
||||||
const [xformFinalColor, setXformFinalColor] = useState(xformFinalColorDefault);
|
const [xformFinalColor, setXformFinalColor] = useState(xformFinalColorDefault);
|
||||||
const resetXformFinalColor = () => setXformFinalColor(xformFinalColorDefault);
|
const resetXformFinalColor = () => setXformFinalColor(xformFinalColorDefault);
|
||||||
|
|
||||||
@ -137,37 +144,37 @@ export default function FlameColor({quality, children}: Props) {
|
|||||||
palette: params.palette,
|
palette: params.palette,
|
||||||
colors: [xform1Color, xform2Color, xform3Color],
|
colors: [xform1Color, xform2Color, xform3Color],
|
||||||
finalColor: xformFinalColor
|
finalColor: xformFinalColor
|
||||||
}
|
};
|
||||||
setPainter(chaosGameColor(gameParams));
|
setPainter(chaosGameColor(gameParams));
|
||||||
}, [xform1Color, xform2Color, xform3Color, xformFinalColor]);
|
}, [xform1Color, xform2Color, xform3Color, xformFinalColor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PaletteBar height={40} palette={params.palette}/>
|
<PaletteBar height={40} palette={params.palette} />
|
||||||
<ColorEditor
|
<ColorEditor
|
||||||
title={"Transform 1"}
|
title={"Transform 1"}
|
||||||
palette={params.palette}
|
palette={params.palette}
|
||||||
transformColor={xform1Color}
|
transformColor={xform1Color}
|
||||||
setTransformColor={setXform1Color}
|
setTransformColor={setXform1Color}
|
||||||
resetTransformColor={resetXform1Color}/>
|
resetTransformColor={resetXform1Color} />
|
||||||
<ColorEditor
|
<ColorEditor
|
||||||
title={"Transform 2"}
|
title={"Transform 2"}
|
||||||
palette={params.palette}
|
palette={params.palette}
|
||||||
transformColor={xform2Color}
|
transformColor={xform2Color}
|
||||||
setTransformColor={setXform2Color}
|
setTransformColor={setXform2Color}
|
||||||
resetTransformColor={resetXform2Color}/>
|
resetTransformColor={resetXform2Color} />
|
||||||
<ColorEditor
|
<ColorEditor
|
||||||
title={"Transform 3"}
|
title={"Transform 3"}
|
||||||
palette={params.palette}
|
palette={params.palette}
|
||||||
transformColor={xform3Color}
|
transformColor={xform3Color}
|
||||||
setTransformColor={setXform3Color}
|
setTransformColor={setXform3Color}
|
||||||
resetTransformColor={resetXform3Color}/>
|
resetTransformColor={resetXform3Color} />
|
||||||
<ColorEditor
|
<ColorEditor
|
||||||
title={"Transform Final"}
|
title={"Transform Final"}
|
||||||
palette={params.palette}
|
palette={params.palette}
|
||||||
transformColor={xformFinalColor}
|
transformColor={xformFinalColor}
|
||||||
setTransformColor={setXformFinalColor}
|
setTransformColor={setXformFinalColor}
|
||||||
resetTransformColor={resetXformFinalColor}/>
|
resetTransformColor={resetXformFinalColor} />
|
||||||
{children}
|
{children}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import React, {useContext, useEffect} from "react";
|
import React, { useContext, useEffect } from "react";
|
||||||
import {xforms as transforms, xformFinal as final} from "../src/params";
|
import { xformFinal as final, xforms as transforms } from "../src/params";
|
||||||
import {PainterContext} from "../src/Canvas";
|
import { PainterContext } from "../src/Canvas";
|
||||||
import {chaosGameHistogram} from "./chaosGameHistogram";
|
import { chaosGameHistogram } from "./chaosGameHistogram";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
paint: (width: number, height: number, histogram: number[]) => ImageData;
|
paint: (width: number, height: number, histogram: number[]) => ImageData;
|
||||||
children?: React.ReactElement;
|
children?: React.ReactElement;
|
||||||
}
|
}
|
||||||
export default function FlameHistogram({paint, children}: Props) {
|
export default function FlameHistogram({ paint, children }: Props) {
|
||||||
const {width, height, setPainter} = useContext(PainterContext);
|
const { width, height, setPainter } = useContext(PainterContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const gameParams = {
|
const gameParams = {
|
||||||
@ -17,7 +17,7 @@ export default function FlameHistogram({paint, children}: Props) {
|
|||||||
transforms,
|
transforms,
|
||||||
final,
|
final,
|
||||||
paint
|
paint
|
||||||
}
|
};
|
||||||
setPainter(chaosGameHistogram(gameParams));
|
setPainter(chaosGameHistogram(gameParams));
|
||||||
}, [width, height]);
|
}, [width, height]);
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import {Props as ChaosGameFinalProps} from "../2-transforms/chaosGameFinal";
|
import { Props as ChaosGameFinalProps } from "../2-transforms/chaosGameFinal";
|
||||||
import {randomBiUnit} from "../src/randomBiUnit";
|
import { randomBiUnit } from "../src/randomBiUnit";
|
||||||
import {randomChoice} from "../src/randomChoice";
|
import { randomChoice } from "../src/randomChoice";
|
||||||
import {camera, histIndex} from "../src/camera";
|
import { camera, histIndex } from "../src/camera";
|
||||||
import {colorFromPalette} from "./colorFromPalette";
|
import { colorFromPalette } from "./colorFromPalette";
|
||||||
import {mixColor} from "./mixColor";
|
import { mixColor } from "./mixColor";
|
||||||
import {paintColor} from "./paintColor";
|
import { paintColor } from "./paintColor";
|
||||||
|
|
||||||
const quality = 15;
|
const quality = 15;
|
||||||
const step = 100_000;
|
const step = 100_000;
|
||||||
@ -20,45 +20,116 @@ export type Props = ChaosGameFinalProps & {
|
|||||||
colors: TransformColor[];
|
colors: TransformColor[];
|
||||||
finalColor: TransformColor;
|
finalColor: TransformColor;
|
||||||
}
|
}
|
||||||
export function* chaosGameColor({width, height, transforms, final, palette, colors, finalColor}: Props) {
|
|
||||||
const imgRed = Array<number>(width * height).fill(0);
|
|
||||||
const imgGreen = Array<number>(width * height).fill(0);
|
|
||||||
const imgBlue = Array<number>(width * height).fill(0);
|
|
||||||
const imgAlpha = Array<number>(width * height).fill(0);
|
|
||||||
|
|
||||||
let [x, y] = [randomBiUnit(), randomBiUnit()];
|
export function* chaosGameColor(
|
||||||
|
{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
transforms,
|
||||||
|
final,
|
||||||
|
palette,
|
||||||
|
colors,
|
||||||
|
finalColor
|
||||||
|
}: Props
|
||||||
|
) {
|
||||||
|
const pixels = width * height;
|
||||||
|
|
||||||
|
// highlight-start
|
||||||
|
const imgRed = Array<number>(pixels)
|
||||||
|
.fill(0);
|
||||||
|
const imgGreen = Array<number>(pixels)
|
||||||
|
.fill(0);
|
||||||
|
const imgBlue = Array<number>(pixels)
|
||||||
|
.fill(0);
|
||||||
|
const imgAlpha = Array<number>(pixels)
|
||||||
|
.fill(0);
|
||||||
|
|
||||||
|
const plotColor = (
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
c: number
|
||||||
|
) => {
|
||||||
|
const [pixelX, pixelY] =
|
||||||
|
camera(x, y, width);
|
||||||
|
|
||||||
|
if (
|
||||||
|
pixelX < 0 ||
|
||||||
|
pixelX >= width ||
|
||||||
|
pixelY < 0 ||
|
||||||
|
pixelY >= width
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const hIndex =
|
||||||
|
histIndex(pixelX, pixelY, width, 1);
|
||||||
|
|
||||||
|
const [r, g, b] =
|
||||||
|
colorFromPalette(palette, c);
|
||||||
|
|
||||||
|
imgRed[hIndex] += r;
|
||||||
|
imgGreen[hIndex] += g;
|
||||||
|
imgBlue[hIndex] += b;
|
||||||
|
imgAlpha[hIndex] += 1;
|
||||||
|
}
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
|
let [x, y] = [
|
||||||
|
randomBiUnit(),
|
||||||
|
randomBiUnit()
|
||||||
|
];
|
||||||
let c = Math.random();
|
let c = Math.random();
|
||||||
|
|
||||||
const iterations = width * height * quality;
|
const iterations = quality * pixels;
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
const [transformIndex, transform] = randomChoice(transforms);
|
const [transformIndex, transform] =
|
||||||
|
randomChoice(transforms);
|
||||||
[x, y] = transform(x, y);
|
[x, y] = transform(x, y);
|
||||||
|
|
||||||
// highlight-start
|
// highlight-start
|
||||||
const transformColor = colors[transformIndex];
|
const transformColor =
|
||||||
c = mixColor(c, transformColor.color, transformColor.colorSpeed);
|
colors[transformIndex];
|
||||||
|
|
||||||
|
c = mixColor(
|
||||||
|
c,
|
||||||
|
transformColor.color,
|
||||||
|
transformColor.colorSpeed
|
||||||
|
);
|
||||||
// highlight-end
|
// highlight-end
|
||||||
|
|
||||||
const [finalX, finalY] = final(x, y);
|
const [finalX, finalY] = final(x, y);
|
||||||
|
|
||||||
if (i > 20) {
|
// highlight-start
|
||||||
const [pixelX, pixelY] = camera(finalX, finalY, width);
|
const finalC = mixColor(
|
||||||
const pixelIndex = histIndex(pixelX, pixelY, width, 1);
|
c,
|
||||||
|
finalColor.color,
|
||||||
|
finalColor.colorSpeed
|
||||||
|
);
|
||||||
|
// highlight-end
|
||||||
|
|
||||||
if (pixelIndex < 0 || pixelIndex >= imgAlpha.length)
|
if (i > 20)
|
||||||
continue;
|
plotColor(
|
||||||
|
finalX,
|
||||||
const colorFinal = mixColor(c, finalColor.color, finalColor.colorSpeed);
|
finalY,
|
||||||
const [r, g, b] = colorFromPalette(palette, colorFinal);
|
finalC
|
||||||
imgRed[pixelIndex] += r;
|
)
|
||||||
imgGreen[pixelIndex] += g;
|
|
||||||
imgBlue[pixelIndex] += b;
|
|
||||||
imgAlpha[pixelIndex] += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i % step === 0)
|
if (i % step === 0)
|
||||||
yield paintColor(width, height, imgRed, imgGreen, imgBlue, imgAlpha);
|
yield paintColor(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
imgRed,
|
||||||
|
imgGreen,
|
||||||
|
imgBlue,
|
||||||
|
imgAlpha
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield paintColor(width, height, imgRed, imgGreen, imgBlue, imgAlpha);
|
yield paintColor(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
imgRed,
|
||||||
|
imgGreen,
|
||||||
|
imgBlue,
|
||||||
|
imgAlpha
|
||||||
|
);
|
||||||
}
|
}
|
@ -1,45 +1,78 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import {randomBiUnit} from "../src/randomBiUnit";
|
import { randomBiUnit } from "../src/randomBiUnit";
|
||||||
import {randomChoice} from "../src/randomChoice";
|
import { randomChoice } from "../src/randomChoice";
|
||||||
import {Props as ChaosGameFinalProps} from "../2-transforms/chaosGameFinal";
|
import { Props as ChaosGameFinalProps } from "../2-transforms/chaosGameFinal";
|
||||||
import {camera, histIndex} from "../src/camera";
|
import { camera, histIndex } from "../src/camera";
|
||||||
|
|
||||||
const quality = 10;
|
const quality = 10;
|
||||||
const step = 100_000;
|
const step = 100_000;
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export type Props = ChaosGameFinalProps & {
|
type Props = ChaosGameFinalProps & {
|
||||||
paint: (width: number, height: number, histogram: number[]) => ImageData;
|
paint: (
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
histogram: number[]
|
||||||
|
) => ImageData;
|
||||||
}
|
}
|
||||||
export function* chaosGameHistogram({width, height, transforms, final, paint}: Props) {
|
|
||||||
let iterations = quality * width * height;
|
export function* chaosGameHistogram(
|
||||||
|
{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
transforms,
|
||||||
|
final,
|
||||||
|
paint
|
||||||
|
}: Props
|
||||||
|
) {
|
||||||
|
const pixels = width * height;
|
||||||
|
const iterations = quality * pixels;
|
||||||
|
|
||||||
// highlight-start
|
// highlight-start
|
||||||
const histogram = Array<number>(width * height).fill(0);
|
const hist = Array<number>(pixels)
|
||||||
|
.fill(0);
|
||||||
|
|
||||||
|
const plotHist = (
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
) => {
|
||||||
|
const [pixelX, pixelY] =
|
||||||
|
camera(x, y, width);
|
||||||
|
|
||||||
|
if (
|
||||||
|
pixelX < 0 ||
|
||||||
|
pixelX >= width ||
|
||||||
|
pixelY < 0 ||
|
||||||
|
pixelY >= height
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const hIndex =
|
||||||
|
histIndex(pixelX, pixelY, width, 1);
|
||||||
|
|
||||||
|
hist[hIndex] += 1;
|
||||||
|
};
|
||||||
// highlight-end
|
// highlight-end
|
||||||
|
|
||||||
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);
|
||||||
const [finalX, finalY] = final(x, y);
|
const [finalX, finalY] = final(x, y);
|
||||||
|
|
||||||
if (i > 20) {
|
if (i > 20) {
|
||||||
// highlight-start
|
// highlight-start
|
||||||
const [pixelX, pixelY] = camera(finalX, finalY, width);
|
plotHist(finalX, finalY);
|
||||||
const hIndex = histIndex(pixelX, pixelY, width, 1);
|
|
||||||
|
|
||||||
if (hIndex < 0 || hIndex >= histogram.length) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
histogram[hIndex] += 1;
|
|
||||||
// highlight-end
|
// highlight-end
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i % step === 0)
|
if (i % step === 0)
|
||||||
yield paint(width, height, histogram);
|
yield paint(width, height, hist);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield paint(width, height, histogram);
|
yield paint(width, height, hist);
|
||||||
}
|
}
|
@ -1,4 +1,14 @@
|
|||||||
export function colorFromPalette(palette: number[], colorIndex: number): [number, number, number] {
|
export function colorFromPalette(
|
||||||
const paletteIndex = Math.floor(colorIndex * (palette.length / 3)) * 3;
|
palette: number[],
|
||||||
return [palette[paletteIndex], palette[paletteIndex + 1], palette[paletteIndex + 2]];
|
colorIndex: number
|
||||||
|
): [number, number, number] {
|
||||||
|
const numColors = palette.length / 3;
|
||||||
|
const paletteIndex = Math.floor(
|
||||||
|
colorIndex * (numColors)
|
||||||
|
) * 3;
|
||||||
|
return [
|
||||||
|
palette[paletteIndex], // red
|
||||||
|
palette[paletteIndex + 1], // green
|
||||||
|
palette[paletteIndex + 2] // blue
|
||||||
|
];
|
||||||
}
|
}
|
@ -165,7 +165,7 @@ import colorFromPaletteSource from "!!raw-loader!./colorFromPalette";
|
|||||||
<details>
|
<details>
|
||||||
<summary>As an alternative...</summary>
|
<summary>As an alternative...</summary>
|
||||||
|
|
||||||
...you could also interpolate between colors in the palette.
|
...you could interpolate between colors in the palette.
|
||||||
For example, `flam3` uses [linear interpolation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L483-L486)
|
For example, `flam3` uses [linear interpolation](https://github.com/scottdraves/flam3/blob/7fb50c82e90e051f00efcc3123d0e06de26594b2/rect.c#L483-L486)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
export function mixColor(color1: number, color2: number, colorSpeed: number) {
|
export function mixColor(
|
||||||
return color1 * (1 - colorSpeed) + color2 * colorSpeed;
|
color1: number,
|
||||||
|
color2: number,
|
||||||
|
colorSpeed: number
|
||||||
|
) {
|
||||||
|
return color1 * (1 - colorSpeed) +
|
||||||
|
color2 * colorSpeed;
|
||||||
}
|
}
|
@ -1,6 +1,3 @@
|
|||||||
// hidden-start
|
|
||||||
import {colorFromPalette} from "./colorFromPalette";
|
|
||||||
// hidden-end
|
|
||||||
export function paintColor(
|
export function paintColor(
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
@ -9,17 +6,29 @@ export function paintColor(
|
|||||||
blue: number[],
|
blue: number[],
|
||||||
alpha: number[]
|
alpha: number[]
|
||||||
): ImageData {
|
): ImageData {
|
||||||
const image = new ImageData(width, height);
|
const pixels = width * height;
|
||||||
|
const img =
|
||||||
|
new ImageData(width, height);
|
||||||
|
|
||||||
for (let i = 0; i < width * height; i++) {
|
for (let i = 0; i < pixels; i++) {
|
||||||
const alphaScale = Math.log10(alpha[i]) / (alpha[i] * 1.5);
|
const scale =
|
||||||
|
Math.log10(alpha[i]) /
|
||||||
|
(alpha[i] * 1.5);
|
||||||
|
|
||||||
const pixelIndex = i * 4;
|
const pixelIndex = i * 4;
|
||||||
image.data[pixelIndex] = red[i] * alphaScale * 0xff;
|
|
||||||
image.data[pixelIndex + 1] = green[i] * alphaScale * 0xff;
|
const rVal = red[i] * scale * 0xff;
|
||||||
image.data[pixelIndex + 2] = blue[i] * alphaScale * 0xff;
|
img.data[pixelIndex] = rVal;
|
||||||
image.data[pixelIndex + 3] = alpha[i] * alphaScale * 0xff;
|
|
||||||
|
const gVal = green[i] * scale * 0xff;
|
||||||
|
img.data[pixelIndex + 1] = gVal;
|
||||||
|
|
||||||
|
const bVal = blue[i] * scale * 0xff;
|
||||||
|
img.data[pixelIndex + 2] = bVal;
|
||||||
|
|
||||||
|
const aVal = alpha[i] * scale * 0xff;
|
||||||
|
img.data[pixelIndex + 3] = aVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return img;
|
||||||
}
|
}
|
@ -1,18 +1,26 @@
|
|||||||
export function paintLinear(width: number, height: number, histogram: number[]): ImageData {
|
export function paintLinear(
|
||||||
const image = new ImageData(width, height);
|
width: number,
|
||||||
|
height: number,
|
||||||
|
hist: number[]
|
||||||
|
) {
|
||||||
|
const img =
|
||||||
|
new ImageData(width, height);
|
||||||
|
|
||||||
let valueMax = 0;
|
let hMax = 0;
|
||||||
for (let value of histogram) {
|
for (let value of hist) {
|
||||||
valueMax = Math.max(valueMax, value);
|
hMax = Math.max(hMax, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < histogram.length; i++) {
|
for (let i = 0; i < hist.length; i++) {
|
||||||
const pixelIndex = i * 4;
|
const pixelIndex = i * 4;
|
||||||
image.data[pixelIndex] = 0;
|
|
||||||
image.data[pixelIndex + 1] = 0;
|
img.data[pixelIndex] = 0;
|
||||||
image.data[pixelIndex + 2] = 0;
|
img.data[pixelIndex + 1] = 0;
|
||||||
image.data[pixelIndex + 3] = histogram[i] / valueMax * 0xff;
|
img.data[pixelIndex + 2] = 0;
|
||||||
|
|
||||||
|
const alpha = hist[i] / hMax * 0xff;
|
||||||
|
img.data[pixelIndex + 3] = alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return img;
|
||||||
}
|
}
|
@ -1,21 +1,29 @@
|
|||||||
export function paintLogarithmic(width: number, height: number, histogram: number[]): ImageData {
|
export function paintLogarithmic(
|
||||||
const image = new ImageData(width, height);
|
width: number,
|
||||||
|
height: number,
|
||||||
|
hist: number[]
|
||||||
|
) {
|
||||||
|
const img =
|
||||||
|
new ImageData(width, height);
|
||||||
|
|
||||||
const histogramLog: number[] = [];
|
const histLog = hist.map(Math.log);
|
||||||
histogram.forEach(value => histogramLog.push(Math.log(value)));
|
|
||||||
|
|
||||||
let histogramLogMax = -Infinity;
|
let hLogMax = -Infinity;
|
||||||
for (let value of histogramLog) {
|
for (let value of histLog) {
|
||||||
histogramLogMax = Math.max(histogramLogMax, value);
|
hLogMax = Math.max(hLogMax, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < histogram.length; i++) {
|
for (let i = 0; i < hist.length; i++) {
|
||||||
const pixelIndex = i * 4;
|
const pixelIndex = i * 4;
|
||||||
image.data[pixelIndex] = 0; // red
|
|
||||||
image.data[pixelIndex + 1] = 0; // green
|
img.data[pixelIndex] = 0; // red
|
||||||
image.data[pixelIndex + 2] = 0; // blue
|
img.data[pixelIndex + 1] = 0; // green
|
||||||
image.data[pixelIndex + 3] = histogramLog[i] / histogramLogMax * 0xff;
|
img.data[pixelIndex + 2] = 0; // blue
|
||||||
|
|
||||||
|
const alpha =
|
||||||
|
histLog[i] / hLogMax * 0xff;
|
||||||
|
img.data[pixelIndex + 3] = alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return img;
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
<Flames name="params">
|
<Flames name="params">
|
||||||
<flame name="post xform" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="0 0 0" brightness="4" gamma="4" >
|
<flame name="post xform" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="0 0 0" brightness="4" gamma="4" >
|
||||||
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
|
<xform weight="0.564534951145298" color="0" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" post="1 0 0 1 2 0"/>
|
||||||
<xform weight="0.564534951145298" color="0" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
|
|
||||||
<xform weight="0.0131350067581356" color="0" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" post="1 0 0 1 0.24 0.27" />
|
<xform weight="0.0131350067581356" color="0" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" post="1 0 0 1 0.24 0.27" />
|
||||||
|
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
|
||||||
<palette count="256" format="RGB">
|
<palette count="256" format="RGB">
|
||||||
3130323635383B3A3D403F424644484B494D504E52565358
|
3130323635383B3A3D403F424644484B494D504E52565358
|
||||||
5B585D605D626562686B676D706C737571787B767D807B83
|
5B585D605D626562686B676D706C737571787B767D807B83
|
||||||
@ -39,9 +39,9 @@
|
|||||||
</palette>
|
</palette>
|
||||||
</flame>
|
</flame>
|
||||||
<flame name="baseline" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="0 0 0" brightness="4" gamma="4" >
|
<flame name="baseline" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="0 0 0" brightness="4" gamma="4" >
|
||||||
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
|
|
||||||
<xform weight="0.564534951145298" color="0.13" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
|
<xform weight="0.564534951145298" color="0.13" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
|
||||||
<xform weight="0.0131350067581356" color="0.844" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" />
|
<xform weight="0.0131350067581356" color="0.844" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" />
|
||||||
|
<xform weight="0.422330042096567" color="0" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
|
||||||
<palette count="256" format="RGB">
|
<palette count="256" format="RGB">
|
||||||
3130323635383B3A3D403F424644484B494D504E52565358
|
3130323635383B3A3D403F424644484B494D504E52565358
|
||||||
5B585D605D626562686B676D706C737571787B767D807B83
|
5B585D605D626562686B676D706C737571787B767D807B83
|
||||||
@ -78,9 +78,9 @@
|
|||||||
</palette>
|
</palette>
|
||||||
</flame>
|
</flame>
|
||||||
<flame name="final xform" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="1 1 1" brightness="4" gamma="4" >
|
<flame name="final xform" version="Apophysis 2.08 beta" size="600 600" center="0 0" scale="150" oversample="1" filter="0.2" quality="1" background="1 1 1" brightness="4" gamma="4" >
|
||||||
<xform weight="0.422330042096567" color="0.349" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
|
|
||||||
<xform weight="0.564534951145298" color="0" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
|
<xform weight="0.564534951145298" color="0" julia="1" coefs="-1.381068 1.381068 -1.381068 -1.381068 0 0" />
|
||||||
<xform weight="0.0131350067581356" color="0.766" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" post="1 0 0 1 0.24 0.27" />
|
<xform weight="0.0131350067581356" color="0.766" linear="1" popcorn="1" coefs="0.031393 -0.031367 0.031367 0.031393 0 0" post="1 0 0 1 0.24 0.27" />
|
||||||
|
<xform weight="0.422330042096567" color="0.349" pdj="1" coefs="1.51523 0.740356 -3.048677 -1.455964 0.724135 -0.362059" pdj_a="1.09358" pdj_b="2.13048" pdj_c="2.54127" pdj_d="2.37267" />
|
||||||
<finalxform color="0" symmetry="1" julia="1" coefs="2 0 0 2 0 0" />
|
<finalxform color="0" symmetry="1" julia="1" coefs="2 0 0 2 0 0" />
|
||||||
<palette count="256" format="RGB">
|
<palette count="256" format="RGB">
|
||||||
3130323635383B3A3D403F424644484B494D504E52565358
|
3130323635383B3A3D403F424644484B494D504E52565358
|
||||||
|
@ -11,7 +11,7 @@ export const PainterContext = createContext<PainterProps>(null)
|
|||||||
const downloadImage = (name: string) =>
|
const downloadImage = (name: string) =>
|
||||||
(e: MouseEvent) => {
|
(e: MouseEvent) => {
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.download = "flame.png";
|
link.download = `${name}.png`;
|
||||||
link.href = (e.target as HTMLCanvasElement).toDataURL("image/png");
|
link.href = (e.target as HTMLCanvasElement).toDataURL("image/png");
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {Transform, Coefs, applyCoefs} from "./transform";
|
import { applyCoefs, Coefs, Transform } from "./transform";
|
||||||
import {blend, VariationBlend} from "./blend";
|
import { blend, Blend } from "./blend";
|
||||||
|
|
||||||
export const applyTransform = (coefs: Coefs, variations: VariationBlend): Transform =>
|
export const applyTransform = (coefs: Coefs, variations: Blend): Transform =>
|
||||||
(x, y) => blend(...applyCoefs(x, y, coefs), variations);
|
(x, y) => blend(...applyCoefs(x, y, coefs), variations)
|
||||||
|
|
||||||
export const applyPost = (coefsPost: Coefs, transform: Transform): Transform =>
|
export const applyPost = (coefsPost: Coefs, transform: Transform): Transform =>
|
||||||
(x, y) => applyCoefs(...transform(x, y), coefsPost);
|
(x, y) => applyCoefs(...transform(x, y), coefsPost);
|
@ -1,16 +1,17 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import {Variation} from "./variation";
|
import { Variation } from "./variation";
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export type VariationBlend = [number, Variation][];
|
export type Blend = [number, Variation][];
|
||||||
|
|
||||||
export function blend(
|
export function blend(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
variations: VariationBlend
|
varFns: Blend
|
||||||
): [number, number] {
|
): [number, number] {
|
||||||
let [outX, outY] = [0, 0];
|
let [outX, outY] = [0, 0];
|
||||||
|
|
||||||
for (const [weight, variation] of variations) {
|
for (const [weight, varFn] of varFns) {
|
||||||
const [varX, varY] = variation(x, y);
|
const [varX, varY] = varFn(x, y);
|
||||||
outX += weight * varX;
|
outX += weight * varX;
|
||||||
outY += weight * varY;
|
outY += weight * varY;
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import { Variation } from './variation'
|
import { Variation } from "./variation";
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export const julia: Variation = (x, y) => {
|
const omega =
|
||||||
const r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
() => Math.random() > 0.5 ? 0 : Math.PI;
|
||||||
|
|
||||||
|
export const julia: Variation =
|
||||||
|
(x, y) => {
|
||||||
|
const x2 = Math.pow(x, 2);
|
||||||
|
const y2 = Math.pow(y, 2);
|
||||||
|
const r = Math.sqrt(x2 + y2);
|
||||||
|
|
||||||
const theta = Math.atan2(x, y);
|
const theta = Math.atan2(x, y);
|
||||||
const omega = Math.random() > 0.5 ? 0 : Math.PI;
|
|
||||||
|
|
||||||
const sqrtR = Math.sqrt(r);
|
const sqrtR = Math.sqrt(r);
|
||||||
const thetaVal = theta / 2 + omega;
|
const thetaVal = theta / 2 + omega();
|
||||||
return [
|
return [
|
||||||
sqrtR * Math.cos(thetaVal),
|
sqrtR * Math.cos(thetaVal),
|
||||||
sqrtR * Math.sin(thetaVal)
|
sqrtR * Math.sin(thetaVal)
|
||||||
]
|
];
|
||||||
}
|
};
|
@ -1,4 +1,5 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import {Variation} from "./variation"
|
import {Variation} from "./variation"
|
||||||
//hidden-end
|
//hidden-end
|
||||||
export const linear: Variation = (x, y) => [x, y];
|
export const linear: Variation =
|
||||||
|
(x, y) => [x, y];
|
@ -3,57 +3,56 @@
|
|||||||
* translated into something that's easier to work with.
|
* translated into something that's easier to work with.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Coefs } from './coefs';
|
import { Blend } from "./blend";
|
||||||
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 { Coefs, Transform } from "./transform";
|
||||||
import {Transform} from "./transform";
|
import { applyPost, applyTransform } from "./applyTransform";
|
||||||
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,
|
||||||
d: 0, e: 1, f: 0,
|
d: 0, e: 1, f: 0
|
||||||
}
|
};
|
||||||
|
|
||||||
export const pdjParams: PdjParams = {
|
export const pdjParams: PdjParams = {
|
||||||
a: 1.09358, b: 2.13048, c: 2.54127, d: 2.37267
|
a: 1.09358, b: 2.13048, c: 2.54127, d: 2.37267
|
||||||
}
|
};
|
||||||
|
|
||||||
export const xform1Weight = 0.56453495;
|
export const xform1Weight = 0.56453495;
|
||||||
export const xform1Coefs = {
|
export const xform1Coefs = {
|
||||||
a: -1.381068, b: -1.381068, c: 0,
|
a: -1.381068, b: -1.381068, c: 0,
|
||||||
d: 1.381068, e: -1.381068, f: 0,
|
d: 1.381068, e: -1.381068, f: 0
|
||||||
}
|
};
|
||||||
export const xform1CoefsPost = identityCoefs;
|
export const xform1CoefsPost = identityCoefs;
|
||||||
export const xform1Variations: VariationBlend = [
|
export const xform1Variations: Blend = [
|
||||||
[1, julia]
|
[1, julia]
|
||||||
]
|
];
|
||||||
export const xform1Color = 0;
|
export const xform1Color = 0;
|
||||||
|
|
||||||
export const xform2Weight = 0.013135;
|
export const xform2Weight = 0.013135;
|
||||||
export const xform2Coefs = {
|
export const xform2Coefs = {
|
||||||
a: 0.031393, b: 0.031367, c: 0,
|
a: 0.031393, b: 0.031367, c: 0,
|
||||||
d: -0.031367, e: 0.031393, f: 0,
|
d: -0.031367, e: 0.031393, f: 0
|
||||||
}
|
};
|
||||||
export const xform2CoefsPost = {
|
export const xform2CoefsPost = {
|
||||||
a: 1, b: 0, c: 0.24,
|
a: 1, b: 0, c: 0.24,
|
||||||
d: 0, e: 1, f: 0.27,
|
d: 0, e: 1, f: 0.27
|
||||||
}
|
};
|
||||||
export const xform2Variations: VariationBlend = [
|
export const xform2Variations: Blend = [
|
||||||
[1, linear],
|
[1, linear],
|
||||||
[1, popcorn(xform2Coefs)]
|
[1, popcorn(xform2Coefs)]
|
||||||
]
|
];
|
||||||
export const xform2Color = 0.844;
|
export const xform2Color = 0.844;
|
||||||
|
|
||||||
export const xform3Weight = 0.42233;
|
export const xform3Weight = 0.42233;
|
||||||
export const xform3Coefs = {
|
export const xform3Coefs = {
|
||||||
a: 1.51523, b: -3.048677, c: 0.724135,
|
a: 1.51523, b: -3.048677, c: 0.724135,
|
||||||
d: 0.740356, e: -1.455964, f: -0.362059,
|
d: 0.740356, e: -1.455964, f: -0.362059
|
||||||
}
|
};
|
||||||
export const xform3CoefsPost = identityCoefs;
|
export const xform3CoefsPost = identityCoefs;
|
||||||
export const xform3Variations: VariationBlend = [
|
export const xform3Variations: Blend = [
|
||||||
[1, pdj(pdjParams)]
|
[1, pdj(pdjParams)]
|
||||||
];
|
];
|
||||||
export const xform3Color = 0.349;
|
export const xform3Color = 0.349;
|
||||||
@ -61,18 +60,18 @@ export const xform3Color = 0.349;
|
|||||||
export const xformFinalCoefs = {
|
export const xformFinalCoefs = {
|
||||||
a: 2, b: 0, c: 0,
|
a: 2, b: 0, c: 0,
|
||||||
d: 0, e: 2, f: 0
|
d: 0, e: 2, f: 0
|
||||||
}
|
};
|
||||||
export const xformFinalCoefsPost = identityCoefs;
|
export const xformFinalCoefsPost = identityCoefs;
|
||||||
export const xformFinalVariations: VariationBlend = [
|
export const xformFinalVariations: Blend = [
|
||||||
[1, julia]
|
[1, julia]
|
||||||
]
|
];
|
||||||
export const xformFinalColor = 0;
|
export const xformFinalColor = 0;
|
||||||
|
|
||||||
export const xforms: [number, Transform][] = [
|
export const xforms: [number, Transform][] = [
|
||||||
[xform1Weight, applyPost(xform1CoefsPost, applyTransform(xform1Coefs, xform1Variations))],
|
[xform1Weight, applyPost(xform1CoefsPost, applyTransform(xform1Coefs, xform1Variations))],
|
||||||
[xform2Weight, applyPost(xform2CoefsPost, applyTransform(xform2Coefs, xform2Variations))],
|
[xform2Weight, applyPost(xform2CoefsPost, applyTransform(xform2Coefs, xform2Variations))],
|
||||||
[xform3Weight, applyPost(xform3CoefsPost, applyTransform(xform3Coefs, xform3Variations))],
|
[xform3Weight, applyPost(xform3CoefsPost, applyTransform(xform3Coefs, xform3Variations))]
|
||||||
]
|
];
|
||||||
|
|
||||||
export const xformFinal: Transform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, xformFinalVariations));
|
export const xformFinal: Transform = applyPost(xformFinalCoefsPost, applyTransform(xformFinalCoefs, xformFinalVariations));
|
||||||
|
|
||||||
@ -108,7 +107,7 @@ export const paletteString =
|
|||||||
"1514091413091413081312081211081110081010070F0F07" +
|
"1514091413091413081312081211081110081010070F0F07" +
|
||||||
"0E0E070D0D060C0D060C0C060B0B050A0A05090A05080904" +
|
"0E0E070D0D060C0D060C0C060B0B050A0A05090A05080904" +
|
||||||
"070804060704050704050603040503030403020402010302" +
|
"070804060704050704050603040503030403020402010302" +
|
||||||
"0608070C0D0D1112121617171B1C1D2121222626272B2B2D"
|
"0608070C0D0D1112121617171B1C1D2121222626272B2B2D";
|
||||||
|
|
||||||
function hexToBytes(hex: string) {
|
function hexToBytes(hex: string) {
|
||||||
let bytes: number[] = [];
|
let bytes: number[] = [];
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import { Variation } from './variation'
|
import { Variation } from './variation'
|
||||||
//hidden-end
|
//hidden-end
|
||||||
export type PdjParams = {a: number, b: number, c: number, d: number};
|
export type PdjParams = {
|
||||||
export function pdj({a, b, c, d}: PdjParams): Variation {
|
a: number,
|
||||||
return (x, y) => [
|
b: number,
|
||||||
|
c: number,
|
||||||
|
d: number
|
||||||
|
};
|
||||||
|
export const pdj =
|
||||||
|
({a, b, c, d}: PdjParams): Variation =>
|
||||||
|
(x, y) => [
|
||||||
Math.sin(a * y) - Math.cos(b * x),
|
Math.sin(a * y) - Math.cos(b * x),
|
||||||
Math.sin(c * x) - Math.cos(d * y)
|
Math.sin(c * x) - Math.cos(d * y)
|
||||||
]
|
]
|
||||||
}
|
|
@ -1,11 +1,11 @@
|
|||||||
import { camera, histIndex } from "./camera"
|
import { camera, histIndex } from "./camera";
|
||||||
|
|
||||||
export function plotBinary(x: number, y: number, image: ImageData) {
|
export function plotBinary(x: number, y: number, image: ImageData) {
|
||||||
const [pixelX, pixelY] = camera(x, y, image.width);
|
const [pixelX, pixelY] = camera(x, y, image.width);
|
||||||
const pixelIndex = histIndex(pixelX, pixelY, image.width, 4);
|
if (pixelX < 0 || pixelX >= image.width || pixelY < 0 || pixelY >= image.height)
|
||||||
if (pixelIndex < 0 || pixelIndex > image.data.length) {
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
const pixelIndex = histIndex(pixelX, pixelY, image.width, 4);
|
||||||
|
|
||||||
image.data[pixelIndex] = 0;
|
image.data[pixelIndex] = 0;
|
||||||
image.data[pixelIndex + 1] = 0;
|
image.data[pixelIndex + 1] = 0;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import {Coefs} from './coefs'
|
import { Coefs } from "./transform";
|
||||||
import {Variation} from './variation'
|
import { Variation } from "./variation";
|
||||||
// hidden-end
|
// hidden-end
|
||||||
export function popcorn({c, f}: Coefs): Variation {
|
export const popcorn =
|
||||||
return (x, y) => [
|
({ c, f }: Coefs): Variation =>
|
||||||
|
(x, y) => [
|
||||||
x + c * Math.sin(Math.tan(3 * y)),
|
x + c * Math.sin(Math.tan(3 * y)),
|
||||||
y + f * Math.sin(Math.tan(3 * x))
|
y + f * Math.sin(Math.tan(3 * x))
|
||||||
];
|
];
|
||||||
}
|
|
@ -7,7 +7,8 @@ export function randomChoice<T>(
|
|||||||
);
|
);
|
||||||
let choice = Math.random() * weightSum;
|
let choice = Math.random() * weightSum;
|
||||||
|
|
||||||
for (const [idx, elem] of choices.entries()) {
|
for (const entry of choices.entries()) {
|
||||||
|
const [idx, elem] = entry;
|
||||||
const [weight, t] = elem;
|
const [weight, t] = elem;
|
||||||
if (choice < weight) {
|
if (choice < weight) {
|
||||||
return [idx, t];
|
return [idx, t];
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
export type Transform = (x: number, y: number) => [number, number];
|
export type Transform =
|
||||||
|
(x: number, y: number) =>
|
||||||
|
[number, number];
|
||||||
|
|
||||||
export interface Coefs {
|
export interface Coefs {
|
||||||
a: number, b: number, c: number,
|
a: number,
|
||||||
d: number, e: number, f: number
|
b: number,
|
||||||
|
c: number,
|
||||||
|
d: number,
|
||||||
|
e: number,
|
||||||
|
f: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyCoefs(x: number, y: number, coefs: Coefs): [number, number] {
|
export function applyCoefs(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
coefs: Coefs
|
||||||
|
): [number, number] {
|
||||||
return [
|
return [
|
||||||
(x * coefs.a + y * coefs.b + coefs.c),
|
(x * coefs.a + y * coefs.b + coefs.c),
|
||||||
(x * coefs.d + y * coefs.e + coefs.f)
|
(x * coefs.d + y * coefs.e + coefs.f)
|
||||||
]
|
];
|
||||||
}
|
}
|
@ -1 +1,4 @@
|
|||||||
export type Variation = (x: number, y: number) => [number, number];
|
export type Variation = (
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
) => [number, number];
|
Loading…
Reference in New Issue
Block a user