Get average color of image via Javascript
Asked Answered
C

16

157

Not sure this is possible, but looking to write a script that would return the average hex or rgb value for an image. I know it can be done in AS but looking to do it in JavaScript.

Cruz answered 29/3, 2010 at 21:42 Comment(1)
my friend Boaz has been down this path before (hopefully he chimes in ) but average typically leaves you with a vomit-like color. What you really want is "dominant color". There are a few ways to go about getting that (hue histogram w/ dynamic bucketing etc) but I think that's probably more precise for your intended result.Commoner
V
188

AFAIK, the only way to do this is with <canvas/>...

DEMO V2: http://jsfiddle.net/xLF38/818/

Note, this will only work with images on the same domain and in browsers that support HTML5 canvas:

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;

}

For IE, check out excanvas.

Violative answered 29/3, 2010 at 22:30 Comment(8)
Is there any way to get the color histogram for an image lying on a different domain?Chondro
Dan: I think you run into the cross-origin restriction if you try to access image data on an image from another domain. Which is a bummer.Unalterable
i think there is woring placement for the blue and green value, you write 'rgb('+rgb.r+','+rgb.b+','+rgb.g+')' and that should be 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')' . it's strange when the dominant color is blue but the result is green :).Acutance
Found a great workaround for cross-origin restrictions: just add img.crossOrigin = ''; before setting the src attribute. Found on: coderwall.com/p/pa-2uwHaleakala
No need to count up, since count === length / 4 / blockSize ;)Ellinger
You can also use an input element with type file, in combination with FileReader, in browsers that support it. For certain use cases. But it's far from trivial. (Hint: feed the FileReader the blob from the input's files array, then use readAsArrayBuffer and put its result in a Uint32Array.)Burrstone
what about the dominant colour? Also is there a way to get dominant colour per sub rectangle, like a mosaic?Confutation
What if the image you are trying to get the dominant color isn't in the data url format? How would you convert it?Fabozzi
H
108

Figured I'd post a project I recently came across to get dominant color:

Color Thief

A script for grabbing the dominant color or a representative color palette from an image. Uses javascript and canvas.

The other solutions mentioning and suggesting dominant color never really answer the question in proper context ("in javascript"). Hopefully this project will help those who want to do just that.

Hancock answered 7/12, 2011 at 9:3 Comment(0)
E
67

"Dominant Color" is tricky. What you want to do is compare the distance between each pixel and every other pixel in color space (Euclidean Distance), and then find the pixel whose color is closest to every other color. That pixel is the dominant color. The average color will usually be mud.

I wish I had MathML in here to show you Euclidean Distance. Google it.

I have accomplished the above execution in RGB color space using PHP/GD here: https://gist.github.com/cf23f8bddb307ad4abd8

This however is very computationally expensive. It will crash your system on large images, and will definitely crash your browser if you try it in the client. I have been working on refactoring my execution to: - store results in a lookup table for future use in the iteration over each pixel. - to divide large images into grids of 20px 20px for localized dominance. - to use the euclidean distance between x1y1 and x1y2 to figure out the distance between x1y1 and x1y3.

Please let me know if you make progress on this front. I would be happy to see it. I will do the same.

Canvas is definitely the best way to do this in the client. SVG is not, SVG is vector based. After I get the execution down, the next thing I want to do is get this running in the canvas (maybe with a webworker for each pixel's overall distance calculation).

Another thing to think about is that RGB is not a good color space for doing this in, because the euclidean distance between colors in RGB space is not very close to the visual distance. A better color space for doing this might be LUV, but I have not found a good library for this, or any algorythims for converting RGB to LUV.

An entirely different approach would be to sort your colors in a rainbow, and build a histogram with tolerance to account for varying shades of a color. I have not tried this, because sorting colors in a rainbow is hard, and so are color histograms. I might try this next. Again, let me know if you make any progress here.

Euphonious answered 30/3, 2010 at 2:6 Comment(5)
This is one of those answers for which I wish I could do more than just +1 :-)Ignazio
Excellent answer even though a clear solution wasn't rendered. This will help me decided my next step...Cruz
This ia a great answer. I wrote a few node module to do euclidean distance calculation not in LUV, but in the Lab* color space. See github.com/zeke/color-namer and github.com/zeke/euclidean-distanceElectrician
@user: I was just wondering: wouldn't a, say, 1% sample be enough on large images to speed up computation?Adenine
Sounds like the geometric median. Maybe this can help? #12934713 and if the point you’re looking for must exist in the original set: en.m.wikipedia.org/wiki/Medoid#Algorithms_to_compute_medoidsSubmicroscopic
B
16

First: it can be done without HTML5 Canvas or SVG.
Actually, someone just managed to generate client-side PNG files using JavaScript, without canvas or SVG, using the data URI scheme.

Edit: You can simply create a PNG with document.getElementById("canvas").toDataURL("image/png", 1.0)

