Detect "overall average" color of the picture

Question:

I have a jpg image.

I need to know “overall average” the color of the image. At first glance there can use the histogram of the image (channel RGB).

At work I use mostly JavaScript and PHP (a little Python) therefore welcomed the decision in these languages. Maybe ther are library for working with images that address similar problems.

I do not need to dynamically determine the color of the picture. I need just once go through the entire array of images and determine the color of each separately (this information I will remember for future use).

Asked By: Kalinin

||

Answers:

You can use PHP to get an array of the color palette like so:

<?php 
function colorPalette($imageFile, $numColors, $granularity = 5) 
{ 
   $granularity = max(1, abs((int)$granularity)); 
   $colors = array(); 
   $size = @getimagesize($imageFile); 
   if($size === false) 
   { 
      user_error("Unable to get image size data"); 
      return false; 
   } 
   $img = @imagecreatefromjpeg($imageFile);
   // Andres mentioned in the comments the above line only loads jpegs, 
   // and suggests that to load any file type you can use this:
   // $img = @imagecreatefromstring(file_get_contents($imageFile)); 

   if(!$img) 
   { 
      user_error("Unable to open image file"); 
      return false; 
   } 
   for($x = 0; $x < $size[0]; $x += $granularity) 
   { 
      for($y = 0; $y < $size[1]; $y += $granularity) 
      { 
         $thisColor = imagecolorat($img, $x, $y); 
         $rgb = imagecolorsforindex($img, $thisColor); 
         $red = round(round(($rgb['red'] / 0x33)) * 0x33); 
         $green = round(round(($rgb['green'] / 0x33)) * 0x33); 
         $blue = round(round(($rgb['blue'] / 0x33)) * 0x33); 
         $thisRGB = sprintf('%02X%02X%02X', $red, $green, $blue); 
         if(array_key_exists($thisRGB, $colors)) 
         { 
            $colors[$thisRGB]++; 
         } 
         else 
         { 
            $colors[$thisRGB] = 1; 
         } 
      } 
   } 
   arsort($colors); 
   return array_slice(array_keys($colors), 0, $numColors); 
} 
// sample usage: 
$palette = colorPalette('rmnp8.jpg', 10, 4); 
echo "<table>n"; 
foreach($palette as $color) 
{ 
   echo "<tr><td style='background-color:#$color;width:2em;'>&nbsp;</td><td>#$color</td></tr>n"; 
} 
echo "</table>n";

Which gives you an array whose values are higher for how often that color has been used.

EDIT
A commenter asked how to use this on all files in a directory, here it is:

    if ($handle = opendir('./path/to/images')) {

        while (false !== ($file = readdir($handle))) {
           $palette = colorPalette($file, 10, 4);
           echo "<table>n"; 
           foreach($palette as $color) { 
               echo "<tr><td style='background-color:#$color;width:2em;'>&nbsp;</td><td>#$color</td></tr>n"; 
           } 
           echo "</table>n";
        }
        closedir($handle);
    }

might not want to do this on too many files, but it’s your server.

Alternatively if you’d rather use Javascript Lokesh’s Color-Theif library does exactly what you’re looking for.

Answered By: JKirchartz

Start with PIL. http://www.pythonware.com/products/pil/

Open the Image object. Use the getdata method to get all pixels. Average the values you get back.

Something like this.

Image color detection using python

Answered By: S.Lott

A shorter solution for true color image would be to scale it down to 1×1 pixel size and sample the color at that pixel:

$scaled = imagescale($img, 1, 1, IMG_BICUBIC);
$meanColor = imagecolorat($img, 0, 0);

…but I haven’t tested this myself.

Combining JKirchartz and Alexander Hugestrand answer:

 function getAverage($sourceURL){

    $image = imagecreatefromjpeg($sourceURL);
    $scaled = imagescale($image, 1, 1, IMG_BICUBIC); 
    $index = imagecolorat($scaled, 0, 0);
    $rgb = imagecolorsforindex($scaled, $index); 
    $red = round(round(($rgb['red'] / 0x33)) * 0x33); 
    $green = round(round(($rgb['green'] / 0x33)) * 0x33); 
    $blue = round(round(($rgb['blue'] / 0x33)) * 0x33); 
    return sprintf('#%02X%02X%02X', $red, $green, $blue); 
 }

