mirror of
				https://github.com/bspeice/speice.io
				synced 2025-11-03 18:10:32 -05:00 
			
		
		
		
	More writing. Hopefully can finish the first draft soon
This commit is contained in:
		@ -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: {
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user