Folio II · The Measuring of Things

Architecture

How the studio turns a picture into pigment studies — the method, the papers, and the path from file to palette.

The algorithm, in plain words

Imagine spilling every tiny colored speck from your photo onto a table. Each speck is a pixel with a little bit of red, green, and blue mixed in — like three dials on a paint mixer.

A pixel as three paint dials One pixel square connected to red, green, and blue dials that mix into a color point on a table of many points. One pixel R G B Cloud of color points Each pixel = a mix of R, G, B → one point on the table
Fig. 1 — Pixels as paint dials, spilled into a color cloud.

We do not need every speck. First we shrink the picture (about 120 pixels on the long side) so the work stays quick. Nearly see-through pixels are set aside. What remains is a cloud of color points.

Shrinking the image before sampling Full image shrink ~120px RGB list
Fig. 2 — Shrink first, then read the opaque color points.

To build a palette of, say, five colors, we use k-means clustering — a way of sorting similar things into groups:

Four steps of k-means Plant flags, assign points to nearest flag, move flags to group averages, then repeat until stable. 1. Plant flags spread-out starts 2. Sort specks nearest flag wins 3. Move flags slide to average 4. Repeat until the flags barely move assign average ~12 times loop
Fig. 3 — k-means: plant, sort, move, repeat.
  1. Plant a few flags. We pick starting “center” colors that are spread apart in the cloud (so we do not start with five nearly identical reds).
  2. Sort every speck. Each pixel joins the nearest flag — “you look most like this group.”
  3. Move the flags. Each flag slides to the average color of its group — the true middle of that pile of paint.
  4. Repeat. Sort again, move again, about a dozen times, until the flags barely budge.

Those final flag positions are the palette. We run the whole sorting three times and keep the tidiest result. Colors that ended up almost the same are merged. Then we line them up from dark to light so the swatches read like a calm strip of paint chips.

Best of three runs, dedupe, then sort dark to light 3 runs run A run B ★ best merge near twins sort dark light
Fig. 4 — Keep the best run, merge near-twins, order dark → light.

The studio does this four times — for 3, 5, 7, and 10 colors — so you see both a simple summary and a finer breakdown of the same image.

Four palette sizes from the same cloud Same image → four studies k = 3 k = 5 k = 7 k = 10
Fig. 5 — Coarse to fine: 3, 5, 7, and 10 hues.

File structure & function overview

The project is a small static site — three pages of parchment and one engine of ink:

Project files as a tree palette-generation/ index.html Studio UI architecture Docs page styles.css Look & layout script.js Engine ★ README.md Deploy notes script.js powers the studio extract render copy hex PNG export
Fig. 6 — Static site files; script.js is the measuring engine.
index.html The studio desk — upload form, preview, swatches, download buttons.
architecture.html This folio — method, structure, and data path.
styles.css Parchment look, navigation, swatches, and documentation layout.
script.js All extraction, rendering, copy, and PNG export logic.
README.md How to open locally and publish with GitHub Pages.

How the main functions work

Function call map handleImageChange calls loadImage, samplePixels, and renderPalettes. renderPalettes calls extractPalette, which uses bestKMeans and helpers. Downloads use downloadPaletteImage. handleImageChange loadImage samplePixels renderPalettes extractPalette ×4 bestKMeans kMeans initCenters assign / average totalError helpers distance · dedupe · luminance sort · hex downloadPaletteImage parchment PNG export
Fig. 7 — Who calls whom inside script.js.
handleImageChange
Fires when you choose a file. Loads the image, samples pixels, asks for four palettes, and updates the page status.
loadImage
Turns a file URL into a usable browser image, or reports failure.
samplePixels
Draws a small copy of the image on an invisible canvas, reads RGBA bytes, and keeps opaque RGB triples.
extractPalette
Runs k-means for a target size, deduplicates near-twins, and sorts by brightness.
bestKMeans / kMeans
The clustering engine: seed centers, assign pixels, recompute averages, score error, keep the best of three runs.
initCenters
Picks spread-out starting colors (farthest-point style) so groups begin in different regions of the color cloud.
colorDistanceSq, sortByLuminance, dedupeColors
Helpers: compare colors without square roots, order dark→light, merge near-identical centers.
renderPalettes
Builds the on-page studies, wires “copy hex” on each swatch, and adds per-palette download buttons.
downloadPaletteImage
Paints one or all palettes onto a parchment-styled canvas and saves a PNG download.

From input image to output palette

Here is the journey of the data — what enters, what it becomes at each step, and what you finally see or download.

Full data pipeline Vertical flow from image file through pixels and k-means to swatches and PNG download. 1 · Image file 2 · Browser Image 3a · Preview on the desk 3b · Shrink canvas ≤ 120px 4 · Pixel list [R,G,B] 5 · k-means × 4 k = 3 · 5 · 7 · 10 (3 runs each) 6 · Clean & sort 7 · { size, colors } Swatches + hex PNG download
Fig. 8 — End-to-end path from file to pigments.
How the data shape changes Shape of the data at each gate File bytes Image W × H pixels Array [[R,G,B], …] Palette { size: 5, colors: […] } File → bitmap → list of points → compact color record
Fig. 9 — The data gets smaller and more meaningful at each step.
  1. Input file A PNG, JPG, WEBP, or GIF from the file browser.
  2. Browser image File → object URL → Image bitmap in memory.
  3. Preview The same image is shown in the “Study · Source” frame.
  4. Downscaled canvas Drawn at most ~120px on the long edge so later math stays light.
  5. Pixel list Flat RGBA bytes become an array of [R, G, B] points (alpha < 128 skipped).
  6. Four clustering passes For k = 3, 5, 7, 10: seed → assign → average → repeat → best of 3 runs.
  7. Cleaned centers Near-duplicate colors merged; remaining hues sorted by luminance.
  8. Palette records Stored as { size, colors } for the UI and for PNG export.
  9. Output On-screen swatches (click to copy hex) and optional parchment PNG downloads.