3
votes

Is there some convention to divide the HSL color circle into degree ranges to define basic colors?

For example, degrees 80-150 would be considered green, degrees 210-280 as blue, and so on.

I'd like to automatically detect pixel's color belonging to some "color-group" and found that HSL is very good to determine the tone. For my purpose is enough to define boundaries for red, orange, yellow, green, cyan, blue, magenta, pink.

Is there already a solution for such dividing?

EDIT

I add more reasoning and examples before putting a bounty on...

My final idea is to index our images' stock by their dominant color, so I could include color as one query parameter.

I have defined some boundaries how to divide HSL color wheel:

  1-15 red
  16-50 orange
  51-72 yellow
  73-155 green
  156-185 cyan
  186-268 blue
  269-310 magenta
  311-344 pink
  345-359 red

I have a function to determine pixel's color:

function getPixelTone(pixel) {
  let [ hue, sat, light ] = rgbToHsl( ...pixel );
  sat   = parseInt(sat);
  light = parseInt(light);

  if ( light < 3 || sat < 50  && light < 5 ) {
    return 'black';
  }

  if ( light > 96 ) {
    return 'white';
  }

  if ( hue === 0 || isPixelGray(pixel) ) {
    return 'gray';
  }

  if ( (hue > 0 && hue < 16 ) || (hue > 344 && hue < 360 ) ) {
    if ( light > 55 ) {
      return 'pink';
    }

    if ( light < 34 || ( sat < 30 && light < 80 ) ) {
      return 'brown';
    }  

    return 'red';
  }

  if ( hue > 15 && hue < 51 ) {
    if ( light < 34 ) {
      return 'brown';
    }  
    return 'orange';
  }
  if ( hue > 50 && hue < 73 ) {
    return 'yellow';
  }
  if ( hue > 72 && hue < 156 ) {
    return 'green';
  }
  if ( hue > 155 && hue < 186 ) {
    return 'cyan';
  }
  if ( hue > 185 && hue < 269 ) {
    return 'blue';
  }
  if ( hue > 268 && hue < 311 ) {
    return 'magenta';
  }
  if ( hue > 310 && hue < 345 ) {
    return 'pink';
  }

  return 'color';

}

Function rgbToHsl is from module rgb-to-hsl, function isPixelGray is defined like:

function isPixelGray(pixel) {
  if ( Math.max(pixel) - Math.min(pixel) < 3 ) {
    return true;
  }
  return false;
}

So all the purpose of my question is to quantize pixel into one of the simplest perceived colors. I think those colors would be: red, orange, yellow, green, cyan, blue, magenta, pink, brown, black, and white, it could include also beige if it can determine easily.

What is the best way to determine the pixel belonging to one of these colors?

PS I chose HSL colorspace as a base, because it has advantages before the RGB, from my point of view. But it has not to be this way.

3

3 Answers

2
votes

Color Name & Hue is the first search result for "rgb to hue name." It's a web app that does exactly what you want:

With this little tool you can either enter RGB (Red-Green-Blue) values, HSB (Hue-Saturation-Brightness) numbers or a hexadecimal code for a color, to find its closest match of a named color and its corresponding hue... The list of colors comprises 1640 different color names extracted from several sources on the web.

The color name is matched to one of the following main color hues: Red, Orange, Yellow, Green, Blue, Violet, Brown, Black, Grey, and White.

Usage instructions for ntc js (Name that Color JavaScript):

var match = ntc.name("#6195ED");
rgb        = match[0]; // RGB value of closest match ("#6495ED")
name       = match[1]; // Color name                 ("Cornflower Blue")
shade_rgb  = match[2]; // RGB value of color's shade ("#0000FF")
shade_name = match[3]; // Color's shade              ("Blue")
exactmatch = match[4]; // True if exact color match  (false)

If you just want the RGB hex values for the names:

// From https://www.color-blindness.com/color-name-hue-tool/js/ntc.js
  shades: [
["FF0000", "Red"],
["FFA500", "Orange"],
["FFFF00", "Yellow"],
["008000", "Green"],
["0000FF", "Blue"],
["EE82EE", "Violet"],
["A52A2A", "Brown"],
["000000", "Black"],
["808080", "Grey"],
["FFFFFF", "White"]
],

  names: [
["35312C", "Acadia", "Brown"],
["75AA94", "Acapulco", "Green"],
["C0E8D5", "Aero Blue", "Green"],

// Many colors omitted...

["DDC283", "Zombie", "Yellow"],
["A29589", "Zorba", "Brown"],
["17462E", "Zuccini", "Green"],
["CDD5D5", "Zumthor", "Grey"]
]
2
votes

Here you have a Color wheel that determines the most simple colors from pixel when you hover it and it is made of pure JS. In the function getColorFromWheel() you get the rba value and then convert it to hsl. When it's converted to hsl the function getColorNameFromHsl() determines what color it is and then it just display the value in a <div>. If you want more precise colors you could just find some more exact list of rba or hsl colors to name array and make a function of that.

Here is a library that will convert rba colors to names: Name that color

Hope this solution was to any help.

function newEl(tag){return document.createElement(tag)}

window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
    var wheel = makeColorWheel(256);
    wheel.addEventListener('mousemove', getColorFromWheel);
    document.body.appendChild( wheel );
}

