mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Update documentation
This commit is contained in:
parent
b7eed2297a
commit
06069fdcea
@ -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);
|
||||||
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||||||
|
import {VictoryArea} from "victory";
|
||||||
|
|
||||||
|
function F() {
|
||||||
|
return <VictoryArea data={}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
28
blog/2024-11-15-playing-with-fire/3-log-density/index.mdx
Normal file
28
blog/2024-11-15-playing-with-fire/3-log-density/index.mdx
Normal 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>
|
@ -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%'
|
||||||
|
Loading…
Reference in New Issue
Block a user