3

I have a dictionary of colors (below) and a color such as #4a7dac which is 'steelblue'. I want to find the name of the closest hex colour in the dictinary to this colour. So the algorithm would decide on #0000ff and return 'blue'.

colors= {
    "red":"#FF0000",
    "yellow":"#FFFF00",
    "green":"#008000",
    "blue":"#0000FF",
    "black":"#000000",
    "white":"#FFFFFF",
    "gray":"#808080",
}

I have thought about calculating some sort of 'distance' between colors, but don't know how to go about writing this.

Joe Iddon
  • 20,101
  • 7
  • 33
  • 54
shahidammer
  • 1,026
  • 2
  • 10
  • 24
  • I doubt this is what you want but if you have the names you could do a string search. ie 'blue' in 'steelblue'. It works perfectly in your example result! – Ant Aug 29 '18 at 16:25

2 Answers2

4

We can create a function that takes two hex strings and returns the sum of the differences between the individual colour components. If you can't understand how the following works, just comment.

def diff(h1, h2):
    def hexs_to_ints(s):
        return [int(s[i:i+2], 16) for i in range(1,7,2)]
    return sum(abs(i - j) for i, j in zip(*map(hexs_to_ints, (h1, h2))))

And we can test it:

>>> diff('#ff00ff', '#0000ff')
255
>>> diff('#0000ff', '#00000a')
245

So now, we can create a function that uses this function to complete the task:

def get_col_name(hex, colors):
    return min([(n,diff(hex,c)) for n,c in colors.items()], key=lambda t: t[1])[0]

Unfortunately, this doesn't work for your colors, since it chooses gray which is [128, 128, 128] so very near to steelblue which is [74, 125, 172] - nearer than blue which is [0, 0, 255]. This means the difference is smaller to gray that to blue. I'll try to think of a better method, but maybe someone has some insight and can drop a comment?

Joe Iddon
  • 20,101
  • 7
  • 33
  • 54
  • 1
    Maybe working in HSV or similar colourspace might work better? – Dan Mašek Aug 29 '18 at 13:44
  • @DanMašek I was thinking that, but `gray`'s hue is meaningless (can be anything) so it would still muck things up. – Joe Iddon Aug 29 '18 at 14:38
  • Ya... saturation first to detect shades of gray? | Or perhaps https://en.wikipedia.org/wiki/Color_difference – Dan Mašek Aug 29 '18 at 14:39
  • I've tried 5 different metrics, and gray always comes as closest. I guess if the OP really wants to get blue as result, then they have to define their own custom metric that will satisfy their desires. – Dan Mašek Aug 29 '18 at 16:21
  • @DanMašek Yepp, HSV just brings us closer to the result :D. – NoorJafri Aug 29 '18 at 19:23
  • Does not it work well with the combination of `colorsys.rgb_to_hls(*rgb)` and `math.sqrt(sum(abs(i - j)**2 for i, j in zip(*map(hexs_to_ints, (h1, h2)))))`. – taront Aug 30 '18 at 00:22
3

How about converting them into RGB and find the dominatant color?

For example

RED FF0000         rgb(255,0,0)
BLUE 0000FF        rgb(0,0,255)
steelblue 4A7DAC   rgb(74,125,172)

You can most likely achieve this target with the RGB rather than the HEX

The rest you can see this algo: https://stackoverflow.com/a/9018100/6198978

EDIT The thing is RGB and HEX calculation will not be able to work with Grey color as every color just has closest distance to the grey. For that purpose you can use the HSV values of the color, I am editing the code with the HSV implemented as well :D

Learned alot :D

I was having fun with it here you go:

    import math
    colors= {
    "red":"#FF0000",
    "yellow":"#FFFF00",
    "green":"#008000",
    "blue":"#0000FF",
    "black":"#000000",
    "white":"#FFFFFF",
    "grey": "#808080"
}

# function for HSV TAKEN FROM HERE: https://gist.github.com/mathebox/e0805f72e7db3269ec22
def rgb_to_hsv(r, g, b):
    r = float(r)
    g = float(g)
    b = float(b)
    high = max(r, g, b)
    low = min(r, g, b)
    h, s, v = high, high, high

    d = high - low
    s = 0 if high == 0 else d/high

    if high == low:
        h = 0.0
    else:
        h = {
            r: (g - b) / d + (6 if g < b else 0),
            g: (b - r) / d + 2,
            b: (r - g) / d + 4,
        }[high]
        h /= 6

return h, s, v

# COLOR YOU WANT TO TEST TESTED
check = "#808000".lstrip('#')
checkRGB = tuple(int(check[i:i+2], 16) for i in (0, 2 ,4))
checkHSV = rgb_to_hsv(checkRGB[0], checkRGB[1], checkRGB[2])


colorsRGB = {}
colorsHSV = {}

for c, v in colors.items():
    h = v.lstrip('#')
    colorsRGB[c] = tuple(int(h[i:i+2], 16) for i in (0, 2 ,4))

for c, v in colorsRGB.items():
    colorsHSV[c] = tuple(rgb_to_hsv(v[0], v[1], v[2]))

def colourdistanceRGB(color1, color2):
    r = float(color2[0] - color1[0])
    g = float(color2[1] - color1[1])
    b = float(color2[2] - color1[2])
    return math.sqrt( ((abs(r))**2) + ((abs(g))**2) + ((abs(b))**2) )

def colourdistanceHSV(color1, color2):
    dh = min(abs(color2[0]-color1[0]), 360-abs(color2[0]-color1[0])) / 180.0
    ds = abs(color2[1]-color1[1])
    dv = abs(color2[2]-color1[2]) / 255.0
    return math.sqrt(dh*dh+ds*ds+dv*dv)

resultRGB = {}
resultHSV = {}

for k, v in colorsRGB.items():
    resultRGB[k]=colourdistanceRGB(v, checkRGB)

for k,v in colorsHSV.items():
    resultHSV[k]=colourdistanceHSV(v, checkHSV)


#THIS WILL NOT WORK FOR GREY
print("RESULT WITH RGB FORMULA")
print(resultRGB)
print(min(resultRGB, key=resultRGB.get))


#THIS WILL WORK FOR EVEN GREY
print(resultHSV)
print(min(resultHSV, key=resultHSV.get))

#OUTPUT FOR RGB
#check = "#808000"  output=GREY
#check = "#4A7DAC"  output=GREY :D

#OUTPUT FOR RGB
#check = "#808000"  output=GREEN
#check = "#4A7DAC"  output=BLUE:D
NoorJafri
  • 1,787
  • 16
  • 27