ยปCore Development>Code coverage>Tools/pynche/ColorDB.py

Python code coverage for Tools/pynche/ColorDB.py

#countcontent
1n/a"""Color Database.
2n/a
3n/aThis file contains one class, called ColorDB, and several utility functions.
4n/aThe class must be instantiated by the get_colordb() function in this file,
5n/apassing it a filename to read a database out of.
6n/a
7n/aThe get_colordb() function will try to examine the file to figure out what the
8n/aformat of the file is. If it can't figure out the file format, or it has
9n/atrouble reading the file, None is returned. You can pass get_colordb() an
10n/aoptional filetype argument.
11n/a
12n/aSupporte file types are:
13n/a
14n/a X_RGB_TXT -- X Consortium rgb.txt format files. Three columns of numbers
15n/a from 0 .. 255 separated by whitespace. Arbitrary trailing
16n/a columns used as the color name.
17n/a
18n/aThe utility functions are useful for converting between the various expected
19n/acolor formats, and for calculating other color values.
20n/a
21n/a"""
22n/a
23n/aimport sys
24n/aimport re
25n/afrom types import *
26n/a
27n/aclass BadColor(Exception):
28n/a pass
29n/a
30n/aDEFAULT_DB = None
31n/aSPACE = ' '
32n/aCOMMASPACE = ', '
33n/a
34n/a
35n/a
36n/a# generic class
37n/aclass ColorDB:
38n/a def __init__(self, fp):
39n/a lineno = 2
40n/a self.__name = fp.name
41n/a # Maintain several dictionaries for indexing into the color database.
42n/a # Note that while Tk supports RGB intensities of 4, 8, 12, or 16 bits,
43n/a # for now we only support 8 bit intensities. At least on OpenWindows,
44n/a # all intensities in the /usr/openwin/lib/rgb.txt file are 8-bit
45n/a #
46n/a # key is (red, green, blue) tuple, value is (name, [aliases])
47n/a self.__byrgb = {}
48n/a # key is name, value is (red, green, blue)
49n/a self.__byname = {}
50n/a # all unique names (non-aliases). built-on demand
51n/a self.__allnames = None
52n/a for line in fp:
53n/a # get this compiled regular expression from derived class
54n/a mo = self._re.match(line)
55n/a if not mo:
56n/a print('Error in', fp.name, ' line', lineno, file=sys.stderr)
57n/a lineno += 1
58n/a continue
59n/a # extract the red, green, blue, and name
60n/a red, green, blue = self._extractrgb(mo)
61n/a name = self._extractname(mo)
62n/a keyname = name.lower()
63n/a # BAW: for now the `name' is just the first named color with the
64n/a # rgb values we find. Later, we might want to make the two word
65n/a # version the `name', or the CapitalizedVersion, etc.
66n/a key = (red, green, blue)
67n/a foundname, aliases = self.__byrgb.get(key, (name, []))
68n/a if foundname != name and foundname not in aliases:
69n/a aliases.append(name)
70n/a self.__byrgb[key] = (foundname, aliases)
71n/a # add to byname lookup
72n/a self.__byname[keyname] = key
73n/a lineno = lineno + 1
74n/a
75n/a # override in derived classes
76n/a def _extractrgb(self, mo):
77n/a return [int(x) for x in mo.group('red', 'green', 'blue')]
78n/a
79n/a def _extractname(self, mo):
80n/a return mo.group('name')
81n/a
82n/a def filename(self):
83n/a return self.__name
84n/a
85n/a def find_byrgb(self, rgbtuple):
86n/a """Return name for rgbtuple"""
87n/a try:
88n/a return self.__byrgb[rgbtuple]
89n/a except KeyError:
90n/a raise BadColor(rgbtuple)
91n/a
92n/a def find_byname(self, name):
93n/a """Return (red, green, blue) for name"""
94n/a name = name.lower()
95n/a try:
96n/a return self.__byname[name]
97n/a except KeyError:
98n/a raise BadColor(name)
99n/a
100n/a def nearest(self, red, green, blue):
101n/a """Return the name of color nearest (red, green, blue)"""
102n/a # BAW: should we use Voronoi diagrams, Delaunay triangulation, or
103n/a # octree for speeding up the locating of nearest point? Exhaustive
104n/a # search is inefficient, but seems fast enough.
105n/a nearest = -1
106n/a nearest_name = ''
107n/a for name, aliases in self.__byrgb.values():
108n/a r, g, b = self.__byname[name.lower()]
109n/a rdelta = red - r
110n/a gdelta = green - g
111n/a bdelta = blue - b
112n/a distance = rdelta * rdelta + gdelta * gdelta + bdelta * bdelta
113n/a if nearest == -1 or distance < nearest:
114n/a nearest = distance
115n/a nearest_name = name
116n/a return nearest_name
117n/a
118n/a def unique_names(self):
119n/a # sorted
120n/a if not self.__allnames:
121n/a self.__allnames = []
122n/a for name, aliases in self.__byrgb.values():
123n/a self.__allnames.append(name)
124n/a self.__allnames.sort(key=str.lower)
125n/a return self.__allnames
126n/a
127n/a def aliases_of(self, red, green, blue):
128n/a try:
129n/a name, aliases = self.__byrgb[(red, green, blue)]
130n/a except KeyError:
131n/a raise BadColor((red, green, blue))
132n/a return [name] + aliases
133n/a
134n/a
135n/aclass RGBColorDB(ColorDB):
136n/a _re = re.compile(
137n/a r'\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
138n/a
139n/a
140n/aclass HTML40DB(ColorDB):
141n/a _re = re.compile(r'(?P<name>\S+)\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
142n/a
143n/a def _extractrgb(self, mo):
144n/a return rrggbb_to_triplet(mo.group('hexrgb'))
145n/a
146n/aclass LightlinkDB(HTML40DB):
147n/a _re = re.compile(r'(?P<name>(.+))\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
148n/a
149n/a def _extractname(self, mo):
150n/a return mo.group('name').strip()
151n/a
152n/aclass WebsafeDB(ColorDB):
153n/a _re = re.compile('(?P<hexrgb>#[0-9a-fA-F]{6})')
154n/a
155n/a def _extractrgb(self, mo):
156n/a return rrggbb_to_triplet(mo.group('hexrgb'))
157n/a
158n/a def _extractname(self, mo):
159n/a return mo.group('hexrgb').upper()
160n/a
161n/a
162n/a
163n/a# format is a tuple (RE, SCANLINES, CLASS) where RE is a compiled regular
164n/a# expression, SCANLINES is the number of header lines to scan, and CLASS is
165n/a# the class to instantiate if a match is found
166n/a
167n/aFILETYPES = [
168n/a (re.compile('Xorg'), RGBColorDB),
169n/a (re.compile('XConsortium'), RGBColorDB),
170n/a (re.compile('HTML'), HTML40DB),
171n/a (re.compile('lightlink'), LightlinkDB),
172n/a (re.compile('Websafe'), WebsafeDB),
173n/a ]
174n/a
175n/adef get_colordb(file, filetype=None):
176n/a colordb = None
177n/a fp = open(file)
178n/a try:
179n/a line = fp.readline()
180n/a if not line:
181n/a return None
182n/a # try to determine the type of RGB file it is
183n/a if filetype is None:
184n/a filetypes = FILETYPES
185n/a else:
186n/a filetypes = [filetype]
187n/a for typere, class_ in filetypes:
188n/a mo = typere.search(line)
189n/a if mo:
190n/a break
191n/a else:
192n/a # no matching type
193n/a return None
194n/a # we know the type and the class to grok the type, so suck it in
195n/a colordb = class_(fp)
196n/a finally:
197n/a fp.close()
198n/a # save a global copy
199n/a global DEFAULT_DB
200n/a DEFAULT_DB = colordb
201n/a return colordb
202n/a
203n/a
204n/a
205n/a_namedict = {}
206n/a
207n/adef rrggbb_to_triplet(color):
208n/a """Converts a #rrggbb color to the tuple (red, green, blue)."""
209n/a rgbtuple = _namedict.get(color)
210n/a if rgbtuple is None:
211n/a if color[0] != '#':
212n/a raise BadColor(color)
213n/a red = color[1:3]
214n/a green = color[3:5]
215n/a blue = color[5:7]
216n/a rgbtuple = int(red, 16), int(green, 16), int(blue, 16)
217n/a _namedict[color] = rgbtuple
218n/a return rgbtuple
219n/a
220n/a
221n/a_tripdict = {}
222n/adef triplet_to_rrggbb(rgbtuple):
223n/a """Converts a (red, green, blue) tuple to #rrggbb."""
224n/a global _tripdict
225n/a hexname = _tripdict.get(rgbtuple)
226n/a if hexname is None:
227n/a hexname = '#%02x%02x%02x' % rgbtuple
228n/a _tripdict[rgbtuple] = hexname
229n/a return hexname
230n/a
231n/a
232n/adef triplet_to_fractional_rgb(rgbtuple):
233n/a return [x / 256 for x in rgbtuple]
234n/a
235n/a
236n/adef triplet_to_brightness(rgbtuple):
237n/a # return the brightness (grey level) along the scale 0.0==black to
238n/a # 1.0==white
239n/a r = 0.299
240n/a g = 0.587
241n/a b = 0.114
242n/a return r*rgbtuple[0] + g*rgbtuple[1] + b*rgbtuple[2]
243n/a
244n/a
245n/a
246n/aif __name__ == '__main__':
247n/a colordb = get_colordb('/usr/openwin/lib/rgb.txt')
248n/a if not colordb:
249n/a print('No parseable color database found')
250n/a sys.exit(1)
251n/a # on my system, this color matches exactly
252n/a target = 'navy'
253n/a red, green, blue = rgbtuple = colordb.find_byname(target)
254n/a print(target, ':', red, green, blue, triplet_to_rrggbb(rgbtuple))
255n/a name, aliases = colordb.find_byrgb(rgbtuple)
256n/a print('name:', name, 'aliases:', COMMASPACE.join(aliases))
257n/a r, g, b = (1, 1, 128) # nearest to navy
258n/a r, g, b = (145, 238, 144) # nearest to lightgreen
259n/a r, g, b = (255, 251, 250) # snow
260n/a print('finding nearest to', target, '...')
261n/a import time
262n/a t0 = time.time()
263n/a nearest = colordb.nearest(r, g, b)
264n/a t1 = time.time()
265n/a print('found nearest color', nearest, 'in', t1-t0, 'seconds')
266n/a # dump the database
267n/a for n in colordb.unique_names():
268n/a r, g, b = colordb.find_byname(n)
269n/a aliases = colordb.aliases_of(r, g, b)
270n/a print('%20s: (%3d/%3d/%3d) == %s' % (n, r, g, b,
271n/a SPACE.join(aliases[1:])))