Second: you might actually not need Canvas, SVG or any of the above at all.
If you only need to process images on the client side, without modifying them, all this is not needed.

You can get the source address from the img tag on the page, make an XHR request for it - it will most probably come from the browser cache - and process it as a byte stream from Javascript.
You will need a good understanding of the image format. (The above generator is partially based on libpng sources and might provide a good starting point.)

Bunni answered 29/3, 2010 at 23:8 Comment(1)
+1 for the bytestream solution. This is a good one if the only option is to make it on the client application.Clod
L
16

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(0, 0, 1, 1).data.slice(0,3);
}
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAEBgIApD5fRAAAAABJRU5ErkJggg==" width="32" height="32" id="img1" onload="console.log(get_average_rgb(this))">
<img src="https://lh3.googleusercontent.com/a/AEdFTp4Wi1oebZlBCwFID8OZZuG0HLsL-xIxO5m2TNw=k-s32" id="img2" crossOrigin="anonymous" onload="console.log(get_average_rgb(this))">

Less accurate but fastest way to get average color of the image with datauri support.

Lorrielorrimer answered 14/4, 2018 at 23:36 Comment(4)
everything is just fine but the last line should be context.getImageData(0, 0, 1, 1).data.slice(0,3) to read the only pixelPrune
It always returns [0, 0, 0] for me. I checked if I am passing the correct image path, and I am. I've tried with and without 'http://....'.Coblenz
@Coblenz Make sure your image CORS enabled, ie available for cross domain request.Lorrielorrimer
@Coblenz Also, make sure your image loaded and you call the function on onload event or after. I've updated my snipped to make it more clear.Lorrielorrimer
T
10

I would say via the HTML canvas tag.

You can find here a post by @Georg talking about a small code by the Opera dev :

// 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);

This invert the image by using the R, G and B value of each pixel. You could easily store the RGB values, then round up the Red, Green and Blue arrays, and finally converting them back into an HEX code.

Tannie answered 29/3, 2010 at 21:58 Comment(2)
any chance there is a IE alternative to the canvas solution?Cruz
@Ben4Himv: There's the IE9 Platform Preview ;-) but seriously, I'm afraid not.Provinciality
T
10

EDIT: Only after posting this, did I realize that @350D's answer does the exact same thing.

Surprisingly, this can be done in just 4 lines of code:

const canvas = document.getElementById("canvas"),
  preview = document.getElementById("preview"),
  ctx = canvas.getContext("2d");

canvas.width = 1;
canvas.height = 1;

preview.width = 400;
preview.height = 400;

function getDominantColor(imageObject) {
  //draw the image to one pixel and let the browser find the dominant color
  ctx.drawImage(imageObject, 0, 0, 1, 1);

  //get pixel color
  const i = ctx.getImageData(0, 0, 1, 1).data;

  console.log(`rgba(${i[0]},${i[1]},${i[2]},${i[3]})`);

  console.log("#" + ((1 << 24) + (i[0] << 16) + (i[1] << 8) + i[2]).toString(16).slice(1));
}



// vvv all of this is to just get the uploaded image vvv
const input = document.getElementById("input");
input.type = "file";
input.accept = "image/*";

input.onchange = event => {
  const file = event.target.files[0];
  const reader = new FileReader();

  reader.onload = readerEvent => {
    const image = new Image();
    image.onload = function() {
      //shows preview of uploaded image
      preview.getContext("2d").drawImage(
        image,
        0,
        0,
        preview.width,
        preview.height,
      );
      getDominantColor(image);
    };
    image.src = readerEvent.target.result;
  };
  reader.readAsDataURL(file, "UTF-8");
};
canvas {
  width: 200px;
  height: 200px;
  outline: 1px solid #000000;
}
<canvas id="preview"></canvas>
<canvas id="canvas"></canvas>
<input id="input" type="file" />

How it works:

Create the canvas context

const context = document.createElement("canvas").getContext("2d");

This will draw the image to only one canvas pixel, making the browser find the dominant color for you.

context.drawImage(imageObject, 0, 0, 1, 1);

After that, just get the image data for the pixel:

const i = context.getImageData(0, 0, 1, 1).data;

Finally, convert to rgba or HEX:

const rgba = `rgba(${i[0]},${i[1]},${i[2]},${i[3]})`;

const HEX = "#" + ((1 << 24) + (i[0] << 16) + (i[1] << 8) + i[2]).toString(16).slice(1);

There is one problem with this method though, and that is that getImageData will sometimes throw errors Unable to get image data from canvas because the canvas has been tainted by cross-origin data., which is the reason you need to upload images in the demo instead of inputting a URL for example.

This method can also be used for pixelating images by increasing the width and height to draw the image.

This works on chrome but may not on other browsers.

Topflight answered 1/2, 2021 at 2:28 Comment(0)
J
8

This is @350D's answer but async (as some images may take time to load) and in typescript

