python-3.xpdfreportlabplatypus

Creating a 4 x 5 grid of images within Report Lab - Python


I'm attempting to use ReportLab to create a 4 x 5 grid of images (per page) from a directory of images, in a similar manner to photographic contact sheets. I need to maintain the aspect ratio of the images and also need the filename of each image underneath.

I originally started by using drawimage and adding everything manually but now I think a table and adding the image and filename into each cell might be a better approach. Can anyone give me some pointers on the best way to do this?

I've spent a few days trying to figure this out and I think I am going around in circles with this. Thank you in advance!

Script as of now;

from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
from reportlab.lib.pagesizes import A4

from PIL import Image

import os

def rowGen(lst, n):

    for i in range(0, len(lst), n):
        yield lst[i:i + n]


def makePDF(document_title, data):

    pdf = SimpleDocTemplate(
    document_title,
    pagesize = A4
)

    style = TableStyle([
        ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
        ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
        ('BOTTOMPADDING', (0, 0), (-1, -1), 12),
        
    ])

    table = Table(data) #Creates and adds the data to the table
    table.setStyle(style) #Sets style to the above defined style

    elements = [] #Creates an empty elements list
    elements.append(table) #Lays out the elements

    pdf.build(elements)


path = 'Images/'
image_list = [i for i in os.listdir(path) if i.endswith('.jpg')] #list of images in selected directory
data = [i for i in rowGen(image_list, 4)]

makePDF('Test.pdf', data)

Solution

  • So, after a few days of working through some ideas I came up with the below. I am fairly new to this and I'm sure there are better approaches but if it helps anybody:

        from reportlab.pdfgen import canvas
        from reportlab.lib.pagesizes import A4
        from reportlab.lib.units import mm
        from reportlab.lib.utils import ImageReader
        
        from PIL import Image
        
        import os
        
        path = 'path/to/my/images/'
        filename = 'test.pdf'
        title = 'Test'
        
        
        def createPDF(path_to_images, document_name, document_title):
        
           def rowGen(list_of_images): #Creates a list of 4 image rows
        
              for i in range(0, len(list_of_images), 4):
                 yield list_of_images[i:i + 4]
        
        
           def renderRow(path_to_images, row, y_pos): #Renders each row to the page
        
              x_pos = 5 #starting x position
              thumb_size = 180, 180 #Thumbnail image size
        
              for i in row:
        
                 image_filename = i #Saves the filename for use in the draw string operation below
                 img = Image.open(os.path.join(path_to_images, i)) #Opens image as a PIL object
                 img.thumbnail(thumb_size) #Creates thumbnail 
        
                 img = ImageReader(img) #Passes PIL object to the Reportlab ImageReader
        
                 #Lays out the image and filename
                 pdf.drawImage(img, x_pos * mm , y_pos * mm, width = 125, height = 125, preserveAspectRatio=True, anchor='c')
                 pdf.drawCentredString((x_pos + 22) * mm, (y_pos - 7) * mm, image_filename)
        
                 x_pos += 51 #Increments the x position ready for the next image
        
        
           images = [i for i in os.listdir(path_to_images) if i.endswith('.jpg')] #Creates list of images filtering out non .jpgs
           row_layout = list(rowGen(images)) #Creates the layout of image rows
           
           pdf = canvas.Canvas(document_name, pagesize=A4, pageCompression=1)
           pdf.setTitle(document_title)
           
           rows_rendered = 0
        
           y_pos = 250 #Sets starting y pos
           pdf.setFont('Helvetica', 10)
        
           for row in row_layout: #Loops through each row in the row_layout list and renders the row. For each 5 rows, makes a new page
        
              if rows_rendered == 5:
              
                 pdf.showPage()
                 pdf.setFont('Helvetica', 10)
                 y_pos = 250
                 rows_rendered = 0
        
              else:
                 
                 renderRow(path_to_images, row, y_pos)
                 y_pos -= 60
                 rows_rendered += 1
        
           pdf.save()
        
        createPDF(path, filename, title)