blob: 66f17cc8c807ca3648c85458c106518f72977585 [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
17import re
Barry Warsawf4562a71998-02-11 17:19:23 +000018from types import *
Barry Warsaw2406b1d1998-01-31 00:29:41 +000019
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000020class BadColor(Exception):
21 pass
22
Barry Warsawf4562a71998-02-11 17:19:23 +000023DEFAULT_DB = None
24
Barry Warsaw2406b1d1998-01-31 00:29:41 +000025
26# generic class
27class ColorDB:
28 def __init__(self, fp, lineno):
29 # Maintain several dictionaries for indexing into the color database.
30 # Note that while Tk supports RGB intensities of 4, 8, 12, or 16 bits,
31 # for now we only support 8 bit intensities. At least on OpenWindows,
32 # all intensities in the /usr/openwin/lib/rgb.txt file are 8-bit
33 #
34 # key is rrggbb, value is (name, [aliases])
35 self.__byrrggbb = {}
36 #
37 # key is name, value is (red, green, blue, rrggbb)
38 self.__byname = {}
39 #
40 while 1:
41 line = fp.readline()
42 if not line:
43 break
44 # get this compiled regular expression from derived class
45 mo = self._re.match(line)
46 if not mo:
47 sys.stderr.write('Error in %s, line %d\n' % (fp.name, lineno))
48 lineno = lineno + 1
49 continue
50 #
51 # extract the red, green, blue, and name
52 red, green, blue = map(int, mo.group('red', 'green', 'blue'))
53 name = mo.group('name')
54 #
55 # calculate the 24 bit representation of the color
56 rrggbb = (red << 16) + (blue << 8) + green
57 #
58 # TBD: for now the `name' is just the first named color with the
59 # rgb values we find. Later, we might want to make the two word
60 # version the `name', or the CapitalizedVersion, etc.
61 foundname, aliases = self.__byrrggbb.get(rrggbb, (name, []))
62 if foundname <> name and foundname not in aliases:
63 aliases.append(name)
64 #
65 # add to by 24bit value
66 self.__byrrggbb[rrggbb] = (foundname, aliases)
67 #
68 # add to byname lookup
69 point = (red, green, blue, rrggbb)
70 self.__byname[name] = point
71 lineno = lineno + 1
72
73 def find(self, red, green, blue):
74 rrggbb = (red << 16) + (blue << 8) + green
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000075 try:
76 return self.__byrrggbb[rrggbb]
77 except KeyError:
78 raise BadColor(red, green, blue)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000079
80 def find_byname(self, name):
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000081 try:
82 return self.__byname[name]
83 except KeyError:
84 raise BadColor(name)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000085
Barry Warsawf4562a71998-02-11 17:19:23 +000086 def nearest(self, rgbtuple):
Barry Warsaw2406b1d1998-01-31 00:29:41 +000087 # TBD: use Voronoi diagrams, Delaunay triangulation, or octree for
88 # speeding up the locating of nearest point. This is really
89 # inefficient!
Barry Warsawf4562a71998-02-11 17:19:23 +000090 red, green, blue = rgbtuple
Barry Warsaw2406b1d1998-01-31 00:29:41 +000091 nearest = -1
92 nearest_name = ''
Barry Warsawbfbe67f1998-01-31 00:32:07 +000093 for name, aliases in self.__byrrggbb.values():
94 r, g, b, rrggbb = self.__byname[name]
Barry Warsaw2406b1d1998-01-31 00:29:41 +000095 rdelta = red - r
96 gdelta = green - g
97 bdelta = blue - b
98 distance = rdelta * rdelta + gdelta * gdelta + bdelta * bdelta
99 if nearest == -1 or distance < nearest:
100 nearest = distance
101 nearest_name = name
102 return nearest_name
103
104
105class RGBColorDB(ColorDB):
106 _re = re.compile(
107 '\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
108
109
110
111# format is a tuple (RE, SCANLINES, CLASS) where RE is a compiled regular
112# expression, SCANLINES is the number of header lines to scan, and CLASS is
113# the class to instantiate if a match is found
114
115X_RGB_TXT = re.compile('XConsortium'), 1, RGBColorDB
116
117def get_colordb(file, filetype=X_RGB_TXT):
118 colordb = None
119 fp = None
120 typere, scanlines, class_ = filetype
121 try:
122 try:
123 lineno = 0
124 fp = open(file)
125 while lineno < scanlines:
126 line = fp.readline()
127 if not line:
128 break
129 mo = typere.search(line)
130 if mo:
131 colordb = class_(fp, lineno)
132 break
133 lineno = lineno + 1
134 except IOError:
135 pass
136 finally:
137 if fp:
138 fp.close()
Barry Warsawf4562a71998-02-11 17:19:23 +0000139 # save a global copy
140 global DEFAULT_DB
141 DEFAULT_DB = colordb
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000142 return colordb
143
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000144
145
146def rrggbb_to_triplet(color):
147 """Converts a #rrggbb color to the tuple (red, green, blue)."""
148 if color[0] <> '#':
149 raise BadColor(color)
150
Barry Warsawf4562a71998-02-11 17:19:23 +0000151 red = color[1:3]
152 green = color[3:5]
153 blue = color[5:7]
154 return tuple(map(lambda v: string.atoi(v, 16), (red, green, blue)))
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000155
Barry Warsawf4562a71998-02-11 17:19:23 +0000156
157def triplet_to_rrggbb(rgbtuple):
158 """Converts a (red, green, blue) tuple to #rrggbb."""
159 def hexify(v):
160 hexstr = hex(v)[2:4]
161 if len(hexstr) < 2:
162 hexstr = '0' + hexstr
163 return hexstr
164 return '#%s%s%s' % tuple(map(hexify, rgbtuple))
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000165
166
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000167
168if __name__ == '__main__':
169 import string
170
171 colordb = get_colordb('/usr/openwin/lib/rgb.txt')
172 if not colordb:
173 print 'No parseable color database found'
174 sys.exit(1)
175 # on my system, this color matches exactly
176 target = 'navy'
177 target = 'snow'
178 red, green, blue, rrggbb = colordb.find_byname(target)
179 print target, ':', red, green, blue, hex(rrggbb)
180 name, aliases = colordb.find(red, green, blue)
181 print 'name:', name, 'aliases:', string.join(aliases, ", ")
182 target = (1, 1, 128) # nearest to navy
183 target = (145, 238, 144) # nearest to lightgreen
184 target = (255, 251, 250) # snow
185 print 'finding nearest to', target, '...'
186 import time
187 t0 = time.time()
188 nearest = apply(colordb.nearest, target)
189 t1 = time.time()
190 print 'found nearest color', nearest, 'in', t1-t0, 'seconds'
Barry Warsaw9f4d73a1998-01-31 23:38:48 +0000191