Obtenez la couleur moyenne de l'image via Javascript

118

Je ne suis pas sûr que cela soit possible, mais je cherche à écrire un script qui renverrait la moyenne hexou la rgbvaleur d'une image. Je sais que cela peut être fait en AS, mais je cherche à le faire en JavaScript.

plus grand
la source
19
mon ami Boaz a déjà emprunté cette voie (j'espère qu'il intervient) mais la moyenne vous laisse généralement avec une couleur de vomi. Ce que vous voulez vraiment, c'est la "couleur dominante". Il y a plusieurs façons d'y parvenir (histogramme de teinte avec compartimentage dynamique, etc.) mais je pense que c'est probablement plus précis pour le résultat souhaité.
Paul Irish

Réponses:

149

AFAIK, la seule façon de le faire est avec <canvas/>...

DÉMO V2 : http://jsfiddle.net/xLF38/818/

Notez que cela ne fonctionnera qu'avec les images du même domaine et dans les navigateurs prenant en charge le canevas HTML5:

function getAverageRGB(imgEl) {

    var blockSize = 5, // only visit every 5 pixels
        defaultRGB = {r:0,g:0,b:0}, // for non-supporting envs
        canvas = document.createElement('canvas'),
        context = canvas.getContext && canvas.getContext('2d'),
        data, width, height,
        i = -4,
        length,
        rgb = {r:0,g:0,b:0},
        count = 0;

    if (!context) {
        return defaultRGB;
    }

    height = canvas.height = imgEl.naturalHeight || imgEl.offsetHeight || imgEl.height;
    width = canvas.width = imgEl.naturalWidth || imgEl.offsetWidth || imgEl.width;

    context.drawImage(imgEl, 0, 0);

    try {
        data = context.getImageData(0, 0, width, height);
    } catch(e) {
        /* security error, img on diff domain */
        return defaultRGB;
    }

    length = data.data.length;

    while ( (i += blockSize * 4) < length ) {
        ++count;
        rgb.r += data.data[i];
        rgb.g += data.data[i+1];
        rgb.b += data.data[i+2];
    }

    // ~~ used to floor values
    rgb.r = ~~(rgb.r/count);
    rgb.g = ~~(rgb.g/count);
    rgb.b = ~~(rgb.b/count);

    return rgb;

}

Pour IE, consultez les excanvas .

James
la source
1
Existe-t-il un moyen d'obtenir l'histogramme des couleurs d'une image se trouvant sur un domaine différent?
Dan Loewenherz
1
Dan: Je pense que vous rencontrez la restriction d'origine croisée si vous essayez d'accéder aux données d'image sur une image d'un autre domaine. Ce qui est décevant.
8
je pense qu'il y a un placement de travail pour la valeur bleue et verte, vous écrivez 'rgb('+rgb.r+','+rgb.b+','+rgb.g+')'et cela devrait être 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')'. c'est étrange quand la couleur dominante est le bleu mais le résultat est vert :).
Ariona Rian
7
Nous avons trouvé une excellente solution de contournement pour les restrictions d'origine croisée: ajoutez simplement img.crossOrigin = '';avant de définir l' srcattribut. Trouvée sur: coderwall.com/p/pa-2uw
mhu
Pas besoin de compter, depuis count === length / 4 / blockSize;)
yckart
84

Je pensais que je publierais un projet que j'ai récemment rencontré pour obtenir une couleur dominante:

Voleur de couleurs

Un script pour saisir la couleur dominante ou une palette de couleurs représentative d'une image. Utilise javascript et canvas.

Les autres solutions mentionnant et suggérant la couleur dominante ne répondent jamais vraiment à la question dans le bon contexte ("en javascript"). Espérons que ce projet aidera ceux qui veulent faire exactement cela.

J. Chase
la source
55

"Dominant Color" est délicat. Ce que vous voulez faire, c'est comparer la distance entre chaque pixel et tous les autres pixels de l'espace colorimétrique (distance euclidienne), puis trouver le pixel dont la couleur est la plus proche de toutes les autres couleurs. Ce pixel est la couleur dominante. La couleur moyenne sera généralement de la boue.

J'aurais aimé avoir MathML ici pour vous montrer la distance euclidienne. Recherche le sur Google.

