Use a sizing ref to avoid resizing the canvas

This commit is contained in:
Bradlee Speice 2024-12-11 17:28:04 -05:00
parent c3c3c65614
commit f643996128
4 changed files with 55 additions and 44 deletions

View File

@ -2,14 +2,32 @@ 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 "./paintColor"; import {colorFromPalette} from "./paintColor";
import {chaosGameColor, 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";
const paletteBarPainter = (palette: number[]) => type PaletteBarProps = {
(width: number, height: number) => { height: number;
palette: number[];
children?: React.ReactNode;
}
const PaletteBar: React.FC<PaletteBarProps> = ({height, palette, children}) => {
const sizingRef = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState(0);
useEffect(() => {
if (sizingRef) {
setWidth(sizingRef.current.offsetWidth);
}
}, [sizingRef]);
const canvasRef = useRef<HTMLCanvasElement>(null);
const paletteImage = useMemo(() => {
if (width === 0) {
return;
}
const image = new ImageData(width, height); const image = new ImageData(width, height);
for (let x = 0; x < width; x++) { for (let x = 0; x < width; x++) {
const colorIndex = x / width; const colorIndex = x / width;
@ -23,21 +41,20 @@ const paletteBarPainter = (palette: number[]) =>
image.data[pixelIndex + 3] = 0xff; image.data[pixelIndex + 3] = 0xff;
} }
} }
return image;
}
type PaletteBarProps = { return image;
height: number; }, [width, height, palette]);
palette: number[];
children?: React.ReactNode; useEffect(() => {
} if (canvasRef && paletteImage) {
const PaletteBar: React.FC<PaletteBarProps> = ({height, palette, children}) => { canvasRef.current.getContext("2d").putImageData(paletteImage, 0, 0);
const painter = useMemo(() => paletteBarPainter(palette), [palette]); }
}, [canvasRef, paletteImage]);
return ( return (
<> <>
<div style={{width: '100%', height, marginTop: '1em', marginBottom: '1em'}}> <div ref={sizingRef} style={{width: '100%', height}}>
{/*<AutoSizingCanvas painter={painter}/>*/} {width > 0 ? <canvas ref={canvasRef} width={width} height={height}/> : null}
</div> </div>
{children} {children}
</> </>
@ -115,7 +132,6 @@ export default function FlameColor({quality, children}: Props) {
height, height,
transforms: params.xforms, transforms: params.xforms,
final: params.xformFinal, final: params.xformFinal,
quality,
palette: params.palette, palette: params.palette,
colors: [xform1Color, xform2Color, xform3Color], colors: [xform1Color, xform2Color, xform3Color],
finalColor: xformFinalColor finalColor: xformFinalColor

View File

@ -4,11 +4,10 @@ import {PainterContext} from "../src/Canvas";
import {chaosGameHistogram} from "./chaosGameHistogram"; import {chaosGameHistogram} from "./chaosGameHistogram";
type Props = { type Props = {
quality?: number; paint: (width: number, height: number, histogram: number[]) => ImageData;
paint: (width: number, histogram: Uint32Array) => ImageData;
children?: React.ReactElement; children?: React.ReactElement;
} }
export default function FlameHistogram({quality, paint, children}: Props) { export default function FlameHistogram({paint, children}: Props) {
const {width, height, setPainter} = useContext(PainterContext); const {width, height, setPainter} = useContext(PainterContext);
useEffect(() => { useEffect(() => {
@ -17,11 +16,10 @@ export default function FlameHistogram({quality, paint, children}: Props) {
height, height,
transforms, transforms,
final, final,
quality,
paint paint
} }
setPainter(chaosGameHistogram(gameParams)); setPainter(chaosGameHistogram(gameParams));
}, []); }, [width, height]);
return children; return children;
} }

View File

@ -17,7 +17,7 @@ function mixColor(color1: number, color2: number, colorSpeed: number) {
return color1 * (1 - colorSpeed) + color2 * colorSpeed; return color1 * (1 - colorSpeed) + color2 * colorSpeed;
} }
type Props = ChaosGameFinalProps & { export type Props = ChaosGameFinalProps & {
palette: number[]; palette: number[];
colors: TransformColor[]; colors: TransformColor[];
finalColor: TransformColor; finalColor: TransformColor;

View File

@ -14,8 +14,17 @@ type CanvasProps = {
children?: React.ReactElement children?: React.ReactElement
} }
export const Canvas: React.FC<CanvasProps> = ({style, children}) => { export const Canvas: React.FC<CanvasProps> = ({style, children}) => {
const canvasRef = useRef<HTMLCanvasElement>(null); const sizingRef = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
useEffect(() => {
if (sizingRef.current) {
setWidth(sizingRef.current.offsetWidth);
setHeight(sizingRef.current.offsetHeight);
}
}, [sizingRef]);
const canvasRef = useRef<HTMLCanvasElement>(null);
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
useEffect(() => { useEffect(() => {
if (!canvasRef.current) { if (!canvasRef.current) {
@ -35,16 +44,7 @@ export const Canvas: React.FC<CanvasProps> = ({style, children}) => {
observer.unobserve(canvasRef.current); observer.unobserve(canvasRef.current);
} }
} }
}, [canvasRef]); }, [canvasRef.current]);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
useEffect(() => {
if (canvasRef.current) {
setWidth(canvasRef.current.offsetWidth);
setHeight(canvasRef.current.offsetHeight);
}
}, [canvasRef]);
const [imageHolder, setImageHolder] = useState<[ImageData]>(null); const [imageHolder, setImageHolder] = useState<[ImageData]>(null);
useEffect(() => { useEffect(() => {
@ -76,25 +76,22 @@ export const Canvas: React.FC<CanvasProps> = ({style, children}) => {
} }
}, [painter]); }, [painter]);
const {colorMode} = useColorMode(); const filter = useColorMode().colorMode === 'dark' ? 'invert(1)' : '';
return ( return (
<> <>
<canvas <center>
ref={canvasRef} <div ref={sizingRef} style={style}>
width={width} {width > 0 ? <canvas ref={canvasRef} width={width} height={height} style={{filter}}/> : null}
height={height} </div>
style={{ </center>
filter: colorMode === 'dark' ? 'invert(1)' : '',
...style
}}
/>
<PainterContext.Provider value={{width, height, setPainter}}> <PainterContext.Provider value={{width, height, setPainter}}>
<BrowserOnly>{() => children}</BrowserOnly> {width > 0 ? children : null}
</PainterContext.Provider> </PainterContext.Provider>
</> </>
) )
} }
export const SquareCanvas: React.FC<CanvasProps> = ({style, children}) => { export const SquareCanvas: React.FC<CanvasProps> = ({style, children}) => {
return <Canvas style={{width: '100%', aspectRatio: '1/1', ...style}} children={children}/> return <center><Canvas style={{width: '75%', aspectRatio: '1/1', ...style}} children={children}/></center>
} }