diff --git a/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx b/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx index 1eeeb86..4a91a38 100644 --- a/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx +++ b/blog/2024-11-15-playing-with-fire/2-transforms/CoefEditor.tsx @@ -18,32 +18,32 @@ export const CoefEditor = ({title, isPost, coefs, setCoefs, resetCoefs}: Props)

{title} {resetButton}

{isPost ? \alpha : 'a'}: {coefs.a}

- setCoefs({...coefs, a: Number(e.currentTarget.value)})}/>

{isPost ? \beta : 'b'}: {coefs.b}

- setCoefs({...coefs, b: Number(e.currentTarget.value)})}/>

{isPost ? \gamma : 'c'}: {coefs.c}

- setCoefs({...coefs, c: Number(e.currentTarget.value)})}/>

{isPost ? \delta : 'd'}: {coefs.d}

- setCoefs({...coefs, d: Number(e.currentTarget.value)})}/>

{isPost ? \epsilon : 'e'}: {coefs.e}

- setCoefs({...coefs, e: Number(e.currentTarget.value)})}/>

{isPost ? \zeta : 'f'}: {coefs.f}

- setCoefs({...coefs, f: Number(e.currentTarget.value)})}/>
diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx b/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx index 0a80624..c125281 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx +++ b/blog/2024-11-15-playing-with-fire/3-log-density/FlameColor.tsx @@ -1,41 +1,15 @@ import React, {useContext, useEffect, useMemo, useRef, useState} from "react"; import * as params from "../src/params"; -import {PainterContext} from "../src/Canvas"; -import {chaosGameColor} from "./chaosGameColor"; +import {InvertibleCanvas, PainterContext} from "../src/Canvas"; +import {chaosGameColor, ChaosGameColorProps, TransformColor} from "./chaosGameColor"; import styles from "../src/css/styles.module.css"; -import {palette} from "../src/params"; - -const colorSwatch = (width: number, height: number, palette: number[], color: number): ImageData => { - return new ImageData(width, height); -} - -type ColorSliderProps = { - title: string; - palette: number[]; - color: number; - setColor: (color: number) => void; - children?: React.ReactNode; -} -const ColorSlider: React.FC = ({title, palette, color, setColor, children}) => { - const swatch = useMemo(() => colorSwatch(50, 20, palette, color), [palette, color]); - - return ( - <> -
-

{title}: {color}

- setColor(Number(e.currentTarget.value))}/> -
- {children} - - ) -} +import {colorFromPalette} from "@site/blog/2024-11-15-playing-with-fire/3-log-density/color"; type PaletteBarProps = { height: number; palette: number[]; - sizingStyle: any; + sizingStyle?: any; children?: React.ReactNode; } const PaletteBar: React.FC = ({height, palette, sizingStyle, children}) => { @@ -61,6 +35,76 @@ const PaletteBar: React.FC = ({height, palette, sizingStyle, ch ) } +type AutoSizingCanvasProps = { + painter: (width: number, height: number) => ImageData; +} +const AutoSizingCanvas: React.FC = ({painter}) => { + const sizingRef = useRef(null); + const [width, setWidth] = useState(0); + const [height, setHeight] = useState(0); + + useEffect(() => { + if (sizingRef) { + setWidth(sizingRef.current.offsetWidth); + setHeight(sizingRef.current.offsetHeight) + } + }, [sizingRef]); + + const image: [ImageData] = useMemo(() => (width && height) ? [painter(width, height)] : null, [painter, width, height]); + + return ( +
+ ) +} + +const colorSwatchPainter = (palette: number[], color: number) => + (width: number, height: number) => { + const [r, g, b] = colorFromPalette(palette, color); + const image = new ImageData(width, height); + for (let i = 0; i < image.data.length; i += 4) { + image.data[i] = r * 0xff; + image.data[i + 1] = g * 0xff; + image.data[i + 2] = b * 0xff; + image.data[i + 3] = 0xff; + } + + return image; + } + +type ColorEditorProps = { + title: string; + palette: number[]; + transformColor: TransformColor; + setTransformColor: (transformColor: TransformColor) => void; + resetTransformColor: () => void; + children?: React.ReactNode; +} +const ColorEditor: React.FC = ({title, palette, transformColor, setTransformColor, resetTransformColor, children}) => { + const painter = useMemo(() => colorSwatchPainter(palette, transformColor.color), [palette, transformColor]); + const resetButton = + + return ( + <> +
+

{title} {resetButton}

+
+

Color: {transformColor.color}

+ setTransformColor({...transformColor, color: Number(e.currentTarget.value)})}/> +
+
+

Color speed: {transformColor.colorSpeed}

+ setTransformColor({...transformColor, colorSpeed: Number(e.currentTarget.value)})}/> +
+
+ +
+
+ + ) +} + type Props = { quality?: number; children?: React.ReactElement; @@ -68,36 +112,64 @@ type Props = { export default function FlameColor({quality, children}: Props) { const {width, height, setPainter} = useContext(PainterContext); - const [xform1Color, setXform1Color] = useState(params.xform1Color); - const [xform2Color, setXform2Color] = useState(params.xform2Color); - const [xform3Color, setXform3Color] = useState(params.xform3Color); - const resetColor = () => { - setXform1Color(params.xform1Color); - setXform2Color(params.xform2Color); - setXform3Color(params.xform3Color); - } + const xform1ColorDefault: TransformColor = {color: params.xform1Color, colorSpeed: 0.5}; + const [xform1Color, setXform1Color] = useState(xform1ColorDefault); + const resetXform1Color = () => setXform1Color(xform1ColorDefault); + + const xform2ColorDefault: TransformColor = {color: params.xform2Color, colorSpeed: 0.5}; + const [xform2Color, setXform2Color] = useState(xform2ColorDefault); + const resetXform2Color = () => setXform2Color(xform2ColorDefault); + + const xform3ColorDefault: TransformColor = {color: params.xform3Color, colorSpeed: 0.5}; + const [xform3Color, setXform3Color] = useState(xform3ColorDefault); + const resetXform3Color = () => setXform3Color(xform3ColorDefault); + + const xformFinalColorDefault: TransformColor = {color: params.xformFinalColor, colorSpeed: 0}; + const [xformFinalColor, setXformFinalColor] = useState(xformFinalColorDefault); + const resetXformFinalColor = () => setXformFinalColor(xformFinalColorDefault); useEffect(() => { - const gameParams = { + const gameParams: ChaosGameColorProps = { width, height, transforms: params.xforms, final: params.xformFinal, quality, palette: params.palette, - colors: [xform1Color, xform2Color, xform3Color] + colors: [xform1Color, xform2Color, xform3Color], + finalColor: xformFinalColor } setPainter(chaosGameColor(gameParams)); - }, [xform1Color, xform2Color, xform3Color]); + }, [xform1Color, xform2Color, xform3Color, xformFinalColor]); - const resetButton = return ( -
-

Transform Colors {resetButton}

- - - - -
+ <> + + + + + + {children} + ); } \ No newline at end of file diff --git a/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameColor.ts b/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameColor.ts index cb02bc1..11b6ed6 100644 --- a/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameColor.ts +++ b/blog/2024-11-15-playing-with-fire/3-log-density/chaosGameColor.ts @@ -4,11 +4,21 @@ import {randomChoice} from "../src/randomChoice"; import {camera, histIndex} from "../src/camera"; import {colorFromPalette, paintColor} from "./color"; -type ChaosGameHistogramProps = ChaosGameFinalProps & { - palette: number[]; - colors: number[]; +export type TransformColor = { + color: number; + colorSpeed: number; } -export function* chaosGameColor({width, height, transforms, final, palette, colors, quality, step}: ChaosGameHistogramProps) { + +function mixColor(color1: number, color2: number, colorSpeed: number) { + return color1 * (1 - colorSpeed) + color2 * colorSpeed; +} + +export type ChaosGameColorProps = ChaosGameFinalProps & { + palette: number[]; + colors: TransformColor[]; + finalColor: TransformColor; +} +export function* chaosGameColor({width, height, transforms, final, palette, colors, finalColor, quality, step}: ChaosGameColorProps) { let iterations = (quality ?? 1) * width * height; step = step ?? 100_000; @@ -33,8 +43,11 @@ export function* chaosGameColor({width, height, transforms, final, palette, colo if (pixelIndex < 0 || pixelIndex >= alpha.length) continue; - currentColor = (currentColor + colors[transformIndex]) / 2; - const [r, g, b] = colorFromPalette(palette, currentColor); + const transformColor = colors[transformIndex]; + currentColor = mixColor(currentColor, transformColor.color, transformColor.colorSpeed); + + const colorFinal = mixColor(currentColor, finalColor.color, finalColor.colorSpeed); + const [r, g, b] = colorFromPalette(palette, colorFinal); red[pixelIndex] += r; green[pixelIndex] += g; blue[pixelIndex] += b; diff --git a/blog/2024-11-15-playing-with-fire/src/Canvas.tsx b/blog/2024-11-15-playing-with-fire/src/Canvas.tsx index 9a9c998..e4b8fbe 100644 --- a/blog/2024-11-15-playing-with-fire/src/Canvas.tsx +++ b/blog/2024-11-15-playing-with-fire/src/Canvas.tsx @@ -26,7 +26,7 @@ type InvertibleCanvasProps = { * @param hidden Hide the canvas element * @param image Image data to draw on the canvas */ -const InvertibleCanvas: React.FC = ({width, height, image}) => { +export const InvertibleCanvas: React.FC = ({width, height, image}) => { const [canvasCtx, setCanvasCtx] = useState(null); const canvasRef = useCallback(node => { if (node !== null) { diff --git a/blog/2024-11-15-playing-with-fire/src/css/styles.module.css b/blog/2024-11-15-playing-with-fire/src/css/styles.module.css index 4ceecaf..6c16604 100644 --- a/blog/2024-11-15-playing-with-fire/src/css/styles.module.css +++ b/blog/2024-11-15-playing-with-fire/src/css/styles.module.css @@ -22,6 +22,10 @@ margin: 0 } +.inputElement > input { + width: 100%; +} + .inputReset { display: flex; float: right;