~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Tools/pynche/ColorDB.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Color Database.
 
2
 
 
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.
 
6
 
 
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.
 
11
 
 
12
Supporte file types are:
 
13
 
 
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.
 
17
 
 
18
The utility functions are useful for converting between the various expected
 
19
color formats, and for calculating other color values.
 
20
 
 
21
"""
 
22
 
 
23
import sys
 
24
import re
 
25
from types import *
 
26
import operator
 
27
 
 
28
class BadColor(Exception):
 
29
    pass
 
30
 
 
31
DEFAULT_DB = None
 
32
SPACE = ' '
 
33
COMMASPACE = ', '
 
34
 
 
35
 
 
36
 
 
37
# generic class
 
38
class ColorDB:
 
39
    def __init__(self, fp):
 
40
        lineno = 2
 
41
        self.__name = fp.name
 
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
 
46
        #
 
47
        # key is (red, green, blue) tuple, value is (name, [aliases])
 
48
        self.__byrgb = {}
 
49
        # key is name, value is (red, green, blue)
 
50
        self.__byname = {}
 
51
        # all unique names (non-aliases).  built-on demand
 
52
        self.__allnames = None
 
53
        for line in fp:
 
54
            # get this compiled regular expression from derived class
 
55
            mo = self._re.match(line)
 
56
            if not mo:
 
57
                print('Error in', fp.name, ' line', lineno, file=sys.stderr)
 
58
                lineno += 1
 
59
                continue
 
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:
 
70
                aliases.append(name)
 
71
            self.__byrgb[key] = (foundname, aliases)
 
72
            # add to byname lookup
 
73
            self.__byname[keyname] = key
 
74
            lineno = lineno + 1
 
75
 
 
76
    # override in derived classes
 
77
    def _extractrgb(self, mo):
 
78
        return [int(x) for x in mo.group('red', 'green', 'blue')]
 
79
 
 
80
    def _extractname(self, mo):
 
81
        return mo.group('name')
 
82
 
 
83
    def filename(self):
 
84
        return self.__name
 
85
 
 
86
    def find_byrgb(self, rgbtuple):
 
87
        """Return name for rgbtuple"""
 
88
        try:
 
89
            return self.__byrgb[rgbtuple]
 
90
        except KeyError:
 
91
            raise BadColor(rgbtuple)
 
92
 
 
93
    def find_byname(self, name):
 
94
        """Return (red, green, blue) for name"""
 
95
        name = name.lower()
 
96
        try:
 
97
            return self.__byname[name]
 
98
        except KeyError:
 
99
            raise BadColor(name)
 
100
 
 
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.
 
106
        nearest = -1
 
107
        nearest_name = ''
 
108
        for name, aliases in self.__byrgb.values():
 
109
            r, g, b = self.__byname[name.lower()]
 
110
            rdelta = red - r
 
111
            gdelta = green - g
 
112
            bdelta = blue - b
 
113
            distance = rdelta * rdelta + gdelta * gdelta + bdelta * bdelta
 
114
            if nearest == -1 or distance < nearest:
 
115
                nearest = distance
 
116
                nearest_name = name
 
117
        return nearest_name
 
118
 
 
119
    def unique_names(self):
 
120
        # sorted
 
121
        if not self.__allnames:
 
122
            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
 
127
 
 
128
    def aliases_of(self, red, green, blue):
 
129
        try:
 
130
            name, aliases = self.__byrgb[(red, green, blue)]
 
131
        except KeyError:
 
132
            raise BadColor((red, green, blue))
 
133
        return [name] + aliases
 
134
 
 
135
 
 
136
class RGBColorDB(ColorDB):
 
137
    _re = re.compile(
 
138
        '\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
 
139
 
 
140
 
 
141
class HTML40DB(ColorDB):
 
142
    _re = re.compile('(?P<name>\S+)\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
 
143
 
 
144
    def _extractrgb(self, mo):
 
145
        return rrggbb_to_triplet(mo.group('hexrgb'))
 
146
 
 
147
class LightlinkDB(HTML40DB):
 
148
    _re = re.compile('(?P<name>(.+))\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
 
149
 
 
150
    def _extractname(self, mo):
 
151
        return mo.group('name').strip()
 
152
 
 
153
class WebsafeDB(ColorDB):
 
154
    _re = re.compile('(?P<hexrgb>#[0-9a-fA-F]{6})')
 
155
 
 
156
    def _extractrgb(self, mo):
 
157
        return rrggbb_to_triplet(mo.group('hexrgb'))
 
158
 
 
159
    def _extractname(self, mo):
 
160
        return mo.group('hexrgb').upper()
 
161
 
 
162
 
 
163
 
 
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
 
167
 
 
168
FILETYPES = [
 
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),
 
174
    ]
 
175
 
 
176
def get_colordb(file, filetype=None):
 
177
    colordb = None
 
178
    fp = open(file)
 
179
    try:
 
180
        line = fp.readline()
 
181
        if not line:
 
182
            return None
 
183
        # try to determine the type of RGB file it is
 
184
        if filetype is None:
 
185
            filetypes = FILETYPES
 
186
        else:
 
187
            filetypes = [filetype]
 
188
        for typere, class_ in filetypes:
 
189
            mo = typere.search(line)
 
190
            if mo:
 
191
                break
 
192
        else:
 
193
            # no matching type
 
194
            return None
 
195
        # we know the type and the class to grok the type, so suck it in
 
196
        colordb = class_(fp)
 
197
    finally:
 
198
        fp.close()
 
199
    # save a global copy
 
200
    global DEFAULT_DB
 
201
    DEFAULT_DB = colordb
 
202
    return colordb
 
203
 
 
204
 
 
205
 
 
206
_namedict = {}
 
207
 
 
208
def rrggbb_to_triplet(color):
 
209
    """Converts a #rrggbb color to the tuple (red, green, blue)."""
 
