Skip to content

Median Cut Palette Plate

Pulls a genuinely distinct palette out of a photograph by median cut — splitting on channel spread, not on population, sorted by relative luminance.

Proeditorialminimal
Category
Components
Added
2026-09-21
Deps
none
Updated
2026-09-21

This is a viberdy Pro component

The live preview, the props panel and the write-up are open to everyone. The full source, the AI prompt and CLI install are part of Pro.

Preview
median cut
0 colours · sorted by relative luminancereading

Props

6
96
No runtime dependencies
"use client";

import { useEffect, useRef, useState } from "react";

type Box = { pixels: number[][] };

//
// MEDIAN CUT, NOT A HISTOGRAM.
//
// Taking the N most frequent colours returns N near-duplicates of whatever
// fills the most area — five shades of the same sky, and the one brilliant
// accent that made the photograph worth using never appears.
//
// Median cut puts every pixel in one box, then repeatedly takes the box with
// the widest single-channel SPREAD and splits it at the MEDIAN of that
// channel. Splitting on spread rather than on population is the whole trick:
// a small vivid region has a wide range and survives; a huge flat background
// has a narrow one and collapses into a single entry.

  // ---------------------------------------------------------------
  // Full source available with a viberdy Pro subscription.
  // https://viberdy.dev/pricing
  // ---------------------------------------------------------------
}
Notes

About this component

Extracting a palette from an image is usually done by counting the most frequent colours, and that is why so many extracted palettes are five shades of the same sky: population favours whatever fills the most area, so the small brilliant accent that made the photograph worth using never survives. Median cut inverts the criterion. Every pixel starts in one box; the box with the widest single-channel spread is split at the median of that channel, repeatedly, and each final box is averaged. Splitting on spread rather than count is the whole idea — a small vivid region has a wide range and survives, a huge flat background has a narrow one and collapses into one entry. Three practical details decide whether it ships. The crossOrigin attribute has to be assigned before the src, because reading pixels from an image the browser did not fetch with CORS taints the canvas permanently, and setting the attribute afterwards has no effect at all; that version reads perfectly and fails only later at getImageData, which is why it is the one usually written. The analysis runs on a small offscreen thumbnail, since a palette needs a few thousand samples rather than the four million in a full-size photograph. And the result is sorted by relative luminance on linearised channels rather than by population or HSL lightness, which is the difference between a designed ramp and a bug report.

Curator’s note

Assigning crossOrigin after src reads perfectly and does nothing — the request already went out, and the failure only surfaces later at getImageData.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

A draggable before/after divider that reveals one image over another.

imageslidercompare
Featured
NewPro

Picks a readable foreground from real relative luminance, and reports all three WCAG thresholds instead of one badge.

colorcontrasta11y
Featured
NewPro

A signature pad with real variable-width ink — pen pressure where it exists, velocity everywhere else, coalesced samples and midpoint quadratics.

canvasinputpointer