blob: 863688a5c73c38d60e3da4c541e5cbceb3c6c558 [file] [log] [blame]
Barry Warsaw2406b1d1998-01-31 00:29:41 +00001"""Color Database.
2
Barry Warsawa9053f61998-10-02 16:01:42 +00003This file contains one class, called ColorDB, and several utility functions.
4The class must be instantiated by the get_colordb() function in this file,
5passing it a filename to read a database out of.
6
7The get_colordb() function will try to examine the file to figure out what the
8format of the file is. If it can't figure out the file format, or it has
9trouble reading the file, None is returned. You can pass get_colordb() an
10optional filetype argument.
Barry Warsaw2406b1d1998-01-31 00:29:41 +000011
12Supporte file types are:
13
14 X_RGB_TXT -- X Consortium rgb.txt format files. Three columns of numbers
15 from 0 .. 255 separated by whitespace. Arbitrary trailing
16 columns used as the color name.
Barry Warsawa9053f61998-10-02 16:01:42 +000017
18The utility functions are useful for converting between the various expected
19color formats, and for calculating other color values.
20
Barry Warsaw2406b1d1998-01-31 00:29:41 +000021"""
22
23import sys
Barry Warsaweb9b8af1998-02-11 18:55:37 +000024import string
Barry Warsaw2406b1d1998-01-31 00:29:41 +000025import re
Barry Warsawf4562a71998-02-11 17:19:23 +000026from types import *
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +000027import operator
Barry Warsaw2406b1d1998-01-31 00:29:41 +000028
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000029class BadColor(Exception):
30 pass
31
Barry Warsawf4562a71998-02-11 17:19:23 +000032DEFAULT_DB = None
33
Barry Warsawa5a018f1998-09-25 22:51:36 +000034
Barry Warsaw2406b1d1998-01-31 00:29:41 +000035
36# generic class
37class ColorDB:
38 def __init__(self, fp, lineno):
39 # Maintain several dictionaries for indexing into the color database.
40 # Note that while Tk supports RGB intensities of 4, 8, 12, or 16 bits,
41 # for now we only support 8 bit intensities. At least on OpenWindows,
42 # all intensities in the /usr/openwin/lib/rgb.txt file are 8-bit
43 #
Barry Warsaweb9b8af1998-02-11 18:55:37 +000044 # key is (red, green, blue) tuple, value is (name, [aliases])
45 self.__byrgb = {}
Barry Warsaw2406b1d1998-01-31 00:29:41 +000046 #
Barry Warsaweb9b8af1998-02-11 18:55:37 +000047 # key is name, value is (red, green, blue)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000048 self.__byname = {}
49 #
Barry Warsaw8be25941998-10-02 14:43:30 +000050 # all unique names (non-aliases). built-on demand
51 self.__allnames = None
Barry Warsaw2406b1d1998-01-31 00:29:41 +000052 while 1:
53 line = fp.readline()
54 if not line:
55 break
56 # get this compiled regular expression from derived class
57 mo = self._re.match(line)
58 if not mo:
59 sys.stderr.write('Error in %s, line %d\n' % (fp.name, lineno))
60 lineno = lineno + 1
61 continue
62 #
63 # extract the red, green, blue, and name
Barry Warsaweb9b8af1998-02-11 18:55:37 +000064 #
Barry Warsaw2406b1d1998-01-31 00:29:41 +000065 red, green, blue = map(int, mo.group('red', 'green', 'blue'))
66 name = mo.group('name')
Barry Warsaweb9b8af1998-02-11 18:55:37 +000067 keyname = string.lower(name)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000068 #
69 # TBD: for now the `name' is just the first named color with the
70 # rgb values we find. Later, we might want to make the two word
71 # version the `name', or the CapitalizedVersion, etc.
Barry Warsaweb9b8af1998-02-11 18:55:37 +000072 #
73 key = (red, green, blue)
74 foundname, aliases = self.__byrgb.get(key, (name, []))
Barry Warsaw2406b1d1998-01-31 00:29:41 +000075 if foundname <> name and foundname not in aliases:
76 aliases.append(name)
Barry Warsaweb9b8af1998-02-11 18:55:37 +000077 self.__byrgb[key] = (foundname, aliases)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000078 #
79 # add to byname lookup
Barry Warsaweb9b8af1998-02-11 18:55:37 +000080 #
81 self.__byname[keyname] = key
Barry Warsaw2406b1d1998-01-31 00:29:41 +000082 lineno = lineno + 1
83
Barry Warsaweb9b8af1998-02-11 18:55:37 +000084 def find_byrgb(self, rgbtuple):
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000085 try:
Barry Warsaweb9b8af1998-02-11 18:55:37 +000086 return self.__byrgb[rgbtuple]
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000087 except KeyError:
Barry Warsaweb9b8af1998-02-11 18:55:37 +000088 raise BadColor(rgbtuple)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000089
90 def find_byname(self, name):
Barry Warsaweb9b8af1998-02-11 18:55:37 +000091 name = string.lower(name)
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000092 try:
93 return self.__byname[name]
94 except KeyError:
95 raise BadColor(name)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000096
Barry Warsaw7a134181998-09-29 20:03:15 +000097 def nearest(self, red, green, blue):
Barry Warsaw2406b1d1998-01-31 00:29:41 +000098 # TBD: use Voronoi diagrams, Delaunay triangulation, or octree for
Barry Warsaweb9b8af1998-02-11 18:55:37 +000099 # speeding up the locating of nearest point. Exhaustive search is
100 # inefficient, but may be fast enough.
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000101 nearest = -1
102 nearest_name = ''
Barry Warsaweb9b8af1998-02-11 18:55:37 +0000103 for name, aliases in self.__byrgb.values():
104 r, g, b = self.__byname[string.lower(name)]
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000105 rdelta = red - r
106 gdelta = green - g
107 bdelta = blue - b
108 distance = rdelta * rdelta + gdelta * gdelta + bdelta * bdelta
109 if nearest == -1 or distance < nearest:
110 nearest = distance
111 nearest_name = name
112 return nearest_name
Barry Warsaw8be25941998-10-02 14:43:30 +0000113
Barry Warsaw9f3ea211998-10-02 15:59:20 +0000114 def unique_names(self):
Barry Warsaw8be25941998-10-02 14:43:30 +0000115 # sorted
116 if not self.__allnames:
117 self.__allnames = []
118 for name, aliases in self.__byrgb.values():
119 self.__allnames.append(name)
Barry Warsaw9f3ea211998-10-02 15:59:20 +0000120 # sort irregardless of case
121 def nocase_cmp(n1, n2):
122 return cmp(string.lower(n1), string.lower(n2))
123 self.__allnames.sort(nocase_cmp)
Barry Warsaw8be25941998-10-02 14:43:30 +0000124 return self.__allnames
Barry Warsaw9f3ea211998-10-02 15:59:20 +0000125
126 def aliases_of(self, red, green, blue):
127 try:
128 name, aliases = self.__byrgb[(red, green, blue)]
129 except KeyError:
130 raise BadColor((red, green, blue))
131 return [name] + aliases
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000132
133
134class RGBColorDB(ColorDB):
135 _re = re.compile(
136 '\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
137
138
139
140# format is a tuple (RE, SCANLINES, CLASS) where RE is a compiled regular
141# expression, SCANLINES is the number of header lines to scan, and CLASS is
142# the class to instantiate if a match is found
143
144X_RGB_TXT = re.compile('XConsortium'), 1, RGBColorDB
145
146def get_colordb(file, filetype=X_RGB_TXT):
147 colordb = None
148 fp = None
149 typere, scanlines, class_ = filetype
150 try:
151 try:
152 lineno = 0
153 fp = open(file)
154 while lineno < scanlines:
155 line = fp.readline()
156 if not line:
157 break
158 mo = typere.search(line)
159 if mo:
160 colordb = class_(fp, lineno)
161 break
162 lineno = lineno + 1
163 except IOError:
164 pass
165 finally:
166 if fp:
167 fp.close()
Barry Warsawf4562a71998-02-11 17:19:23 +0000168 # save a global copy
169 global DEFAULT_DB
170 DEFAULT_DB = colordb
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000171 return colordb
172
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000173
174
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000175_namedict = {}
Barry Warsaw2e7a3201998-02-18 17:01:12 +0000176def rrggbb_to_triplet(color, atoi=string.atoi):
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000177 """Converts a #rrggbb color to the tuple (red, green, blue)."""
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000178 rgbtuple = _namedict.get(color)
179 if rgbtuple is None:
Barry Warsawb7b1cf01998-10-06 18:10:59 +0000180 if color[0] <> '#':
181 raise BadColor(color)
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000182 red = color[1:3]
183 green = color[3:5]
184 blue = color[5:7]
Barry Warsaw2e7a3201998-02-18 17:01:12 +0000185 rgbtuple = (atoi(red, 16), atoi(green, 16), atoi(blue, 16))
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000186 _namedict[color] = rgbtuple
187 return rgbtuple
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000188
Barry Warsawf4562a71998-02-11 17:19:23 +0000189
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000190_tripdict = {}
Barry Warsawf4562a71998-02-11 17:19:23 +0000191def triplet_to_rrggbb(rgbtuple):
192 """Converts a (red, green, blue) tuple to #rrggbb."""
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000193 hexname = _tripdict.get(rgbtuple)
194 if hexname is None:
Barry Warsaw2e7a3201998-02-18 17:01:12 +0000195 hexname = '#%02x%02x%02x' % rgbtuple
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000196 _tripdict[rgbtuple] = hexname
197 return hexname
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000198
199
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000200_maxtuple = (256.0,) * 3
Barry Warsawa5a018f1998-09-25 22:51:36 +0000201def triplet_to_fractional_rgb(rgbtuple):
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000202 return map(operator.__div__, rgbtuple, _maxtuple)
Barry Warsaw2662e151998-02-13 21:27:56 +0000203
204
Barry Warsaw0e3e6991998-09-28 23:39:18 +0000205def triplet_to_brightness(rgbtuple):
206 # return the brightness (grey level) along the scale 0.0==black to
207 # 1.0==white
208 r = 0.299
209 g = 0.587
210 b = 0.114
211 return r*rgbtuple[0] + g*rgbtuple[1] + b*rgbtuple[2]
212
213
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000214
215if __name__ == '__main__':
216 import string
217
218 colordb = get_colordb('/usr/openwin/lib/rgb.txt')
219 if not colordb:
220 print 'No parseable color database found'
221 sys.exit(1)
222 # on my system, this color matches exactly
223 target = 'navy'
Barry Warsawa5a018f1998-09-25 22:51:36 +0000224 red, green, blue = rgbtuple = colordb.find_byname(target)
225 print target, ':', red, green, blue, triplet_to_rrggbb(rgbtuple)
226 name, aliases = colordb.find_byrgb(rgbtuple)
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000227 print 'name:', name, 'aliases:', string.join(aliases, ", ")
Barry Warsaw840a84d1998-10-15 02:18:08 +0000228 r, g, b = (1, 1, 128) # nearest to navy
229 r, g, b = (145, 238, 144) # nearest to lightgreen
230 r, g, b = (255, 251, 250) # snow
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000231 print 'finding nearest to', target, '...'
232 import time
233 t0 = time.time()
Barry Warsaw840a84d1998-10-15 02:18:08 +0000234 nearest = colordb.nearest(r, g, b)
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000235 t1 = time.time()
236 print 'found nearest color', nearest, 'in', t1-t0, 'seconds'
Barry Warsaw840a84d1998-10-15 02:18:08 +0000237 # dump the database
238 for n in colordb.unique_names():
239 r, g, b = colordb.find_byname(n)
240 aliases = colordb.aliases_of(r, g, b)
241 print '%20s: (%3d/%3d/%3d) == %s' % (n, r, g, b,
242 string.join(aliases[1:]))