J'ai effectué l'exécution ci-dessus dans l'espace colorimétrique RVB en utilisant PHP / GD ici: https://gist.github.com/cf23f8bddb307ad4abd8

Ceci est cependant très coûteux en calcul. Il plantera votre système sur de grandes images et plantera définitivement votre navigateur si vous l'essayez dans le client. J'ai travaillé sur la refactorisation de mon exécution pour: - stocker les résultats dans une table de recherche pour une utilisation future dans l'itération sur chaque pixel. - pour diviser de grandes images en grilles de 20px 20px pour une dominance localisée. - utiliser la distance euclidienne entre x1y1 et x1y2 pour déterminer la distance entre x1y1 et x1y3.

Veuillez me faire savoir si vous faites des progrès sur ce front. Je serais heureux de le voir. Je vais faire la même chose.

Canvas est certainement le meilleur moyen de le faire chez le client. SVG ne l'est pas, SVG est basé sur des vecteurs. Une fois l'exécution terminée, la prochaine chose que je veux faire est de faire fonctionner cela dans le canevas (peut-être avec un webworker pour le calcul de la distance globale de chaque pixel).

Une autre chose à laquelle il faut penser est que RVB n'est pas un bon espace colorimétrique pour faire cela, car la distance euclidienne entre les couleurs dans l'espace RVB n'est pas très proche de la distance visuelle. Un meilleur espace colorimétrique pour faire cela pourrait être LUV, mais je n'ai pas trouvé de bonne bibliothèque pour cela, ni d'algorithmes pour convertir RVB en LUV.

Une approche entièrement différente consisterait à trier vos couleurs dans un arc-en-ciel et à créer un histogramme avec une tolérance pour tenir compte des différentes nuances d'une couleur. Je n'ai pas essayé cela, car le tri des couleurs dans un arc-en-ciel est difficile, tout comme les histogrammes de couleurs. Je pourrais essayer ceci ensuite. Encore une fois, faites-moi savoir si vous faites des progrès ici.


la source
6
C'est l'une de ces réponses pour lesquelles j'aimerais pouvoir faire plus que +1 :-)
David Johnstone
3
Excellente réponse même si une solution claire n'a pas été rendue. Cela m'aidera à décider de ma prochaine étape ...
bgreater
C'est une excellente réponse. J'ai écrit quelques modules de nœuds pour faire le calcul de la distance euclidienne non pas dans LUV, mais dans l' espace colorimétrique L a b *. Voir github.com/zeke/color-namer et github.com/zeke/euclidean-distance
Zeke
1
@user: Je me demandais simplement: un échantillon de 1% ne serait-il pas suffisant sur de grandes images pour accélérer le calcul?
jackthehipster
Sonne comme la médiane géométrique. Peut-être que cela peut aider? stackoverflow.com/questions/12934213/… et si le point que vous recherchez doit exister dans l'ensemble d'origine: en.m.wikipedia.org/wiki/Medoid#Algorithms_to_compute_medoids
Lawrence Weru
16

Premièrement: cela peut être fait sans HTML5 Canvas ou SVG.
En fait, quelqu'un a juste réussi à générer des fichiers PNG côté client en utilisant JavaScript , sans canevas ni SVG, en utilisant le schéma d'URI de données .

Deuxièmement: vous n'aurez peut-être pas besoin du tout de Canvas, SVG ou de tout ce qui précède.
Si vous avez seulement besoin de traiter les images côté client, sans les modifier, tout cela n'est pas nécessaire.

Vous pouvez obtenir l'adresse source à partir de la balise img sur la page, lui faire une requête XHR - elle proviendra probablement du cache du navigateur - et la traiter comme un flux d'octets à partir de Javascript.
Vous aurez besoin d'une bonne compréhension du format d'image. (Le générateur ci-dessus est partiellement basé sur des sources libpng et pourrait fournir un bon point de départ.)

Andras Vass
la source
+1 pour la solution bytestream. C'est une bonne option si la seule option est de le faire sur l'application cliente.
loretoparisi
11

Je dirais via la balise HTML canvas.

Vous pouvez trouver ici un article de @Georg parlant d'un petit code du développeur Opera:

// Get the CanvasPixelArray from the given coordinates and dimensions.
var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;