210
    rgbtuple = _namedict.get(color)
 
211
    if rgbtuple is None:
 
212
        if color[0] != '#':
 
213
            raise BadColor(color)
 
214
        red = color[1:3]
 
215
        green = color[3:5]
 
216
        blue = color[5:7]
 
217
        rgbtuple = int(red, 16), int(green, 16), int(blue, 16)
 
218
        _namedict[color] = rgbtuple
 
219
    return rgbtuple
 
220
 
 
221
 
 
222
_tripdict = {}
 
223
def triplet_to_rrggbb(rgbtuple):
 
224
    """Converts a (red, green, blue) tuple to #rrggbb."""
 
225
    global _tripdict
 
226
    hexname = _tripdict.get(rgbtuple)
 
227
    if hexname is None:
 
228
        hexname = '#%02x%02x%02x' % rgbtuple
 
229
        _tripdict[rgbtuple] = hexname
 
230
    return hexname
 
231
 
 
232
 
 
233
_maxtuple = (256.0,) * 3
 
234
def triplet_to_fractional_rgb(rgbtuple):
 
235
    return list(map(operator.__div__, rgbtuple, _maxtuple))
 
236
 
 
237
 
 
238
def triplet_to_brightness(rgbtuple):
 
239
    # return the brightness (grey level) along the scale 0.0==black to
 
240
    # 1.0==white
 
241
    r = 0.299
 
242
    g = 0.587
 
243
    b = 0.114
 
244
    return r*rgbtuple[0] + g*rgbtuple[1] + b*rgbtuple[2]
 
245
 
 
246
 
 
247
 
 
248
if __name__ == '__main__':
 
249
    colordb = get_colordb('/usr/openwin/lib/rgb.txt')
 
250
    if not colordb:
 
251
        print('No parseable color database found')
 
252
        sys.exit(1)
 
253
    # on my system, this color matches exactly
 
254
    target = 'navy'
 
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, '...')
 
263
    import time
 
264
    t0 = time.time()
 
265
    nearest = colordb.nearest(r, g, b)
 
266
    t1 = time.time()
 
267
    print('found nearest color', nearest, 'in', t1-t0, 'seconds')
 
268
    # dump the database
 
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:])))