2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2004 André Wobst <wobsta@users.sourceforge.net>
7 # This file adds bitmap functionality to PyX. Its designed for usage
8 # with PyX 0.6.x and will, in a similar form, become part of future
12 import cStringIO
, struct
, sys
19 import base
, bbox
, prolog
, trafo
, unit
21 def ascii85stream(file, data
):
23 for i
in range(len(data
)):
31 c1
, c2
= divmod(l
, 85)
32 file.write(struct
.pack('BBBBB', c1
+33, c2
+33, c3
+33, c4
+33, c5
+33))
39 for x
in range(3-(i
%4)):
44 c1
, c2
= divmod(l
, 85)
45 file.write(struct
.pack('BBBB', c1
+33, c2
+33, c3
+33, c4
+33)[:(i
%4)+2])
50 def __init__(self
, width
, height
, mode
, data
, compressed
=None):
51 if width
<= 0 or height
<= 0:
52 raise ValueError("valid image size")
53 if mode
not in ["L", "RGB", "CMYK"]:
54 raise ValueError("invalid mode")
55 if compressed
is None and len(mode
)*width
*height
!= len(data
):
56 raise ValueError("wrong size of uncompressed data")
57 self
.size
= width
, height
60 self
.compressed
= compressed
62 def tostring(self
, *args
):
64 raise RuntimeError("encoding not supported in this implementation")
67 def convert(self
, model
):
68 raise RuntimeError("color model conversion not supported in this implementation")
71 class jpegimage(image
):
73 def __init__(self
, file):
77 data
= open(file, "rb").read()
82 if data
[pos
] == "\377" and data
[pos
+1] not in ["\0", "\377"]:
83 # print "marker: 0x%02x \\%03o" % (ord(data[pos+1]), ord(data[pos+1]))
84 if data
[pos
+1] == "\330":
88 elif not nestinglevel
:
89 raise ValueError("begin marker expected")
90 elif data
[pos
+1] == "\331":
95 elif data
[pos
+1] in ["\300", "\301"]:
96 l
, bits
, height
, width
, components
= struct
.unpack(">HBHHB", data
[pos
+2:pos
+10])
98 raise ValueError("implementation limited to 8 bit per component only")
100 mode
= {1: "L", 3: "RGB", 4: "CMYK"}[components
]
102 raise ValueError("invalid number of components")
104 elif data
[pos
+1] == "\340":
105 l
, id, major
, minor
, dpikind
, xdpi
, ydpi
= struct
.unpack(">H5sBBBHH", data
[pos
+2:pos
+16])
107 self
.info
= {"dpi": (xdpi
, ydpi
)}
109 self
.info
= {"dpi": (xdpi
*2.54, ydpi
*2.45)}
110 # else do not provide dpi information
114 raise ValueError("end marker expected")
115 image
.__init
__(self
, width
, height
, mode
, data
[begin
:end
], compressed
="DCT")
118 class bitmap(base
.PSCmd
):
120 def __init__(self
, xpos
, ypos
, image
,
121 width
=None, height
=None, ratio
=None,
122 storedata
=0, maxstrlen
=4093,
123 compressmode
="Flate",
124 flatecompresslevel
=6,
125 dctquality
=75, dctoptimize
=0, dctprogression
=0):
128 self
.imagewidth
, self
.imageheight
= image
.size
129 self
.storedata
= storedata
130 self
.maxstrlen
= maxstrlen
131 self
.imagedataid
= "imagedata%d" % id(self
)
134 if width
is not None or height
is not None:
137 if self
.width
is None:
139 self
.width
= self
.height
* self
.imagewidth
/ float(self
.imageheight
)
141 self
.width
= ratio
* self
.height
142 elif self
.height
is None:
144 self
.height
= self
.width
* self
.imageheight
/ float(self
.imagewidth
)
146 self
.height
= (1.0/ratio
) * self
.width
147 elif ratio
is not None:
148 raise ValueError("can't specify a ratio when setting width and height")
150 if ratio
is not None:
151 raise ValueError("must specify width or height to set a ratio")
152 widthdpi
, heightdpi
= image
.info
["dpi"] # XXX fails when no dpi information available
153 self
.width
= unit
.inch(self
.imagewidth
/ float(widthdpi
))
154 self
.height
= unit
.inch(self
.imageheight
/ float(heightdpi
))
156 self
.xpos_pt
= unit
.topt(self
.xpos
)
157 self
.ypos_pt
= unit
.topt(self
.ypos
)
158 self
.width_pt
= unit
.topt(self
.width
)
159 self
.height_pt
= unit
.topt(self
.height
)
161 # create decode and colorspace
162 self
.palettedata
= None
163 if image
.mode
== "P":
164 palettemode
, self
.palettedata
= image
.palette
.getdata()
165 self
.decode
= "[0 255]"
166 # palettedata and closing ']' is inserted in outputPS
167 if palettemode
== "L":
168 self
.colorspace
= "[ /Indexed /DeviceGray %i" % (len(self
.palettedata
)/1-1)
169 elif palettemode
== "RGB":
170 self
.colorspace
= "[ /Indexed /DeviceRGB %i" % (len(self
.palettedata
)/3-1)
171 elif palettemode
== "CMYK":
172 self
.colorspace
= "[ /Indexed /DeviceCMYK %i" % (len(self
.palettedata
)/4-1)
174 image
= image
.convert("RGB")
175 self
.decode
= "[0 1 0 1 0 1]"
176 self
.colorspace
= "/DeviceRGB"
177 self
.palettedata
= None
178 sys
.stderr
.write("*** PyX Info: image with unknown palette mode converted to rgb image\n")
179 elif len(image
.mode
) == 1:
180 if image
.mode
!= "L":
181 image
= image
.convert("L")
182 sys
.stderr
.write("*** PyX Info: specific single channel image mode not natively supported, converted to regular grayscale\n")
183 self
.decode
= "[0 1]"
184 self
.colorspace
= "/DeviceGray"
185 elif image
.mode
== "CMYK":
186 self
.decode
= "[0 1 0 1 0 1 0 1]"
187 self
.colorspace
= "/DeviceCMYK"
189 if image
.mode
!= "RGB":
190 image
= image
.convert("RGB")
191 sys
.stderr
.write("*** PyX Info: image with unknown mode converted to rgb\n")
192 self
.decode
= "[0 1 0 1 0 1]"
193 self
.colorspace
= "/DeviceRGB"
196 self
.imagematrix
= str(trafo
.mirror(0)
197 .translated_pt(-self
.xpos_pt
, self
.ypos_pt
+self
.height_pt
)
198 .scaled_pt(self
.imagewidth
/self
.width_pt
, self
.imageheight
/self
.height_pt
))
200 # savely check whether imagedata is compressed or not
202 imagecompressed
= image
.compressed
204 imagecompressed
= None
205 if compressmode
!= None and imagecompressed
!= None:
206 raise ValueError("compression of a compressed image not supported")
207 if not haszlib
and compressmode
== "Flate":
208 sys
.stderr
.write("*** PyX Info: zlib module not available, disable compression")
212 if compressmode
== "Flate":
213 self
.data
= zlib
.compress(image
.tostring(), flatecompresslevel
)
214 elif compressmode
== "DCT":
215 self
.data
= image
.tostring("jpeg", image
.mode
,
216 dctquality
, dctoptimize
, dctprogression
)
218 self
.data
= image
.tostring()
219 self
.singlestring
= self
.storedata
and len(self
.data
) < self
.maxstrlen
223 if self
.singlestring
:
224 self
.datasource
= "/%s load" % self
.imagedataid
226 self
.datasource
= "/imagedataaccess load" # some printers do not allow for inline code here
227 self
.prologs
.append(prolog
.definition("imagedataaccess",
228 "{ /imagedataindex load " # get list index
229 "dup 1 add /imagedataindex exch store " # store increased index
230 "/imagedataid load exch get }")) # select string from array
232 self
.datasource
= "currentfile /ASCII85Decode filter"
233 if compressmode
== "Flate" or imagecompressed
== "Flate":
234 self
.datasource
+= " /FlateDecode filter"
235 elif compressmode
== "DCT" or imagecompressed
== "DCT":
236 self
.datasource
+= " /DCTDecode filter"
238 if compressmode
!= None:
239 raise ValueError("invalid compressmode '%s'" % compressmode
)
240 if imagecompressed
!= None:
241 raise ValueError("invalid compressed image '%s'" % imagecompressed
)
245 # TODO resource data could be written directly on the output stream
246 # after proper code reorganization
247 buffer = cStringIO
.StringIO()
248 if self
.singlestring
:
250 ascii85stream(buffer, self
.data
)
254 datalen
= len(self
.data
)
255 tailpos
= datalen
- datalen
% self
.maxstrlen
256 for i
in xrange(0, tailpos
, self
.maxstrlen
):
258 ascii85stream(buffer, self
.data
[i
: i
+self
.maxstrlen
])
260 if datalen
!= tailpos
:
262 ascii85stream(buffer, self
.data
[tailpos
:])
266 self
.prologs
.append(prolog
.definition(self
.imagedataid
, buffer.getvalue()))
269 return bbox
.bbox_pt(self
.xpos_pt
, self
.ypos_pt
,
270 self
.xpos_pt
+self
.width_pt
, self
.ypos_pt
+self
.height_pt
)
275 def outputPS(self
, file):
277 "%s" % self
.colorspace
)
278 if self
.palettedata
is not None:
279 # insert palette data
281 ascii85stream(file, self
.palettedata
)
283 file.write(" setcolorspace\n")
285 if self
.storedata
and not self
.singlestring
:
286 file.write("/imagedataindex 0 store\n" # not use the stack since interpreters differ in their stack usage
287 "/imagedataid %s store\n" % self
.imagedataid
)
291 "/Width %i\n" # imagewidth
292 "/Height %i\n" # imageheight
293 "/BitsPerComponent 8\n"
294 "/ImageMatrix %s\n" # imagematrix
295 "/Decode %s\n" # decode
296 "/DataSource %s\n" # datasource
298 "image\n" % (self
.imagewidth
, self
.imageheight
,
299 self
.imagematrix
, self
.decode
, self
.datasource
))
300 if not self
.storedata
:
301 ascii85stream(file, self
.data
)
304 file.write("grestore\n")