Update documentation

This commit is contained in:
Bradlee Speice 2024-12-01 15:16:30 -05:00
parent b7eed2297a
commit 06069fdcea
6 changed files with 95 additions and 24 deletions

View File

@ -1,5 +1,7 @@
export function plot(x: number, y: number, image: ImageData) { export function plot(x: number, y: number, image: ImageData) {
// Translate (x,y) coordinates to pixel coordinates. // Translate (x,y) coordinates to pixel coordinates;
// also known as a "camera" function.
//
// The display range we care about is x=[0, 1], y=[0, 1], // The display range we care about is x=[0, 1], y=[0, 1],
// so our pixelX and pixelY coordinates are easy to calculate: // so our pixelX and pixelY coordinates are easy to calculate:
const pixelX = Math.floor(x * image.width); const pixelX = Math.floor(x * image.width);

View File

@ -1,9 +0,0 @@
---
slug: 2024/11/playing-with-fire-log-density
title: "Playing with fire: Log-density display"
date: 2024-11-15 14:00:00
authors: [bspeice]
tags: []
---
Testing

View File

@ -0,0 +1,5 @@
import {VictoryArea} from "victory";
function F() {
return <VictoryArea data={}
}

View File

@ -0,0 +1,35 @@
// hidden-start
import {VictoryChart} from "victory";
import {camera, histIndex} from "../src/camera";
// hidden-end
export class PlotHistogram {
public readonly pixels: Uint32Array;
public constructor(private readonly width: number, height: number) {
this.pixels = new Uint32Array(width * height);
}
public plot(x: number, y: number) {
const [pixelX, pixelY] = camera(x, y, this.width);
const pixelIndex = histIndex(pixelX, pixelY, this.width, 1);
this.pixels[pixelIndex] += 1;
}
public getHistogram() {
const data = new Map<number, number>();
this.pixels.forEach(value => {
const bucket = 32 - Math.clz32(value);
if (bucket in data) {
data[bucket] += 1;
} else {
data[bucket] = 1;
}
})
const output: {x: number, y: number}[] = [];
data.forEach((bucket, value) =>
output.push({x: Math.pow(bucket, 2), y: value}));
return output;
}
}

View File

@ -0,0 +1,28 @@
---
slug: 2024/11/playing-with-fire-log-density
title: "Playing with fire: Log-density display"
date: 2024-11-15 14:00:00
authors: [bspeice]
tags: []
---
So far, our `plot()` function has been fairly simple; map an input coordinate
to a specific pixel, and color in that pixel.
This works well for simple function systems (like Sierpinski's Gasket),
but more complex systems (like our reference parameters) produce grainy images.
Every time we "turn on" pixels that have already been enabled, we're wasting work.
Can we do something more intelligent with that information?
<!-- truncate -->
## Image histograms
To start with, it's worth demonstrating how much work is actually "wasted."
We'll render the reference image again, but this time, counting the times
we tried to turn on a pixel.
import CodeBlock from "@theme/CodeBlock";
import plotHistogramSource from "!!raw-loader!./PlotHistogram";
<CodeBlock language="typescript">{plotHistogramSource}</CodeBlock>

View File

@ -16,6 +16,7 @@ export const PainterContext = createContext<PainterProps>(null);
interface CanvasProps { interface CanvasProps {
width?: number; width?: number;
height?: number; height?: number;
hidden?: boolean;
children?: React.ReactNode; children?: React.ReactNode;
} }
@ -23,31 +24,39 @@ interface CanvasProps {
* Draw fractal flames to a canvas. * Draw fractal flames to a canvas.
* *
* This component is a bit involved because it attempts to solve * This component is a bit involved because it attempts to solve
* a couple problems at the same time: * a couple problems at once:
* - Incrementally drawing an image to the canvas * - Incrementally drawing an image to the canvas
* - Interrupting drawing with new parameters on demand * - Interrupting drawing with new parameters
* - Dark mode * - Dark mode
* *
* Image iterators provide a means to draw incremental images; * Running a full render is labor-intensive, so we model it
* iterators can easily checkpoint state, and this component will * as an iterator that yields an image of the current system.
* request the next image on the next animation frame. As a result, * Internally, that iterator is re-queued on each new image;
* the browser should be responsive even though we run CPU-heavy * so long as that image is returned quickly, we keep
* code on the main thread. * the main loop running even with CPU-heavy code.
* *
* Swapping a new iterator allows interrupting a render in progress, * To interrupt drawing, children set the active iterator
* as the canvas completely repaints on each provided image. * through the context provider. This component doesn't care
* about which iterator is in progress, it exists only
* to fetch the next image and paint it to our canvas.
* *
* Finally, check whether dark mode is active, and invert the most * Finally, we make a distinction between "render" and "paint" buffers.
* recent image prior to painting if so. * The render image is provided by the iterator, and then:
* - If light mode is active, draw it to the canvas as-is
* - If dark mode is active, copy the "render" buffer to the "paint" buffer,
* invert colors, and then draw the image
* *
* PainterContext is used to allow child elements to swap in * TODO(bspeice): Can we make this "re-queueing iterator" pattern generic?
* new iterators. * It would be nice to have iterators returning arbitrary objects,
* but we rely on contexts to manage the iterator, and there's
* no good way to make those generic.
* *
* @param width Canvas draw width * @param width Canvas draw width
* @param height Canvas draw height * @param height Canvas draw height
* @param hidden Hide the canvas
* @param children Child elements * @param children Child elements
*/ */
export default function Canvas({width, height, children}: CanvasProps) { export default function Canvas({width, height, hidden, children}: CanvasProps) {
const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D>(null); const [canvasCtx, setCanvasCtx] = useState<CanvasRenderingContext2D>(null);
const canvasRef = useCallback(node => { const canvasRef = useCallback(node => {
if (node !== null) { if (node !== null) {
@ -134,6 +143,7 @@ export default function Canvas({width, height, children}: CanvasProps) {
ref={canvasRef} ref={canvasRef}
width={width} width={width}
height={height} height={height}
hidden={hidden ?? false}
style={{ style={{
aspectRatio: width / height, aspectRatio: width / height,
width: '80%' width: '80%'