blob: 435e0ad046d0ce565eaeabb3806c9e21af339a03 [file] [log] [blame]
Barry Warsaw2406b1d1998-01-31 00:29:41 +00001"""Color Database.
2
3To create a class that contains color lookup methods, use the module global
4function `get_colordb(file)'. This function will try to examine the file to
5figure out what the format of the file is. If it can't figure out the file
6format, or it has trouble reading the file, None is returned. You can pass
7get_colordb() an optional filetype argument.
8
9Supporte file types are:
10
11 X_RGB_TXT -- X Consortium rgb.txt format files. Three columns of numbers
12 from 0 .. 255 separated by whitespace. Arbitrary trailing
13 columns used as the color name.
14"""
15
16import sys
Barry Warsaweb9b8af1998-02-11 18:55:37 +000017import string
Barry Warsaw2406b1d1998-01-31 00:29:41 +000018import re
Barry Warsawf4562a71998-02-11 17:19:23 +000019from types import *
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +000020import operator
Barry Warsaw2406b1d1998-01-31 00:29:41 +000021
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000022class BadColor(Exception):
23 pass
24
Barry Warsawf4562a71998-02-11 17:19:23 +000025DEFAULT_DB = None
26
Barry Warsawa5a018f1998-09-25 22:51:36 +000027
Barry Warsaw2406b1d1998-01-31 00:29:41 +000028
29# generic class
30class ColorDB:
31 def __init__(self, fp, lineno):
32 # Maintain several dictionaries for indexing into the color database.
33 # Note that while Tk supports RGB intensities of 4, 8, 12, or 16 bits,
34 # for now we only support 8 bit intensities. At least on OpenWindows,
35 # all intensities in the /usr/openwin/lib/rgb.txt file are 8-bit
36 #
Barry Warsaweb9b8af1998-02-11 18:55:37 +000037 # key is (red, green, blue) tuple, value is (name, [aliases])
38 self.__byrgb = {}
Barry Warsaw2406b1d1998-01-31 00:29:41 +000039 #
Barry Warsaweb9b8af1998-02-11 18:55:37 +000040 # key is name, value is (red, green, blue)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000041 self.__byname = {}
42 #
Barry Warsaw8be25941998-10-02 14:43:30 +000043 # all unique names (non-aliases). built-on demand
44 self.__allnames = None
Barry Warsaw2406b1d1998-01-31 00:29:41 +000045 while 1:
46 line = fp.readline()
47 if not line:
48 break
49 # get this compiled regular expression from derived class
50 mo = self._re.match(line)
51 if not mo:
52 sys.stderr.write('Error in %s, line %d\n' % (fp.name, lineno))
53 lineno = lineno + 1
54 continue
55 #
56 # extract the red, green, blue, and name
Barry Warsaweb9b8af1998-02-11 18:55:37 +000057 #
Barry Warsaw2406b1d1998-01-31 00:29:41 +000058 red, green, blue = map(int, mo.group('red', 'green', 'blue'))
59 name = mo.group('name')
Barry Warsaweb9b8af1998-02-11 18:55:37 +000060 keyname = string.lower(name)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000061 #
62 # TBD: for now the `name' is just the first named color with the
63 # rgb values we find. Later, we might want to make the two word
64 # version the `name', or the CapitalizedVersion, etc.
Barry Warsaweb9b8af1998-02-11 18:55:37 +000065 #
66 key = (red, green, blue)
67 foundname, aliases = self.__byrgb.get(key, (name, []))
Barry Warsaw2406b1d1998-01-31 00:29:41 +000068 if foundname <> name and foundname not in aliases:
69 aliases.append(name)
Barry Warsaweb9b8af1998-02-11 18:55:37 +000070 self.__byrgb[key] = (foundname, aliases)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000071 #
72 # add to byname lookup
Barry Warsaweb9b8af1998-02-11 18:55:37 +000073 #
74 self.__byname[keyname] = key
Barry Warsaw2406b1d1998-01-31 00:29:41 +000075 lineno = lineno + 1
76
Barry Warsaweb9b8af1998-02-11 18:55:37 +000077 def find_byrgb(self, rgbtuple):
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000078 try:
Barry Warsaweb9b8af1998-02-11 18:55:37 +000079 return self.__byrgb[rgbtuple]
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000080 except KeyError:
Barry Warsaweb9b8af1998-02-11 18:55:37 +000081 raise BadColor(rgbtuple)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000082
83 def find_byname(self, name):
Barry Warsaweb9b8af1998-02-11 18:55:37 +000084 name = string.lower(name)
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000085 try:
86 return self.__byname[name]
87 except KeyError:
88 raise BadColor(name)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000089
Barry Warsaw7a134181998-09-29 20:03:15 +000090 def nearest(self, red, green, blue):
Barry Warsaw2406b1d1998-01-31 00:29:41 +000091 # TBD: use Voronoi diagrams, Delaunay triangulation, or octree for
Barry Warsaweb9b8af1998-02-11 18:55:37 +000092 # speeding up the locating of nearest point. Exhaustive search is
93 # inefficient, but may be fast enough.
Barry Warsaw2406b1d1998-01-31 00:29:41 +000094 nearest = -1
95 nearest_name = ''
Barry Warsaweb9b8af1998-02-11 18:55:37 +000096 for name, aliases in self.__byrgb.values():
97 r, g, b = self.__byname[string.lower(name)]
Barry Warsaw2406b1d1998-01-31 00:29:41 +000098 rdelta = red - r
99 gdelta = green - g
100 bdelta = blue - b
101 distance = rdelta * rdelta + gdelta * gdelta + bdelta * bdelta
102 if nearest == -1 or distance < nearest:
103 nearest = distance
104 nearest_name = name
105 return nearest_name
Barry Warsaw8be25941998-10-02 14:43:30 +0000106
107 def all_names(self):
108 # sorted
109 if not self.__allnames:
110 self.__allnames = []
111 for name, aliases in self.__byrgb.values():
112 self.__allnames.append(name)
113 self.__allnames.sort()
114 return self.__allnames
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000115
116
117class RGBColorDB(ColorDB):
118 _re = re.compile(
119 '\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
120
121
122
123# format is a tuple (RE, SCANLINES, CLASS) where RE is a compiled regular
124# expression, SCANLINES is the number of header lines to scan, and CLASS is
125# the class to instantiate if a match is found
126
127X_RGB_TXT = re.compile('XConsortium'), 1, RGBColorDB
128
129def get_colordb(file, filetype=X_RGB_TXT):
130 colordb = None
131 fp = None
132 typere, scanlines, class_ = filetype
133 try:
134 try:
135 lineno = 0
136 fp = open(file)
137 while lineno < scanlines:
138 line = fp.readline()
139 if not line:
140 break
141 mo = typere.search(line)
142 if mo:
143 colordb = class_(fp, lineno)
144 break
145 lineno = lineno + 1
146 except IOError:
147 pass
148 finally:
149 if fp:
150 fp.close()
Barry Warsawf4562a71998-02-11 17:19:23 +0000151 # save a global copy
152 global DEFAULT_DB
153 DEFAULT_DB = colordb
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000154 return colordb
155
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000156
157
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000158_namedict = {}
Barry Warsaw2e7a3201998-02-18 17:01:12 +0000159def rrggbb_to_triplet(color, atoi=string.atoi):
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000160 """Converts a #rrggbb color to the tuple (red, green, blue)."""
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000161 rgbtuple = _namedict.get(color)
162 if rgbtuple is None:
163 assert color[0] == '#'
164 red = color[1:3]
165 green = color[3:5]
166 blue = color[5:7]
Barry Warsaw2e7a3201998-02-18 17:01:12 +0000167 rgbtuple = (atoi(red, 16), atoi(green, 16), atoi(blue, 16))
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000168 _namedict[color] = rgbtuple
169 return rgbtuple
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000170
Barry Warsawf4562a71998-02-11 17:19:23 +0000171
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000172_tripdict = {}
Barry Warsawf4562a71998-02-11 17:19:23 +0000173def triplet_to_rrggbb(rgbtuple):
174 """Converts a (red, green, blue) tuple to #rrggbb."""
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000175 hexname = _tripdict.get(rgbtuple)
176 if hexname is None:
Barry Warsaw2e7a3201998-02-18 17:01:12 +0000177 hexname = '#%02x%02x%02x' % rgbtuple
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000178 _tripdict[rgbtuple] = hexname
179 return hexname
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000180
181
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000182_maxtuple = (256.0,) * 3
Barry Warsawa5a018f1998-09-25 22:51:36 +0000183def triplet_to_fractional_rgb(rgbtuple):
Barry Warsaw8d3e5ee1998-02-18 00:02:26 +0000184 return map(operator.__div__, rgbtuple, _maxtuple)
Barry Warsaw2662e151998-02-13 21:27:56 +0000185
186
Barry Warsaw0e3e6991998-09-28 23:39:18 +0000187def triplet_to_brightness(rgbtuple):
188 # return the brightness (grey level) along the scale 0.0==black to
189 # 1.0==white
190 r = 0.299
191 g = 0.587
192 b = 0.114
193 return r*rgbtuple[0] + g*rgbtuple[1] + b*rgbtuple[2]
194
195
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000196
197if __name__ == '__main__':
198 import string
199
200 colordb = get_colordb('/usr/openwin/lib/rgb.txt')
201 if not colordb:
202 print 'No parseable color database found'
203 sys.exit(1)
204 # on my system, this color matches exactly
205 target = 'navy'
Barry Warsawa5a018f1998-09-25 22:51:36 +0000206 red, green, blue = rgbtuple = colordb.find_byname(target)
207 print target, ':', red, green, blue, triplet_to_rrggbb(rgbtuple)
208 name, aliases = colordb.find_byrgb(rgbtuple)
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000209 print 'name:', name, 'aliases:', string.join(aliases, ", ")
210 target = (1, 1, 128) # nearest to navy
211 target = (145, 238, 144) # nearest to lightgreen
212 target = (255, 251, 250) # snow
213 print 'finding nearest to', target, '...'
214 import time
215 t0 = time.time()
Barry Warsawa5a018f1998-09-25 22:51:36 +0000216 nearest = colordb.nearest(target)
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000217 t1 = time.time()
218 print 'found nearest color', nearest, 'in', t1-t0, 'seconds'
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000219