Skip to content
← 1001 Widgets
N° 034 · ●●●

Palette from an image

Drop a photo into the page, and its five dominant colours come out, with their share and their code to copy. The right exercise to read the pixels of an image and group them.

JSCanvas
What you'll learn
  • Read an image's pixels with getImageData
  • Group colours with k-means
  • Open a local file without uploading anything
Why this language

Pourquoi une palette tirée d'une image ? Pour une affiche, un site ou une carte de visite, on part souvent d'une photo qu'on aime. Retrouver ses couleurs à la pipette est long et approximatif.

L'image est dessinée dans un canvas, qui donne accès à chaque pixel. On en prélève quelques milliers, puis on les regroupe en cinq familles avec l'algorithme des k-moyennes : chaque couleur rejoint le centre le plus proche, chaque centre se déplace au milieu de sa famille, et on recommence.

Pourquoi en JavaScript simple ? FileReader n'est même pas nécessaire : URL.createObjectURL suffit, et la photo ne quitte jamais l'ordinateur.

How it works
  1. CHARGER LA PHOTO

    L'image reste dans le navigateur : rien n'est envoyé à un serveur.

    function charger(fichier) {
      const url = URL.createObjectURL(fichier);
      const img = new Image();
      img.onload = () => {
        cv.width = 360;
        cv.height = Math.round(img.height * 360 / img.width);
        ctx.drawImage(img, 0, 0, cv.width, cv.height);
        URL.revokeObjectURL(url);              // on libère la mémoire
        analyser();
      };
      img.src = url;
    }
  2. LIRE LES PIXELS

    getImageData renvoie r, g, b, a à la suite. On saute des pixels pour aller vite.

    function pixels() {
      const d = ctx.getImageData(0, 0, cv.width, cv.height).data, px = [];
      const pas = Math.max(1, Math.floor(cv.width * cv.height / 6000));
      for (let i = 0; i < d.length; i += 4 * pas) if (d[i + 3] > 128) px.push([d[i], d[i + 1], d[i + 2]]);
      return px;
    }
  3. LA DISTANCE ENTRE DEUX COULEURS

    L'œil est plus sensible au vert : on lui donne plus de poids.

    const dist = (a, b) => 2 * (a[0] - b[0]) ** 2 + 4 * (a[1] - b[1]) ** 2 + 3 * (a[2] - b[2]) ** 2;
  4. LES K-MOYENNES

    Chaque pixel rejoint le centre le plus proche, puis chaque centre va au milieu de son groupe.

    function kmoyennes(px, centres, passes = 8) {
      for (let n = 0; n < passes; n++) {
        const g = centres.map(() => ({ s: [0, 0, 0], n: 0 }));
        for (const p of px) {
          let j = 0;
          centres.forEach((c, k) => { if (dist(p, c) < dist(p, centres[j])) j = k; });
          g[j].s = g[j].s.map((v, i) => v + p[i]); g[j].n++;
        }
        centres = g.map((x, k) => x.n ? x.s.map(v => v / x.n) : centres[k]);
      }
      return centres;
    }
  5. EN HEXADÉCIMAL

    const hex = c => '#' + c.map(v => Math.round(v).toString(16).padStart(2, '0')).join('');
Pitfalls

PIÈGE 1 : Analyser tous les pixels

Pitfall

Mauvais : passer les 12 millions de pixels d'une photo de téléphone dans la boucle. La page se fige plusieurs secondes.

Fix

Correct : réduire l'image dans le canvas, puis ne prélever qu'un pixel sur plusieurs.

PIÈGE 2 : Des centres de départ au hasard

Pitfall

Mauvais : tirer les cinq centres au hasard. Deux tombent dans le même ciel bleu, et une couleur forte disparaît.

Fix

Correct : partir de couleurs bien espacées, en prenant à chaque fois le pixel le plus éloigné des centres déjà choisis.

PIÈGE 3 : Une image d'un autre site

Pitfall

Mauvais : dessiner une image venue d'un autre domaine, puis lire ses pixels. Le canvas est « contaminé » et getImageData lève une erreur.

Fix

Correct : utiliser un fichier local, ou une image servie avec l'en-tête CORS et crossOrigin="anonymous".

PERSONNALISER
  • Triez la palette par clarté plutôt que par part, pour un dégradé naturel.
  • Ignorez les blancs et les noirs presque purs, souvent du fond.
  • Exportez la palette en fichier .ase pour la reprendre dans un logiciel de dessin.
OPTIMISATION
  • Pour de grandes images, lancez l'analyse dans un Web Worker : l'interface reste fluide.
  • Calculez dans l'espace OKLab plutôt qu'en RGB : les familles de couleurs correspondent mieux à ce que l'œil voit.
VERS REACT

Le canvas est accessible par un useRef, l'analyse se lance quand le fichier change.

La palette est un useState, rendue en liste de boutons avec une key par couleur.

Pensez à révoquer l'URL de l'objet dans le nettoyage du useEffect.

Comments
0/1000

Loading comments…

Audience measurement

With your consent, this site measures its traffic with Google Analytics and the Meta pixel, via Google Tag Manager. Nothing is loaded until you choose, and you can change your mind at any time with the "Cookies" link at the bottom of the page.