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