Incremental rendering

This commit is contained in:
Bradlee Speice 2024-11-24 18:59:11 -05:00
parent 22a4ffff7c
commit 4c3f4246a4
3 changed files with 64 additions and 61 deletions

View File

@ -1,24 +1,40 @@
// Hint: try increasing the iteration count function Gasket() {
const iterations = 10000; // Hint: try increasing the iteration count
const iterations = 10000;
// Hint: negating `x` and `y` creates some interesting images // Hint: negating `x` and `y` creates some interesting images
const functions = [ const functions = [
(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(image) { const image = new ImageData(600, 600);
var [x, y] = [randomBiUnit(), randomBiUnit()]; function* chaosGame() {
var [x, y] = [randomBiUnit(), randomBiUnit()];
for (var count = 0; count < iterations; count++) { for (var count = 0; count < iterations; count++) {
const i = randomInteger(0, functions.length); const i = randomInteger(0, functions.length);
[x, y] = functions[i](x, y); [x, y] = functions[i](x, y);
if (count > 20) { if (count > 20) {
plot(x, y, image); plot(x, y, image);
}
if (count % 1000 === 0) {
yield image;
}
} }
yield image;
} }
return (
<Canvas
width={image.width}
height={image.height}
painter={chaosGame()}/>
)
} }
render(<Gasket renderFn={chaosGame}/>) render(<Gasket/>)

View File

@ -1,30 +1,12 @@
import React, {useContext, useEffect} from "react";
import randomBiUnit from './biunit'; import randomBiUnit from './biunit';
import plot from './plot'; import plot from './plot';
import randomInteger from './randint'; import randomInteger from './randint';
import Canvas, {ImageContext} from "../src/Canvas"; import Canvas from "../src/Canvas";
interface Props {
renderFn: (image: ImageData) => void;
}
function Render({renderFn}: Props) {
const {width, height, setImage} = useContext(ImageContext);
const image = new ImageData(width, height);
useEffect(() => {
renderFn(image);
setImage(image);
}, []);
return <></>;
}
const Scope = { const Scope = {
plot, plot,
randomBiUnit, randomBiUnit,
randomInteger, randomInteger,
Canvas, Canvas
Gasket: ({renderFn}: Props) =>
<Canvas width={600} height={600}><Render renderFn={renderFn}/></Canvas>
} }
export default Scope; export default Scope;

View File

@ -1,49 +1,54 @@
import React, {createContext, useCallback, useContext, useEffect, useRef, useState} from "react"; import React, {useCallback, useEffect, useState} from "react";
import {useColorMode} from "@docusaurus/theme-common"; import {useColorMode} from "@docusaurus/theme-common";
interface IImageContext {
width: number;
height: number;
setImage: (image: ImageData) => void;
}
export const ImageContext = createContext<IImageContext>(null);
interface Props { interface Props {
width: number; width: number;
height: number; height: number;
painter: Iterator<ImageData>;
children?: React.ReactNode; children?: React.ReactNode;
} }
export default function Canvas({width, height, children}) { export default function Canvas({width, height, painter, children}: Props) {
const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D | null>(null); const {colorMode} = useColorMode();
const [image, setImage] = useState<[ImageData]>(null);
const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D>(null);
const canvasRef = useCallback(node => { const canvasRef = useCallback(node => {
if (node !== null) { if (node !== null) {
setCanvasCtx(node.getContext("2d")); setCanvasCtx(node.getContext("2d"));
} }
}, []); }, []);
const {colorMode} = useColorMode();
const [image, setImage] = useState<ImageData>(new ImageData(width, height));
const paintImage = new ImageData(width, height); const paintImage = new ImageData(width, height);
const paint = () => {
useEffect(() => { if (!canvasCtx || !image) {
if (!canvasCtx) {
return; return;
} }
for (const [index, value] of image.data.entries()) { for (const [index, value] of image[0].data.entries()) {
// If dark mode is active, invert the color scheme if (index % 4 === 3) {
const adjustValue = colorMode === 'light' ? value : 255 - value; // Alpha values are copied as-is
paintImage.data[index] = value;
// Alpha values never change } else {
paintImage.data[index] = index % 4 === 3 ? value : adjustValue; // If dark mode is active, invert the color
paintImage.data[index] = colorMode === 'light' ? value : 255 - value;
}
} }
console.log("Painting image");
canvasCtx.putImageData(paintImage, 0, 0); canvasCtx.putImageData(paintImage, 0, 0);
}, [canvasCtx, colorMode, image]); }
useEffect(paint, [colorMode, image]);
const animate = () => {
const nextImage = painter.next().value;
if (nextImage) {
setImage([nextImage])
requestAnimationFrame(animate);
}
}
useEffect(animate, [canvasCtx]);
return ( return (
<ImageContext.Provider value={{width, height, setImage}}> <>
<canvas <canvas
ref={canvasRef} ref={canvasRef}
width={width} width={width}
@ -54,6 +59,6 @@ export default function Canvas({width, height, children}) {
}} }}
/> />
{children} {children}
</ImageContext.Provider> </>
) )
} }