// Loop over each pixel and invert the color.
for (var i = 0, n = pix.length; i < n; i += 4) {
    pix[i  ] = 255 - pix[i  ]; // red
    pix[i+1] = 255 - pix[i+1]; // green
    pix[i+2] = 255 - pix[i+2]; // blue
    // i+3 is alpha (the fourth element)
}

// Draw the ImageData at the given (x,y) coordinates.
context.putImageData(imgd, x, y);

Cela inverse l'image en utilisant les valeurs R, V et B de chaque pixel. Vous pouvez facilement stocker les valeurs RVB, puis arrondir les tableaux rouge, vert et bleu, et enfin les reconvertir en un code HEX.

rnaud
la source
est-il possible qu'il existe une alternative IE à la solution canvas?
bgreater
@ Ben4Himv: Il y a l'aperçu de la plate-forme IE9 ;-) mais sérieusement, j'ai peur que non.
Andy E
4

Javascript n'a pas accès aux données de couleur des pixels individuels d'une image. Au moins, peut-être pas avant html5 ... à quel point il va de soi que vous serez capable de dessiner une image sur une toile, puis d'inspecter la toile (peut-être que je ne l'ai jamais fait moi-même).

Joël Martinez
la source
2

Solution tout-en-un

Personnellement, je combinerais Color Thief avec cette version modifiée de Name that Color pour obtenir un éventail plus que suffisant de résultats de couleurs dominantes pour les images.

Exemple:

Considérez l'image suivante:

entrez la description de l'image ici

Vous pouvez utiliser le code suivant pour extraire les données d'image relatives à la couleur dominante:

let color_thief = new ColorThief();
let sample_image = new Image();

sample_image.onload = () => {
  let result = ntc.name('#' + color_thief.getColor(sample_image).map(x => {
    const hex = x.toString(16);
    return hex.length === 1 ? '0' + hex : hex;
    
  }).join(''));
  
  console.log(result[0]); // #f0c420     : Dominant HEX/RGB value of closest match
  console.log(result[1]); // Moon Yellow : Dominant specific color name of closest match
  console.log(result[2]); // #ffff00     : Dominant HEX/RGB value of shade of closest match
  console.log(result[3]); // Yellow      : Dominant color name of shade of closest match
  console.log(result[4]); // false       : True if exact color match
};

sample_image.crossOrigin = 'anonymous';
sample_image.src = document.getElementById('sample-image').src;
Grant Miller
la source
1

Il s'agit de la "Quantification des couleurs" qui a plusieurs approches comme MMCQ (Quantification Médiane Modifiée) ou OQ (Quantification Octree). Différentes approches utilisent des K-Means pour obtenir des groupes de couleurs.

J'ai tout rassemblé ici, depuis que je cherchais une solution tvOSlà où il y a un sous-ensemble de XHTML, qui n'a pas<canvas/> élément:

Générer les couleurs dominantes pour une image RVB avec XMLHttpRequest

Loretoparisi
la source
1

Moyen moins précis mais le plus rapide d'obtenir une couleur moyenne de l'image avec dataurisupport:

function get_average_rgb(img) {
    var context = document.createElement('canvas').getContext('2d');
    if (typeof img == 'string') {
        var src = img;
        img = new Image;
        img.setAttribute('crossOrigin', ''); 
        img.src = src;
    }
    context.imageSmoothingEnabled = true;
    context.drawImage(img, 0, 0, 1, 1);
    return context.getImageData(1, 1, 1, 1).data.slice(0,3);
}
350D
la source
1

Comme indiqué dans d'autres réponses, souvent ce que vous voulez vraiment la couleur dominante par opposition à la couleur moyenne qui a tendance à être brune. J'ai écrit un script qui obtient la couleur la plus courante et je l'ai posté sur cet essentiel

dctalbot
la source
-2

Il existe un outil en ligne pickimagecolor.com qui vous aide à trouver la couleur moyenne ou dominante de l'image, il vous suffit de télécharger une image depuis votre ordinateur puis de cliquer sur l'image. Il donne la couleur moyenne en HEX, RVB et HSV. Il trouve également les nuances de couleur correspondant à cette couleur à choisir. Je l'ai utilisé plusieurs fois.

shubham agrawal
la source