1 # -*- encoding: utf-8 -*-
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-2007 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 binascii
, colorsys
, math
, struct
, warnings
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
36 def set(UCRc
=None, UCRm
=None, UCRy
=None, BG
=None):
52 class color(attr
.exclusiveattr
, style
.strokestyle
, style
.fillstyle
):
54 """base class for all colors"""
57 attr
.exclusiveattr
.__init
__(self
, color
)
60 clear
= attr
.clearclass(color
)
67 def __init__(self
, gray
):
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
)
79 file.write("%(gray)f g\n" % self
.color
)
82 return cmyk(0, 0, 0, 1 - self
.color
["gray"])
85 return grey(**self
.color
)
89 return hsb(0, 0, self
.color
["gray"])
92 return rgb(self
.color
["gray"], self
.color
["gray"], self
.color
["gray"])
94 def colorspacestring(self
):
97 def to8bitstring(self
):
98 return chr(int(self
.color
["gray"]*255))
100 grey
.black
= grey(0.0)
101 grey
.white
= grey(1.0)
109 def __init__(self
, r
=0.0, g
=0.0, b
=0.0):
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
)
121 file.write("%(r)f %(g)f %(b)f rg\n" % self
.color
)
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
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
))),
134 return grey(0.3*self
.color
["r"] + 0.59*self
.color
["g"] + 0.11*self
.color
["b"])
139 values
= self
.color
.values()
142 r
, g
, b
= self
.color
["r"], self
.color
["g"], self
.color
["b"]
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
)
158 except ZeroDivisionError:
162 return rgb(**self
.color
)
164 def colorspacestring(self
):
167 def to8bitstring(self
):
168 return struct
.pack("BBB", int(self
.color
["r"]*255), int(self
.color
["g"]*255), int(self
.color
["b"]*255))
170 def tohexstring(self
, cssstrip
=1, addhash
=1):
171 hexstring
= binascii
.b2a_hex(self
.to8bitstring())
172 if cssstrip
and hexstring
[0] == hexstring
[1] and hexstring
[2] == hexstring
[3] and hexstring
[4] == hexstring
[5]:
173 hexstring
= "".join([hexstring
[0], hexstring
[1], hexstring
[2]])
175 hexstring
= "#" + hexstring
179 def rgbfromhexstring(hexstring
):
180 hexstring
= hexstring
.strip().lstrip("#")
181 if len(hexstring
) == 3:
182 hexstring
= "".join([hexstring
[0], hexstring
[0], hexstring
[1], hexstring
[1], hexstring
[2], hexstring
[2]])
183 elif len(hexstring
) != 6:
184 raise ValueError("3 or 6 digit hex number expected (with optional leading hash character)")
185 return rgb(*[value
/255.0 for value
in struct
.unpack("BBB", binascii
.a2b_hex(hexstring
))])
187 rgb
.red
= rgb(1, 0, 0)
188 rgb
.green
= rgb(0, 1, 0)
189 rgb
.blue
= rgb(0, 0, 1)
190 rgb
.white
= rgb(1, 1, 1)
191 rgb
.black
= rgb(0, 0, 0)
198 def __init__(self
, h
=0.0, s
=0.0, b
=0.0):
200 if h
<0 or h
>1 or s
<0 or s
>1 or b
<0 or b
>1: raise ValueError
201 self
.color
= {"h": h
, "s": s
, "b": b
}
203 def processPS(self
, file, writer
, context
, registry
, bbox
):
204 file.write("%(h)g %(s)g %(b)g sethsbcolor\n" % self
.color
)
206 def processPDF(self
, file, writer
, context
, registry
, bbox
):
207 r
, g
, b
= colorsys
.hsv_to_rgb(self
.color
["h"], self
.color
["s"], self
.color
["b"])
208 rgb(r
, g
, b
).processPDF(file, writer
, context
, registry
, bbox
)
211 return self
.rgb().cmyk()
214 return self
.rgb().grey()
218 return hsb(**self
.color
)
221 h
, s
, b
= self
.color
["h"], self
.color
["s"], self
.color
["b"]
224 m
, n
, k
= 1 - s
, 1 - s
*f
, 1 - s
*(1-f
)
226 return rgb(b
*n
, b
, b
*m
)
228 return rgb(b
*m
, b
, b
*k
)
230 return rgb(b
*m
, b
*n
, b
)
232 return rgb(b
*k
, b
*m
, b
)
234 return rgb(b
, b
*m
, b
*n
)
236 return rgb(b
, b
*k
, b
*m
)
238 def colorspacestring(self
):
239 raise RuntimeError("colorspace string not available for hsb colors")
246 def __init__(self
, c
=0.0, m
=0.0, y
=0.0, k
=0.0):
248 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
249 self
.color
= {"c": c
, "m": m
, "y": y
, "k": k
}
251 def processPS(self
, file, writer
, context
, registry
, bbox
):
252 file.write("%(c)g %(m)g %(y)g %(k)g setcmykcolor\n" % self
.color
)
254 def processPDF(self
, file, writer
, context
, registry
, bbox
):
255 if context
.strokeattr
:
256 file.write("%(c)f %(m)f %(y)f %(k)f K\n" % self
.color
)
258 file.write("%(c)f %(m)f %(y)f %(k)f k\n" % self
.color
)
261 return cmyk(**self
.color
)
264 return grey(1 - min([1, 0.3*self
.color
["c"] + 0.59*self
.color
["m"] +
265 0.11*self
.color
["y"] + self
.color
["k"]]))
269 return self
.rgb().hsb()
273 c
= min(1, self
.color
["c"] + self
.color
["k"])
274 m
= min(1, self
.color
["m"] + self
.color
["k"])
275 y
= min(1, self
.color
["y"] + self
.color
["k"])
276 # conversion from cmy to rgb:
277 return rgb(1 - c
, 1 - m
, 1 - y
)
279 def colorspacestring(self
):
282 def to8bitstring(self
):
283 return struct
.pack("BBBB", int(self
.color
["c"]*255), int(self
.color
["m"]*255), int(self
.color
["y"]*255), int(self
.color
["k"]*255))
285 cmyk
.GreenYellow
= cmyk(0.15, 0, 0.69, 0)
286 cmyk
.Yellow
= cmyk(0, 0, 1, 0)
287 cmyk
.Goldenrod
= cmyk(0, 0.10, 0.84, 0)
288 cmyk
.Dandelion
= cmyk(0, 0.29, 0.84, 0)
289 cmyk
.Apricot
= cmyk(0, 0.32, 0.52, 0)
290 cmyk
.Peach
= cmyk(0, 0.50, 0.70, 0)
291 cmyk
.Melon
= cmyk(0, 0.46, 0.50, 0)
292 cmyk
.YellowOrange
= cmyk(0, 0.42, 1, 0)
293 cmyk
.Orange
= cmyk(0, 0.61, 0.87, 0)
294 cmyk
.BurntOrange
= cmyk(0, 0.51, 1, 0)
295 cmyk
.Bittersweet
= cmyk(0, 0.75, 1, 0.24)
296 cmyk
.RedOrange
= cmyk(0, 0.77, 0.87, 0)
297 cmyk
.Mahogany
= cmyk(0, 0.85, 0.87, 0.35)
298 cmyk
.Maroon
= cmyk(0, 0.87, 0.68, 0.32)
299 cmyk
.BrickRed
= cmyk(0, 0.89, 0.94, 0.28)
300 cmyk
.Red
= cmyk(0, 1, 1, 0)
301 cmyk
.OrangeRed
= cmyk(0, 1, 0.50, 0)
302 cmyk
.RubineRed
= cmyk(0, 1, 0.13, 0)
303 cmyk
.WildStrawberry
= cmyk(0, 0.96, 0.39, 0)
304 cmyk
.Salmon
= cmyk(0, 0.53, 0.38, 0)
305 cmyk
.CarnationPink
= cmyk(0, 0.63, 0, 0)
306 cmyk
.Magenta
= cmyk(0, 1, 0, 0)
307 cmyk
.VioletRed
= cmyk(0, 0.81, 0, 0)
308 cmyk
.Rhodamine
= cmyk(0, 0.82, 0, 0)
309 cmyk
.Mulberry
= cmyk(0.34, 0.90, 0, 0.02)
310 cmyk
.RedViolet
= cmyk(0.07, 0.90, 0, 0.34)
311 cmyk
.Fuchsia
= cmyk(0.47, 0.91, 0, 0.08)
312 cmyk
.Lavender
= cmyk(0, 0.48, 0, 0)
313 cmyk
.Thistle
= cmyk(0.12, 0.59, 0, 0)
314 cmyk
.Orchid
= cmyk(0.32, 0.64, 0, 0)
315 cmyk
.DarkOrchid
= cmyk(0.40, 0.80, 0.20, 0)
316 cmyk
.Purple
= cmyk(0.45, 0.86, 0, 0)
317 cmyk
.Plum
= cmyk(0.50, 1, 0, 0)
318 cmyk
.Violet
= cmyk(0.79, 0.88, 0, 0)
319 cmyk
.RoyalPurple
= cmyk(0.75, 0.90, 0, 0)
320 cmyk
.BlueViolet
= cmyk(0.86, 0.91, 0, 0.04)
321 cmyk
.Periwinkle
= cmyk(0.57, 0.55, 0, 0)
322 cmyk
.CadetBlue
= cmyk(0.62, 0.57, 0.23, 0)
323 cmyk
.CornflowerBlue
= cmyk(0.65, 0.13, 0, 0)
324 cmyk
.MidnightBlue
= cmyk(0.98, 0.13, 0, 0.43)
325 cmyk
.NavyBlue
= cmyk(0.94, 0.54, 0, 0)
326 cmyk
.RoyalBlue
= cmyk(1, 0.50, 0, 0)
327 cmyk
.Blue
= cmyk(1, 1, 0, 0)
328 cmyk
.Cerulean
= cmyk(0.94, 0.11, 0, 0)
329 cmyk
.Cyan
= cmyk(1, 0, 0, 0)
330 cmyk
.ProcessBlue
= cmyk(0.96, 0, 0, 0)
331 cmyk
.SkyBlue
= cmyk(0.62, 0, 0.12, 0)
332 cmyk
.Turquoise
= cmyk(0.85, 0, 0.20, 0)
333 cmyk
.TealBlue
= cmyk(0.86, 0, 0.34, 0.02)
334 cmyk
.Aquamarine
= cmyk(0.82, 0, 0.30, 0)
335 cmyk
.BlueGreen
= cmyk(0.85, 0, 0.33, 0)
336 cmyk
.Emerald
= cmyk(1, 0, 0.50, 0)
337 cmyk
.JungleGreen
= cmyk(0.99, 0, 0.52, 0)
338 cmyk
.SeaGreen
= cmyk(0.69, 0, 0.50, 0)
339 cmyk
.Green
= cmyk(1, 0, 1, 0)
340 cmyk
.ForestGreen
= cmyk(0.91, 0, 0.88, 0.12)
341 cmyk
.PineGreen
= cmyk(0.92, 0, 0.59, 0.25)
342 cmyk
.LimeGreen
= cmyk(0.50, 0, 1, 0)
343 cmyk
.YellowGreen
= cmyk(0.44, 0, 0.74, 0)
344 cmyk
.SpringGreen
= cmyk(0.26, 0, 0.76, 0)
345 cmyk
.OliveGreen
= cmyk(0.64, 0, 0.95, 0.40)
346 cmyk
.RawSienna
= cmyk(0, 0.72, 1, 0.45)
347 cmyk
.Sepia
= cmyk(0, 0.83, 1, 0.70)
348 cmyk
.Brown
= cmyk(0, 0.81, 1, 0.60)
349 cmyk
.Tan
= cmyk(0.14, 0.42, 0.56, 0)
350 cmyk
.Gray
= cmyk(0, 0, 0, 0.50)
351 cmyk
.Grey
= cmyk
.Gray
352 cmyk
.Black
= cmyk(0, 0, 0, 1)
353 cmyk
.White
= cmyk(0, 0, 0, 0)
354 cmyk
.white
= cmyk
.White
355 cmyk
.black
= cmyk
.Black
357 class palette(attr
.changelist
):
360 A color palette is a discrete, ordered list of colors"""
362 palette
.clear
= attr
.clearclass(palette
)
365 class gradient(attr
.changeattr
):
367 """base class for color gradients
369 A gradient is a continuous collection of colors with a single parameter ranging from 0 to 1
372 def getcolor(self
, param
):
373 """return color corresponding to param"""
376 def select(self
, index
, n_indices
):
377 """return a color corresponding to an index out of n_indices"""
381 param
= index
/ (n_indices
- 1.0)
382 return self
.getcolor(param
)
384 gradient
.clear
= attr
.clearclass(gradient
)
387 class lineargradient(gradient
):
389 """collection of two colors for a linear transition between them"""
391 def __init__(self
, mincolor
, maxcolor
):
392 if mincolor
.__class
__ != maxcolor
.__class
__:
394 self
.colorclass
= mincolor
.__class
__
395 self
.mincolor
= mincolor
396 self
.maxcolor
= maxcolor
398 def getcolor(self
, param
):
400 for key
in self
.mincolor
.color
.keys():
401 colordict
[key
] = param
* self
.maxcolor
.color
[key
] + (1 - param
) * self
.mincolor
.color
[key
]
402 return self
.colorclass(**colordict
)
405 class functiongradient(gradient
):
407 """collection of colors for an arbitray non-linear transition between them
410 functions: a dictionary for the color values
411 type: a string indicating the color class
414 def __init__(self
, functions
, cls
):
415 self
.functions
= functions
418 def getcolor(self
, param
):
420 for key
in self
.functions
.keys():
421 colordict
[key
] = self
.functions
[key
](param
)
422 return self
.cls(**colordict
)
425 gradient
.Gray
= lineargradient(gray
.white
, gray
.black
)
426 gradient
.Grey
= gradient
.Gray
427 gradient
.ReverseGray
= lineargradient(gray
.black
, gray
.white
)
428 gradient
.ReverseGrey
= gradient
.ReverseGray
429 gradient
.BlackYellow
= functiongradient({ # compare this with reversegray above
430 "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)),
431 "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),
432 "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)},
434 gradient
.RedGreen
= lineargradient(rgb
.red
, rgb
.green
)
435 gradient
.RedBlue
= lineargradient(rgb
.red
, rgb
.blue
)
436 gradient
.GreenRed
= lineargradient(rgb
.green
, rgb
.red
)
437 gradient
.GreenBlue
= lineargradient(rgb
.green
, rgb
.blue
)
438 gradient
.BlueRed
= lineargradient(rgb
.blue
, rgb
.red
)
439 gradient
.BlueGreen
= lineargradient(rgb
.blue
, rgb
.green
)
440 gradient
.RedBlack
= lineargradient(rgb
.red
, rgb
.black
)
441 gradient
.BlackRed
= lineargradient(rgb
.black
, rgb
.red
)
442 gradient
.RedWhite
= lineargradient(rgb
.red
, rgb
.white
)
443 gradient
.WhiteRed
= lineargradient(rgb
.white
, rgb
.red
)
444 gradient
.GreenBlack
= lineargradient(rgb
.green
, rgb
.black
)
445 gradient
.BlackGreen
= lineargradient(rgb
.black
, rgb
.green
)
446 gradient
.GreenWhite
= lineargradient(rgb
.green
, rgb
.white
)
447 gradient
.WhiteGreen
= lineargradient(rgb
.white
, rgb
.green
)
448 gradient
.BlueBlack
= lineargradient(rgb
.blue
, rgb
.black
)
449 gradient
.BlackBlue
= lineargradient(rgb
.black
, rgb
.blue
)
450 gradient
.BlueWhite
= lineargradient(rgb
.blue
, rgb
.white
)
451 gradient
.WhiteBlue
= lineargradient(rgb
.white
, rgb
.blue
)
452 gradient
.Rainbow
= lineargradient(hsb(0, 1, 1), hsb(2.0/3.0, 1, 1))
453 gradient
.ReverseRainbow
= lineargradient(hsb(2.0/3.0, 1, 1), hsb(0, 1, 1))
454 gradient
.Hue
= lineargradient(hsb(0, 1, 1), hsb(1, 1, 1))
455 gradient
.ReverseHue
= lineargradient(hsb(1, 1, 1), hsb(0, 1, 1))
458 class PDFextgstate(pdfwriter
.PDFobject
):
460 def __init__(self
, name
, extgstate
, registry
):
461 pdfwriter
.PDFobject
.__init
__(self
, "extgstate", name
)
462 registry
.addresource("ExtGState", name
, self
)
464 self
.extgstate
= extgstate
466 def write(self
, file, writer
, registry
):
467 file.write("%s\n" % self
.extgstate
)
470 class transparency(attr
.exclusiveattr
, style
.strokestyle
, style
.fillstyle
):
472 def __init__(self
, value
):
474 attr
.exclusiveattr
.__init
__(self
, transparency
)
476 def processPS(self
, file, writer
, context
, registry
, bbox
):
477 warnings
.warn("Transparency not available in PostScript, proprietary ghostscript extension code inserted.")
478 file.write("%f .setshapealpha\n" % self
.value
)
480 def processPDF(self
, file, writer
, context
, registry
, bbox
):
481 if context
.strokeattr
and context
.fillattr
:
482 registry
.add(PDFextgstate("Transparency-%f" % self
.value
,
483 "<< /Type /ExtGState /CA %f /ca %f >>" % (self
.value
, self
.value
), registry
))
484 file.write("/Transparency-%f gs\n" % self
.value
)
485 elif context
.strokeattr
:
486 registry
.add(PDFextgstate("Transparency-Stroke-%f" % self
.value
,
487 "<< /Type /ExtGState /CA %f >>" % self
.value
, registry
))
488 file.write("/Transparency-Stroke-%f gs\n" % self
.value
)
489 elif context
.fillattr
:
490 registry
.add(PDFextgstate("Transparency-Fill-%f" % self
.value
,
491 "<< /Type /ExtGState /ca %f >>" % self
.value
, registry
))
492 file.write("/Transparency-Fill-%f gs\n" % self
.value
)