Chapter I
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.
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.
To build a palette of, say, five colors, we use k-means clustering — a way of sorting similar things into groups:
- 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).
- Sort every speck. Each pixel joins the nearest flag — “you look most like this group.”
- Move the flags. Each flag slides to the average color of its group — the true middle of that pile of paint.
- 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.
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.
Chapter II
File structure & function overview
The project is a small static site — three pages of parchment and one engine of ink:
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
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.
Chapter III
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.
- Input file A PNG, JPG, WEBP, or GIF from the file browser.
-
Browser image
File→ object URL →Imagebitmap in memory. - Preview The same image is shown in the “Study · Source” frame.
- Downscaled canvas Drawn at most ~120px on the long edge so later math stays light.
-
Pixel list
Flat RGBA bytes become an array of
[R, G, B]points (alpha < 128 skipped). -
Four clustering passes
For
k = 3, 5, 7, 10: seed → assign → average → repeat → best of 3 runs. - Cleaned centers Near-duplicate colors merged; remaining hues sorted by luminance.
-
Palette records
Stored as
{ size, colors }for the UI and for PNG export. - Output On-screen swatches (click to copy hex) and optional parchment PNG downloads.