Convert baseline renderer to animation

This commit is contained in:
Bradlee Speice 2023-07-18 02:21:01 +00:00
parent 782b00320b
commit 7f44243cd0
4 changed files with 104 additions and 14 deletions

View File

@ -35,14 +35,16 @@ export type CanvasParams = {
size: number; size: number;
qualityMax: number; qualityMax: number;
qualityStep: number; qualityStep: number;
renderer: Renderer; renderer: (size: number) => Renderer;
}; };
export const CanvasRenderer: React.FC<{ params: CanvasParams }> = ({ export const CanvasRenderer: React.FC<{ params: CanvasParams }> = ({
params, params,
}) => { }) => {
const canvasRef = useRef<HTMLCanvasElement | null>(null); const canvasRef = useRef<HTMLCanvasElement | null>(null);
var qualityCurrent: number = 0; var qualityCurrent: number = 0;
var rendererCurrent: Renderer = params.renderer(params.size);
const animate = () => { const animate = () => {
const ctx = canvasRef.current?.getContext("2d"); const ctx = canvasRef.current?.getContext("2d");
@ -51,21 +53,34 @@ export const CanvasRenderer: React.FC<{ params: CanvasParams }> = ({
} }
const image = ctx.createImageData(params.size, params.size); const image = ctx.createImageData(params.size, params.size);
params.renderer.run(params.qualityStep); rendererCurrent.run(params.qualityStep);
params.renderer.render(image); rendererCurrent.render(image);
ctx.putImageData(image, 0, 0); ctx.putImageData(image, 0, 0);
qualityCurrent += params.qualityStep;
if (qualityCurrent < params.qualityMax) { if (qualityCurrent < params.qualityMax) {
qualityCurrent += params.qualityStep;
requestAnimationFrame(animate); requestAnimationFrame(animate);
} }
}; };
const reset = () => {
qualityCurrent = 0;
rendererCurrent = params.renderer(params.size);
requestAnimationFrame(animate);
};
useEffect(() => { useEffect(() => {
if (canvasRef.current) { if (canvasRef.current) {
requestAnimationFrame(animate); requestAnimationFrame(animate);
} }
}, []); }, []);
return <canvas ref={canvasRef} width={params.size} height={params.size} />; return (
<canvas
ref={canvasRef}
width={params.size}
height={params.size}
onClick={reset}
/>
);
}; };

View File

@ -1,3 +1,4 @@
import { CanvasParams } from "./0-canvas";
import { import {
randomBiUnit, randomBiUnit,
randomInteger, randomInteger,

View File

@ -1,10 +1,17 @@
import { randomBiUnit, weightedChoice } from "./0-utility"; import {
Renderer,
histIndex,
imageIndex,
randomBiUnit,
weightedChoice,
} from "./0-utility";
export type Variation = ( export type Variation = (
x: number, x: number,
y: number, y: number,
transformCoefs: Coefs transformCoefs: Coefs
) => [number, number]; ) => [number, number];
export type Coefs = { export type Coefs = {
a: number; a: number;
b: number; b: number;
@ -83,6 +90,66 @@ export class Transform {
} }
} }
export class RendererFlame extends Renderer {
private values = new Uint8Array(this.size * this.size);
constructor(size: number, public readonly transforms: [number, Transform][]) {
super(size);
}
plot(x: number, y: number) {
// By default, Apophysis uses a camera that covers the range [-2, 2]
// (specifically, the `scale` parameter becomes `pixels_per_unit` in flam3)
// Shift the coordinate system to [0, 4], then scale to pixel coordinates
const pixelX = Math.floor(((x + 2) * this.size) / 4);
const pixelY = Math.floor(((y + 2) * this.size) / 4);
if (
pixelX < 0 ||
pixelX >= this.size ||
pixelY < 0 ||
pixelY >= this.size
) {
return;
}
const hIndex = histIndex(pixelX, pixelY, this.size);
this.values[hIndex] = 1;
}
run(quality: number): void {
var x = randomBiUnit();
var y = randomBiUnit();
const iterations = quality * this.size * this.size;
for (var i = 0; i < iterations; i++) {
const [_, transform] = weightedChoice(this.transforms);
[x, y] = transform.apply(x, y);
if (i > 20) {
this.plot(x, y);
}
}
}
render(image: ImageData): void {
for (var x = 0; x < this.size; x++) {
for (var y = 0; y < this.size; y++) {
const hIndex = histIndex(x, y, this.size);
if (!this.values[hIndex]) {
continue;
}
const iIndex = imageIndex(x, y, this.size);
image.data[iIndex + 0] = 0;
image.data[iIndex + 1] = 0;
image.data[iIndex + 2] = 0;
image.data[iIndex + 3] = 0xff;
}
}
}
}
export class Flame { export class Flame {
protected x: number = randomBiUnit(); protected x: number = randomBiUnit();
protected y: number = randomBiUnit(); protected y: number = randomBiUnit();
@ -186,12 +253,10 @@ export const transform3 = new Transform(
[[1, pdj(1.09358, 2.13048, 2.54127, 2.37267)]] [[1, pdj(1.09358, 2.13048, 2.54127, 2.37267)]]
); );
export function renderBaseline(image: ImageData) { export function rendererBaseline(size: number) {
const flame = new Flame([ return new RendererFlame(size, [
[transform1Weight, transform1], [transform1Weight, transform1],
[transform2Weight, transform2], [transform2Weight, transform2],
[transform3Weight, transform3], [transform3Weight, transform3],
]); ]);
render(flame, 1, image);
} }

View File

@ -2,7 +2,7 @@ import Blog from "../../../LayoutBlog";
import { Canvas, CanvasRenderer } from "./0-canvas"; import { Canvas, CanvasRenderer } from "./0-canvas";
import { RendererGasket } from "./1-gasket"; import { RendererGasket } from "./1-gasket";
import { renderBaseline } from "./2a-variations"; import { rendererBaseline } from "./2a-variations";
import { renderPost } from "./2b-post"; import { renderPost } from "./2b-post";
import { renderFinal } from "./2c-final"; import { renderFinal } from "./2c-final";
import { renderBinary } from "./3a-binary"; import { renderBinary } from "./3a-binary";
@ -23,13 +23,22 @@ export default function () {
}); });
return ( return (
<Layout> <Layout>
<CanvasRenderer {/* <CanvasRenderer
params={{ params={{
defaultUrl: "", defaultUrl: "",
size: 400, size: 400,
renderer: new RendererGasket(400), renderer: new RendererGasket(400),
qualityMax: 0.25, qualityMax: 0.3,
qualityStep: 0.25, qualityStep: 0.1,
}}
/> */}
<CanvasRenderer
params={{
defaultUrl: "",
size: 400,
renderer: (size) => rendererBaseline(size),
qualityMax: 1,
qualityStep: 0.1,
}} }}
/> />
{/* <div> {/* <div>