mirror of
				https://github.com/bspeice/speice.io
				synced 2025-11-03 18:10:32 -05:00 
			
		
		
		
	Color mixing is working
...even though I kinda hate the code for it
This commit is contained in:
		@ -18,32 +18,32 @@ export const CoefEditor = ({title, isPost, coefs, setCoefs, resetCoefs}: Props)
 | 
			
		||||
            <p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>{title} {resetButton}</p>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <p>{isPost ? <TeX>\alpha</TeX> : 'a'}: {coefs.a}</p>
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.a}
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} value={coefs.a}
 | 
			
		||||
                       onInput={e => setCoefs({...coefs, a: Number(e.currentTarget.value)})}/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <p>{isPost ? <TeX>\beta</TeX> : 'b'}: {coefs.b}</p>
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.b}
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} value={coefs.b}
 | 
			
		||||
                       onInput={e => setCoefs({...coefs, b: Number(e.currentTarget.value)})}/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <p>{isPost ? <TeX>\gamma</TeX> : 'c'}: {coefs.c}</p>
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.c}
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} value={coefs.c}
 | 
			
		||||
                       onInput={e => setCoefs({...coefs, c: Number(e.currentTarget.value)})}/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <p>{isPost ? <TeX>\delta</TeX> : 'd'}: {coefs.d}</p>
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.d}
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} value={coefs.d}
 | 
			
		||||
                       onInput={e => setCoefs({...coefs, d: Number(e.currentTarget.value)})}/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <p>{isPost ? <TeX>\epsilon</TeX> : 'e'}: {coefs.e}</p>
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.e}
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} value={coefs.e}
 | 
			
		||||
                       onInput={e => setCoefs({...coefs, e: Number(e.currentTarget.value)})}/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <p>{isPost ? <TeX>\zeta</TeX> : 'f'}: {coefs.f}</p>
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} style={{width: '100%'}} value={coefs.f}
 | 
			
		||||
                <input type={'range'} min={0} max={2} step={0.01} value={coefs.f}
 | 
			
		||||
                       onInput={e => setCoefs({...coefs, f: Number(e.currentTarget.value)})}/>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -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<ColorSliderProps> = ({title, palette, color, setColor, children}) => {
 | 
			
		||||
    const swatch = useMemo(() => colorSwatch(50, 20, palette, color), [palette, color]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <p>{title}: {color}</p>
 | 
			
		||||
                <input type={'range'} min={0} max={1} step={.01} style={{width: '100%'}} value={color}
 | 
			
		||||
                       onInput={e => setColor(Number(e.currentTarget.value))}/>
 | 
			
		||||
            </div>
 | 
			
		||||
            {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<PaletteBarProps> = ({height, palette, sizingStyle, children}) => {
 | 
			
		||||
@ -61,6 +35,76 @@ const PaletteBar: React.FC<PaletteBarProps> = ({height, palette, sizingStyle, ch
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AutoSizingCanvasProps = {
 | 
			
		||||
    painter: (width: number, height: number) => ImageData;
 | 
			
		||||
}
 | 
			
		||||
const AutoSizingCanvas: React.FC<AutoSizingCanvasProps> = ({painter}) => {
 | 
			
		||||
    const sizingRef = useRef<HTMLDivElement>(null);
 | 
			
		||||
    const [width, setWidth] = useState<number>(0);
 | 
			
		||||
    const [height, setHeight] = useState<number>(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 (
 | 
			
		||||
        <div ref={sizingRef}><InvertibleCanvas width={width} height={height} image={image}/></div>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<ColorEditorProps> = ({title, palette, transformColor, setTransformColor, resetTransformColor, children}) => {
 | 
			
		||||
    const painter = useMemo(() => colorSwatchPainter(palette, transformColor.color), [palette, transformColor]);
 | 
			
		||||
    const resetButton = <button className={styles.inputReset} onClick={resetTransformColor}>Reset</button>
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: '2fr 2fr 1fr'}}>
 | 
			
		||||
                <p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>{title} {resetButton}</p>
 | 
			
		||||
                <div className={styles.inputElement}>
 | 
			
		||||
                    <p>Color: {transformColor.color}</p>
 | 
			
		||||
                    <input type={'range'} min={0} max={1} step={.001} value={transformColor.color}
 | 
			
		||||
                           onInput={e => setTransformColor({...transformColor, color: Number(e.currentTarget.value)})}/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className={styles.inputElement}>
 | 
			
		||||
                    <p>Color speed: {transformColor.colorSpeed}</p>
 | 
			
		||||
                    <input type={'range'} min={0} max={1} step={.001} value={transformColor.colorSpeed}
 | 
			
		||||
                           onInput={e => setTransformColor({...transformColor, colorSpeed: Number(e.currentTarget.value)})}/>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className={styles.inputElement}>
 | 
			
		||||
                    <AutoSizingCanvas painter={painter}/>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 = <button className={styles.inputReset} onClick={resetColor}>Reset</button>
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: 'auto auto auto'}}>
 | 
			
		||||
            <p className={styles.inputTitle} style={{gridColumn: '1/-1'}}>Transform Colors {resetButton}</p>
 | 
			
		||||
            <PaletteBar height={40} palette={palette} sizingStyle={{gridColumn: '1/-1'}}/>
 | 
			
		||||
            <ColorSlider title={"Transform 1"} palette={palette} color={xform1Color} setColor={setXform1Color}/>
 | 
			
		||||
            <ColorSlider title={"Transform 2"} palette={palette} color={xform2Color} setColor={setXform2Color}/>
 | 
			
		||||
            <ColorSlider title={"Transform 3"} palette={palette} color={xform3Color} setColor={setXform3Color}/>
 | 
			
		||||
        </div>
 | 
			
		||||
        <>
 | 
			
		||||
            <PaletteBar height={40} palette={params.palette}/>
 | 
			
		||||
            <ColorEditor
 | 
			
		||||
                title={"Transform 1"}
 | 
			
		||||
                palette={params.palette}
 | 
			
		||||
                transformColor={xform1Color}
 | 
			
		||||
                setTransformColor={setXform1Color}
 | 
			
		||||
                resetTransformColor={resetXform1Color}/>
 | 
			
		||||
            <ColorEditor
 | 
			
		||||
                title={"Transform 2"}
 | 
			
		||||
                palette={params.palette}
 | 
			
		||||
                transformColor={xform2Color}
 | 
			
		||||
                setTransformColor={setXform2Color}
 | 
			
		||||
                resetTransformColor={resetXform2Color}/>
 | 
			
		||||
            <ColorEditor
 | 
			
		||||
                title={"Transform 3"}
 | 
			
		||||
                palette={params.palette}
 | 
			
		||||
                transformColor={xform3Color}
 | 
			
		||||
                setTransformColor={setXform3Color}
 | 
			
		||||
                resetTransformColor={resetXform3Color}/>
 | 
			
		||||
            <ColorEditor
 | 
			
		||||
                title={"Transform Final"}
 | 
			
		||||
                palette={params.palette}
 | 
			
		||||
                transformColor={xformFinalColor}
 | 
			
		||||
                setTransformColor={setXformFinalColor}
 | 
			
		||||
                resetTransformColor={resetXformFinalColor}/>
 | 
			
		||||
            {children}
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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<InvertibleCanvasProps> = ({width, height, image}) => {
 | 
			
		||||
export const InvertibleCanvas: React.FC<InvertibleCanvasProps> = ({width, height, image}) => {
 | 
			
		||||
    const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D>(null);
 | 
			
		||||
    const canvasRef = useCallback(node => {
 | 
			
		||||
        if (node !== null) {
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,10 @@
 | 
			
		||||
    margin: 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.inputElement > input {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.inputReset {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    float: right;
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user