---
slug: 2024/11/playing-with-fire-log-density
title: "Playing with fire: Log-density and color"
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.

In this post, we'll refine the image quality and add color to really make things shine.

<!-- truncate -->

## Image histograms

:::note
This post covers sections 4 and 5 of the Fractal Flame Algorithm paper
:::

To start, it's worth demonstrating how much work is actually "wasted"
when we treat pixels as a binary "on" (opaque) or "off" (transparent).

We'll render the reference image again, but this time, track each time
we encounter each pixel during the chaos game. When the chaos game finishes,
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 paintLinearSource from "!!raw-loader!./paintLinear"

<CodeBlock language="typescript">{paintLinearSource}</CodeBlock>

import {SquareCanvas} from "../src/Canvas";
import FlameHistogram from "./FlameHistogram";
import {paintLinear} from "./paintLinear";

<SquareCanvas><FlameHistogram quality={15} paint={paintLinear}/></SquareCanvas>

## Log display

While using a histogram to paint the image improves the quality,
it also leads to some parts vanishing entirely.
In the reference parameters, the outer circle
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](https://en.wikipedia.org/wiki/Tone_mapping).
This is a technique used in computer graphics to compensate for differences in how
computers represent color, and how color is perceived by people.

As a concrete example, high dynamic range (HDR) photography uses this technique to capture
nice images of scenes with wide brightness ranges. To take a picture of something dark,
you need a long exposure time. However, long exposures can lead to images that "wash out" and become pure white.
By taking multiple pictures using different exposure times, we can combine them to create
a final image where everything is visible.

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.

<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"

<CodeBlock language="typescript">{paintLogarithmicSource}</CodeBlock>

import {paintLogarithmic} from './paintLogarithmic'

<SquareCanvas><FlameHistogram quality={15} paint={paintLogarithmic}/></SquareCanvas>

## 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"

<CodeBlock language="typescript">{paintColorSource}</CodeBlock>

import FlameColor from "./FlameColor";

<SquareCanvas><FlameColor quality={15}/></SquareCanvas>