2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2004 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 import cStringIO
, struct
, sys
31 import base
, bbox
, prolog
, trafo
, unit
33 def ascii85lines(datalen
):
36 return (datalen
+ 56)/60
38 def ascii85stream(file, data
):
39 """Encodes the string data in ASCII85 and writes it to
40 the stream file. The number of lines written to the stream
41 is known just from the length of the data by means of the
42 ascii85lines function. Note that the tailing newline character
43 of the last line is not added by this function, but it is taken
44 into account in the ascii85lines function."""
45 i
= 3 # go on smoothly in case of data length equals zero
47 for i
in range(len(data
)):
51 if i
%60 == 3 and i
!= 3:
57 c1
, c2
= divmod(l
, 85)
58 file.write(struct
.pack('BBBBB', c1
+33, c2
+33, c3
+33, c4
+33, c5
+33))
63 for x
in range(3-(i
%4)):
68 c1
, c2
= divmod(l
, 85)
69 file.write(struct
.pack('BBBB', c1
+33, c2
+33, c3
+33, c4
+33)[:(i
%4)+2])
74 def __init__(self
, width
, height
, mode
, data
, compressed
=None):
75 if width
<= 0 or height
<= 0:
76 raise ValueError("valid image size")
77 if mode
not in ["L", "RGB", "CMYK"]:
78 raise ValueError("invalid mode")
79 if compressed
is None and len(mode
)*width
*height
!= len(data
):
80 raise ValueError("wrong size of uncompressed data")
81 self
.size
= width
, height
84 self
.compressed
= compressed
86 def tostring(self
, *args
):
88 raise RuntimeError("encoding not supported in this implementation")
91 def convert(self
, model
):
92 raise RuntimeError("color model conversion not supported in this implementation")
95 class jpegimage(image
):
97 def __init__(self
, file):
101 data
= open(file, "rb").read()
106 if data
[pos
] == "\377" and data
[pos
+1] not in ["\0", "\377"]:
107 # print "marker: 0x%02x \\%03o" % (ord(data[pos+1]), ord(data[pos+1]))
108 if data
[pos
+1] == "\330":
112 elif not nestinglevel
:
113 raise ValueError("begin marker expected")
114 elif data
[pos
+1] == "\331":
119 elif data
[pos
+1] in ["\300", "\301"]:
120 l
, bits
, height
, width
, components
= struct
.unpack(">HBHHB", data
[pos
+2:pos
+10])
122 raise ValueError("implementation limited to 8 bit per component only")
124 mode
= {1: "L", 3: "RGB", 4: "CMYK"}[components
]
126 raise ValueError("invalid number of components")
128 elif data
[pos
+1] == "\340":
129 l
, id, major
, minor
, dpikind
, xdpi
, ydpi
= struct
.unpack(">H5sBBBHH", data
[pos
+2:pos
+16])
131 self
.info
= {"dpi": (xdpi
, ydpi
)}
133 self
.info
= {"dpi": (xdpi
*2.54, ydpi
*2.45)}
134 # else do not provide dpi information
138 raise ValueError("end marker expected")
139 image
.__init
__(self
, width
, height
, mode
, data
[begin
:end
], compressed
="DCT")
142 class bitmap(base
.canvasitem
):
144 def __init__(self
, xpos
, ypos
, image
,
145 width
=None, height
=None, ratio
=None,
146 storedata
=0, maxstrlen
=4093,
147 compressmode
="Flate",
148 flatecompresslevel
=6,
149 dctquality
=75, dctoptimize
=0, dctprogression
=0):
152 self
.imagewidth
, self
.imageheight
= image
.size
153 self
.storedata
= storedata
154 self
.maxstrlen
= maxstrlen
155 self
.imagedataid
= "imagedata%d" % id(self
)
158 if width
is not None or height
is not None:
161 if self
.width
is None:
163 self
.width
= self
.height
* self
.imagewidth
/ float(self
.imageheight
)
165 self
.width
= ratio
* self
.height
166 elif self
.height
is None:
168 self
.height
= self
.width
* self
.imageheight
/ float(self
.imagewidth
)
170 self
.height
= (1.0/ratio
) * self
.width
171 elif ratio
is not None:
172 raise ValueError("can't specify a ratio when setting width and height")
174 if ratio
is not None:
175 raise ValueError("must specify width or height to set a ratio")
176 widthdpi
, heightdpi
= image
.info
["dpi"] # XXX fails when no dpi information available
177 self
.width
= unit
.inch(self
.imagewidth
/ float(widthdpi
))
178 self
.height
= unit
.inch(self
.imageheight
/ float(heightdpi
))
180 self
.xpos_pt
= unit
.topt(self
.xpos
)
181 self
.ypos_pt
= unit
.topt(self
.ypos
)
182 self
.width_pt
= unit
.topt(self
.width
)
183 self
.height_pt
= unit
.topt(self
.height
)
185 # create decode and colorspace
186 self
.palettedata
= None
187 if image
.mode
== "P":
188 palettemode
, self
.palettedata
= image
.palette
.getdata()
189 self
.decode
= "[0 255]"
190 # palettedata and closing ']' is inserted in outputPS
191 if palettemode
== "L":
192 self
.colorspace
= "[ /Indexed /DeviceGray %i" % (len(self
.palettedata
)/1-1)
193 elif palettemode
== "RGB":
194 self
.colorspace
= "[ /Indexed /DeviceRGB %i" % (len(self
.palettedata
)/3-1)
195 elif palettemode
== "CMYK":
196 self
.colorspace
= "[ /Indexed /DeviceCMYK %i" % (len(self
.palettedata
)/4-1)
198 image
= image
.convert("RGB")
199 self
.decode
= "[0 1 0 1 0 1]"
200 self
.colorspace
= "/DeviceRGB"
201 self
.palettedata
= None
202 sys
.stderr
.write("*** PyX Info: image with unknown palette mode converted to rgb image\n")
203 elif len(image
.mode
) == 1:
204 if image
.mode
!= "L":
205 image
= image
.convert("L")
206 sys
.stderr
.write("*** PyX Info: specific single channel image mode not natively supported, converted to regular grayscale\n")
207 self
.decode
= "[0 1]"
208 self
.colorspace
= "/DeviceGray"
209 elif image
.mode
== "CMYK":
210 self
.decode
= "[0 1 0 1 0 1 0 1]"
211 self
.colorspace
= "/DeviceCMYK"
213 if image
.mode
!= "RGB":
214 image
= image
.convert("RGB")
215 sys
.stderr
.write("*** PyX Info: image with unknown mode converted to rgb\n")
216 self
.decode
= "[0 1 0 1 0 1]"
217 self
.colorspace
= "/DeviceRGB"
220 self
.imagematrix
= str(trafo
.mirror(0)
221 .translated_pt(-self
.xpos_pt
, self
.ypos_pt
+self
.height_pt
)
222 .scaled_pt(self
.imagewidth
/self
.width_pt
, self
.imageheight
/self
.height_pt
))
224 # savely check whether imagedata is compressed or not
226 imagecompressed
= image
.compressed
228 imagecompressed
= None
229 if compressmode
!= None and imagecompressed
!= None:
230 raise ValueError("compression of a compressed image not supported")
231 if not haszlib
and compressmode
== "Flate":
232 sys
.stderr
.write("*** PyX Info: zlib module not available, disable compression")
236 if compressmode
== "Flate":
237 self
.data
= zlib
.compress(image
.tostring(), flatecompresslevel
)
238 elif compressmode
== "DCT":
239 self
.data
= image
.tostring("jpeg", image
.mode
,
240 dctquality
, dctoptimize
, dctprogression
)
242 self
.data
= image
.tostring()
243 self
.singlestring
= self
.storedata
and len(self
.data
) < self
.maxstrlen
247 if self
.singlestring
:
248 self
.datasource
= "/%s load" % self
.imagedataid
250 self
.datasource
= "/imagedataaccess load" # some printers do not allow for inline code here
251 self
.prologs
.append(prolog
.definition("imagedataaccess",
252 "{ /imagedataindex load " # get list index
253 "dup 1 add /imagedataindex exch store " # store increased index
254 "/imagedataid load exch get }")) # select string from array
256 self
.datasource
= "currentfile /ASCII85Decode filter"
257 if compressmode
== "Flate" or imagecompressed
== "Flate":
258 self
.datasource
+= " /FlateDecode filter"
259 elif compressmode
== "DCT" or imagecompressed
== "DCT":
260 self
.datasource
+= " /DCTDecode filter"
262 if compressmode
!= None:
263 raise ValueError("invalid compressmode '%s'" % compressmode
)
264 if imagecompressed
!= None:
265 raise ValueError("invalid compressed image '%s'" % imagecompressed
)
269 # TODO resource data could be written directly on the output stream
270 # after proper code reorganization
271 buffer = cStringIO
.StringIO()
272 if self
.singlestring
:
273 buffer.write("%%%%BeginData: %i ASCII Lines\n"
274 "<~" % ascii85lines(len(self
.data
)))
275 ascii85stream(buffer, self
.data
)
276 buffer.write("~>\n%%EndData\n")
278 datalen
= len(self
.data
)
279 tailpos
= datalen
- datalen
% self
.maxstrlen
280 buffer.write("%%%%BeginData: %i ASCII Lines\n" %
281 ((tailpos
/self
.maxstrlen
) * ascii85lines(self
.maxstrlen
) + ascii85lines(datalen
-tailpos
)))
283 for i
in xrange(0, tailpos
, self
.maxstrlen
):
285 ascii85stream(buffer, self
.data
[i
: i
+self
.maxstrlen
])
287 if datalen
!= tailpos
:
289 ascii85stream(buffer, self
.data
[tailpos
:])
291 buffer.write("]\n%%EndData\n")
292 self
.prologs
.append(prolog
.definition(self
.imagedataid
, buffer.getvalue()))
295 return bbox
.bbox_pt(self
.xpos_pt
, self
.ypos_pt
,
296 self
.xpos_pt
+self
.width_pt
, self
.ypos_pt
+self
.height_pt
)
301 def outputPS(self
, file):
303 "%s" % self
.colorspace
)
304 if self
.palettedata
is not None:
305 # insert palette data
307 ascii85stream(file, self
.palettedata
)
309 file.write(" setcolorspace\n")
311 if self
.storedata
and not self
.singlestring
:
312 file.write("/imagedataindex 0 store\n" # not use the stack since interpreters differ in their stack usage
313 "/imagedataid %s store\n" % self
.imagedataid
)
317 "/Width %i\n" # imagewidth
318 "/Height %i\n" # imageheight
319 "/BitsPerComponent 8\n"
320 "/ImageMatrix %s\n" # imagematrix
321 "/Decode %s\n" # decode
322 "/DataSource %s\n" # datasource
323 ">>\n" % (self
.imagewidth
, self
.imageheight
,
324 self
.imagematrix
, self
.decode
, self
.datasource
))
326 file.write("image\n")
328 # the datasource is currentstream (plus some filters)
329 file.write("%%%%BeginData: %i ASCII Lines\n"
330 "image\n" % (ascii85lines(len(self
.data
)) + 1))
331 ascii85stream(file, self
.data
)
332 file.write("~>\n%%EndData\n")
334 file.write("grestore\n")