3
This file contains one class, called ColorDB, and several utility functions.
4
The class must be instantiated by the get_colordb() function in this file,
5
passing it a filename to read a database out of.
7
The get_colordb() function will try to examine the file to figure out what the
8
format of the file is. If it can't figure out the file format, or it has
9
trouble reading the file, None is returned. You can pass get_colordb() an
10
optional filetype argument.
12
Supporte file types are:
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.
18
The utility functions are useful for converting between the various expected
19
color formats, and for calculating other color values.
28
class BadColor(Exception):
39
def __init__(self, fp):
42
# Maintain several dictionaries for indexing into the color database.
43
# Note that while Tk supports RGB intensities of 4, 8, 12, or 16 bits,
44
# for now we only support 8 bit intensities. At least on OpenWindows,
45
# all intensities in the /usr/openwin/lib/rgb.txt file are 8-bit
47
# key is (red, green, blue) tuple, value is (name, [aliases])
49
# key is name, value is (red, green, blue)
51
# all unique names (non-aliases). built-on demand
52
self.__allnames = None
54
# get this compiled regular expression from derived class
55
mo = self._re.match(line)
57
print('Error in', fp.name, ' line', lineno, file=sys.stderr)
60
# extract the red, green, blue, and name
61
red, green, blue = self._extractrgb(mo)
62
name = self._extractname(mo)
63
keyname = name.lower()
64
# BAW: for now the `name' is just the first named color with the
65
# rgb values we find. Later, we might want to make the two word
66
# version the `name', or the CapitalizedVersion, etc.
67
key = (red, green, blue)
68
foundname, aliases = self.__byrgb.get(key, (name, []))
69
if foundname != name and foundname not in aliases:
71
self.__byrgb[key] = (foundname, aliases)
72
# add to byname lookup
73
self.__byname[keyname] = key
76
# override in derived classes
77
def _extractrgb(self, mo):
78
return [int(x) for x in mo.group('red', 'green', 'blue')]
80
def _extractname(self, mo):
81
return mo.group('name')
86
def find_byrgb(self, rgbtuple):
87
"""Return name for rgbtuple"""
89
return self.__byrgb[rgbtuple]
91
raise BadColor(rgbtuple)
93
def find_byname(self, name):
94
"""Return (red, green, blue) for name"""
97
return self.__byname[name]
101
def nearest(self, red, green, blue):
102
"""Return the name of color nearest (red, green, blue)"""
103
# BAW: should we use Voronoi diagrams, Delaunay triangulation, or
104
# octree for speeding up the locating of nearest point? Exhaustive
105
# search is inefficient, but seems fast enough.
108
for name, aliases in self.__byrgb.values():
109
r, g, b = self.__byname[name.lower()]
113
distance = rdelta * rdelta + gdelta * gdelta + bdelta * bdelta
114
if nearest == -1 or distance < nearest:
119
def unique_names(self):
121
if not self.__allnames:
123
for name, aliases in self.__byrgb.values():
124
self.__allnames.append(name)
125
self.__allnames.sort(key=str.lower)
126
return self.__allnames
128
def aliases_of(self, red, green, blue):
130
name, aliases = self.__byrgb[(red, green, blue)]
132
raise BadColor((red, green, blue))
133
return [name] + aliases
136
class RGBColorDB(ColorDB):
138
'\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
141
class HTML40DB(ColorDB):
142
_re = re.compile('(?P<name>\S+)\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
144
def _extractrgb(self, mo):
145
return rrggbb_to_triplet(mo.group('hexrgb'))
147
class LightlinkDB(HTML40DB):
148
_re = re.compile('(?P<name>(.+))\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
150
def _extractname(self, mo):
151
return mo.group('name').strip()
153
class WebsafeDB(ColorDB):
154
_re = re.compile('(?P<hexrgb>#[0-9a-fA-F]{6})')
156
def _extractrgb(self, mo):
157
return rrggbb_to_triplet(mo.group('hexrgb'))
159
def _extractname(self, mo):
160
return mo.group('hexrgb').upper()
164
# format is a tuple (RE, SCANLINES, CLASS) where RE is a compiled regular
165
# expression, SCANLINES is the number of header lines to scan, and CLASS is
166
# the class to instantiate if a match is found
169
(re.compile('Xorg'), RGBColorDB),
170
(re.compile('XConsortium'), RGBColorDB),
171
(re.compile('HTML'), HTML40DB),
172
(re.compile('lightlink'), LightlinkDB),
173
(re.compile('Websafe'), WebsafeDB),
176
def get_colordb(file, filetype=None):
183
# try to determine the type of RGB file it is
185
filetypes = FILETYPES
187
filetypes = [filetype]
188
for typere, class_ in filetypes:
189
mo = typere.search(line)
195
# we know the type and the class to grok the type, so suck it in
208
def rrggbb_to_triplet(color):
209
"""Converts a #rrggbb color to the tuple (red, green, blue)."""
210
rgbtuple = _namedict.get(color)
213
raise BadColor(color)
217
rgbtuple = int(red, 16), int(green, 16), int(blue, 16)
218
_namedict[color] = rgbtuple
223
def triplet_to_rrggbb(rgbtuple):
224
"""Converts a (red, green, blue) tuple to #rrggbb."""
226
hexname = _tripdict.get(rgbtuple)
228
hexname = '#%02x%02x%02x' % rgbtuple
229
_tripdict[rgbtuple] = hexname
233
_maxtuple = (256.0,) * 3
234
def triplet_to_fractional_rgb(rgbtuple):
235
return list(map(operator.__div__, rgbtuple, _maxtuple))
238
def triplet_to_brightness(rgbtuple):
239
# return the brightness (grey level) along the scale 0.0==black to
244
return r*rgbtuple[0] + g*rgbtuple[1] + b*rgbtuple[2]
248
if __name__ == '__main__':
249
colordb = get_colordb('/usr/openwin/lib/rgb.txt')
251
print('No parseable color database found')
253
# on my system, this color matches exactly
255
red, green, blue = rgbtuple = colordb.find_byname(target)
256
print(target, ':', red, green, blue, triplet_to_rrggbb(rgbtuple))
257
name, aliases = colordb.find_byrgb(rgbtuple)
258
print('name:', name, 'aliases:', COMMASPACE.join(aliases))
259
r, g, b = (1, 1, 128) # nearest to navy
260
r, g, b = (145, 238, 144) # nearest to lightgreen
261
r, g, b = (255, 251, 250) # snow
262
print('finding nearest to', target, '...')
265
nearest = colordb.nearest(r, g, b)
267
print('found nearest color', nearest, 'in', t1-t0, 'seconds')
269
for n in colordb.unique_names():
270
r, g, b = colordb.find_byname(n)
271
aliases = colordb.aliases_of(r, g, b)
272
print('%20s: (%3d/%3d/%3d) == %s' % (n, r, g, b,
273
SPACE.join(aliases[1:])))