underscore
underscore

Reputation: 59

How to add text in a "textbox" to an image?

I'm working on a tool to add text to specific regions of an image, like inside a speech bubble. I'm generating a bounding box and would like to restrict the text to only be inside of this bounding box, but so far looking into the cv2 and PIL libraries they only seem to take a starting point for the text, not a bounding box.

cv2:

import cv2

myimg = "471.jpg"

img = cv2.imread(myimg)

f_face = cv2.FONT_HERSHEY_PLAIN
f_size = 1
f_color = (255,255,255)
f_thickness = 2
text = 'Hello World'
x,y = 400,800
img = cv2.putText(img, text, (x,y), f_face, f_size, f_color, f_thickness)

cv2.imshow("Test", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

PIL:

from PIL import Image
from PIL import ImageDraw

myimg = "471.jpg"

img = Image.open(myimg)

id = ImageDraw.Draw(img)

id.text((400, 800), "Hello World", fill=(255, 255, 255))

img.show()

Ideally, I'd be looking for a library that could give me the same amount of control over the text as you'd have when using Photoshop, meaning: font size, font face, center (to bounding box/text box) vertically and center (to bounding box/text box) horizontally as well as auto-resizing either the font size or bounding box to fit the text to it is not cut off (or at the very least give some kind of warning that the text has overflowed the textbox so that I can make a process that reduces the font size and tries again).

Are there other libraries out there that have this kind of control? Or are there ways of doing this inside of cv2 or PIL?

Thank you for your help

Upvotes: 3

Views: 9844

Answers (1)

furas
furas

Reputation: 142711

Pillow has ImageFont to select font face, font size which you can use in text(..., font=...).

In newer version you can use text((center_x, center_y), anchor='mm') to center text in region. See more: Text Anchor

Problem makes only to auto-resize font.

You have to use ImageFont.FreeTypeFont.getbbox() to calculate region's size used by text with selected font size. If you use for-loop to calculate region's size for different font size then you can auto-resize it.


import PIL
print('PIL version:', PIL.__version__) 

from PIL import Image, ImageDraw, ImageFont

# create empty image
img = Image.new(size=(400, 300), mode='RGB')
draw = ImageDraw.Draw(img)

# draw white rectangle 200x100 with center in 200,150
draw.rectangle((200-100, 150-50, 200+100, 150+50), fill='white')
draw.line(((0, 150), (400, 150)), 'gray')
draw.line(((200, 0), (200, 300)), 'gray')

# find font size for text `"Hello World"` to fit in rectangle 200x100
selected_size = 1
for size in range(1, 150):
    arial = ImageFont.FreeTypeFont('/home/furas/.wine/drive_c/windows/Fonts/arial.ttf', size=size)
    w, h = arial.getsize("Hello World")  # older versions
    #left, top, right, bottom = arial.getbbox("Hello World")  # needs PIL 8.0.0
    #w = right - left
    #h = bottom - top
    print(w, h)
    
    if w > 200 or h > 100:
        break
        
    selected_size = size

    print(arial.size)
    
# draw text in center of rectangle 200x100        
arial = ImageFont.FreeTypeFont('/home/furas/.wine/drive_c/windows/Fonts/arial.ttf', size=selected_size)

#draw.text((200-w//2, 150-h//2), "Hello World", fill='black', font=arial)  # older versions
#img.save('center-older.png')

draw.text((200, 150), "Hello World", fill='black', anchor='mm', font=arial)
img.save('center-newer.png')

img.show()

Result with (200-w//2, 150-h//2) and getbbox()

enter image description here

Result with (200-w//2, 150-h//2) and getsize()

enter image description here

Result with anchor='mm' (with getbbox() or getsize())

enter image description here

Upvotes: 5

Related Questions