mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 08:38:09 -05:00
More writing. Hopefully can finish the first draft soon
This commit is contained in:
parent
b526b02e7b
commit
9b1a3895d0
@ -243,8 +243,8 @@ This allows us to manipulate individual pixels an image,
|
|||||||
and display it on screen.
|
and display it on screen.
|
||||||
|
|
||||||
First, we need to convert from Fractal Flame coordinates to pixel coordinates.
|
First, we need to convert from Fractal Flame coordinates to pixel coordinates.
|
||||||
To simplify things, we'll assume that we're plotting a square image,
|
To simplify things, we'll assume that we're plotting a square image
|
||||||
and we'll focus on the range $[0, 1]$ for both $x$ and $y$:
|
with range $[0, 1]$ for both $x$ and $y$:
|
||||||
|
|
||||||
import cameraSource from "!!raw-loader!./cameraGasket"
|
import cameraSource from "!!raw-loader!./cameraGasket"
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ type PaletteBarProps = {
|
|||||||
palette: number[];
|
palette: number[];
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
const PaletteBar: React.FC<PaletteBarProps> = ({height, palette, children}) => {
|
export const PaletteBar: React.FC<PaletteBarProps> = ({height, palette, children}) => {
|
||||||
const sizingRef = useRef<HTMLDivElement>(null);
|
const sizingRef = useRef<HTMLDivElement>(null);
|
||||||
const [width, setWidth] = useState(0);
|
const [width, setWidth] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -51,10 +51,12 @@ const PaletteBar: React.FC<PaletteBarProps> = ({height, palette, children}) => {
|
|||||||
}
|
}
|
||||||
}, [canvasRef, paletteImage]);
|
}, [canvasRef, paletteImage]);
|
||||||
|
|
||||||
|
const canvasStyle = {filter: useColorMode().colorMode === 'dark' ? 'invert(1)' : ''};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={sizingRef} style={{width: '100%', height}}>
|
<div ref={sizingRef} style={{width: '100%', height}}>
|
||||||
{width > 0 ? <canvas ref={canvasRef} width={width} height={height}/> : null}
|
{width > 0 ? <canvas ref={canvasRef} width={width} height={height} style={canvasStyle}/> : null}
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</>
|
</>
|
||||||
|
@ -6,13 +6,11 @@ authors: [bspeice]
|
|||||||
tags: []
|
tags: []
|
||||||
---
|
---
|
||||||
|
|
||||||
So far, our `plot()` function has been fairly simple; map an input coordinate
|
So far, our `plot()` function has been fairly simple; map an input coordinate to a specific pixel,
|
||||||
to a specific pixel, and color in that pixel.
|
and color in that pixel. This works well for simple function systems (like Sierpinski's Gasket),
|
||||||
This works well for simple function systems (like Sierpinski's Gasket),
|
|
||||||
but more complex systems (like our reference parameters) produce grainy images.
|
but more complex systems (like our reference parameters) produce grainy images.
|
||||||
|
|
||||||
Every additional time we plot a pixel, we're wasting work.
|
In this post, we'll refine the image quality and add color to really make things shine.
|
||||||
Can we do something more intelligent instead?
|
|
||||||
|
|
||||||
<!-- truncate -->
|
<!-- truncate -->
|
||||||
|
|
||||||
@ -22,13 +20,14 @@ Can we do something more intelligent instead?
|
|||||||
This post covers sections 4 and 5 of the Fractal Flame Algorithm paper
|
This post covers sections 4 and 5 of the Fractal Flame Algorithm paper
|
||||||
:::
|
:::
|
||||||
|
|
||||||
To start with, it's worth demonstrating how much work is actually "wasted."
|
To start, it's worth demonstrating how much work is actually "wasted"
|
||||||
Previously, pixels would be either transparent or opaque depending on whether
|
when we treat pixels as a binary "on" (opaque) or "off" (transparent).
|
||||||
we encountered them while running the chaos game.
|
|
||||||
|
|
||||||
We'll render the reference image again, but this time, keep track of how many times
|
We'll render the reference image again, but this time, track each time
|
||||||
we encounter each pixel during the chaos game. At the end, we'll "paint" our final image
|
we encounter each pixel during the chaos game. When the chaos game finishes,
|
||||||
by setting each pixel's transparency based on how frequently we encounter it:
|
find the pixel we encountered most frequently. Finally, "paint" the image
|
||||||
|
by setting each pixel's transparency to ratio of times encountered
|
||||||
|
divided by the maximum value:
|
||||||
|
|
||||||
import CodeBlock from "@theme/CodeBlock";
|
import CodeBlock from "@theme/CodeBlock";
|
||||||
|
|
||||||
@ -40,27 +39,40 @@ import {SquareCanvas} from "../src/Canvas";
|
|||||||
import FlameHistogram from "./FlameHistogram";
|
import FlameHistogram from "./FlameHistogram";
|
||||||
import {paintLinear} from "./paintLinear";
|
import {paintLinear} from "./paintLinear";
|
||||||
|
|
||||||
<SquareCanvas><FlameHistogram quality={5} paint={paintLinear}/></SquareCanvas>
|
<SquareCanvas><FlameHistogram quality={15} paint={paintLinear}/></SquareCanvas>
|
||||||
|
|
||||||
## Log display
|
## Log display
|
||||||
|
|
||||||
Using a histogram to paint our image is definitely a quality improvement,
|
While using a histogram to paint the image improves the quality,
|
||||||
but it produces "ghostly" images. In our reference parameters, the outer circle
|
it also leads to some parts vanishing entirely.
|
||||||
|
In the reference parameters, the outer circle
|
||||||
is preserved, but the interior appears to be missing!
|
is preserved, but the interior appears to be missing!
|
||||||
|
|
||||||
To fix this, we'll introduce the second major innovation of the fractal flame algorithm: tone mapping.
|
To fix this, we'll introduce the second major innovation of the fractal flame algorithm: [tone mapping](https://en.wikipedia.org/wiki/Tone_mapping).
|
||||||
This technique is used in computer graphics to adjust for the fact that
|
This is a technique used in computer graphics to compensate for differences in how
|
||||||
people perceive brightness on a logarithmic scale.
|
computers represent color, and how color is perceived by people.
|
||||||
|
|
||||||
As a concrete example, high dynamic range (HDR) photography uses this technique to capture
|
As a concrete example, high dynamic range (HDR) photography uses this technique to capture
|
||||||
nice images of scenes with very different brightness levels. If you want to take a picture of something dark,
|
nice images of scenes with wide brightness ranges. To take a picture of something dark,
|
||||||
you need a long exposure time. However, long exposures lead to bright spots that "wash out" and become nothing but white.
|
you need a long exposure time. However, long exposures can lead to images that "wash out" and become pure white.
|
||||||
If we take multiple images using different exposure times, we can blend them together to create
|
By taking multiple pictures using different exposure times, we can combine them to create
|
||||||
a final image where everything is visible.
|
a final image where everything is visible.
|
||||||
|
|
||||||
TODO: HDR link?
|
In fractal flames, this "tone map" is accomplished by scaling brightness according to the _logarithm_
|
||||||
|
of how many times we encounter a pixel. This way, "dark spots" (pixels the chaos game visits infrequently)
|
||||||
|
will still be visible, and "bright spots" (pixels the chaos game visits frequently) won't wash out.
|
||||||
|
|
||||||
In fractal flames, this "tone map" is accomplished
|
<details>
|
||||||
|
<summary>Log-scale vibrancy is also why fractal flames appear to be 3D...</summary>
|
||||||
|
|
||||||
|
As explained in the Fractal Flame paper:
|
||||||
|
|
||||||
|
> Where one branch of the fractal crosses another, one may appear to occlude the other
|
||||||
|
> if their densities are different enough because the lesser density is inconsequential in sum.
|
||||||
|
> For example, branches of densities 1000 and 100 might have brightnesses of 30 and 20.
|
||||||
|
> Where they cross the density is 1100, whose brightness is 30.4, which is
|
||||||
|
> hardly distinguishable from 30.
|
||||||
|
</details>
|
||||||
|
|
||||||
import paintLogarithmicSource from "!!raw-loader!./paintLogarithmic"
|
import paintLogarithmicSource from "!!raw-loader!./paintLogarithmic"
|
||||||
|
|
||||||
@ -68,10 +80,29 @@ import paintLogarithmicSource from "!!raw-loader!./paintLogarithmic"
|
|||||||
|
|
||||||
import {paintLogarithmic} from './paintLogarithmic'
|
import {paintLogarithmic} from './paintLogarithmic'
|
||||||
|
|
||||||
<SquareCanvas><FlameHistogram quality={10} paint={paintLogarithmic}/></SquareCanvas>
|
<SquareCanvas><FlameHistogram quality={15} paint={paintLogarithmic}/></SquareCanvas>
|
||||||
|
|
||||||
## Color
|
## Color
|
||||||
|
|
||||||
|
Finally, we'll spice things up with the last innovation introduced by
|
||||||
|
the fractal flame algorithm: color. By including a color coordinate
|
||||||
|
in the chaos game, we can illustrate the transforms that are responsible
|
||||||
|
for each part of an image.
|
||||||
|
|
||||||
|
### Palette
|
||||||
|
|
||||||
|
Our first step is to define a color palette for the image. Fractal flames
|
||||||
|
typically use a palette of 256 colors that transition smoothly
|
||||||
|
from one to another. In the diagram below, each color in our palette is plotted
|
||||||
|
on a small strip. Putting the strips side by side shows the palette for our image:
|
||||||
|
|
||||||
|
import * as params from "../src/params"
|
||||||
|
import {PaletteBar} from "./FlameColor"
|
||||||
|
|
||||||
|
<PaletteBar height="40" palette={params.palette}/>
|
||||||
|
|
||||||
|
### Color coordinate
|
||||||
|
|
||||||
import paintColorSource from "!!raw-loader!./paintColor"
|
import paintColorSource from "!!raw-loader!./paintColor"
|
||||||
|
|
||||||
<CodeBlock language="typescript">{paintColorSource}</CodeBlock>
|
<CodeBlock language="typescript">{paintColorSource}</CodeBlock>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export function paintLogarithmic(width: number, height: number, histogram: number[]): ImageData {
|
export function paintLogarithmic(width: number, height: number, histogram: number[]): ImageData {
|
||||||
const image = new ImageData(width, height);
|
const image = new ImageData(width, height);
|
||||||
|
|
||||||
const histogramLog = new Array<number>();
|
const histogramLog: number[] = [];
|
||||||
histogram.forEach(value => histogramLog.push(Math.log(value)));
|
histogram.forEach(value => histogramLog.push(Math.log(value)));
|
||||||
|
|
||||||
let histogramLogMax = -Infinity;
|
let histogramLogMax = -Infinity;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, {useEffect, useState, createContext, useRef} from "react";
|
import React, {useEffect, useState, createContext, useRef, MouseEvent} from "react";
|
||||||
import {useColorMode} from "@docusaurus/theme-common";
|
import {useColorMode} from "@docusaurus/theme-common";
|
||||||
|
|
||||||
type PainterProps = {
|
type PainterProps = {
|
||||||
@ -8,6 +8,13 @@ type PainterProps = {
|
|||||||
}
|
}
|
||||||
export const PainterContext = createContext<PainterProps>(null)
|
export const PainterContext = createContext<PainterProps>(null)
|
||||||
|
|
||||||
|
const downloadImage = (e: MouseEvent) => {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = "flame.png";
|
||||||
|
link.href = (e.target as HTMLCanvasElement).toDataURL("image/png");
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
|
||||||
type CanvasProps = {
|
type CanvasProps = {
|
||||||
style?: any;
|
style?: any;
|
||||||
children?: React.ReactElement
|
children?: React.ReactElement
|
||||||
@ -86,7 +93,7 @@ export const Canvas: React.FC<CanvasProps> = ({style, children}) => {
|
|||||||
<>
|
<>
|
||||||
<center>
|
<center>
|
||||||
<div ref={sizingRef} style={style}>
|
<div ref={sizingRef} style={style}>
|
||||||
{width > 0 ? <canvas {...canvasProps}/> : null}
|
{width > 0 ? <canvas {...canvasProps} onDoubleClick={downloadImage}/> : null}
|
||||||
</div>
|
</div>
|
||||||
</center>
|
</center>
|
||||||
<PainterContext.Provider value={{width, height, setPainter}}>
|
<PainterContext.Provider value={{width, height, setPainter}}>
|
||||||
|
@ -99,11 +99,8 @@ const config: Config = {
|
|||||||
plugins: [require.resolve('docusaurus-lunr-search')],
|
plugins: [require.resolve('docusaurus-lunr-search')],
|
||||||
stylesheets: [
|
stylesheets: [
|
||||||
{
|
{
|
||||||
href: 'https://cdn.jsdelivr.net/npm/katex@0.13.24/dist/katex.min.css',
|
href: '/katex/katex.min.css',
|
||||||
type: 'text/css',
|
type: 'text/css',
|
||||||
integrity:
|
|
||||||
'sha384-odtC+0UGzzFL/6PNoE8rX/SPcQDXBJ+uRepguP4QkPCm2LBxH3FA3y+fKSiJ+AmM',
|
|
||||||
crossorigin: 'anonymous',
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
future: {
|
future: {
|
||||||
|
Loading…
Reference in New Issue
Block a user