5

I'm creating a fast lookup table for colors. Let's say you give me shade of red, then I'll convert it to closest web safe color and perform the lookup. It will return many different shades of red and it's ok for my purposes.

The only problem I have right now is how do I convert a given RGB value to a web safe value? I'm using PIL and Python. I don't mind writing my own algorithm either but it's a bit too difficult for me.

bodacydo
  • 75,521
  • 93
  • 229
  • 319

3 Answers3

4

I don't know if there is a specific way to do it with PIL, but if you have an rgb colour and we define the closest web safe colour as the one where the distance in the rgb space is the smallest, you can do it as follows:

def getwebsafe(r,g,b):
  rw = 51 * ((int(r)+25)//51)
  gw = 51 * ((int(g)+25)//51)
  bw = 51 * ((int(b)+25)//51)
  return (rw,gw,bw)

This is because web safe colors have 6 possible shades for each component: (0,51,102,153,204,255). If you a component in your colour is in the range [0,25] the closest is 0, if it's in the range [26-76] the closest is 51, etc.

Alternatively, a shorter version for an rgb colour represented as a list:

def getwebsafe(colour):
  return [51*((int(c)+25)//51) for c in colour]

EDIT: Edited to make sure it works even when Python uses non-integer numbers for colours and for Python 2 and 3.

pedrosorio
  • 870
  • 5
  • 8
  • Thanks for your answer. Can you check it? It fails for [0,25] range. Let's say `r` is 20. Then `rw = 51 * ((r+25)/51) = 51 * ((20+25)/51) = 45`. – bodacydo Oct 24 '12 at 22:29
  • I just plugged in more numbers and it never really returns values (0, 51, 102, 153, 204, 255). For example, r = 200. Then `rw = 51 * ((200+25)/51) = 225`. For `r = 255` it returns 280. – bodacydo Oct 24 '12 at 22:33
  • Seems like this could be done better with 3 256-entry-table lookups. – martineau Oct 24 '12 at 22:34
  • @bodacydo are you using integers? If you actually run this code in python it uses integer division and, for example, 45/51 is 0. – pedrosorio Oct 24 '12 at 22:37
  • I've hex values for RGB. I can easily split it to (R, G, B) and have 3 hex values, and I can also convert them to decimal values (R_dec, G_dec, B_dec). – bodacydo Oct 24 '12 at 22:39
  • Apparently your decimal values aren't integers. It should work now. – pedrosorio Oct 24 '12 at 22:42
  • @pedrosorio It's still not working. Take `r=1` for example, then `rw = 51 * ((int(r)+25)/51) = 51 * ((int(1)+25)/51) = 51 * ((1+25)/51) = 26`. 26 is not 0. – bodacydo Oct 24 '12 at 22:46
  • 1
    pedrosorio's answer should work in Python 2 (by default) but not in Python 3 where `/` does "true" division even when given two integers. To make it work in both versions, replace the `/` division operator with `//` which always does integer division. – Blckknght Oct 24 '12 at 22:49
  • Thank you @Blckknght! I only use Python 2, I was unaware that in Python 3 / did floating point division. – pedrosorio Oct 24 '12 at 22:51
  • @bodacydo: OK, I've added a separate answer. – martineau Oct 24 '12 at 23:59
  • This should also work in any Python version: `rw = 51 * round( ( r / 255 ) * 5 )`. – Beejor Mar 12 '15 at 05:51
1

As I said in a comment under @pedrosorio's answer, I think this could be done more efficently by using table lookups. This means virtually all of the mathematical calculations are done once, when the table is created, and is only done 256 times, rather for each and every pixel. Here's what I mean, using pedrosorio's formula:

tbl = tuple(51*((int(i)+25)//51) for i in xrange(256))

def websafe(*color):
    return tuple(tbl[c] for c in color)

safecolor = websafe(34,55,13)

However, I'm not sure about how the formula maps the 0-255 range values -- because it assigns the following number of entries for each of the six primary shades:

shade count
 0x00  26
 0x33  51
 0x66  51
 0x99  51
 0xcc  51
 0xff  26

Instead, I would use this slightly simpler formula:

tbl = tuple(51*(i//43) for i in xrange(256))

which gives a more even distribution of the shades:

shade count
 0x00  43
 0x33  43
 0x66  43
 0x99  43
 0xcc  43
 0xff  41

Once you know the distribution count you could even do something like this which involves very little math. Of course there's little reason to bother optimizing non-speed-crucial code.

tbl = ((0x00,)*43 + (0x33,)*43 + (0x66,)*43 +
       (0x99,)*43 + (0xcc,)*43 + (0xff,)*41)
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Well said. For maximum efficiency we can just use table lookup. – pedrosorio Oct 25 '12 at 00:04
  • Regarding the "simple formula", I disagree. if you have RGB(42,0,0) it's much closer to RGB(51,0,0) than to RGB(0,0,0). The problem is web safe uses extreme points in RGB and so these are closer to less colours than the middle points. In order to get smallest overall distance to any colour web safe could have been designed with spacing of 42.5 starting at 21, spanning the range [21 - 234] with 6 points. This would give equal count for all shades and perfect spacing at the expense of not having "black" and "white". – pedrosorio Oct 25 '12 at 00:11
  • @pedrosorio: As mentioned in the Wikipedia article, the websafe colors were selected because they allow exactly six equally-spaced shades of red, green, and blue -- which is another way of saying they divide the RGB colorspace up into equally-sized mini-cubes. Sure you can find a worst-case in any table, the more important point is how does using it do on average for any color to which it might be applied. – martineau Oct 25 '12 at 00:31
  • Your formula allows for six equally spaced shades that span the range [0-255] but the conversion is biased (there are rgb values that get assigned a shade which is not the closest in web safe to the real shade). I doubt that's the conversion most users would expect. – pedrosorio Oct 25 '12 at 00:41
  • @pedrosorio: To resolve our little dispute to my own satisfaction, I wrote a simple program to actually determined what the average error would be for using the different values for the table -- and found that one based on your formula would was indeed almost 1% (an average absolute error about 2.5 units less) more accurate than either of my alternatives, so I stand corrected on that point. – martineau Oct 25 '12 at 18:59
  • Table lookup is the way to go for things like converting an image, but for quick one-offs, converting to a web safe color shouldn't take more than a few trivial calculations. As for modifying the distribution of shades, there's really no reason this should be necessary, since the color table is evenly spaced and the source color is being quantized. For example with a color table of 10 values, rounding 2.3456 to 2 gives you the closest value (nearer to 2 than 1 or 3) simply by the nature of rounding. – Beejor Mar 12 '15 at 06:02
0

I had similar kind of problem which was needed to convert all the pixels in an image to it's nearest primary and secondary colors

For that i used K-dimentional tree method which was mentioned in this answer https://stackoverflow.com/a/22478139/9799700 as follow

  # import the necessary packages
  import scipy.spatial as sp

  #Add more colors to this array if you need 
  main_colors = [(0,0,0),
              (255,255,255),
              (255,0,0),
              (0,255,0),
              (0,0,255),
              (255,255,0),
              (0,255,255),
              (255,0,255),
              ] 

  #replace r,g,b variables with your pixel rgb values
  pixelColor = (r,g,b)
  kdTree = sp.KDTree(main_colors) 
  ditsance,result = kdTree.query(pixelColor) 
  nearest_color = main_colors[result]

My complete Code : https://gist.github.com/ssethsara/bc4ece5f2d959e6b434b73db29959248

S.Sethsara
  • 1
  • 2
  • 4