123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- #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))
|