Tadeck
Tadeck

Reputation: 137390

Including base64-encoded image in ReportLab-generated PDF

I am trying to decode base64-encoded image and put it into PDF I generate using ReportLab. I currently do it like that (image_data is base64-encoded image, story is already a ReportLab's story):

# There is some "story" I append every element
img_height = 1.5 * inch  # max image height
img_file = tempfile.NamedTemporaryFile(mode='wb', suffix='.png')
img_file.seek(0)
img_file.write(image_data.decode('base64'))
img_file.seek(0)
img_size = ImageReader(img_file.name).getSize()
img_ratio = img_size[0] / float(img_size[1])
img = Image(img_file.name,
    width=img_ratio * img_height,
    height=img_height,
)
story.append(img)

and it works (although still looks ugly to me). I thought about getting rid of the temporary file (shouldn't file-like object do the trick?).

To get rid of the temporary file I tried to use StringIO module, to create file-like object and pass it instead of file name:

# There is some "story" I append every element
img_height = 1.5 * inch  # max image height
img_file = StringIO.StringIO()
img_file.seek(0)
img_file.write(image_data.decode('base64'))
img_file.seek(0)
img_size = ImageReader(img_file).getSize()
img_ratio = img_size[0] / float(img_size[1])
img = Image(img_file,
    width=img_ratio * img_height,
    height=img_height,
)
story.append(img)

But this gives me IOError with the following message: "cannot identify image file".

I know ReportLab uses PIL to read images different than jpg, but is there any way I can avoid creating named temporary files and do this only with file-like objects, without writing files to the disk?

Upvotes: 6

Views: 4446

Answers (4)

ASSILI Taher
ASSILI Taher

Reputation: 1349

This solution works for me. I'm using Flask with Google App Engine.

from reportlab.platypus import Image
from reportlab.lib.units import mm
import cStringIO
from base64 import b64decode

story=[]
encoded_image = "...."
decoded_img = b64decode(encoded_image)
img_string = cStringIO.StringIO(decoded_img)
img_string.seek(0)
im = Image(img_string, 180*mm, 100*mm, kind='bound')
story.append(im)

I have received the image from the client and saved in the database:

from base64 import b64decode
image = request.files['image'].read()
encoded_image = b64encode(image)

Upvotes: 0

okm
okm

Reputation: 23871

You should wrap StringIO() by PIL.Image.open, so simply img_size = ImageReader(PIL.Image.open(img_file)).getSize(). Its actually a thin wrapper around Image.size, as Tommaso's answer suggests. Also, there is actually no need to calculate the desc size on your own, bound mode of reportlab.Image could do it for you:

img_height = 1.5 * inch  # max image height
img_file = StringIO.StringIO(image_data.decode('base64'))
img_file.seek(0)
img = Image(PIL.Image.open(img_file),
            width=float('inf'),
            height=img_height,
            kind='bound')
)
story.append(img)

Upvotes: 2

Steven Lowenthal
Steven Lowenthal

Reputation: 656

This code is working for me without PIL, as the image is already a JPEG: raw just pulls the base64 string out of a dictionary. I just wrap the decoded "string" in a StringIO.

        raw = element['photographs'][0]['jpeg']
        photo = base64.b64decode(raw)
        c.drawImage(ImageReader(StringIO.StringIO(photo)), 0.5*inch, self.y, height = self.PHOTOHEIGHT, preserveAspectRatio = True)

Upvotes: 0

Tommaso Barbugli
Tommaso Barbugli

Reputation: 12031

I am not familiar with ReportLab but if you can use PIL directly this will work:

...
img = Image.open(img_file)
width, height = img.size
...

you can have a look here for PIL Image class references

Upvotes: 0

Related Questions