var convertToHsl = function convertToHsl(rgbArr) {
  var r1 = Number(rgbArr[0]) / 255, g1 = Number(rgbArr[1]) / 255, b1 = Number(rgbArr[2]) / 255;
  var maxColor = Math.max(r1,g1,b1), minColor = Math.min(r1,g1,b1);
  var L = (maxColor + minColor) / 2 , S = 0, H = 0;
  if(maxColor != minColor){
    if(L < 0.5){
      S = (maxColor - minColor) / (maxColor + minColor);
    }else{
      S = (maxColor - minColor) / (2.0 - maxColor - minColor);
    }
    if(r1 == maxColor){
      H = (g1-b1) / (maxColor - minColor);
    }else if(g1 == maxColor){
      H = 2.0 + (b1 - r1) / (maxColor - minColor);
    }else{
      H = 4.0 + (r1 - g1) / (maxColor - minColor);
    }
  }
  L = L * 100;
  S = S * 100;
  H = H * 60;
  if(H<0){
    H += 360;
  }
  return {h:H, s:S, l:L};
}

var getColorNameFromHsl = function (hsl) {
        var l = Math.floor(hsl.l), s = Math.floor(hsl.s), h = Math.floor(hsl.h);
        if (s <= 10 && l >= 90) {
            return ("White")
        } else if ((s <= 10 && l <= 70) || s === 0) {
            return ("Gray")
        } else if (l <= 15) {
            return ("Black")
        } else if ((h >= 0 && h <= 15) || h >= 346) {
            return ("Red");
        } else if (h >= 16 && h <= 35) {
            if (s < 90) {
                return ("Brown");
            } else {
                return ("Orange");
            }
        } else if (h >= 36 && h <= 54) {
            if (s < 90) {
                return ("Brown");
            } else {
                return ("Yellow");
            }
        } else if (h >= 55 && h <= 165) {
            return ("Green");
        } else if (h >= 166 && h <= 260) {
            return ("Blue")
        } else if (h >= 261 && h <= 290) {
            return ("Purple")
        } else if (h >= 291 && h <= 345) {
            return ("Pink")
        }
    }

var hsv2rgb = function(hsv) {
  var h = hsv.hue, s = hsv.sat, v = hsv.val;
  var rgb, i, data = [];
  if (s === 0) {
    rgb = [v,v,v];
  } else {
    h = h / 60;
    i = Math.floor(h);
    data = [v*(1-s), v*(1-s*(h-i)), v*(1-s*(1-(h-i)))];
    switch(i) {
      case 0:
        rgb = [v, data[2], data[0]];
        break;
      case 1:
        rgb = [data[1], v, data[0]];
        break;
      case 2:
        rgb = [data[0], v, data[2]];
        break;
      case 3:
        rgb = [data[0], data[1], v];
        break;
      case 4:
        rgb = [data[2], data[0], v];
        break;
      default:
        rgb = [v, data[0], data[1]];
        break;
    }
  }
  return rgb;
};

function clamp(min, max, val)
{
    if (val < min) return min;
    if (val > max) return max;
    return val;
}

function makeColorWheel(diameter)
{
    var can = newEl('canvas');
    var ctx = can.getContext('2d');
    can.width = diameter;
    can.height = diameter;
    var imgData = ctx.getImageData(0,0,diameter,diameter);
    var maxRange = diameter / 2;
    
    for (var y=0; y<diameter; y++)
    {
        for (var x=0; x<diameter; x++)
        {
            var xPos = x - (diameter/2);
            var yPos = (diameter-y) - (diameter/2);
            
            
            var polar = pos2polar( {x:xPos, y:yPos} );
            var sat = clamp(0,1,polar.len / ((maxRange/2)));
            var val = clamp(0,1, (maxRange-polar.len) / (maxRange/2) );
            
            var rgb = hsv2rgb( {hue:polar.ang, sat:sat, val:val} );
            
            var index = 4 * (x + y*diameter);
            imgData.data[index + 0] = rgb[0]*255;
            imgData.data[index + 1] = rgb[1]*255;
            imgData.data[index + 2] = rgb[2]*255;
            imgData.data[index + 3] = 255;
        }
    }
    ctx.putImageData(imgData, 0,0);
    return can;
}

function deg2rad(deg)
{
    return (deg / 360) * ( 2 * Math.PI );
}
function rad2deg(rad)
{
    return (rad / (Math.PI * 2)) * 360;
}

function pos2polar(inPos)
{
    var vecLen = Math.sqrt( inPos.x*inPos.x + inPos.y*inPos.y );
    var something = Math.atan2(inPos.y,inPos.x);
    while (something < 0)
        something += 2*Math.PI;
        
    return { ang: rad2deg(something), len: vecLen };
}



function getColorFromWheel(event) 
{
    var can = this;
    var ctx = can.getContext('2d');
    var color = document.getElementById('color');
    
  var x = event.layerX;
  var y = event.layerY;
  var pixel = ctx.getImageData(x, y, 1, 1);
  var data = pixel.data;
  var rgba = 'rgba(' + data[0] + ',' + data[1] +
             ',' + data[2] + ',' + (data[3] / 255) + ')';
  colorName.style.background =  rgba;
 var rgbArray = [data[0], data[1], data[2]];
 var color = getColorNameFromHsl(convertToHsl(rgbArray));
   colorName.textContent = color;
}
div {
width: 200px; 
height: 100px;
float: right;
border-radius: 25px;
text-align: center;
vertical-align: middle;
line-height: 100px; 
}
<div id="colorName" ></div>
0
votes

Well, it depends a lot on what you consider orange or red, or yellow and green. Looking at the circle of colours I'd say that every 15 or 30 or 60 or 90 degrees (depends on the range you want to consider) you have a transition. For example, the transition colour from red to yellow is called orange, but for example the transition colour from yellow to green does not have a name. So, from 0 to 15 (of the Hue parameter) you have the red colour, from 15 to 45 you have orange, from 45 to 75 (more or less) you have yellow, from 75 to 165 you have green, then you have cyan and so on. Edit: You can try to use a color picker such as the one in Powertoys from Microsoft that, not only it tells you the colour in HSV or RGB or HEX, but also tells you the colour name