26

I'm trying to insert a barcode image into Reportlab. I know there are a lot of questions asked on this, but all of them assume that you already have the image file in the directory or on the filesystem.

Due to the fact that Reportlab has issues with EAN13 barcodes, I decided to use another package called pyBarcode to generate the image for me.

Initially I saved the image in a StringIO instance and passed it directly to reportlab.platypus.flowables.Image but that didn't seem to work. Then I read the documentation:

Formats supported by PIL/Java 1.4 (the Python/Java Imaging Library) are supported.

Does this mean that if I pass a PIL image, this should work? I got an exception when I tried the following:

>>> import PIL
>>> from reportlab.platypus.flowables import Image
>>> fp = StringIO(the_barcode.getvalue())
>>> barcode_image = PIL.Image.open(fp)
>>> doc = SimpleDocTemplate('barcode.pdf')
>>> story = [Image(barcode_image)]
>>> Traceback (most recent call last):
  File "create.py", line 57, in <module>
    main()
  File "create.py", line 24, in main
    save_pdf(fp, STYLE, ART, COLOR, SIZE)
  File "create.py", line 28, in save_pdf
    fp = StringIO(fp.getvalue())
  File "/home/mark/.virtualenvs/barcode/local/lib/python2.7/site-packages/reportlab-2.6-py2.7-linux-i686.egg/reportlab/platypus/flowables.py", line 402, in __init__
    if not fp and os.path.splitext(filename)[1] in ['.jpg', '.JPG', '.jpeg', '.JPEG']:
  File "/home/mark/.virtualenvs/barcode/lib/python2.7/posixpath.py", line 95, in splitext
    return genericpath._splitext(p, sep, altsep, extsep)
  File "/home/mark/.virtualenvs/barcode/lib/python2.7/genericpath.py", line 91, in _splitext
    sepIndex = p.rfind(sep)
  File "/home/mark/.virtualenvs/barcode/local/lib/python2.7/site-packages/PIL/Image.py", line 512, in __getattr__
    raise AttributeError(name)
AttributeError: rfind

Somehow PIL Image doesn't seem to work either. What should I pass as the first argument to Reportlab's Image function if I don't have the filename of the image (because my images are created in memory)?

Mark
  • 2,137
  • 4
  • 27
  • 42

4 Answers4

15

I had no luck with the proposed methods.

Checking the code in pdfdoc.py shows, that the AttributError results from treating the StringIO as a filename:

    if source is None:
        pass # use the canned one.
    elif hasattr(source,'jpeg_fh'):
        self.loadImageFromSRC(source)   #it is already a PIL Image
    else:
        # it is a filename

Further checking the source, shows that jpeg_fh is an attribute of class ImageReader in reportlab.lib.utils. ImageReader accepts both StringIO and PIL images.

So wrapping the StringIO in an ImageReader solved the problem for me:

import PIL
from reportlab.lib.utils import ImageReader

io_img = StringIO(data)
pil_img = PIL.Image.open(StringIO(data))

reportlab_io_img = ImageReader(io_img)
reportlab_pil_img = ImageReader(pil_img)

canvas.drawImage(reportlab_io_img, ...)
canvas.drawImage(reportlab_pil_img, ...)
wolfmanx
  • 481
  • 5
  • 12
11

The repetitive declaration "Formats supported by PIL/Java 1.4 (the Python/Java Imaging Library) are supported" simply means that data formats supported by PIL are supported by reportlab (since it uses PIL to read them).

Now, from peeking in reportlab.platypus.flowables.Image code it is possible to see that it accepts either a filename or a file object as input. The former is not what you want, so let us focus on the later. You said StringIO didn't seem to work, but it does if you take some care. You probably did something wrong with it, here are two correct ways to use StringIO:

import sys
import PIL
from cStringIO import StringIO
from reportlab.platypus.flowables import Image

# Method 1
data = open(sys.argv[1]).read()
img1 = StringIO(data)

# Method 2
img2 = StringIO()
PIL.Image.open(sys.argv[2]).save(img2, 'PNG')
img2.seek(0)

# Method 3 (fails)
img3 = StringIO(PIL.Image.open(sys.argv[2]).tostring())

story = [Image(img1), Image(img2)]
#Image(img3)

The method 3 fails because img3 now holds the raw data of the image, so it has no idea about the actual format of this data. There is no reason to attempt to use this method for such task.

If you have raw data and you know the image mode of your data ('L', 'RGB', etc) and also its width, height, then you can use a fourth (correct) method based on PIL.Image.fromstring(...).save(mystrio, 'someformat').

mmgp
  • 18,901
  • 3
  • 53
  • 80
  • 1
    In Python 3, use 'from io import BytesIO' instead of 'from cStringIO import StringIO'. And 'img2 = BytesIO()' instead of 'img2 = StringIO()' – Rui Martins Mar 03 '18 at 12:26
  • 1
    `Image()` can also take [`response.raw`](http://docs.python-requests.org/en/master/api/#requests.Response.raw), the "File-like object representation of response" from `requests.get()`. [Reportlab User Guide](https://www.reportlab.com/documentation/) now admits that the Image() filename parameter can be a "file like object". So, phew, it's not an undocumented feature. – Bob Stein Apr 25 '18 at 15:24
  • `Image(requests.get("http://example.com/foo.png", stream=True).raw, w, h)` for example. – Bob Stein Apr 25 '18 at 15:29
1

I found two different patterns useful depending on whether I'm drawing on the canvas or building flowable elements for multiBuild. I am using matplotlib figures saved to a BytesIO buffer, but I assume the same buffer will serve your barcode needs.

For both:

import io

For the canvas:

from reportlab.lib.units import inch
from reportlab.lib.utils import ImageReader

# assume a proper Canvas object is instantiated as c

buf = io.BytesIO(the_barcode.getvalue())
buf.seek(0)
c.drawImage(ImageReader(buf), 0.5*inch, 5.5*inch, )

For a flowable element for a document:

from reportlab.platypus.flowables import Image

# assume story is a list of flowable elements for multiBuild

buf = io.BytesIO(the_barcode.getvalue())
buf.seek(0)
story.append(Image(buf))

After much trial and error, these worked for me.

mightypile
  • 7,589
  • 3
  • 37
  • 42
0

I believe that what the PIL docs mean to say is that it is using PIL internally to process the image data.

From what I see in the source code, you can pass a file object directly, so, something with a read() method:

https://github.com/ejucovy/reportlab/blob/master/src/reportlab/platypus/flowables.py#L314

I guess you can somehow wrap the raw image data in a file-like object (StringIO or such).

EDIT: I guess that's what you were doing before, sorry. Anyway, it seems to be the correct way. Maybe if you tell us what is the problem in that case, we'll be able to sort it out.

ubik
  • 4,440
  • 2
  • 23
  • 29