ReportLab and Python Imaging Library images from memory issue

Question:

I’ve run into an issue I can’t seem to figure out with PIL and reportlab. Specifically, I would like to use drawImage on a canvas in reportlab using a PIL Image object.

In the past I’ve inserted images into reportlab documents from the web using raw data, StringIO and reportlab’s ImageReader class. Unfortunately, ImageReader takes a file name or a file buffer like object.

The ultimate goal is to be able to put QR codes, (which are PIL objects) into the reportlab PDFs. One thing that does work is the following:

    size, qrcode = PyQrcodec.encode('http://www.google.com')
    qrcode.save("img.jpeg")
    self.pdf.drawImage(ImageReader("img.jpeg"), 25, 25, width=125, height=125)
    self.pdf.showPage()

This saves the image and then reads it into the pdf. Obviously doing it like this doesn’t make sense.

My efforts are compounded by the relatively long development history of reportlab which makes finding the answers relevant to the latest version (2.4).

Thanks for the help.

(By the way, I’m using 1.1.6 PIL)

Asked By: philipk

||

Answers:

Looking at the source for ReportLab 2.4, it seems that ImageReader will accept a PIL Image object as “filename”.


def _isPILImage(im):
    try:
        return isinstance(im,Image.Image)
    except ImportError:
        return 0

class ImageReader(object):
    "Wraps up either PIL or Java to get data from bitmaps"
    _cache={}
    def __init__(self, fileName):
        if isinstance(fileName,ImageReader):
            self.__dict__ = fileName.__dict__   #borgize
            return
        #start wih lots of null private fields, to be populated by
        #the relevant engine.
        self.fileName = fileName
        self._image = None
        self._width = None
        self._height = None
        self._transparent = None
        self._data = None
        if _isPILImage(fileName):
            self._image = fileName
            self.fp = getattr(fileName,'fp',None)
            try:
                self.fileName = self._image.fileName
            except AttributeError:
                self.fileName = 'PILIMAGE_%d' % id(self)
Answered By: MattH

Although it does look like it should work, it really doesn’t. I finally was able to track down the problem, and it was in the _isPILImage() function. The problem is that “Image.Image” is actually “from PIL import Image” whereas my object is actually just from Image. I would have assumed they were the same, but in any case isinstance doesn’t evaluate them as the same. My hack solution was to change _isPILImage(fileName): … to

519 def _isPILImage(im):
520     import Image as PIL_Image
521     try:
522         return isinstance(im,Image.Image) or isinstance(im, PIL_Image.Image)
523     except ImportError:
524         return 0

That solves my error. Since you pointed me in the right direction I originally tried to post this as a comment then accept your answer, but it doesn’t allow enough characters.

Thank you for the input! If you can think of a more elegant way to fix this… (I tried to wrap the Image.Image object in a PIL Image object) let me know!

Answered By: philipk

weired
the documentation claims that drawImage and drawInlineImage work the same way, but it works with drawInlineImage out of the box, and do not work in drawImage

Answered By: Ohad Cohen

This is what I did, using plotly, BytesIO and reportlab. It puts an image in the pdf from memory, without having to save it on disk first.

import io
import plotly.graph_objects as go
from reportlab.platypus import SimpleDocTemplate, Image

image_in_memory = io.BytesIO()
figure = go.Figure(data=data)  # as it's not part of the question I'm leaving data out
figure.write_image(image_in_memory, scale=5, width=1000, height=400)
pdf_image = Image(image_in_memory, width=400, height=160)
pdf_document = SimpleDocTemplate(path, title='Title')
pdf_document.multiBuild([pdf_image])
Answered By: smoquet