blob: 33dfe0238c53a31315a265a28952773d427e9542 [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 Warsaw2406b1d1998-01-31 00:29:41 +000020
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000021class BadColor(Exception):
22 pass
23
Barry Warsawf4562a71998-02-11 17:19:23 +000024DEFAULT_DB = None
25
Barry Warsaw2406b1d1998-01-31 00:29:41 +000026
27# generic class
28class ColorDB:
29 def __init__(self, fp, lineno):
30 # Maintain several dictionaries for indexing into the color database.
31 # Note that while Tk supports RGB intensities of 4, 8, 12, or 16 bits,
32 # for now we only support 8 bit intensities. At least on OpenWindows,
33 # all intensities in the /usr/openwin/lib/rgb.txt file are 8-bit
34 #
Barry Warsaweb9b8af1998-02-11 18:55:37 +000035 # key is (red, green, blue) tuple, value is (name, [aliases])
36 self.__byrgb = {}
Barry Warsaw2406b1d1998-01-31 00:29:41 +000037 #
Barry Warsaweb9b8af1998-02-11 18:55:37 +000038 # key is name, value is (red, green, blue)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000039 self.__byname = {}
40 #
41 while 1:
42 line = fp.readline()
43 if not line:
44 break
45 # get this compiled regular expression from derived class
46 mo = self._re.match(line)
47 if not mo:
48 sys.stderr.write('Error in %s, line %d\n' % (fp.name, lineno))
49 lineno = lineno + 1
50 continue
51 #
52 # extract the red, green, blue, and name
Barry Warsaweb9b8af1998-02-11 18:55:37 +000053 #
Barry Warsaw2406b1d1998-01-31 00:29:41 +000054 red, green, blue = map(int, mo.group('red', 'green', 'blue'))
55 name = mo.group('name')
Barry Warsaweb9b8af1998-02-11 18:55:37 +000056 keyname = string.lower(name)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000057 #
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.
Barry Warsaweb9b8af1998-02-11 18:55:37 +000061 #
62 key = (red, green, blue)
63 foundname, aliases = self.__byrgb.get(key, (name, []))
Barry Warsaw2406b1d1998-01-31 00:29:41 +000064 if foundname <> name and foundname not in aliases:
65 aliases.append(name)
Barry Warsaweb9b8af1998-02-11 18:55:37 +000066 self.__byrgb[key] = (foundname, aliases)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000067 #
68 # add to byname lookup
Barry Warsaweb9b8af1998-02-11 18:55:37 +000069 #
70 self.__byname[keyname] = key
Barry Warsaw2406b1d1998-01-31 00:29:41 +000071 lineno = lineno + 1
72
Barry Warsaweb9b8af1998-02-11 18:55:37 +000073 def find_byrgb(self, rgbtuple):
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000074 try:
Barry Warsaweb9b8af1998-02-11 18:55:37 +000075 return self.__byrgb[rgbtuple]
Barry Warsaw9f4d73a1998-01-31 23:38:48 +000076 except KeyError:
Barry Warsaweb9b8af1998-02-11 18:55:37 +000077 raise BadColor(rgbtuple)
Barry Warsaw2406b1d1998-01-31 00:29:41 +000078
79 def find_byname(self, name):
Barry Warsaweb9b8af1998-02-11 18:55:37 +000080 name = string.lower(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
Barry Warsaweb9b8af1998-02-11 18:55:37 +000088 # speeding up the locating of nearest point. Exhaustive search is
89 # inefficient, but may be fast enough.
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 Warsaweb9b8af1998-02-11 18:55:37 +000093 for name, aliases in self.__byrgb.values():
94 r, g, b = self.__byname[string.lower(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'
Barry Warsaweb9b8af1998-02-11 18:55:37 +0000178 red, green, blue = colordb.find_byname(target)
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000179 print target, ':', red, green, blue, hex(rrggbb)
Barry Warsaweb9b8af1998-02-11 18:55:37 +0000180 name, aliases = colordb.find_byrgb((red, green, blue))
Barry Warsaw2406b1d1998-01-31 00:29:41 +0000181 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