adjust the return values of the rgb() cmyk() etc methods
[PyX/mjg.git] / pyx / color.py
blob889a4f854930378bc51bed17faa8746ddd2fa57c
1 # -*- coding: ISO-8859-1 -*-
4 # Copyright (C) 2002-2004, 2006 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2003-2006 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 import colorsys, math, struct
25 import attr, style, pdfwriter
27 # device-dependend (nonlinear) functions for color conversion
28 # UCRx : [0,1] -> [-1, 1] UnderColorRemoval (removes black from c, y, m)
29 # BG : [0,1] -> [0, 1] BlackGeneration (generate the black from the nominal k-value)
30 # as long as we have no further knowledge we define them linearly with constants 1
31 def _UCRc(x): return x
32 def _UCRm(x): return x
33 def _UCRy(x): return x
34 def _BG(x): return x
36 def set(UCRc=None, UCRm=None, UCRy=None, BG=None):
37 global _UCRc
38 global _UCRm
39 global _UCRy
40 global _BG
42 if UCRc is not None:
43 _UCRc = UCRc
44 if UCRm is not None:
45 _UCRm = UCRm
46 if UCRy is not None:
47 _UCRy = UCRy
48 if BG is not None:
49 _BG = BG
52 class color(attr.exclusiveattr, style.strokestyle, style.fillstyle):
54 """base class for all colors"""
56 def __init__(self):
57 attr.exclusiveattr.__init__(self, color)
60 clear = attr.clearclass(color)
63 class grey(color):
65 """grey tones"""
67 def __init__(self, gray):
68 color.__init__(self)
69 if gray<0 or gray>1: raise ValueError
70 self.color = {"gray": gray}
72 def processPS(self, file, writer, context, registry, bbox):
73 file.write("%(gray)g setgray\n" % self.color)
75 def processPDF(self, file, writer, context, registry, bbox):
76 if context.strokeattr:
77 file.write("%(gray)f G\n" % self.color)
78 if context.fillattr:
79 file.write("%(gray)f g\n" % self.color)
81 def cmyk(self):
82 return cmyk(0, 0, 0, 1 - self.color["gray"])
84 def grey(self):
85 return grey(**self.color)
86 gray = grey
88 def hsb(self):
89 return hsb(0, 0, self.color["gray"])
91 def rgb(self):
92 return rgb(self.color["gray"], self.color["gray"], self.color["gray"])
94 def colorspacestring(self):
95 return "/DeviceGray"
97 def tostring8bit(self):
98 return chr(round(self.color["gray"]*255))
100 grey.black = grey(0.0)
101 grey.white = grey(1.0)
102 gray = grey
105 class rgb(color):
107 """rgb colors"""
109 def __init__(self, r=0.0, g=0.0, b=0.0):
110 color.__init__(self)
111 if r<0 or r>1 or g<0 or g>1 or b<0 or b>1: raise ValueError
112 self.color = {"r": r, "g": g, "b": b}
114 def processPS(self, file, writer, context, registry, bbox):
115 file.write("%(r)g %(g)g %(b)g setrgbcolor\n" % self.color)
117 def processPDF(self, file, writer, context, registry, bbox):
118 if context.strokeattr:
119 file.write("%(r)f %(g)f %(b)f RG\n" % self.color)
120 if context.fillattr:
121 file.write("%(r)f %(g)f %(b)f rg\n" % self.color)
123 def cmyk(self):
124 # conversion to cmy
125 c, m, y = 1 - self.color["r"], 1 - self.color["g"], 1 - self.color["b"]
126 # conversion from cmy to cmyk with device-dependent functions
127 k = min([c, m, y])
128 return cmyk(min(1, max(0, c - _UCRc(k))),
129 min(1, max(0, m - _UCRm(k))),
130 min(1, max(0, y - _UCRy(k))),
131 _BG(k))
133 def grey(self):
134 return grey(0.3*self.color["r"] + 0.59*self.color["g"] + 0.11*self.color["b"])
135 gray = grey
137 def hsb(self):
139 values = self.color.values()
140 values.sort()
141 z, y, x = values
142 r, g, b = self.color["r"], self.color["g"], self.color["b"]
143 try:
144 if r == x and g == z:
145 return hsb((5 + (x-b)/(x-z)) / 6.0, (x - z) / x, x)
146 elif r == x and g > z:
147 return hsb((1 - (x-g)/(x-z)) / 6.0, (x - z) / x, x)
148 elif g == x and b == z:
149 return hsb((1 + (x-r)/(x-z)) / 6.0, (x - z) / x, x)
150 elif g == x and b > z:
151 return hsb((3 - (x-b)/(x-z)) / 6.0, (x - z) / x, x)
152 elif b == x and r == z:
153 return hsb((3 + (x-g)/(x-z)) / 6.0, (x - z) / x, x)
154 elif b == x and r > z:
155 return hsb((5 - (x-r)/(x-z)) / 6.0, (x - z) / x, x)
156 else:
157 raise ValueError
158 except ZeroDivisionError:
159 return hsb(0, 0, x)
161 def rgb(self):
162 return rgb(**self.color)
164 def colorspacestring(self):
165 return "/DeviceRGB"
167 def tostring8bit(self):
168 return struct.pack("BBB", round(self.color["r"]*255), round(self.color["g"]*255), round(self.color["b"]*255))
170 rgb.red = rgb(1 ,0, 0)
171 rgb.green = rgb(0 ,1, 0)
172 rgb.blue = rgb(0 ,0, 1)
173 rgb.white = rgb(1 ,1, 1)
174 rgb.black = rgb(0 ,0, 0)
177 class hsb(color):
179 """hsb colors"""
181 def __init__(self, h=0.0, s=0.0, b=0.0):
182 color.__init__(self)
183 if h<0 or h>1 or s<0 or s>1 or b<0 or b>1: raise ValueError
184 self.color = {"h": h, "s": s, "b": b}
186 def processPS(self, file, writer, context, registry, bbox):
187 file.write("%(h)g %(s)g %(b)g sethsbcolor\n" % self.color)
189 def processPDF(self, file, writer, context, registry, bbox):
190 r, g, b = colorsys.hsv_to_rgb(self.color["h"], self.color["s"], self.color["b"])
191 rgb(r, g, b).processPDF(file, writer, context, registry, bbox)
193 def cmyk(self):
194 return self.rgb().cmyk()
196 def grey(self):
197 return self.rgb().grey()
198 gray = grey
200 def hsb(self):
201 return hsb(**self.color)
203 def rgb(self):
204 h, s, b = self.color["h"], self.color["s"], self.color["b"]
205 i = int(6*h)
206 f = 6*h - i
207 m, n, k = 1 - s, 1 - s*f, 1 - s*(1-f)
208 if i == 1:
209 return rgb(b*n, b, b*m)
210 elif i == 2:
211 return rgb(b*m, b, b*k)
212 elif i == 3:
213 return rgb(b*m, b*n, b)
214 elif i == 4:
215 return rgb(b*k, b*m, b)
216 elif i == 5:
217 return rgb(b, b*m, b*n)
218 else:
219 return rgb(b, b*k, b*m)
222 class cmyk(color):
224 """cmyk colors"""
226 def __init__(self, c=0.0, m=0.0, y=0.0, k=0.0):
227 color.__init__(self)
228 if c<0 or c>1 or m<0 or m>1 or y<0 or y>1 or k<0 or k>1: raise ValueError
229 self.color = {"c": c, "m": m, "y": y, "k": k}
231 def processPS(self, file, writer, context, registry, bbox):
232 file.write("%(c)g %(m)g %(y)g %(k)g setcmykcolor\n" % self.color)
234 def processPDF(self, file, writer, context, registry, bbox):
235 if context.strokeattr:
236 file.write("%(c)f %(m)f %(y)f %(k)f K\n" % self.color)
237 if context.fillattr:
238 file.write("%(c)f %(m)f %(y)f %(k)f k\n" % self.color)
240 def cmyk(self):
241 return cmyk(**self.color)
243 def grey(self):
244 return grey(1 - min([1, 0.3*self.color["c"] + 0.59*self.color["m"] +
245 0.11*self.color["y"] + self.color["k"]]))
246 gray = grey
248 def hsb(self):
249 return self.rgb().hsb()
251 def rgb(self):
252 # conversion to cmy:
253 c = min(1, self.color["c"] + self.color["k"])
254 m = min(1, self.color["m"] + self.color["k"])
255 y = min(1, self.color["y"] + self.color["k"])
256 # conversion from cmy to rgb:
257 return rgb(1 - c, 1 - m, 1 - y)
259 def colorspacestring(self):
260 return "/DeviceCMYK"
262 def tostring8bit(self):
263 return struct.pack("BBBB", round(self.color["c"]*255), round(self.color["m"]*255), round(self.color["y"]*255), round(self.color["k"]*255))
265 cmyk.GreenYellow = cmyk(0.15, 0, 0.69, 0)
266 cmyk.Yellow = cmyk(0, 0, 1, 0)
267 cmyk.Goldenrod = cmyk(0, 0.10, 0.84, 0)
268 cmyk.Dandelion = cmyk(0, 0.29, 0.84, 0)
269 cmyk.Apricot = cmyk(0, 0.32, 0.52, 0)
270 cmyk.Peach = cmyk(0, 0.50, 0.70, 0)
271 cmyk.Melon = cmyk(0, 0.46, 0.50, 0)
272 cmyk.YellowOrange = cmyk(0, 0.42, 1, 0)
273 cmyk.Orange = cmyk(0, 0.61, 0.87, 0)
274 cmyk.BurntOrange = cmyk(0, 0.51, 1, 0)
275 cmyk.Bittersweet = cmyk(0, 0.75, 1, 0.24)
276 cmyk.RedOrange = cmyk(0, 0.77, 0.87, 0)
277 cmyk.Mahogany = cmyk(0, 0.85, 0.87, 0.35)
278 cmyk.Maroon = cmyk(0, 0.87, 0.68, 0.32)
279 cmyk.BrickRed = cmyk(0, 0.89, 0.94, 0.28)
280 cmyk.Red = cmyk(0, 1, 1, 0)
281 cmyk.OrangeRed = cmyk(0, 1, 0.50, 0)
282 cmyk.RubineRed = cmyk(0, 1, 0.13, 0)
283 cmyk.WildStrawberry = cmyk(0, 0.96, 0.39, 0)
284 cmyk.Salmon = cmyk(0, 0.53, 0.38, 0)
285 cmyk.CarnationPink = cmyk(0, 0.63, 0, 0)
286 cmyk.Magenta = cmyk(0, 1, 0, 0)
287 cmyk.VioletRed = cmyk(0, 0.81, 0, 0)
288 cmyk.Rhodamine = cmyk(0, 0.82, 0, 0)
289 cmyk.Mulberry = cmyk(0.34, 0.90, 0, 0.02)
290 cmyk.RedViolet = cmyk(0.07, 0.90, 0, 0.34)
291 cmyk.Fuchsia = cmyk(0.47, 0.91, 0, 0.08)
292 cmyk.Lavender = cmyk(0, 0.48, 0, 0)
293 cmyk.Thistle = cmyk(0.12, 0.59, 0, 0)
294 cmyk.Orchid = cmyk(0.32, 0.64, 0, 0)
295 cmyk.DarkOrchid = cmyk(0.40, 0.80, 0.20, 0)
296 cmyk.Purple = cmyk(0.45, 0.86, 0, 0)
297 cmyk.Plum = cmyk(0.50, 1, 0, 0)
298 cmyk.Violet = cmyk(0.79, 0.88, 0, 0)
299 cmyk.RoyalPurple = cmyk(0.75, 0.90, 0, 0)
300 cmyk.BlueViolet = cmyk(0.86, 0.91, 0, 0.04)
301 cmyk.Periwinkle = cmyk(0.57, 0.55, 0, 0)
302 cmyk.CadetBlue = cmyk(0.62, 0.57, 0.23, 0)
303 cmyk.CornflowerBlue = cmyk(0.65, 0.13, 0, 0)
304 cmyk.MidnightBlue = cmyk(0.98, 0.13, 0, 0.43)
305 cmyk.NavyBlue = cmyk(0.94, 0.54, 0, 0)
306 cmyk.RoyalBlue = cmyk(1, 0.50, 0, 0)
307 cmyk.Blue = cmyk(1, 1, 0, 0)
308 cmyk.Cerulean = cmyk(0.94, 0.11, 0, 0)
309 cmyk.Cyan = cmyk(1, 0, 0, 0)
310 cmyk.ProcessBlue = cmyk(0.96, 0, 0, 0)
311 cmyk.SkyBlue = cmyk(0.62, 0, 0.12, 0)
312 cmyk.Turquoise = cmyk(0.85, 0, 0.20, 0)
313 cmyk.TealBlue = cmyk(0.86, 0, 0.34, 0.02)
314 cmyk.Aquamarine = cmyk(0.82, 0, 0.30, 0)
315 cmyk.BlueGreen = cmyk(0.85, 0, 0.33, 0)
316 cmyk.Emerald = cmyk(1, 0, 0.50, 0)
317 cmyk.JungleGreen = cmyk(0.99, 0, 0.52, 0)
318 cmyk.SeaGreen = cmyk(0.69, 0, 0.50, 0)
319 cmyk.Green = cmyk(1, 0, 1, 0)
320 cmyk.ForestGreen = cmyk(0.91, 0, 0.88, 0.12)
321 cmyk.PineGreen = cmyk(0.92, 0, 0.59, 0.25)
322 cmyk.LimeGreen = cmyk(0.50, 0, 1, 0)
323 cmyk.YellowGreen = cmyk(0.44, 0, 0.74, 0)
324 cmyk.SpringGreen = cmyk(0.26, 0, 0.76, 0)
325 cmyk.OliveGreen = cmyk(0.64, 0, 0.95, 0.40)
326 cmyk.RawSienna = cmyk(0, 0.72, 1, 0.45)
327 cmyk.Sepia = cmyk(0, 0.83, 1, 0.70)
328 cmyk.Brown = cmyk(0, 0.81, 1, 0.60)
329 cmyk.Tan = cmyk(0.14, 0.42, 0.56, 0)
330 cmyk.Gray = cmyk(0, 0, 0, 0.50)
331 cmyk.Grey = cmyk.Gray
332 cmyk.Black = cmyk(0, 0, 0, 1)
333 cmyk.White = cmyk(0, 0, 0, 0)
334 cmyk.white = cmyk.White
335 cmyk.black = cmyk.Black
337 class palette(attr.changelist):
338 """color palettes
340 A color palette is a discrete, ordered list of colors"""
342 palette.clear = attr.clearclass(palette)
345 class gradient(attr.changeattr):
347 """base class for color gradients
349 A gradient is a continuous collection of colors with a single parameter ranging from 0 to 1
350 to address them"""
352 def getcolor(self, param):
353 """return color corresponding to param"""
354 pass
356 def select(self, index, n_indices):
357 """return a color corresponding to an index out of n_indices"""
358 if n_indices == 1:
359 param = 0
360 else:
361 param = index / (n_indices - 1.0)
362 return self.getcolor(param)
364 gradient.clear = attr.clearclass(gradient)
367 class lineargradient(gradient):
369 """collection of two colors for a linear transition between them"""
371 def __init__(self, mincolor, maxcolor):
372 if mincolor.__class__ != maxcolor.__class__:
373 raise ValueError
374 self.colorclass = mincolor.__class__
375 self.mincolor = mincolor
376 self.maxcolor = maxcolor
378 def getcolor(self, param):
379 colordict = {}
380 for key in self.mincolor.color.keys():
381 colordict[key] = param * self.maxcolor.color[key] + (1 - param) * self.mincolor.color[key]
382 return self.colorclass(**colordict)
385 class functiongradient(gradient):
387 """collection of colors for an arbitray non-linear transition between them
389 parameters:
390 functions: a dictionary for the color values
391 type: a string indicating the color class
394 def __init__(self, functions, type):
395 if type == "cmyk":
396 self.colorclass = cmyk
397 elif type == "rgb":
398 self.colorclass = rgb
399 elif type == "hsb":
400 self.colorclass = hsb
401 elif type == "grey" or type == "gray":
402 self.colorclass = grey
403 else:
404 raise ValueError
405 self.functions = functions
407 def getcolor(self, param):
408 colordict = {}
409 for key in self.functions.keys():
410 colordict[key] = self.functions[key](param)
411 return self.colorclass(**colordict)
414 gradient.Gray = lineargradient(gray.white, gray.black)
415 gradient.Grey = gradient.Gray
416 gradient.ReverseGray = lineargradient(gray.black, gray.white)
417 gradient.ReverseGrey = gradient.ReverseGray
418 gradient.BlackYellow = functiongradient(functions={ # compare this with reversegray above
419 "r":(lambda x: 2*x*(1-x)**5 + 3.5*x**2*(1-x)**3 + 2.1*x*x*(1-x)**2 + 3.0*x**3*(1-x)**2 + x**0.5*(1-(1-x)**2)),
420 "g":(lambda x: 1.5*x**2*(1-x)**3 - 0.8*x**3*(1-x)**2 + 2.0*x**4*(1-x) + x**4),
421 "b":(lambda x: 5*x*(1-x)**5 - 0.5*x**2*(1-x)**3 + 0.3*x*x*(1-x)**2 + 5*x**3*(1-x)**2 + 0.5*x**6)},
422 type="rgb")
423 gradient.RedGreen = lineargradient(rgb.red, rgb.green)
424 gradient.RedBlue = lineargradient(rgb.red, rgb.blue)
425 gradient.GreenRed = lineargradient(rgb.green, rgb.red)
426 gradient.GreenBlue = lineargradient(rgb.green, rgb.blue)
427 gradient.BlueRed = lineargradient(rgb.blue, rgb.red)
428 gradient.BlueGreen = lineargradient(rgb.blue, rgb.green)
429 gradient.RedBlack = lineargradient(rgb.red, rgb.black)
430 gradient.BlackRed = lineargradient(rgb.black, rgb.red)
431 gradient.RedWhite = lineargradient(rgb.red, rgb.white)
432 gradient.WhiteRed = lineargradient(rgb.white, rgb.red)
433 gradient.GreenBlack = lineargradient(rgb.green, rgb.black)
434 gradient.BlackGreen = lineargradient(rgb.black, rgb.green)
435 gradient.GreenWhite = lineargradient(rgb.green, rgb.white)
436 gradient.WhiteGreen = lineargradient(rgb.white, rgb.green)
437 gradient.BlueBlack = lineargradient(rgb.blue, rgb.black)
438 gradient.BlackBlue = lineargradient(rgb.black, rgb.blue)
439 gradient.BlueWhite = lineargradient(rgb.blue, rgb.white)
440 gradient.WhiteBlue = lineargradient(rgb.white, rgb.blue)
441 gradient.Rainbow = lineargradient(hsb(0, 1, 1), hsb(2.0/3.0, 1, 1))
442 gradient.ReverseRainbow = lineargradient(hsb(2.0/3.0, 1, 1), hsb(0, 1, 1))
443 gradient.Hue = lineargradient(hsb(0, 1, 1), hsb(1, 1, 1))
444 gradient.ReverseHue = lineargradient(hsb(1, 1, 1), hsb(0, 1, 1))
447 class PDFextgstate(pdfwriter.PDFobject):
449 def __init__(self, name, extgstate, registry):
450 pdfwriter.PDFobject.__init__(self, "extgstate", name)
451 registry.addresource("ExtGState", name, self)
452 self.name = name
453 self.extgstate = extgstate
455 def write(self, file, writer, registry):
456 file.write("%s\n" % self.extgstate)
459 class transparency(attr.exclusiveattr, style.strokestyle, style.fillstyle):
461 def __init__(self, value):
462 value = 1-value
463 attr.exclusiveattr.__init__(self, transparency)
464 self.name = "Transparency-%f" % value
465 self.extgstate = "<< /Type /ExtGState /CA %f /ca %f >>" % (value, value)
467 def processPS(self, file, writer, context, registry, bbox):
468 raise NotImplementedError("transparency not available in PostScript")
470 def processPDF(self, file, writer, context, registry, bbox):
471 registry.add(PDFextgstate(self.name, self.extgstate, registry))
472 file.write("/%s gs\n" % self.name)