220 lines
8.4 KiB
Python
220 lines
8.4 KiB
Python
#Copyright ReportLab Europe Ltd. 2000-2017
|
|
#see license.txt for license details
|
|
#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/pdfgen/pdfimages.py
|
|
__version__='3.3.0'
|
|
__doc__="""
|
|
Image functionality sliced out of canvas.py for generalization
|
|
"""
|
|
|
|
import os
|
|
import reportlab
|
|
from reportlab import rl_config
|
|
from reportlab.pdfbase import pdfutils
|
|
from reportlab.pdfbase import pdfdoc
|
|
from reportlab.lib.utils import import_zlib, haveImages, getBytesIO, isStr
|
|
from reportlab.lib.rl_accel import fp_str, asciiBase85Encode
|
|
from reportlab.lib.boxstuff import aspectRatioFix
|
|
|
|
|
|
class PDFImage:
|
|
"""Wrapper around different "image sources". You can make images
|
|
from a PIL Image object, a filename (in which case it uses PIL),
|
|
an image we previously cached (optimisation, hardly used these
|
|
days) or a JPEG (which PDF supports natively)."""
|
|
|
|
def __init__(self, image, x,y, width=None, height=None, caching=0):
|
|
self.image = image
|
|
self.x = x
|
|
self.y = y
|
|
self.width = width
|
|
self.height = height
|
|
self.filename = None
|
|
self.imageCaching = caching
|
|
# the following facts need to be determined,
|
|
# whatever the source. Declare what they are
|
|
# here for clarity.
|
|
self.colorSpace = 'DeviceRGB'
|
|
self.bitsPerComponent = 8
|
|
self.filters = []
|
|
self.source = None # JPEG or PIL, set later
|
|
self.getImageData()
|
|
|
|
def jpg_imagedata(self):
|
|
#directly process JPEG files
|
|
#open file, needs some error handling!!
|
|
fp = open(self.image, 'rb')
|
|
try:
|
|
result = self._jpg_imagedata(fp)
|
|
finally:
|
|
fp.close()
|
|
return result
|
|
|
|
def _jpg_imagedata(self,imageFile):
|
|
info = pdfutils.readJPEGInfo(imageFile)
|
|
self.source = 'JPEG'
|
|
imgwidth, imgheight = info[0], info[1]
|
|
if info[2] == 1:
|
|
colorSpace = 'DeviceGray'
|
|
elif info[2] == 3:
|
|
colorSpace = 'DeviceRGB'
|
|
else: #maybe should generate an error, is this right for CMYK?
|
|
colorSpace = 'DeviceCMYK'
|
|
imageFile.seek(0) #reset file pointer
|
|
imagedata = []
|
|
#imagedata.append('BI /Width %d /Height /BitsPerComponent 8 /ColorSpace /%s /Filter [/Filter [ /ASCII85Decode /DCTDecode] ID' % (info[0], info[1], colorSpace))
|
|
imagedata.append('BI /W %d /H %d /BPC 8 /CS /%s /F [%s/DCT] ID' % (imgwidth, imgheight, colorSpace, rl_config.useA85 and '/A85 ' or ''))
|
|
#write in blocks of (??) 60 characters per line to a list
|
|
data = imageFile.read()
|
|
if rl_config.useA85:
|
|
data = asciiBase85Encode(data)
|
|
pdfutils._chunker(data,imagedata)
|
|
imagedata.append('EI')
|
|
return (imagedata, imgwidth, imgheight)
|
|
|
|
def cache_imagedata(self):
|
|
image = self.image
|
|
if not pdfutils.cachedImageExists(image):
|
|
zlib = import_zlib()
|
|
if not zlib: return
|
|
if not haveImages: return
|
|
pdfutils.cacheImageFile(image)
|
|
|
|
#now we have one cached, slurp it in
|
|
cachedname = os.path.splitext(image)[0] + (rl_config.useA85 and '.a85' or '.bin')
|
|
imagedata = open(cachedname,'rb').readlines()
|
|
#trim off newlines...
|
|
imagedata = list(map(str.strip, imagedata))
|
|
return imagedata
|
|
|
|
def PIL_imagedata(self):
|
|
image = self.image
|
|
if image.format=='JPEG':
|
|
fp=image.fp
|
|
fp.seek(0)
|
|
return self._jpg_imagedata(fp)
|
|
self.source = 'PIL'
|
|
zlib = import_zlib()
|
|
if not zlib: return
|
|
|
|
bpc = 8
|
|
# Use the colorSpace in the image
|
|
if image.mode == 'CMYK':
|
|
myimage = image
|
|
colorSpace = 'DeviceCMYK'
|
|
bpp = 4
|
|
elif image.mode == '1':
|
|
myimage = image
|
|
colorSpace = 'DeviceGray'
|
|
bpp = 1
|
|
bpc = 1
|
|
elif image.mode == 'L':
|
|
myimage = image
|
|
colorSpace = 'DeviceGray'
|
|
bpp = 1
|
|
else:
|
|
myimage = image.convert('RGB')
|
|
colorSpace = 'RGB'
|
|
bpp = 3
|
|
imgwidth, imgheight = myimage.size
|
|
|
|
# this describes what is in the image itself
|
|
# *NB* according to the spec you can only use the short form in inline images
|
|
imagedata=['BI /W %d /H %d /BPC %d /CS /%s /F [%s/Fl] ID' % (imgwidth, imgheight, bpc, colorSpace, rl_config.useA85 and '/A85 ' or '')]
|
|
|
|
#use a flate filter and, optionally, Ascii Base 85 to compress
|
|
raw = (myimage.tobytes if hasattr(myimage,'tobytes') else myimage.tostring)()
|
|
rowstride = (imgwidth*bpc*bpp+7)>>3
|
|
assert len(raw) == rowstride*imgheight, "Wrong amount of data for image"
|
|
data = zlib.compress(raw) #this bit is very fast...
|
|
if rl_config.useA85:
|
|
data = asciiBase85Encode(data) #...sadly this may not be
|
|
#append in blocks of 60 characters
|
|
pdfutils._chunker(data,imagedata)
|
|
imagedata.append('EI')
|
|
return (imagedata, imgwidth, imgheight)
|
|
|
|
def non_jpg_imagedata(self,image):
|
|
if not self.imageCaching:
|
|
imagedata = pdfutils.cacheImageFile(image,returnInMemory=1)
|
|
else:
|
|
imagedata = self.cache_imagedata()
|
|
words = imagedata[1].split()
|
|
imgwidth = int(words[1])
|
|
imgheight = int(words[3])
|
|
return imagedata, imgwidth, imgheight
|
|
|
|
def getImageData(self,preserveAspectRatio=False):
|
|
"Gets data, height, width - whatever type of image"
|
|
image = self.image
|
|
|
|
if isStr(image):
|
|
self.filename = image
|
|
if os.path.splitext(image)[1] in ['.jpg', '.JPG', '.jpeg', '.JPEG']:
|
|
try:
|
|
imagedata, imgwidth, imgheight = self.jpg_imagedata()
|
|
except:
|
|
imagedata, imgwidth, imgheight = self.non_jpg_imagedata(image) #try for normal kind of image
|
|
else:
|
|
imagedata, imgwidth, imgheight = self.non_jpg_imagedata(image)
|
|
else:
|
|
import sys
|
|
if sys.platform[0:4] == 'java':
|
|
#jython, PIL not available
|
|
imagedata, imgwidth, imgheight = self.JAVA_imagedata()
|
|
else:
|
|
imagedata, imgwidth, imgheight = self.PIL_imagedata()
|
|
self.imageData = imagedata
|
|
self.imgwidth = imgwidth
|
|
self.imgheight = imgheight
|
|
self.width = self.width or imgwidth
|
|
self.height = self.height or imgheight
|
|
|
|
def drawInlineImage(self, canvas, preserveAspectRatio=False,anchor='sw', anchorAtXY=False, showBoundary=False):
|
|
"""Draw an Image into the specified rectangle. If width and
|
|
height are omitted, they are calculated from the image size.
|
|
Also allow file names as well as images. This allows a
|
|
caching mechanism"""
|
|
width = self.width
|
|
height = self.height
|
|
if width<1e-6 or height<1e-6: return False
|
|
x,y,self.width,self.height, scaled = aspectRatioFix(preserveAspectRatio,anchor,self.x,self.y,width,height,self.imgwidth,self.imgheight,anchorAtXY)
|
|
# this says where and how big to draw it
|
|
if not canvas.bottomup: y = y+height
|
|
canvas._code.append('q %s 0 0 %s cm' % (fp_str(self.width), fp_str(self.height, x, y)))
|
|
# self._code.extend(imagedata) if >=python-1.5.2
|
|
for line in self.imageData:
|
|
canvas._code.append(line)
|
|
canvas._code.append('Q')
|
|
if showBoundary:
|
|
canvas.drawBoundary(showBoundary,x,y,width,height)
|
|
return True
|
|
|
|
def format(self, document):
|
|
"""Allow it to be used within pdfdoc framework. This only
|
|
defines how it is stored, not how it is drawn later."""
|
|
|
|
dict = pdfdoc.PDFDictionary()
|
|
dict['Type'] = '/XObject'
|
|
dict['Subtype'] = '/Image'
|
|
dict['Width'] = self.width
|
|
dict['Height'] = self.height
|
|
dict['BitsPerComponent'] = 8
|
|
dict['ColorSpace'] = pdfdoc.PDFName(self.colorSpace)
|
|
content = '\n'.join(self.imageData[3:-1]) + '\n'
|
|
strm = pdfdoc.PDFStream(dictionary=dict, content=content)
|
|
return strm.format(document)
|
|
|
|
if __name__=='__main__':
|
|
srcfile = os.path.join(
|
|
os.path.dirname(reportlab.__file__),
|
|
'test',
|
|
'pythonpowered.gif'
|
|
)
|
|
assert os.path.isfile(srcfile), 'image not found'
|
|
pdfdoc.LongFormat = 1
|
|
img = PDFImage(srcfile, 100, 100)
|
|
import pprint
|
|
doc = pdfdoc.PDFDocument()
|
|
print('source=',img.source)
|
|
print(img.format(doc))
|