Search code examples
python-3.xpdfreportlab

Centering a rotated image using Reportlab


I'm trying to center a rotated image on Reportlab, but I'm having issues using the correct calculation for the placement.

Here's the current code:

from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
from PIL import Image as PILImage

import requests
import math


def main(rotation):
    # create a new PDF with Reportlab
    a4 = (595.275590551181, 841.8897637795275)
    c = canvas.Canvas('output.pdf', pagesize=a4)
    c.saveState()

    # loading the image:
    img = requests.get('https://i.sstatic.net/dI5Rj.png', stream=True)

    img = PILImage.open(img.raw)
    width, height = img.size

    # We calculate the bouding box of a rotated rectangle
    angle_radians = rotation * (math.pi / 180)
    bounding_height = abs(width * math.sin(angle_radians)) + abs(height * math.cos(angle_radians))
    bounding_width = abs(width * math.cos(angle_radians)) + abs(height * math.sin(angle_radians))

    a4_pixels = [x * (100 / 75) for x in a4]

    offset_x = (a4_pixels[0] / 2) - (bounding_width / 2)
    offset_y = (a4_pixels[1] / 2) - (bounding_height / 2)
    c.translate(offset_x, offset_y)

    c.rotate(rotation)
    c.drawImage(ImageReader(img), 0, 0, width, height, 'auto')

    c.restoreState()
    c.save()


if __name__ == '__main__':
    main(45)

So far, here's what I did:

  1. Calculating the boundaries of a rotated rectangle (since it will be bigger)
  2. Using these to calculate the position of the center of the image (size / 2 - image / 2) for width and height.

Two issues appear that I can't explain:

  1. The "a4" variable is in points, everything else is in pixels. If I change them to pixels for calculating the position (which is logical, using a4_pixels = [x * (100 / 75) for x in a4]). The placement is incorrect for a rotation of 0 degree. If I keep the a4 in points, it works ... ?
  2. If I change the rotation, it breaks even more.

So my final question: How can I calculate the offset_x and offset_y values to ensure it's always centered regardless of the rotation?


Solution

  • When you translate the canvas, you are literally moving the origin (0,0) point and all draw operations will be relative to that.

    So in the code below, I moved the origin to the middle of the page. Then I rotated the "page" and drew the image on the "page". No need to rotate the image since its canvas axes have rotated.

    from reportlab.pdfgen import canvas
    from reportlab.lib.utils import ImageReader
    from reportlab.lib.pagesizes import A4
    from PIL import Image as PILImage
    import requests
    
    def main(rotation):
        c = canvas.Canvas('output.pdf', pagesize=A4)
        c.saveState()
    
        # loading the image:
        img = requests.get('https://i.sstatic.net/dI5Rj.png', stream=True)
        img = PILImage.open(img.raw)
        # The image dimensions in cm
        width, height = img.size
    
        # now move the canvas origin to the middle of the page
        c.translate(A4[0] / 2, A4[1] / 2)
        # and rotate it
        c.rotate(rotation)
        # now draw the image relative to the origin
        c.drawImage(ImageReader(img), -width/2, -height/2, width, height, 'auto')
    
        c.restoreState()
        c.save()
    
    if __name__ == '__main__':
        main(45)