Tried and tested, returns hex string.

Answered By: Johnny Rockex

I’ve created the composer package that provides the library for picking an average color from the given image by its path.

You can install it by running the following command within your project directory:

composer require tooleks/php-avg-color-picker

Usage example:

<?php

use TooleksPhpAvgColorPickerGdAvgColorPicker;

$imageAvgHexColor = (new AvgColorPicker)->getImageAvgHexByPath('/absolute/path/to/the/image.(jpg|jpeg|png|gif)');

// The `$imageAvgHexColor` variable contains the average color of the given image in HEX format (#fffff).

See the documentation.

Answered By: tooleks
$img = glob('img/*');
foreach ($img as $key => $value) {
    $info = getimagesize($value);
    $mime = $info['mime'];
    switch ($mime) {
        case 'image/jpeg':
            $image_create_func = 'imagecreatefromjpeg';
            break;
        case 'image/png':
            $image_create_func = 'imagecreatefrompng';
            break;
        case 'image/gif':
            $image_create_func = 'imagecreatefromgif';
            break;
    }
    $avg = $image_create_func($value);
    list($width, $height) = getimagesize($value);
    $tmp = imagecreatetruecolor(1, 1);
    imagecopyresampled($tmp, $avg, 0, 0, 0, 0, 1, 1, $width, $height);
    $rgb = imagecolorat($tmp, 0, 0);
    $r = ($rgb >> 16) & 0xFF;
    $g = ($rgb >> 8) & 0xFF;
    $b = $rgb & 0xFF;
    echo '<div style="text-align:center; vertical-align: top; display:inline-block; width:100px; height:150px; margin:5px; padding:5px; background-color:rgb('.$r.','.$g.','.$b.');">';
    echo '<img style="width:auto; max-height:100%; max-width: 100%; vertical-align:middle; height:auto; margin-bottom:5px;" src="'.$value.'">';
    echo '</div>';

you can get the value of the average color with $r, $g, & $b
resample the image is much more better than only scale it !

Answered By: antoine demacon

Here’s a solution using php-vips. It’s very fast, and will find the most common colour, rather than the average colour.

Most photos will have grey as the average, since that’s what auto white balance does. What you really want is the colour which appears most often.

This program uses a 3D histogram. It makes a 10 x 10 x 10 cube (you can change this, see $n_bins) to represent the whole of RGB colourspace, then loops through the image counting the number of pixels which fall into each bin. It sets the count in bin (0, 0, 0) to zero (black is usually uninteresting background), then searches for the bin with the highest count. The index of that bin is the most common RGB colour.

This won’t work for most PNGs (you’ll need to flatten out the alpha) or CMYKs (you’ll need to convert to RGB first).

#!/usr/bin/env php
<?php

require __DIR__ . '/vendor/autoload.php';
use JcupittVips;

$im = VipsImage::newFromFile($argv[1], ['access' => 'sequential']);

# 3D histogram ... make 10 x 10 x 10 bins, so 1000 possible colours
$n_bins = 10;
$hist = $im->hist_find_ndim(['bins' => $n_bins]);

# black is usually background or boring, so set that cell to 0 counts
# fetch (0, 0, set the 0th element of that to 0, paste back
$pixel = $hist->getpoint(0, 0);
$pixel[0] = 0;
$pixel = VipsImage::black(1, 1)->add($pixel);
$hist = $hist->insert($pixel, 0, 0);

# (x, y) pixel with the most counts
[$v, $x, $y] = $hist->maxpos();
$pixel = $hist->getpoint($x, $y);
$z = array_search($v, $pixel);

# convert indexes to rgb ... +0.5 to get the centre of each bin
$r = ($x + 0.5) * 256 / $n_bins;
$g = ($y + 0.5) * 256 / $n_bins;
$b = ($z + 0.5) * 256 / $n_bins;

echo("r = " . $r . "n");
echo("g = " . $g . "n");
echo("b = " . $b . "n");

I can run it like this:

$ time ./try302.php ~/pics/shark.jpg 
r = 38.4
g = 38.4
b = 12.8
real    0m0.077s
user    0m0.068s
sys 0m0.016s

So 70ms on this modest laptop for a 700 x 700 pixel jpg.

Answered By: jcupitt