mirror of
				https://github.com/bspeice/speice.io
				synced 2025-11-03 18:10:32 -05:00 
			
		
		
		
	Auto-sizing canvas, starting cleanup for display on mobile browsers
This commit is contained in:
		@ -1,16 +1,21 @@
 | 
			
		||||
import Canvas, {PainterContext} from "../src/Canvas";
 | 
			
		||||
import {useContext} from "react";
 | 
			
		||||
import {SquareCanvas, PainterContext} from "../src/Canvas";
 | 
			
		||||
import {useContext, useEffect} from "react";
 | 
			
		||||
 | 
			
		||||
export function Render({f}) {
 | 
			
		||||
    const {setPainter} = useContext(PainterContext);
 | 
			
		||||
    setPainter(f);
 | 
			
		||||
    const {width, height, setPainter} = useContext(PainterContext);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (width && height) {
 | 
			
		||||
            const painter = f({width, height});
 | 
			
		||||
            setPainter(painter);
 | 
			
		||||
        }
 | 
			
		||||
    }, [width, height]);
 | 
			
		||||
    return <></>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function Gasket({f}) {
 | 
			
		||||
    return (
 | 
			
		||||
        <Canvas width={500} height={500}>
 | 
			
		||||
        <SquareCanvas>
 | 
			
		||||
            <Render f={f}/>
 | 
			
		||||
        </Canvas>
 | 
			
		||||
        </SquareCanvas>
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import {useEffect, useState, useContext} from "react";
 | 
			
		||||
import {useEffect, useState, useContext, useRef} from "react";
 | 
			
		||||
import {PainterContext} from "../src/Canvas";
 | 
			
		||||
import {chaosGameWeighted} from "./chaosGameWeighted";
 | 
			
		||||
import TeX from '@matejmazur/react-katex';
 | 
			
		||||
@ -30,7 +30,7 @@ export default function GasketWeighted() {
 | 
			
		||||
    const weightInput = (title, weight, setWeight) => (
 | 
			
		||||
        <>
 | 
			
		||||
            <div className={styles.inputElement}>
 | 
			
		||||
                <p><TeX>{title}</TeX> weight: {weight}</p>
 | 
			
		||||
                <p><TeX>{title}</TeX>: {weight}</p>
 | 
			
		||||
                <input type={'range'} min={0} max={1} step={.01} style={{width: '100%'}} value={weight}
 | 
			
		||||
                    onInput={e => setWeight(Number(e.currentTarget.value))}/>
 | 
			
		||||
            </div>
 | 
			
		||||
@ -39,7 +39,7 @@ export default function GasketWeighted() {
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: 'auto auto auto'}}>
 | 
			
		||||
            <div className={styles.inputGroup} style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr'}}>
 | 
			
		||||
                {weightInput("F_0", f0Weight, setF0Weight)}
 | 
			
		||||
                {weightInput("F_1", f1Weight, setF1Weight)}
 | 
			
		||||
                {weightInput("F_2", f2Weight, setF2Weight)}
 | 
			
		||||
 | 
			
		||||
@ -1,30 +1,33 @@
 | 
			
		||||
// Hint: try increasing the iteration count
 | 
			
		||||
const iterations = 10000;
 | 
			
		||||
// Hint: try changing the iteration count
 | 
			
		||||
const iterations = 100000;
 | 
			
		||||
 | 
			
		||||
// Hint: negating `x` and `y` creates some interesting images
 | 
			
		||||
const transforms = [
 | 
			
		||||
    (x, y) => [x / 2, y / 2],
 | 
			
		||||
    (x, y) => [(x + 1) / 2, y / 2],
 | 
			
		||||
    (x, y) => [x / 2, (y + 1) / 2]
 | 
			
		||||
// Hint: negating `x` and `y` creates some cool images
 | 
			
		||||
const xforms = [
 | 
			
		||||
  (x, y) => [x / 2, y / 2],
 | 
			
		||||
  (x, y) => [(x + 1) / 2, y / 2],
 | 
			
		||||
  (x, y) => [x / 2, (y + 1) / 2]
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
function* chaosGame() {
 | 
			
		||||
    let image = new ImageData(500, 500);
 | 
			
		||||
    let [x, y] = [randomBiUnit(), randomBiUnit()];
 | 
			
		||||
function* chaosGame({width, height}) {
 | 
			
		||||
  let img = new ImageData(width, height);
 | 
			
		||||
  let [x, y] = [
 | 
			
		||||
    randomBiUnit(),
 | 
			
		||||
    randomBiUnit()
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
    for (var count = 0; count < iterations; count++) {
 | 
			
		||||
        const i = randomInteger(0, transforms.length);
 | 
			
		||||
        [x, y] = transforms[i](x, y);
 | 
			
		||||
  for (let c = 0; c < iterations; c++) {
 | 
			
		||||
    const i = randomInteger(0, xforms.length);
 | 
			
		||||
    [x, y] = xforms[i](x, y);
 | 
			
		||||
 | 
			
		||||
        if (count > 20)
 | 
			
		||||
            plot(x, y, image);
 | 
			
		||||
    if (c > 20)
 | 
			
		||||
      plot(x, y, img);
 | 
			
		||||
 | 
			
		||||
        if (count % 1000 === 0)
 | 
			
		||||
            yield image;
 | 
			
		||||
    }
 | 
			
		||||
    if (c % 1000 === 0)
 | 
			
		||||
      yield img;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    yield image;
 | 
			
		||||
  yield img;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Wiring so the code above displays properly
 | 
			
		||||
render(<Gasket f={chaosGame()}/>)
 | 
			
		||||
render(<Gasket f={chaosGame}/>)
 | 
			
		||||
 | 
			
		||||
@ -6,27 +6,33 @@ import {Transform} from "../src/transform";
 | 
			
		||||
const iterations = 50_000;
 | 
			
		||||
const step = 1000;
 | 
			
		||||
// hidden-end
 | 
			
		||||
export type ChaosGameWeightedProps = {
 | 
			
		||||
type Props = {
 | 
			
		||||
    width: number,
 | 
			
		||||
    height: number,
 | 
			
		||||
    transforms: [number, Transform][]
 | 
			
		||||
}
 | 
			
		||||
export function* chaosGameWeighted({width, height, transforms}: ChaosGameWeightedProps) {
 | 
			
		||||
    let image = new ImageData(width, height);
 | 
			
		||||
    var [x, y] = [randomBiUnit(), randomBiUnit()];
 | 
			
		||||
export function* chaosGameWeighted(
 | 
			
		||||
    {width, height, transforms}: Props
 | 
			
		||||
) {
 | 
			
		||||
  let img = new ImageData(width, height);
 | 
			
		||||
  let [x, y] = [
 | 
			
		||||
      randomBiUnit(),
 | 
			
		||||
      randomBiUnit()
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < iterations; i++) {
 | 
			
		||||
        // highlight-start
 | 
			
		||||
        const [_, transform] = randomChoice(transforms);
 | 
			
		||||
        // highlight-end
 | 
			
		||||
        [x, y] = transform(x, y);
 | 
			
		||||
  const iterations = width * height * 0.5;
 | 
			
		||||
  for (let c = 0; c < iterations; c++) {
 | 
			
		||||
    // highlight-start
 | 
			
		||||
    const [_, xform] = randomChoice(transforms);
 | 
			
		||||
    // highlight-end
 | 
			
		||||
    [x, y] = xform(x, y);
 | 
			
		||||
 | 
			
		||||
        if (i > 20)
 | 
			
		||||
            plot(x, y, image);
 | 
			
		||||
    if (c > 20)
 | 
			
		||||
      plot(x, y, img);
 | 
			
		||||
 | 
			
		||||
        if (i % step === 0)
 | 
			
		||||
            yield image;
 | 
			
		||||
    }
 | 
			
		||||
    if (c % step === 0)
 | 
			
		||||
      yield img;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    yield image;
 | 
			
		||||
  yield img;
 | 
			
		||||
}
 | 
			
		||||
@ -101,7 +101,7 @@ export const shiftData = simpleData.map(({x, y}) => { return {x: x + 1, y} })
 | 
			
		||||
This is a simple example designed to illustrate the principle. In general, $F_i$ functions have the form:
 | 
			
		||||
 | 
			
		||||
$$
 | 
			
		||||
F_i(x,y) = (a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
 | 
			
		||||
F_i(x,y) = (a_i \cdot x + b_i \cdot y + c_i, d_i \cdot x + e_i \cdot y + f_i)
 | 
			
		||||
$$
 | 
			
		||||
 | 
			
		||||
The parameters ($a_i$, $b_i$, etc.) are values we get to choose. In the example above, we can represent our shift
 | 
			
		||||
@ -126,10 +126,10 @@ Fractal flames use more complex functions to produce a wide variety of images, b
 | 
			
		||||
Using these definitions, we can build the first image. The paper defines a function system for us:
 | 
			
		||||
 | 
			
		||||
$$
 | 
			
		||||
F_0(x, y) = \left({x \over 2}, {y \over 2} \right)
 | 
			
		||||
\hspace{0.8cm}
 | 
			
		||||
F_1(x, y) = \left({{x + 1} \over 2}, {y \over 2} \right)
 | 
			
		||||
\hspace{0.8cm}
 | 
			
		||||
F_0(x, y) = \left({x \over 2}, {y \over 2} \right) \\
 | 
			
		||||
~\\
 | 
			
		||||
F_1(x, y) = \left({{x + 1} \over 2}, {y \over 2} \right) \\
 | 
			
		||||
~\\
 | 
			
		||||
F_2(x, y) = \left({x \over 2}, {{y + 1} \over 2} \right)
 | 
			
		||||
$$
 | 
			
		||||
 | 
			
		||||
@ -141,11 +141,11 @@ Next, how do we find out all the points in $S$? The paper lays out an algorithm
 | 
			
		||||
 | 
			
		||||
$$
 | 
			
		||||
\begin{align*}
 | 
			
		||||
&(x, y) = \text{a random point in the bi-unit square} \\
 | 
			
		||||
&(x, y) = \text{random point in the bi-unit square} \\
 | 
			
		||||
&\text{iterate } \{ \\
 | 
			
		||||
&\hspace{1cm} i = \text{a random integer from 0 to } n - 1 \text{ inclusive} \\
 | 
			
		||||
&\hspace{1cm} i = \text{random integer from 0 to } n - 1 \\
 | 
			
		||||
&\hspace{1cm} (x,y) = F_i(x,y) \\
 | 
			
		||||
&\hspace{1cm} \text{plot}(x,y) \text{ except during the first 20 iterations} \\
 | 
			
		||||
&\hspace{1cm} \text{plot}(x,y) \text{ if iterations} > 20 \\
 | 
			
		||||
\}
 | 
			
		||||
\end{align*}
 | 
			
		||||
$$
 | 
			
		||||
@ -199,6 +199,6 @@ import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted";
 | 
			
		||||
<CodeBlock language={'typescript'}>{chaosGameWeightedSource}</CodeBlock>
 | 
			
		||||
 | 
			
		||||
import GasketWeighted from "./GasketWeighted";
 | 
			
		||||
import Canvas from "../src/Canvas";
 | 
			
		||||
import {SquareCanvas} from "../src/Canvas";
 | 
			
		||||
 | 
			
		||||
<Canvas><GasketWeighted/></Canvas>
 | 
			
		||||
<SquareCanvas><GasketWeighted/></SquareCanvas>
 | 
			
		||||
@ -1,34 +1,52 @@
 | 
			
		||||
export function plot(x: number, y: number, image: ImageData) {
 | 
			
		||||
    // 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],
 | 
			
		||||
    // so our pixelX and pixelY coordinates are easy to calculate:
 | 
			
		||||
    const pixelX = Math.floor(x * image.width);
 | 
			
		||||
    const pixelY = Math.floor(y * image.height);
 | 
			
		||||
/**
 | 
			
		||||
 * ImageData is an array that contains
 | 
			
		||||
 * four elements per pixel (one for each
 | 
			
		||||
 * red, green, blue, and alpha value).
 | 
			
		||||
 * This maps from pixel coordinates
 | 
			
		||||
 * to the array index
 | 
			
		||||
 */
 | 
			
		||||
function imageIndex(
 | 
			
		||||
  width: number,
 | 
			
		||||
  x: number,
 | 
			
		||||
  y: number
 | 
			
		||||
) {
 | 
			
		||||
  return y * (width * 4) + x * 4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    // If we have an (x,y) coordinate outside the display range,
 | 
			
		||||
    // skip it
 | 
			
		||||
    if (
 | 
			
		||||
        pixelX < 0 ||
 | 
			
		||||
        pixelX > image.width ||
 | 
			
		||||
        pixelY < 0 ||
 | 
			
		||||
        pixelY > image.height
 | 
			
		||||
    ) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
export function plot(
 | 
			
		||||
  x: number,
 | 
			
		||||
  y: number,
 | 
			
		||||
  img: ImageData
 | 
			
		||||
) {
 | 
			
		||||
  // Translate (x,y) coordinates
 | 
			
		||||
  // to pixel coordinates.
 | 
			
		||||
  // Also known as a "camera" function.
 | 
			
		||||
  //
 | 
			
		||||
  // The display range is:
 | 
			
		||||
  //  x=[0, 1]
 | 
			
		||||
  //  y=[0, 1]
 | 
			
		||||
  let pixelX = Math.floor(x * img.width);
 | 
			
		||||
  let pixelY = Math.floor(y * img.height);
 | 
			
		||||
 | 
			
		||||
    // ImageData is an array that contains four bytes per pixel
 | 
			
		||||
    // (one for each of the red, green, blue, and alpha values).
 | 
			
		||||
    // The (pixelX, pixelY) coordinates are used to find where
 | 
			
		||||
    // in the image we need to write.
 | 
			
		||||
    const index = pixelY * (image.width * 4) + pixelX * 4;
 | 
			
		||||
  const index = imageIndex(
 | 
			
		||||
      img.width,
 | 
			
		||||
      pixelX,
 | 
			
		||||
      pixelY
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    // Set the pixel to black by writing a 0 to the first three
 | 
			
		||||
    // bytes (red, green, blue), and 256 to the last byte (alpha),
 | 
			
		||||
    // starting at our index:
 | 
			
		||||
    image.data[index] = 0;
 | 
			
		||||
    image.data[index + 1] = 0;
 | 
			
		||||
    image.data[index + 2] = 0;
 | 
			
		||||
    image.data[index + 3] = 0xff;
 | 
			
		||||
  // Skip pixels outside the display range
 | 
			
		||||
  if (
 | 
			
		||||
      index < 0 ||
 | 
			
		||||
      index > img.data.length
 | 
			
		||||
  ) {
 | 
			
		||||
      return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set the pixel to black by writing 0
 | 
			
		||||
  // to the first three elements,
 | 
			
		||||
  // and 255 to the last element
 | 
			
		||||
  img.data[index] = 0;
 | 
			
		||||
  img.data[index + 1] = 0;
 | 
			
		||||
  img.data[index + 2] = 0;
 | 
			
		||||
  img.data[index + 3] = 0xff;
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user