async function get_average_rgb(src: string): Promise<Uint8ClampedArray> {
/* https://mcmap.net/q/144557/-get-average-color-of-image-via-javascript */
    return new Promise(resolve => {
        let context = document.createElement('canvas').getContext('2d');
        context!.imageSmoothingEnabled = true;

        let img = new Image;
        img.src = src;
        img.crossOrigin = "";

        img.onload = () => {
            context!.drawImage(img, 0, 0, 1, 1);
            resolve(context!.getImageData(0, 0, 1, 1).data.slice(0,3));
        };
    });
}
Johppah answered 24/6, 2021 at 3:21 Comment(0)
C
6

I recently came across a jQuery plugin which does what I originally wanted https://github.com/briangonzalez/jquery.adaptive-backgrounds.js in regards to getting a dominiate color from an image.

Cruz answered 18/5, 2016 at 14:14 Comment(0)
P
6

All-In-One Solution

I would personally combine Color Thief along with this modified version of Name that Color to obtain a more-than-sufficient array of dominant color results for images.

Example:

Consider the following image:

enter image description here

You can use the following code to extract image data relating to the dominant color:

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;
Phira answered 3/5, 2018 at 4:27 Comment(0)
A
4

Javascript does not have access to an image's individual pixel color data. At least, not maybe until html5 ... at which point it stands to reason that you'll be able to draw an image to a canvas, and then inspect the canvas (maybe, I've never done it myself).

Acyclic answered 29/3, 2010 at 21:48 Comment(0)
C
2

This is about "Color Quantization" that has several approachs like MMCQ (Modified Median Cut Quantization) or OQ (Octree Quantization). Different approach use K-Means to obtain clusters of colors.

I have putted all together here, since I was finding a solution for tvOS where there is a subset of XHTML, that has no <canvas/> element:

Generate the Dominant Colors for an RGB image with XMLHttpRequest

Clod answered 26/10, 2015 at 14:30 Comment(0)
S
2

To get the average (not the dominant) color of an image, create a tiny canvas of the scaled-down original image (of max size i.e: 10 px). Then loop the canvas area imageData to construct the average RGB:

const getAverageColor = (img) => {
  
  const max = 10; // Max size (Higher num = better precision but slower)
  const {naturalWidth: iw, naturalHeight: ih} = img;
  const ctx = document.createElement`canvas`.getContext`2d`; 
  const sr = Math.min(max / iw, max / ih); // Scale ratio
  const w = Math.ceil(iw * sr); // Width
  const h = Math.ceil(ih * sr); // Height
  const a = w * h;              // Area
  
  img.crossOrigin = 1;
  ctx.canvas.width = w;
  ctx.canvas.height = h;
  ctx.drawImage(img, 0, 0, w, h);
  
  const data = ctx.getImageData(0, 0, w, h).data;
  let r = g = b = 0;
  
  for (let i=0; i<data.length; i+=4) {
    r += data[i];
    g += data[i+1];
    b += data[i+2];
  }

  r = ~~(r/a);
  g = ~~(g/a);
  b = ~~(b/a);

  return {r, g, b};
};

const setBgFromAverage = (img) => {
  img.addEventListener("load", () => {
    const {r,g,b} = getAverageColor(img);
    img.style.backgroundColor = `rgb(${r},${g},${b})`;
  });
};

document.querySelectorAll('.thumb').forEach(setBgFromAverage);
.thumbs { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.thumb { height: 100px; padding: 20px; }
<div class="thumbs">
  <img class="thumb" alt="image" src="https://i.imgur.com/22BrBjx.jpeg">
  <img class="thumb" alt="image" src="https://i.imgur.com/MR0dUpw.png">
  <img class="thumb" alt="image" src="https://i.imgur.com/o7lpiDR.png">
  <img class="thumb" alt="image" src="https://i.imgur.com/egYvHp6.jpeg">
  <img class="thumb" alt="image" src="https://i.imgur.com/62EAzOY.jpeg">
  <img class="thumb" alt="image" src="https://i.imgur.com/3VxBMeF.jpeg">
</div>
Softa answered 22/8, 2022 at 23:48 Comment(0)
A
1

As pointed out in other answers, often what you really want the dominant color as opposed to the average color which tends to be brown. I wrote a script that gets the most common color and posted it on this gist

Awildaawkward answered 26/6, 2019 at 20:42 Comment(0)
A
1

Colortheif is a very good package. I used to use it and it works fine. Combined with canvas I can create a palette image with the colors extracted. But moving to typescript, colortheif is not typed so that's a problem for me.

Aureus answered 27/7, 2023 at 8:47 Comment(0)
S
-4

There is a online tool pickimagecolor.com that helps you to find the average or the dominant color of image.You just have to upload a image from your computer and then click on the image. It gives the average color in HEX , RGB and HSV. It also find the color shades matching that color to choose from. I have used it multiple times.

Selfcontent answered 2/10, 2017 at 10:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.