Dan
Dan

Reputation: 2302

Programmatically determine human readable color (e.g. Red, Green, etc..) of an image

I'm trying to come up with a script that will programmatically run through an image and tell me it's primary color(s).

Currently the script gets the RGB value of each pixel. Compares them against predefined rules and attempts to count up the number of pixels of each colour.

My problem is the script is a little hit and miss. Does anyone know of a better way of doing this (maybe using a different colour coding system that's easier to translate to english) or an existing set of rules defining colours via their RGB?

<?php
$file = "8629.jpg";

$colors = array("Red" => array("rel" => true, "r" => 0.65, "g" => 0.09, "b" => 0.25, "var" => 0.3),
                "Blue" => array("rel" => true, "r" => 0.21, "g" => 0.32, "b" => 0.46, "var" => 0.3),
                "Green" => array("rel" => true, "r" => 0, "g" => 0.67,"b" =>  0.33, "var" => 0.3),
                "Black" => array("rel" => false, "r" => 0, "g" => 0,"b" =>  0, "var" => 30),
                "White" => array("rel" => false, "r" => 255, "g" => 255,"b" =>  255, "var" => 30));                 

$total = 0;

$im = imagecreatefromjpeg($file);
$size = getimagesize($file);

if (!$im) {
    exit("No image found.");
}

for ($x = 1; $x <= $size[0]; $x++) {
    for($y = 1; $y <= $size[1]; $y++) {
        $rgb = imagecolorat($im, $x, $y);
        $r = ($rgb >> 16) & 0xFF;
        $g = ($rgb >> 8) & 0xFF;
        $b = $rgb & 0xFF;

        $colorTotal = $r + $g + $b;

        $rRatio = $r > 0 ? $r / $colorTotal : 0;
        $gRatio = $g > 0 ? $g / $colorTotal : 0;
        $bRatio = $b > 0 ? $b / $colorTotal : 0;

        foreach($colors as $key => $color) {
            if ($color["rel"]) {
                if ((($color["r"] - $color["var"]) <= $rRatio && $rRatio <= ($color["r"] + $color["var"])) &&
                    (($color["g"] - $color["var"]) <= $gRatio && $gRatio <= ($color["g"] + $color["var"])) &&
                    (($color["b"] - $color["var"]) <= $bRatio && $bRatio <= ($color["b"] + $color["var"]))) {

                    $colourCount[$key]++;
                    $total++;
                }
            } else {
                if ((($color["r"] - $color["var"]) <= $r && $r <= ($color["r"] + $color["var"])) &&
                    (($color["g"] - $color["var"]) <= $g && $g <= ($color["g"] + $color["var"])) &&
                    (($color["b"] - $color["var"]) <= $b && $b <= ($color["b"] + $color["var"]))) {

                    $colourCount[$key]++;
                    $total++;
                }
            }
        }
    }
}

var_dump($colourCount);

foreach($colourCount as $key => $color) {
    $colourPrecent[$key] = $color / $total;
}

arsort($colourPrecent);
var_dump($colourPrecent);

foreach($colourPrecent as $key => $color) {
    if ($prevVal) {
        if ($color < ($prevVal - 0.1)) {
            break;
        }
    }

    $primary[] = $key;
    $prevVal = $color;
}

echo("The primary colours in this image are " . implode(" and ", $primary));

?>

Upvotes: 8

Views: 2169

Answers (3)

Jim Dandy BOA
Jim Dandy BOA

Reputation: 590

I know this post is 12 years old but I have struggled with tis myself recently. I tried most of the latest GitHub solutions either they weren't good enough or the PHP code executed libraries on the writer's server which you can't download.

I have a solution that works for me after days of struggling with pieces of code I got from various places, however the algorithm is crazy but its mine. I hope this can be of use to someone even though it may not be perfect.

Screenshot of PHP output: enter image description here

The PHP Code:

<?php

// Tested with PHP version 8.0
// Copy to your server and it should run with no errors as is, check your error log file to make sure.
// Please use Photoshop to resize images to a reasonable size.
// NOTE: THIS SCRIPT DOES NOT OUTPUT BLACK OR WHITE COLORS WITH THE CURRENT SETTINGS.
// Here are some test images, make your own.  Most of these are 600px X 600PX 72-96DPI.
// I have not tested images with lower dimensions than 400px X 400px.
// Uncomment the image you want to try.
$filename = 'https://lgr-wp.com/test_images//google.png';
//$filename = 'https://lgr-wp.com/test_images//reep_logo_green_white_back.png';
//$filename = 'https://lgr-wp.com/test_images//x_logo.png';  // Set $saturationThreshold = 0
//$filename = 'https://lgr-wp.com/test_images//depositphotos_58387439-stock-illustration-abstract-vector-logo.png';
//$filename = 'https://lgr-wp.com/test_images//Twitter-logo.svg_.png';
//$filename = 'https://lgr-wp.com/test_images//Pepsi-logo.png';
//$filename = 'https://lgr-wp.com/test_images//LEGO_logo.svg.png';
//$filename = 'https://lgr-wp.com/test_images//pngtree-beauty-logo-design-png-image_8947095.png';  // Set $saturationThreshold = 0
//$filename = 'https://lgr-wp.com/test_images//Ford_logo_flat.svg.png';
//$filename = 'https://lgr-wp.com/test_images//logo-1933884_640.png';
//$filename = 'https://lgr-wp.com/test_images//tiger_hd_1080p.jpg';
//$filename = 'https://lgr-wp.com/test_images/APSLogoWeb600x600.png';

// BEGINING USER SETTINGS

$saturationThreshold = 64; // 64 work best in testing. Change to 0
// for images that have one color that is very dark or very light
// (i.e. dark gray, light gray).  You can play with this setting.
$percentResolution = 13.5; // Must be between 2 and 100 13.5 works
// best in testing. Change this to skip pixels, helps to reduce the
// number of colors rendered (Image Dependant). Lower this number
// if image contains only thin lines with white, black or transparent
// background.
$hueDeltaTransitionPoint = 22; // 0 returns the most number of colors,
// 22 works good in most cases. This setting controls how much change
// from the current hue, will trigger a color spectrum change. The lower
// the number the more color groups that will be created. Each color
// group will have one color selected for output, that color will be
// picked from the center of the group not from the transition edge.
// IMAGES WITH EXTREAM DETAIL AND A LOT OF COLORS WILL RENDER AS A
// LARGER LIST OF COLORS BEFORE TRANSITIONING.

// ENDING USER SETTINGS

// Begin Rendering
$im = loadImage($filename); // PNG images work the best
// Display the full image in browser
echo '<div style="display: block;margin: 0 auto;text-align: center;"><img src="' . $filename . '"></div>';
$imageWidth = imagesx($im);
$imageHeight = imagesy($im);
echo '$imageWidth = ' . $imageWidth . '<br>';
$stepPixels = intval($imageWidth/$percentResolution);
// Get the image resolution
$imageresolution = imageresolution($im);
$dpiH = $imageresolution[0];
$dpiV = $imageresolution[1];

$previousHue = 0;
$previousHexColor = 0;

$arrayRangesOfColors = array();
$arraySelectedColors = array();
$counterSelected = 0;

$arrayColorIndicies = array();
for ($y = 0; $y <= $imageHeight; $y+=$stepPixels) {
  for ($x = 0; $x <= $imageWidth; $x+=$stepPixels) {
    $hexColor = get_pixel_color($im, $x, $y);
    $colorIndex = get_pixel_color_index($im, $x, $y);
    $colorIndex = str_pad($colorIndex, 11, '0', STR_PAD_LEFT);
    $rgb = hex2rgb($hexColor);
    $r = $rgb['red'];
    $g = $rgb['green'];
    $b = $rgb['blue'];
    $hsb = rgb2hsb($rgb["red"],$rgb["green"],$rgb["blue"]);
    $hue = round($hsb["hue"]);
    $saturation = round($hsb["saturation"]*100);
    $brightness = round($hsb["brightness"]*100);
    // Combine hue and saturation and brightness to get an integer for sorting level 1
    $hsbSortKey = intval(str_pad($hue, 3, '0', STR_PAD_LEFT) . str_pad($saturation, 3, '0', STR_PAD_LEFT) . str_pad($brightness, 3, '0', STR_PAD_LEFT));

    if(isset($hsbSortKey)){
      if($saturation > $saturationThreshold){
        if(isset($arrayColorIndicies[$hsbSortKey])){
          try{
            if(isset($arrayColorIndicies[$colorIndex]['colorIndex'])) $arrayColorIndicies[$colorIndex]['colorIndex'] = $colorIndex;
            if(isset($arrayColorIndicies[$colorIndex]['colorCount'])) $arrayColorIndicies[$colorIndex]['colorCount'] +=1;
            if(isset($arrayColorIndicies[$colorIndex]['hexColor'])) $arrayColorIndicies[$colorIndex]['hexColor'] = $hexColor;
            if(isset($arrayColorIndicies[$colorIndex]['red'])) $arrayColorIndicies[$colorIndex]['red'] = $r;
            if(isset($arrayColorIndicies[$colorIndex]['green'])) $arrayColorIndicies[$colorIndex]['green'] = $g;
            if(isset($arrayColorIndicies[$colorIndex]['blue'])) $arrayColorIndicies[$colorIndex]['blue'] = $b;
            if(isset($arrayColorIndicies[$colorIndex]['hue'])) $arrayColorIndicies[$colorIndex]['hue'] = $hue;
            if(isset($arrayColorIndicies[$colorIndex]['saturation'])) $arrayColorIndicies[$colorIndex]['saturation'] = $saturation;
            if(isset($arrayColorIndicies[$colorIndex]['brightness'])) $arrayColorIndicies[$colorIndex]['brightness'] = $brightness;
            if(isset($arrayColorIndicies[$colorIndex]['hsbSortKey'])) $arrayColorIndicies[$colorIndex]['hsbSortKey'] = $hsbSortKey;
            if(isset($arrayColorIndicies[$colorIndex]['y'])) $arrayColorIndicies[$colorIndex]['y'] = $y;
            if(isset($arrayColorIndicies[$colorIndex]['x'])) $arrayColorIndicies[$colorIndex]['x'] = $x;
            if(isset($arrayColorIndicies[$colorIndex]['dpiH'])) $arrayColorIndicies[$colorIndex]['dpiH'] = $dpiH;
            if(isset($arrayColorIndicies[$colorIndex]['dpiV'])) $arrayColorIndicies[$colorIndex]['dpiV'] = $dpiV;
          }
          catch(Throwable $e){
            // oopsies!
            echo $e . '<br>';
          }
        }
        else{
          $arrayColorIndicies[$hsbSortKey] = array( 
              "colorIndex" => $colorIndex,  
              "colorCount" => 1,  
              "hexColor" => $hexColor,  
              "red" => $r,  
              "green" => $g,  
              "blue" => $b,  
              "hue" => $hue,  
              "saturation" => $saturation,  
              "brightness" => $brightness,  
              "hsbSortKey" => $hsbSortKey,  
              "y" => $y,  
              "x" => $x,  
              "dpiH" => $dpiH,  
              "dpiV" => $dpiV  
          );
        }
      }
    }
  } // end for
} // end for

// We don't need the original image anymore so destroy it.
imageDestroy($im);

$indexColumn  = array_column($arrayColorIndicies, 'hsbSortKey');
$colorCountColumn  = array_column($arrayColorIndicies, 'colorCount');
$brightnessColumn  = array_column($arrayColorIndicies, 'brightness');
$saturationColumn  = array_column($arrayColorIndicies, 'saturation');
array_multisort($indexColumn, SORT_DESC, SORT_NUMERIC, $colorCountColumn, SORT_DESC, SORT_NUMERIC, $brightnessColumn, SORT_DESC, SORT_NUMERIC, $saturationColumn, SORT_DESC, SORT_NUMERIC, $arrayColorIndicies);
$rangeOfColorsIndex = 0;
$rangeOfHuesIndex = 0;
// Create arrayRangesOfColors to store groups of colors based on the sorted $arrayColorIndicies (i.e. reds. yellows, blues, greens...)
$arrayRangesOfColors[$rangeOfColorsIndex] = array();
echo '<div style="text-align: center;">';
foreach ($arrayColorIndicies as $key => $value) {
  if(isset($key)){
    if(getHueDelta($value['hue'], $previousHue) > $hueDeltaTransitionPoint){ // 22 seems to work the best

      echo '<br>' . 'HUE CHANGED FROM ' . $previousHue . ' TO ' . $value['hue'] . ' OLD COLOR: ' . $previousHexColor . ' NEW COLOR: ' . $value['hexColor'] . '<br>';
      $rangeOfColorsIndex += 1;
      $rangeOfHuesIndex = 0;

    }

    $arrayRangesOfColors[$rangeOfColorsIndex][$rangeOfHuesIndex] = array();
    $arrayRangesOfColors[$rangeOfColorsIndex][$rangeOfHuesIndex] = $value;
    $rangeOfHuesIndex +=1;

    $previousHue = $value['hue'];
    $previousHexColor = $value['hexColor'];

    $colorsFromIndicies = imagecolorsforindex($im, $key);
    $theHexColor = rgb2Hex($colorsFromIndicies['red'], $colorsFromIndicies['green'], $colorsFromIndicies['blue']);
    echo '<div style="color: white; border: 1px solid black; height:180px; width:180px; display: inline-block; background-color:' . $value['hexColor'] . ';"><div style="color:white; position: relative; float: left; top: 0px; left: 10px;">hexColor=' . $value['hexColor'] . '<br>colorCount=' . $value['colorCount']  . '<br>colorIndex=' . $value['colorIndex'] . '<br>hue=' . $value['hue'] . '<br>saturation=' . $value['saturation'] . '<br>brightness=' . $value['brightness'] . '<br>hsbSortKey=' . $value['hsbSortKey'] . '<br>y=' . $value['y'] . '<br>x=' . $value['x'] . '</div></div>';

  }
}

// We don't need the $arrayColorIndicies array any more so unset it.
unset($arrayColorIndicies);

// Select and display final output colors from the center of the color's spectrom from the arrayRangesOfColors groups.
echo '<h2>Final Selected Colors from Middle of Color Groups</h2>';
foreach($arrayRangesOfColors as $spectrums){
  if(count($spectrums) >= 2){
    $thisGroup = $spectrums[intval(count($spectrums)/2)]; // Center of group
  }
  else{
    if(count($spectrums) == 1){
      $thisGroup = $spectrums[0];
    }
  }
  if(isset($thisGroup)){
    echo '<div style="color: white; border: 1px solid black; height:180px; width:180px; display: inline-block; background-color:' . $thisGroup['hexColor'] . ';"><div style="color:white; position: relative; float: left; top: 0px; left: 10px;">hexColor=' . $thisGroup['hexColor'] . '<br>colorCount=' . $thisGroup['colorCount']  . '<br>colorIndex=' . $thisGroup['colorIndex'] . '<br>hue=' . $thisGroup['hue'] . '<br>saturation=' . $thisGroup['saturation'] . '<br>brightness=' . $thisGroup['brightness'] . '<br>hsbSortKey=' . $thisGroup['hsbSortKey'] . '<br>y=' . $thisGroup['y'] . '<br>x=' . $thisGroup['x'] . '</div></div>';    
  }
}

// We don't need the arrayRangesOfColors array any more so unset it.
unset($arrayRangesOfColors);


///////////////////////////
// Functions
///////////////////////////

function get_pixel_color($image, $x, $y) {
  $rgb = imagecolorat($image, $x, $y);
  $r = ($rgb >> 16) & 0xFF;
  $g = ($rgb >> 8) & 0xFF;
  $b = $rgb & 0xFF;
  return rgb2Hex($r, $g, $b);
}

function get_pixel_color_index($image, $x, $y) {
  return imagecolorat($image, $x, $y);
}


function hex2rgb( $hex ) {

    if ($hex[0] == '#') {
        $hex = substr($hex, 1);
    }
    list($r, $g, $b) = array_map("hexdec", str_split($hex, (strlen( $hex ) / 3)));
    return array( 'red' => $r, 'green' => $g, 'blue' => $b );
}

function rgb2Hex($r, $g=-1, $b=-1) {
  (is_array($r) && sizeof($r) == 3) ? list($r, $g, $b) = $r : NULL;
  $r = intval($r); $g = intval($g);
  $b = intval($b);
  $r = dechex($r<0?0:($r>255?255:$r));
  $g = dechex($g<0?0:($g>255?255:$g));
  $b = dechex($b<0?0:($b>255?255:$b));
  $color = (strlen($r) < 2?'0':'').$r;
  $color .= (strlen($g) < 2?'0':'').$g;
  $color .= (strlen($b) < 2?'0':'').$b;
  return '#'.$color;
}   

function rgb2hsb ($r, $g, $b) {
  $r = $r/255;
  $g = $g/255;
  $b = $b/255;
  $MAX = max($r,$g,$b);
  $MIN = min($r,$g,$b);
  if     ($MAX == $MIN) return array('hue' => 0,'saturation' => 0,'brightness' => $MAX);
  if     ($r == $MAX) $HUE = ((0 + (($g - $b)/($MAX-$MIN))) * 60);
  elseif ($g == $MAX) $HUE = ((2 + (($b - $r)/($MAX-$MIN))) * 60);
  elseif ($b == $MAX) $HUE = ((4 + (($r - $g)/($MAX-$MIN))) * 60);
  if     ( $HUE < 0 ) $HUE += 360;
  $SATURATION = (($MAX - $MIN)/$MAX);

  return [
      'hue' => $HUE,
      'saturation' => $SATURATION,
      'brightness' => $MAX
  ];
}

function loadImage($imagefile) {
  $image_mime = image_type_to_mime_type(exif_imagetype($imagefile));
  $loadedImage = null;
  // Note @ suppresses warnings
  switch($image_mime)
  {
    case 'image/png';
      if(!$loadedImage = @imagecreatefrompng($imagefile)) {
        die("Error loading image: {$imagefile}");
      }
      break;
    case 'image/jpeg';
      if(!$loadedImage = @imagecreatefromjpeg($imagefile)) {
        die("Error loading image: {$imagefile}");
      }
      break;
    case 'image/gif';
      if(!$loadedImage = @imagecreatefromgif($imagefile)) {
        die("Error loading image: {$imagefile}");
      }
      break;
    case 'image/bmp';
      if(!$loadedImage = @imagecreatefrombmp($imagefile)) {
        die("Error loading image: {$imagefile}");
      }
      break;
    default;
      echo 'Image type ' . $image_mime . ' is not supported';
      break;
  } 
  return $loadedImage;

}


function hsb2rgb($iH, $iS, $iV) {

  if($iH < 0)   $iH = 0;   // Hue:
  if($iH > 360) $iH = 360; //   0-360
  if($iS < 0)   $iS = 0;   // Saturation:
  if($iS > 100) $iS = 100; //   0-100
  if($iV < 0)   $iV = 0;   // Lightness:
  if($iV > 100) $iV = 100; //   0-100

  $dS = $iS/100.0; // Saturation: 0.0-1.0
  $dV = $iV/100.0; // Lightness:  0.0-1.0
  $dC = $dV*$dS;   // Chroma:     0.0-1.0
  $dH = $iH/60.0;  // H-Prime:    0.0-6.0
  $dT = $dH;       // Temp variable

  while($dT >= 2.0) $dT -= 2.0; // php modulus does not work with float
  $dX = $dC*(1-abs($dT-1));     // as used in the Wikipedia link

  switch(floor($dH)) {
    case 0:
        $dR = $dC; $dG = $dX; $dB = 0.0; break;
    case 1:
        $dR = $dX; $dG = $dC; $dB = 0.0; break;
    case 2:
        $dR = 0.0; $dG = $dC; $dB = $dX; break;
    case 3:
        $dR = 0.0; $dG = $dX; $dB = $dC; break;
    case 4:
        $dR = $dX; $dG = 0.0; $dB = $dC; break;
    case 5:
        $dR = $dC; $dG = 0.0; $dB = $dX; break;
    default:
        $dR = 0.0; $dG = 0.0; $dB = 0.0; break;
  }

  $dM  = $dV - $dC;
  $dR += $dM; $dG += $dM; $dB += $dM;
  $dR *= 255; $dG *= 255; $dB *= 255;

  return [
      'red' => round($dR),
      'green' => round($dG),
      'blue' => round($dB)
  ];

}

function getHueDelta($currentHue, $previousHue){
  return abs($currentHue - $previousHue);
}

?>

Upvotes: 0

Dan
Dan

Reputation: 2302

Solution was to convert the RGB to HSL as suggested by Herbert. Function for converting to human still needs a little tweaking / finishing off but here it is:

function hslToHuman($h, $s, $l) {

$colors = array();

// Gray
if ($s <= 10 && (9 <= $l && $l <= 90)) {
    $colors[] = "gray";
}

$l_var = $s / 16;

// White
$white_limit = 93;
if (($white_limit + $l_var) <= $l && $l <= 100) {
    $colors[] = "white";
}

// Black
$black_limit = 9;
if (0 <= $l && $l <= ($black_limit - $l_var)) {
    $colors[] = "black";
}

// If we have colorless colors stop here
if (sizeof($colors) > 0) {
    return $colors;
}

// Red
if (($h <= 8 || $h >= 346)) {
    $colors[] = "red";
}

// Orange && Brown
// TODO

// Yellow
if (40 <= $h && $h <= 65) {
    $colors[] = "yellow";
}

// Green
if (65 <= $h && $h <= 170) {
    $colors[] = "green";
}

// Blue
if (165 <= $h && $h <= 260) {
    $colors[] = "blue";
}

// Pink && Purple
// TODO

return $colors;
}

Upvotes: 2

Morg.
Morg.

Reputation: 701

Alright so you've got a graphics library, there must be an average thingy in there, you average your picture, take any pixel and tadaam you're done ?

And the simplest solution found on here is : resize to 1x1px, get colorat :

Get image color

After that it's pretty easy, find somewhere a detailed list of rgb to human readable (for example html colors), encode that as an array and use it in your script -> round() your r,g,b, vals to the precision of your data and retrieve the color.

You should determine what color granularity you want and go from there -> find your set of named colors (I think all 8bit colors have a name somewhere) and then reduce your rgb information to that - either by reducing color information of the image before reading it (faster) or by reducing color information at read time (more flexible in terms of color list.

Basic example of some rgb-human readable resources :

http://www.w3schools.com/html/html_colornames.asp

Others :

http://chir.ag/projects/name-that-color/#2B7C97

http://r0k.us/graphics/SIHwheel.html

Upvotes: 1

Related Questions