Search code examples
pythonpython-imaging-library

How to add text next to an image with Pillow or OpenCV?


I am trying to add some text next to and under a QR code.

The problem is that I am struggling on how to edit the image into a QR Code + text. The image below is what I would like to have as a result. The function signature can be changed too.

Target Image

This is the code I have so far

""" 
requirements.txt

qrcode==7.4.2
Pillow==8.1.0
opencv-python==4.7.0.68
"""
import os
from pathlib import Path

import cv2
import qrcode
from PIL import Image, ImageFont, ImageDraw, ImageOps

def create_qr_img() -> str:
    QRcode = qrcode.QRCode(
        error_correction=qrcode.constants.ERROR_CORRECT_H,
        box_size=5,
    )
    url = 'google.com'
    QRcode.add_data(url)
    QRcode.make()
    # adding color to QR code
    QRimg = QRcode.make_image(
        fill_color='Black', back_color="white").convert('RGB')
    # save the QR code generated
    out_fp = f'temp_/QR.png'
    QRimg.save(out_fp)
    return out_fp

def add_str_to_img(img_path: str, 
                               str1: str,
                               str2: str,
                               show:bool=False) -> str:

    black_color_rgb = (0,0,0)
    white_color_rgb = (255,255,255)
    img = Image.open(img_path)
    
    #failed attempt 1)
    # expanding the border works only for writing on top or under the QR code
    # but if the string is too long, it gets cut off
    img = ImageOps.expand(img, border=30, fill=white_color_rgb)

    
    # failed attempt 2)
    # add empty space to the left of the QR code
    # exp_cm = 3
    # exp_px = int(exp_cm * 37.79527559055118)
    # new_shape_pixels = (img.width+exp_px, img.height)
    # img = ImageOps.fit(img, new_shape_pixels, method=Image.ANTIALIAS,
    #              #bleed=0.0, centering=(0.5, 0.5)
    # )
    # end failed attempt 2)
    draw = ImageDraw.Draw(img)
    font_path = os.path.join(cv2.__path__[0],'qt','fonts','DejaVuSans.ttf')
    font = ImageFont.truetype(font_path, size=52)
    # on top of the QR code
    draw.text((62,0),str1,(0,0,0),font=font,
              align='center'
            )
    # bottom
    draw.text((0,470),str2,black_color_rgb,font=font,
              align='center',
              )
    print('QR code TO BE generated!')
    out_fp = f'temp_/QR_print.png'
    Path(out_fp).unlink(missing_ok=True)
    img.save(out_fp)
    if show:
        img.show()
    print('QR code generated!')
    return out_fp

if __name__ == '__main__':
    img_path = create_qr_img()
    add_str_to_img(img_path, 
                   'ExampleAboveQr', 
                   'This is some long string. It could be multi-line. 22222222', 
                   show=True)

I think the solution should be something like with ImageOps.fit but I could not get it work how I wanted (see attempt 2)) in code.


Solution

  • Thanks to the comment from Paul-ET suggesting: Add Text on Image using PIL

    My solution

    I created a white image with create_background_image and then pasted the QR and the text later.

    
    def cm_to_pixels(cm: float) -> int:
        return int(cm * 37.79527559055118)
    
    
    def create_background_image(cm_width: float,
                                cm_height: float,
        ) -> Image.Image:
        w, h = cm_to_pixels(cm=cm_width), cm_to_pixels(cm=cm_height)
        # creating new Image object
        img = Image.new("RGB", (w, h), color='white')
        return img
    
    if __name__ == '__main__':
        img_path = create_qr_img()
        # resize it to make it constant
        qr_img = Image.open(img_path)
        qr_img = qr_img.resize((70, 70))
        # start making the background image
        background_img = create_background_image(cm_width=5, cm_height=2.9)
        # paste qr
        background_img.paste(qr_img, (2, 0), qr_img.convert('RGBA'))
        d = ImageDraw.Draw(background_img)
        widht_and_height_pixels=(5, 68)
        font_path = os.path.join(cv2.__path__[0],'qt','fonts','DejaVuSans.ttf')
        font = ImageFont.truetype(font_path, size=15)
            
        d.text(widht_and_height_pixels,
                "123123345345",fill='black',
                font=font
        )
        background_img.show()