mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
Flame blending example
This commit is contained in:
parent
137bd74d4d
commit
ce5a28b7bd
@ -3,16 +3,9 @@ import Canvas from "../src/Canvas";
|
|||||||
import { Params, chaosGameWeighted } from "./chaosGameWeighted";
|
import { Params, chaosGameWeighted } from "./chaosGameWeighted";
|
||||||
import TeX from '@matejmazur/react-katex';
|
import TeX from '@matejmazur/react-katex';
|
||||||
|
|
||||||
type Transform = (x: number, y: number) => [number, number];
|
import styles from "../src/css/styles.module.css"
|
||||||
|
|
||||||
function WeightInput({value, setValue, children}) {
|
type Transform = (x: number, y: number) => [number, number];
|
||||||
return (
|
|
||||||
<div style={{paddingLeft: '1.5em', paddingRight: '1.5em'}}>
|
|
||||||
{children}
|
|
||||||
<input type={'range'} min={0} max={1} step={.01} style={{width: '100%'}} value={value} onInput={e => setValue(Number(e.currentTarget.value))}/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function GasketWeighted() {
|
export default function GasketWeighted() {
|
||||||
const image = new ImageData(600, 600);
|
const image = new ImageData(600, 600);
|
||||||
@ -42,19 +35,23 @@ export default function GasketWeighted() {
|
|||||||
setGame(chaosGameWeighted(params))
|
setGame(chaosGameWeighted(params))
|
||||||
}, [f0Weight, f1Weight, f2Weight]);
|
}, [f0Weight, f1Weight, f2Weight]);
|
||||||
|
|
||||||
|
const weightInput = (title, weight, setWeight) => (
|
||||||
|
<>
|
||||||
|
<div className={styles.inputDiv}>
|
||||||
|
<p><TeX>{title}</TeX> weight:<span style={{float: 'right'}}>{weight}</span></p>
|
||||||
|
<input type={'range'} min={0} max={1} step={.01} style={{width: '100%'}} value={weight}
|
||||||
|
onInput={e => setWeight(Number(e.currentTarget.value))}/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Canvas width={image.width} height={image.height} painter={game}/>
|
<Canvas width={image.width} height={image.height} painter={game}/>
|
||||||
<div style={{paddingTop: '1em', display: 'grid', gridTemplateColumns: 'auto auto auto'}}>
|
<div style={{paddingTop: '1em', display: 'grid', gridTemplateColumns: 'auto auto auto'}}>
|
||||||
<WeightInput value={f0Weight} setValue={setF0Weight}>
|
{weightInput("F_0", f0Weight, setF0Weight)}
|
||||||
<p><TeX>F_0</TeX> weight:<span style={{float: 'right'}}>{f0Weight}</span></p>
|
{weightInput("F_1", f1Weight, setF1Weight)}
|
||||||
</WeightInput>
|
{weightInput("F_2", f2Weight, setF2Weight)}
|
||||||
<WeightInput value={f1Weight} setValue={setF1Weight}>
|
|
||||||
<p><TeX>F_1</TeX> weight:<span style={{float: 'right'}}>{f1Weight}</span></p>
|
|
||||||
</WeightInput>
|
|
||||||
<WeightInput value={f2Weight} setValue={setF2Weight}>
|
|
||||||
<p><TeX>F_2</TeX> weight:<span style={{float: 'right'}}>{f2Weight}</span></p>
|
|
||||||
</WeightInput>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -2,9 +2,6 @@ function Gasket() {
|
|||||||
// Hint: try increasing the iteration count
|
// Hint: try increasing the iteration count
|
||||||
const iterations = 10000;
|
const iterations = 10000;
|
||||||
|
|
||||||
// Display the progress every `step` iterations
|
|
||||||
const step = 1000;
|
|
||||||
|
|
||||||
// Hint: negating `x` and `y` creates some interesting images
|
// Hint: negating `x` and `y` creates some interesting images
|
||||||
const transforms = [
|
const transforms = [
|
||||||
(x, y) => [x / 2, y / 2],
|
(x, y) => [x / 2, y / 2],
|
||||||
@ -21,16 +18,12 @@ function Gasket() {
|
|||||||
const i = randomInteger(0, transforms.length);
|
const i = randomInteger(0, transforms.length);
|
||||||
[x, y] = transforms[i](x, y);
|
[x, y] = transforms[i](x, y);
|
||||||
|
|
||||||
if (count > 20) {
|
if (count > 20)
|
||||||
plot(x, y, image);
|
plot(x, y, image);
|
||||||
}
|
|
||||||
|
|
||||||
if (count % 1000 === 0) {
|
if (count % 1000 === 0)
|
||||||
yield image;
|
yield image;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yield image;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -40,5 +33,4 @@ function Gasket() {
|
|||||||
painter={chaosGame()}/>
|
painter={chaosGame()}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
render(<Gasket/>)
|
render(<Gasket/>)
|
||||||
|
@ -19,13 +19,11 @@ export function* chaosGameWeighted({transforms, image, iterations, step}: Params
|
|||||||
// highlight-end
|
// highlight-end
|
||||||
[x, y] = transform(x, y);
|
[x, y] = transform(x, y);
|
||||||
|
|
||||||
if (i > 20) {
|
if (i > 20)
|
||||||
plot(x, y, image);
|
plot(x, y, image);
|
||||||
}
|
|
||||||
|
|
||||||
if (i % step === 0) {
|
if (i % step === 0)
|
||||||
yield image;
|
yield image;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
yield image;
|
yield image;
|
||||||
|
@ -13,13 +13,10 @@ Wikipedia [describes](https://en.wikipedia.org/wiki/Fractal_flame) fractal flame
|
|||||||
I think of them a different way: beauty in mathematics.
|
I think of them a different way: beauty in mathematics.
|
||||||
|
|
||||||
import isDarkMode from '@site/src/isDarkMode'
|
import isDarkMode from '@site/src/isDarkMode'
|
||||||
import bannerDark from '../banner-dark.png'
|
import banner from '../banner.png'
|
||||||
import bannerLight from '../banner-light.png'
|
|
||||||
|
|
||||||
<center>
|
<center>
|
||||||
<!-- Why are these backwards? -->
|
<img src={banner} style={{filter: isDarkMode() ? '' : 'invert(1)'}}/>
|
||||||
<img src={bannerLight} hidden={isDarkMode()}/>
|
|
||||||
<img src={bannerDark} hidden={!isDarkMode()}/>
|
|
||||||
</center>
|
</center>
|
||||||
|
|
||||||
<!-- truncate -->
|
<!-- truncate -->
|
||||||
@ -204,6 +201,4 @@ import chaosGameWeightedSource from "!!raw-loader!./chaosGameWeighted";
|
|||||||
import BrowserOnly from "@docusaurus/BrowserOnly";
|
import BrowserOnly from "@docusaurus/BrowserOnly";
|
||||||
import GasketWeighted from "./GasketWeighted"
|
import GasketWeighted from "./GasketWeighted"
|
||||||
|
|
||||||
<BrowserOnly>
|
<BrowserOnly>{() => <GasketWeighted/>}</BrowserOnly>
|
||||||
<GasketWeighted/>
|
|
||||||
</BrowserOnly>
|
|
142
blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx
Normal file
142
blog/2024-11-15-playing-with-fire/2-transforms/FlameBlend.tsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import {useState} from "react";
|
||||||
|
import { blend } from "./blend";
|
||||||
|
import { applyCoefs, Coefs } from "../src/coefs"
|
||||||
|
import {randomBiUnit} from "../src/randomBiUnit";
|
||||||
|
import {linear} from "../src/linear";
|
||||||
|
import {julia} from "../src/julia";
|
||||||
|
import {popcorn} from "../src/popcorn";
|
||||||
|
import {pdj} from "../src/pdj";
|
||||||
|
import {Variation} from "../src/variation";
|
||||||
|
import {Transform} from "../src/transform";
|
||||||
|
import {
|
||||||
|
pdjParams,
|
||||||
|
xform1Coefs,
|
||||||
|
xform1Weight,
|
||||||
|
xform2Coefs,
|
||||||
|
xform2Weight,
|
||||||
|
xform3Coefs,
|
||||||
|
xform3Weight
|
||||||
|
} from "../src/params";
|
||||||
|
import {randomChoice} from "../src/randomChoice";
|
||||||
|
import {plotBinary} from "../src/plotBinary"
|
||||||
|
import Canvas from "../src/Canvas"
|
||||||
|
|
||||||
|
import styles from "../src/css/styles.module.css"
|
||||||
|
|
||||||
|
type VariationBlend = {
|
||||||
|
linear: number,
|
||||||
|
julia: number,
|
||||||
|
popcorn: number,
|
||||||
|
pdj: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FlameBlend() {
|
||||||
|
const image = new ImageData(400, 400);
|
||||||
|
const quality = 2;
|
||||||
|
const step = 5000;
|
||||||
|
|
||||||
|
const xform1Default: VariationBlend = {
|
||||||
|
linear: 0,
|
||||||
|
julia: 1,
|
||||||
|
popcorn: 0,
|
||||||
|
pdj: 0,
|
||||||
|
}
|
||||||
|
const [xform1Variations, setXform1Variations] = useState(xform1Default)
|
||||||
|
|
||||||
|
const xform2Default: VariationBlend = {
|
||||||
|
linear: 1,
|
||||||
|
julia: 0,
|
||||||
|
popcorn: 1,
|
||||||
|
pdj: 0
|
||||||
|
}
|
||||||
|
const [xform2Variations, setXform2Variations] = useState(xform2Default)
|
||||||
|
|
||||||
|
const xform3Default: VariationBlend = {
|
||||||
|
linear: 0,
|
||||||
|
julia: 0,
|
||||||
|
popcorn: 0,
|
||||||
|
pdj: 1
|
||||||
|
}
|
||||||
|
const [xform3Variations, setXform3Variations] = useState(xform3Default)
|
||||||
|
|
||||||
|
function buildTransform(coefs: Coefs, variations: VariationBlend): Transform {
|
||||||
|
return (x: number, y: number) => {
|
||||||
|
const [varX, varY] = applyCoefs(x, y, coefs);
|
||||||
|
const varFunctions: [number, Variation][] = [
|
||||||
|
[variations.linear, linear],
|
||||||
|
[variations.julia, julia],
|
||||||
|
[variations.popcorn, popcorn(coefs)],
|
||||||
|
[variations.pdj, pdj(pdjParams)]
|
||||||
|
]
|
||||||
|
|
||||||
|
return blend(varX, varY, varFunctions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* chaosGame() {
|
||||||
|
let [x, y] = [randomBiUnit(), randomBiUnit()];
|
||||||
|
const transforms: [number, Transform][] = [
|
||||||
|
[xform1Weight, buildTransform(xform1Coefs, xform1Variations)],
|
||||||
|
[xform2Weight, buildTransform(xform2Coefs, xform2Variations)],
|
||||||
|
[xform3Weight, buildTransform(xform3Coefs, xform3Variations)]
|
||||||
|
]
|
||||||
|
|
||||||
|
const iterations = quality * image.width * image.height;
|
||||||
|
for (let i = 0; i < iterations; i++) {
|
||||||
|
let [_, transform] = randomChoice(transforms);
|
||||||
|
[x, y] = transform(x, y);
|
||||||
|
|
||||||
|
if (i > 20)
|
||||||
|
plotBinary(x, y, image);
|
||||||
|
|
||||||
|
if (i % step === 0) {
|
||||||
|
console.log(`Checking in; iterations=${i}`)
|
||||||
|
yield image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield image;
|
||||||
|
}
|
||||||
|
|
||||||
|
const variationEditor = (title, variations, setVariations) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p style={{gridColumn: '1/-1'}}>{title}:</p>
|
||||||
|
<div className={styles.inputDiv}>
|
||||||
|
<p>Linear: {variations.linear}</p>
|
||||||
|
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.linear}
|
||||||
|
onInput={e => setVariations({...variations, linear: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputDiv}>
|
||||||
|
<p>Julia: {variations.julia}</p>
|
||||||
|
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.julia}
|
||||||
|
onInput={e => setVariations({...variations, julia: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputDiv}>
|
||||||
|
<p>Popcorn: {variations.popcorn}</p>
|
||||||
|
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.popcorn}
|
||||||
|
onInput={e => setVariations({...variations, popcorn: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputDiv}>
|
||||||
|
<p>PDJ: {variations.pdj}</p>
|
||||||
|
<input type={'range'} min={0} max={1} step={0.01} style={{width: '100%'}} value={variations.pdj}
|
||||||
|
onInput={e => setVariations({...variations, pdj: Number(e.currentTarget.value)})}/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Canvas
|
||||||
|
width={image.width}
|
||||||
|
height={image.height}
|
||||||
|
painter={chaosGame()}/>
|
||||||
|
<div style={{paddingTop: '1em', display: 'grid', gridTemplateColumns: 'auto auto auto auto'}}>
|
||||||
|
{variationEditor("Transform 1", xform1Variations, setXform1Variations)}
|
||||||
|
{variationEditor("Transform 2", xform2Variations, setXform2Variations)}
|
||||||
|
{variationEditor("Transform 3", xform3Variations, setXform3Variations)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
14
blog/2024-11-15-playing-with-fire/2-transforms/blend.ts
Normal file
14
blog/2024-11-15-playing-with-fire/2-transforms/blend.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// hidden-start
|
||||||
|
import { Variation } from "../src/variation"
|
||||||
|
// hidden-end
|
||||||
|
export function blend(x: number, y: number, variations: [number, Variation][]): [number, number] {
|
||||||
|
let [finalX, finalY] = [0, 0];
|
||||||
|
|
||||||
|
for (const [weight, variation] of variations) {
|
||||||
|
const [varX, varY] = variation(x, y);
|
||||||
|
finalX += weight * varX;
|
||||||
|
finalY += weight * varY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [finalX, finalY];
|
||||||
|
}
|
@ -15,6 +15,8 @@ This blog post uses a set of reference parameters ([available here](../params.fl
|
|||||||
implementation of the fractal flame algorithm. If you're interested in tweaking the parameters, or generating
|
implementation of the fractal flame algorithm. If you're interested in tweaking the parameters, or generating
|
||||||
your own art, [Apophysis](https://sourceforge.net/projects/apophysis/) is a good introductory tool.
|
your own art, [Apophysis](https://sourceforge.net/projects/apophysis/) is a good introductory tool.
|
||||||
|
|
||||||
|
TODO: Include the reference image here
|
||||||
|
|
||||||
## Transforms and variations
|
## Transforms and variations
|
||||||
|
|
||||||
import CodeBlock from '@theme/CodeBlock'
|
import CodeBlock from '@theme/CodeBlock'
|
||||||
@ -120,8 +122,9 @@ import pdjSrc from '!!raw-loader!../src/pdj'
|
|||||||
### Blending
|
### Blending
|
||||||
|
|
||||||
Now, one variation is fun, but we can also combine variations in a single transform by "blending."
|
Now, one variation is fun, but we can also combine variations in a single transform by "blending."
|
||||||
First, each variation is assigned a value that describes how much it affects the transform function ($v_j$).
|
Each variation receives the same $x$ and $y$ inputs, and we add together each variation's $x$ and $y$ outputs.
|
||||||
Afterward, sum up the $x$ and $y$ values respectively:
|
We'll also give each variation a weight ($v_j$) that scales the output, which allows us to control
|
||||||
|
how much each variation contributes to the transform:
|
||||||
|
|
||||||
$$
|
$$
|
||||||
F_i(x,y) = \sum_{j} v_{ij} V_j(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) = \sum_{j} v_{ij} V_j(a_i \cdot x + b_i \cdot y + c_i, \hspace{0.2cm} d_i \cdot x + e_i \cdot y + f_i)
|
||||||
@ -132,3 +135,7 @@ The formula looks intimidating, but it's not hard to implement:
|
|||||||
TODO: Blending implementation?
|
TODO: Blending implementation?
|
||||||
|
|
||||||
And with that in place, we have enough to render a first full fractal flame:
|
And with that in place, we have enough to render a first full fractal flame:
|
||||||
|
|
||||||
|
import FlameBlend from "./FlameBlend";
|
||||||
|
|
||||||
|
<FlameBlend/>
|
Binary file not shown.
Before Width: | Height: | Size: 558 KiB |
Before Width: | Height: | Size: 557 KiB After Width: | Height: | Size: 557 KiB |
44
blog/2024-11-15-playing-with-fire/src/camera.ts
Normal file
44
blog/2024-11-15-playing-with-fire/src/camera.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Translate values in the flame coordinate system to pixel coordinates
|
||||||
|
*
|
||||||
|
* The way `flam3` actually calculates the "camera" for mapping a point
|
||||||
|
* to its pixel coordinate is fairly involved - it also needs to calculate
|
||||||
|
* zoom and rotation (see the bucket accumulator code in rect.c).
|
||||||
|
* We'll make some simplifying assumptions:
|
||||||
|
* - The final image is square
|
||||||
|
* - We want to plot the range [-2, 2]
|
||||||
|
*
|
||||||
|
* The reference parameters were designed in Apophysis, which uses the
|
||||||
|
* range [-2, 2] by default (the `scale` parameter in XML defines the
|
||||||
|
* "pixels per unit", and with the default zoom, is chosen to give a
|
||||||
|
* range of [-2, 2]).
|
||||||
|
*
|
||||||
|
* @param x point in the range [-2, 2]
|
||||||
|
* @param y point in the range [-2, 2]
|
||||||
|
* @param size image size
|
||||||
|
* @returns pair of pixel coordinates
|
||||||
|
*/
|
||||||
|
export function camera(x: number, y: number, size: number): [number, number] {
|
||||||
|
return [Math.floor(((x + 2) * size) / 4), Math.floor(((y + 2) * size) / 4)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate values in pixel coordinates to a 1-dimensional array index
|
||||||
|
*
|
||||||
|
* Unlike the camera function, this mapping doesn't assume a square image,
|
||||||
|
* and only requires knowing the image width.
|
||||||
|
*
|
||||||
|
* The stride parameter is used to calculate indices that take into account
|
||||||
|
* how many "values" each pixel has. For example, in an ImageData, each pixel
|
||||||
|
* has a red, green, blue, and alpha component per pixel, so a stride of 4
|
||||||
|
* is appropriate. For situations where there are separate red/green/blue/alpha
|
||||||
|
* arrays per pixel, a stride of 1 is appropriate
|
||||||
|
*
|
||||||
|
* @param x point in the range [0, size)
|
||||||
|
* @param y point in the range [0, size)
|
||||||
|
* @param width width of image in pixel units
|
||||||
|
* @param stride values per pixel coordinate
|
||||||
|
*/
|
||||||
|
export function histIndex(x: number, y: number, width: number, stride: number): number {
|
||||||
|
return y * (width * stride) + x * stride;
|
||||||
|
}
|
@ -2,3 +2,10 @@ export interface Coefs {
|
|||||||
a: number, b: number, c: number,
|
a: number, b: number, c: number,
|
||||||
d: number, e: number, f: number
|
d: number, e: number, f: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyCoefs(x: number, y: number, coefs: Coefs) {
|
||||||
|
return [
|
||||||
|
(x * coefs.a + y * coefs.b + coefs.c),
|
||||||
|
(x * coefs.d + y * coefs.e + coefs.f)
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
.inputDiv {
|
||||||
|
padding-left: .5em;
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
@ -7,13 +7,18 @@ import { Coefs } from './coefs';
|
|||||||
import { linear } from './linear'
|
import { linear } from './linear'
|
||||||
import { julia } from './julia'
|
import { julia } from './julia'
|
||||||
import { popcorn } from './popcorn'
|
import { popcorn } from './popcorn'
|
||||||
import { pdj } from './pdj'
|
import {pdj, PdjParams} from './pdj'
|
||||||
|
import {Variation} from "./variation"
|
||||||
|
|
||||||
export const identityCoefs: Coefs = {
|
export const identityCoefs: Coefs = {
|
||||||
a: 1, b: 0, c: 0,
|
a: 1, b: 0, c: 0,
|
||||||
d: 0, e: 1, f: 0,
|
d: 0, e: 1, f: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const pdjParams: PdjParams = {
|
||||||
|
a: 1.09358, b: 2.13048, c: 2.54127, d: 2.37267
|
||||||
|
}
|
||||||
|
|
||||||
export const xform1Weight = 0.56453495;
|
export const xform1Weight = 0.56453495;
|
||||||
export const xform1Coefs = {
|
export const xform1Coefs = {
|
||||||
a: -1.381068, b: -1.381068, c: 0,
|
a: -1.381068, b: -1.381068, c: 0,
|
||||||
@ -47,7 +52,7 @@ export const xform3Coefs = {
|
|||||||
}
|
}
|
||||||
export const xform3CoefsPost = identityCoefs;
|
export const xform3CoefsPost = identityCoefs;
|
||||||
export const xform3Variations = [
|
export const xform3Variations = [
|
||||||
[1, pdj(1.09358, 2.13048, 2.54127, 2.37267)]
|
[1, pdj(pdjParams)]
|
||||||
];
|
];
|
||||||
export const xform3Color = 0.349;
|
export const xform3Color = 0.349;
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// hidden-start
|
// hidden-start
|
||||||
import { Variation } from './variation'
|
import { Variation } from './variation'
|
||||||
//hidden-end
|
//hidden-end
|
||||||
export function pdj(a: number, b: number, c: number, d: number): Variation {
|
export type PdjParams = {a: number, b: number, c: number, d: number};
|
||||||
|
export function pdj({a, b, c, d}: PdjParams): Variation {
|
||||||
return (x, y) => [
|
return (x, y) => [
|
||||||
Math.sin(a * y) - Math.cos(b * x),
|
Math.sin(a * y) - Math.cos(b * x),
|
||||||
Math.sin(c * x) - Math.cos(d * y)
|
Math.sin(c * x) - Math.cos(d * y)
|
||||||
|
17
blog/2024-11-15-playing-with-fire/src/plotBinary.ts
Normal file
17
blog/2024-11-15-playing-with-fire/src/plotBinary.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { camera, histIndex } from "./camera"
|
||||||
|
|
||||||
|
export function plotBinary(x: number, y: number, image: ImageData) {
|
||||||
|
const [pixelX, pixelY] = camera(x, y, image.width);
|
||||||
|
if (
|
||||||
|
pixelX < 0 || pixelX >= image.width || pixelY < 0 || pixelY > image.height
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pixelIndex = histIndex(pixelX, pixelY, image.width, 4);
|
||||||
|
|
||||||
|
image.data[pixelIndex] = 0;
|
||||||
|
image.data[pixelIndex + 1] = 0;
|
||||||
|
image.data[pixelIndex + 2] = 0;
|
||||||
|
image.data[pixelIndex + 3] = 0xff;
|
||||||
|
}
|
@ -1,9 +1 @@
|
|||||||
import { Coefs } from './coefs'
|
export type Transform = (x: number, y: number) => [number, number];
|
||||||
import { Variation } from './variation'
|
|
||||||
|
|
||||||
export interface Transform {
|
|
||||||
coefs: Coefs,
|
|
||||||
variations: [number, Variation][],
|
|
||||||
coefsPost: Coefs,
|
|
||||||
color: number
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user