9

I use the Pillow (PIL) 6.0 and add text in the image. And I want to put the text in the center of the image. Here is my code,

import os
import string
from PIL import Image
from PIL import ImageFont, ImageDraw, ImageOps

width, height = 100, 100

text = 'H'
font_size = 100

os.makedirs('./{}'.format(text), exist_ok=True)

img = Image.new("L", (width, height), color=0)   # "L": (8-bit pixels, black and white)
font = ImageFont.truetype("arial.ttf", font_size)
draw = ImageDraw.Draw(img)
w, h = draw.textsize(text, font=font)
draw.text(((width-w)/2, (height-h)/2), text=text, fill='white', font=font)

img.save('H.png')

Here is the output:

enter image description here

Question:

The text is in the center horizontally, but not in the center vertically. How can I put it in the center horizontally and vertically?

qinlong
  • 731
  • 8
  • 19
  • Have you compared `h` to the measured height of your "H"? It can look like you're seeing a line-height issue... – thebjorn Apr 20 '19 at 13:36
  • Does this answer your question? [Center-/middle-align text with PIL?](https://stackoverflow.com/questions/1970807/center-middle-align-text-with-pil) – bfontaine Jan 06 '23 at 09:57

2 Answers2

17

Text always have some added space around characters, e.g. if we create a box that is the exact size reported for your 'H'

img = Image.new("L", (width, height), color=0)   # "L": (8-bit pixels, black and white)
font = ImageFont.truetype("arial.ttf", font_size)
draw = ImageDraw.Draw(img)
w, h = draw.textsize(text, font=font)
# draw.text(((width-w)/2, (height-h)/2), text=text, fill='white', font=font)
# img.save('H.png')
img2 = Image.new("L", (w, h), color=0)   # "L": (8-bit pixels, black and white)
draw2 = ImageDraw.Draw(img2)
draw2.text((0, 0)), text=text, fill='white', font=font)
img2.save('H.png')

gives the bounding box:

enter image description here

Knowing that line height is normally ~20% larger than the glyphs/characters (+ some trial and error), and we can figure out the extent of the extra space. (The extra space for width is equally distributed so not interesting for centering).

draw2.text((0, 0 - int(h*0.21)), text=text, fill='white', font=font)

which moves the 'H' to the top:

enter image description here

Plugging this back into your original code:

img = Image.new("L", (width, height), color=0)   # "L": (8-bit pixels, black and white)
font = ImageFont.truetype("arial.ttf", font_size)
draw = ImageDraw.Draw(img)
w, h = draw.textsize(text, font=font)
h += int(h*0.21)
draw.text(((width-w)/2, (height-h)/2), text=text, fill='white', font=font)
img.save('H.png')

gives:

enter image description here

The 0.21 factor usually works well for a large range of font sizes for the same font. E.g. just plugging in font size 30:

enter image description here

thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • will it handle non english letters as well ? – Akhil Nadh PC Jun 25 '20 at 13:33
  • 1
    The process certainly does (remember the trial and error part), e.g. for an `Å` the `h += int(h*0.21)` needed to be `h += int(n*0.1)` because the ring make the letter taller than `H` (I'm a litte surprised that you haven't just tried this out yourself though, all the code is there...) – thebjorn Jun 25 '20 at 17:35
1

Use of anchors can help with this

import os
import string
from PIL import Image
from PIL import ImageFont, ImageDraw, ImageOps

width, height = 100, 100

text = 'H'
font_size = 100

os.makedirs('./{}'.format(text), exist_ok=True)

img = Image.new("L", (width, height), color=0)   # "L": (8-bit pixels, black and white)
font = ImageFont.truetype("arial.ttf", font_size)
draw = ImageDraw.Draw(img)
draw.text(((width)/2, (height)/2), text=text, fill='white', font=font, anchor="mm", align='center')

img.save('H.png')

It works fine without w and h
P.S.: I've tested it, and it can work well with non-English characters also