How One Formula Draws Any Image

Tupper's self-referential formula is often described as a mathematical curiosity, but its trick isn't hard to unpack. It plots arbitrary 106x17 pixel grids when given the right constant K. The formula is actually a relation over real coordinates; by restricting x and y to discrete integers, it becomes far simpler to reason about and implement.

Here's the distilled version of the formula used in a typical implementation:

let d = (y / 17n) >> (17n * x + y % 17n);
return d % 2n == 1n;

From Math to JavaScript

An interactive demo that renders Tupper plots is available at https://eliben.github.io/tupperformula/ (source on GitHub). It plots the relation across x in [0, 105] and y in [K, K+16]. The numerical heavy lifting is done with JavaScript's BigInt, which is necessary since K values are hundreds of digits long.

The core function checks one pixel at a time:

function tupperFormula(x, y) {
    let d = (y / 17n) >> (17n * x + y % 17n);
    return d % 2n == 1n;
}

That code looks quite different from the original mathematical inequality, but with integer ranges the simplification is straightforward. Since x and y are natural numbers, floor operations no longer matter. A modulo-2 comparison against 1/2 is just a check for equality with 1. The remaining negative power of two becomes a right-shift of the accumulated value by y/17 bits. The demo's grid generation ties it all together:

const GridWidth = 106;
const GridHeight = 17;
let K = BigInt(Knum.value);

for (let x = 0; x < GridWidth; x++) {
    for (let y = 0; y < GridHeight; y++) {
        Grid.setCell(x, y, tupperFormula(BigInt(x), K + BigInt(y)));
    }
}

Encoding an Image Into K

The formula can produce any image of the given size—so long as the correct K is used. To encode an image, each pixel becomes a single bit: 1 for dark, 0 for light. Reading starts at the bottom-left corner of the plot (x=0, y=K) and moves upward through the first column. At the top of a column (y=K+16), the read continues from the bottom of the next column. This is simply a column-major memory layout for a 2D matrix.

For instance, the first several bits when reading the Tupper plot look like this:

00110010101000100 00101010101111100 ...

After all 106×17 = 1802 bits are collected into a single number IMG, the final constant is K = IMG × 17.

Decoding with the Formula

Decoding works just the reverse way. Consider the first coordinate in the plot, x=0, y=K:

d = (y/17) >> (17x + y%17)
...
substitute x=0, y=K (and recall that K = IMG * 17)
...
d = IMG >> 0

The modulo division by two yields exactly the least significant bit of the encoded number. Moving up one step in y pulls out the second bit:

d = (y/17) >> (17x + y%17)
...
substitute x=0, y=K+1 (and recall that K = IMG * 17)
...
d = IMG >> 1

The pattern holds for every x,y pair across the entire grid: each coordinate maps to a distinct bit off the packed value. That mapping is why the formula works with any 106x17 bit pattern.

The result:

d = (y/17) >> (17x + y%17)
...
x=0  y=K+2:  IMG >> (0 + 2)
x=0  y=K+3:  IMG >> (0 + 3)
...
x=0  y=K+16  IMG >> (0 + 16)
x=1  y=K:    IMG >> (17 + 0)
x=1  y=K+1:  IMG >> (17 + 1)
x=1  y=K+2:  IMG >> (17 + 2)

The demo also includes an encoder that reconstructs K from a selected grid, processing bits in reverse order from the most significant downwards.

Notes on the Original Paper

Jeff Tupper described the relation in his 2001 paper on graphing methods for mathematical formulae:

Screenshot from Tupper's paper describing the formula

The figure in Tupper's paper is rendered flipped on both axes relative to the typical plot used in this discussion, which is why the interactive demo includes flip controls.

Screenshot from Tupper's paper showing the formula itself