Draw underline text with PIL - python

There is a post related to bold/italic:
Draw bold/italic text with PIL?
However, how to draw underline text with PIL?

Looks like there is no standard way of doing this, but you always can implement it.
Possible solution:
import Image
import ImageDraw
import ImageFont
def draw_underlined_text(draw, pos, text, font, **options):
twidth, theight = draw.textsize(text, font=font)
lx, ly = pos[0], pos[1] + theight
draw.text(pos, text, font=font, **options)
draw.line((lx, ly, lx + twidth, ly), **options)
im = Image.new('RGB', (400, 400), (255,)*3)
draw = ImageDraw.Draw(im)
font = ImageFont.truetype("arial.ttf", 50)
draw_underlined_text(draw, (50, 150), 'Hello PIL!', font, fill=0)
draw_underlined_text(draw, (50, 300), 'Test', font, fill=128)
im.show()

Related

Create a PNG and make it transparent

I'm trying to dynamically create a PNG (code is working) and make it transparent (code is not working on the color white).
I can create the PNG, but making it transparent isn't working.
Code:
from PIL import Image, ImageDraw, ImageFont
import os
def text_on_img(filename='01.png', text="Hello", size=12):
"Draw a text on an Image, saves it, show it"
fnt = ImageFont.truetype('arial.ttf', 52)
# create image
image = Image.new(mode="RGB", size=(150, 75), color="white")
draw = ImageDraw.Draw(image)
# draw text
draw.text((10, 10), text, font=fnt, fill=(0, 0, 0))
newData = []
newData.append((255, 255, 255, 0))
image.save(filename)
Here's a minimal example that creates the image in RGBA mode and sets the background to be transparent.
from PIL import Image, ImageDraw, ImageFont
fnt = ImageFont.truetype("arial.ttf", 52)
img = Image.new(mode="RGBA", size=(150, 75), color=(255, 255, 255, 127))
draw = ImageDraw.Draw(img)
draw.text((10, 10), "Hello", font=fnt, fill=(0, 0, 0))
img.save("test.png", "PNG")
This creates the following image:
Changing the alpha to 127 (50%) results in the following image being created:

Draw text with background color

I want to know how can I draw text like this check image
As you can see text is on a green image and text has pink color background
My code, this is part of my code I'm using PIL
draw = ImageDraw.Draw(background)
font = ImageFont.truetype("assets/font2.ttf", 40)
font2 = ImageFont.truetype("assets/font2.ttf", 70)
arial = ImageFont.truetype("assets/font2.ttf", 30)
name_font = ImageFont.truetype("assets/font.ttf", 30)
para = textwrap.wrap(title, width=32)
j = 0
draw.text(
(10, 10), f"{h}", fill="red", font=name_font
)
draw.text(
(600, 150),
"NOW PLAYING",
fill="white",
stroke_width=2,
stroke_fill="white",
font=font2,
)
Thanks in advance :-)
You can use the draw.textbbox method to get a bounding box for your text string and fill it using the draw.rectangle method.
from PIL import Image, ImageDraw, ImageFont
image = Image.new("RGB", (500, 100), "white")
font = ImageFont.truetype("segoeui.ttf", 40)
draw = ImageDraw.Draw(image)
position = (10, 10)
text = "Hello world"
bbox = draw.textbbox(position, text, font=font)
draw.rectangle(bbox, fill="red")
draw.text(position, text, font=font, fill="black")
image.show()
If you want a larger margin for the background rectangle, you can adjust the returned bounding box like so:
left, top, right, bottom = draw.textbbox(position, text, font=font)
draw.rectangle((left-5, top-5, right+5, bottom+5), fill="red")
draw.text(position, text, font=font, fill="black")

How to set anchor to fit the text at center of image with PIL.ImageDraw.Draw.text?

Drawn text on an image with Pillow library, tried to fit the text anchored at center of image by option anchor='mm', but it looks not exactly as center of image.
Demo code
from PIL import Image, ImageDraw, ImageFont
im = Image.new("RGBA", (500, 500), (255, 255, 255, 255))
font = ImageFont.truetype(font='arial.ttf', size=320)
draw = ImageDraw.Draw(im)
draw.text((250, 250), "123", font=font, fill='black', anchor='mm')
im.show()
result:
expectation:
The reason why the text looks misaligned is because there is a little margin at the left of the 1 and pillow will align the text including this margin.
Adding 0 at both ends will visualize that it is correctly centered and that there is a large margin at 1.
If you want to align the text excluding margins, ImageFont.getmask is helpful.
def get_offset_for_true_mm(text, draw, font):
anchor_bbox = draw.textbbox((0, 0), text, font=font, anchor='lt')
anchor_center = (anchor_bbox[0] + anchor_bbox[2]) // 2, (anchor_bbox[1] + anchor_bbox[3]) // 2
mask_bbox = font.getmask(text).getbbox()
mask_center = (mask_bbox[0] + mask_bbox[2]) // 2, (mask_bbox[1] + mask_bbox[3]) // 2
return anchor_center[0] - mask_center[0], anchor_center[1] - mask_center[1]
im = Image.new("RGBA", (500, 500), (255, 255, 255, 255))
font = ImageFont.truetype(font='arial.ttf', size=320)
draw = ImageDraw.Draw(im)
text = "123"
offset = get_offset_for_true_mm(text, draw, font)
draw.text((250 + offset[0], 250 + offset[1]), text, font=font, fill='black', anchor='mm')
im.show()
Result:

How to get the font pixel height using PIL's ImageFont class?

I am using PIL' ImageFont module to load fonts to generate text images.
I want the text to tightly bound to the edge, however, when using the ImageFont to get the font height, It seems that it includes the character's padding. As the red rectangle indicates.
c = 'A'
font = ImageFont.truetype(font_path, font_size)
width = font.getsize(c)[0]
height = font.getsize(c)[1]
im = Image.new("RGBA", (width, height), (0, 0, 0))
draw = ImageDraw.Draw(im)
draw.text((0, 0), 'A', (255, 255, 255), font=font)
im.show('charimg')
If I can get the actual height of the character, then I could skip the bounding rows in the bottom rectangle, could this info got from the font?
Thank you.
Exact size depends on many factors. I'll just show you how to calculate different metrics of font.
font = ImageFont.truetype('arial.ttf', font_size)
ascent, descent = font.getmetrics()
(width, baseline), (offset_x, offset_y) = font.font.getsize(text)
Height of red area: offset_y
Height of green area: ascent - offset_y
Height of blue area: descent
Black rectangle: font.getmask(text).getbbox()
Hope it helps.
The top voted answer is outdated. There is a new function in Pillow 8.0.0: ImageDraw.textbbox.
See the release notes for other text-related functions added in Pillow 8.0.0.
Note that ImageDraw.textsize, ImageFont.getsize and ImageFont.getoffset are broken, and should not be used for new code. These have been effectively replaced by the new functions with a cleaner API. See the documentaion for details.
To get a tight bounding box for a whole string you can use the following code:
from PIL import Image, ImageDraw, ImageFont
image = Image.new("RGB", (200, 80))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("arial.ttf", 30)
draw.text((20, 20), "Hello World", font=font)
bbox = draw.textbbox((20, 20), "Hello World", font=font)
draw.rectangle(bbox, outline="red")
print(bbox)
# (20, 26, 175, 48)
image.show()
You can combine it with the new ImageDraw.textlength to get individual bounding boxes per letter:
from PIL import Image, ImageDraw, ImageFont
image = Image.new("RGB", (200, 80))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("arial.ttf", 30)
xy = (20, 20)
text = "Example"
draw.text(xy, text, font=font)
x, y = xy
for c in text:
bbox = draw.textbbox((x, y), c, font=font)
draw.rectangle(bbox, outline="red")
x += draw.textlength(c, font=font)
image.show()
Note that this ignores the effect of kerning. Kering is currently broken with basic text layout, but could introduce a slight inaccuracy with Raqm layout. To fix it you would add the text length of pairs of letters instead:
for a, b in zip(text, text[1:] + " "):
bbox = draw.textbbox((x, y), a, font=font)
draw.rectangle(bbox, outline="red")
x += draw.textlength(a + b, font=font) - draw.textlength(b, font=font)
from PIL import Image, ImageDraw, ImageFont
im = Image.new('RGB', (400, 300), (200, 200, 200))
text = 'AQj'
font = ImageFont.truetype('arial.ttf', size=220)
ascent, descent = font.getmetrics()
(width, height), (offset_x, offset_y) = font.font.getsize(text)
draw = ImageDraw.Draw(im)
draw.rectangle([(0, 0), (width, offset_y)], fill=(237, 127, 130)) # Red
draw.rectangle([(0, offset_y), (width, ascent)], fill=(202, 229, 134)) # Green
draw.rectangle([(0, ascent), (width, ascent + descent)], fill=(134, 190, 229)) # Blue
draw.rectangle(font.getmask(text).getbbox(), outline=(0, 0, 0)) # Black
draw.text((0, 0), text, font=font, fill=(0, 0, 0))
im.save('result.jpg')
print(width, height)
print(offset_x, offset_y)
print('Red height', offset_y)
print('Green height', ascent - offset_y)
print('Blue height', descent)
print('Black', font.getmask(text).getbbox())
result
Calculate area pixel
from PIL import Image, ImageDraw, ImageFont
im = Image.new('RGB', (400, 300), (200, 200, 200))
text = 'AQj'
font = ImageFont.truetype('arial.ttf', size=220)
ascent, descent = font.getmetrics()
(width, height), (offset_x, offset_y) = font.font.getsize(text)
draw = ImageDraw.Draw(im)
draw.rectangle([(0, offset_y), (font.getmask(text).getbbox()[2], ascent + descent)], fill=(202, 229, 134))
draw.text((0, 0), text, font=font, fill=(0, 0, 0))
im.save('result.jpg')
print('Font pixel', (ascent + descent - offset_y) * (font.getmask(text).getbbox()[2]))
result

Python image library - font positioning

EDIT: added complete working example
I have the following program:
from PIL import Image, ImageDraw, ImageFont
FULL_SIZE = 50
filename = 'font_test.png'
font="/usr/share/fonts/truetype/msttcorefonts/arial.ttf"
text="5"
image = Image.new("RGBA", (FULL_SIZE, FULL_SIZE), color="grey")
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(font, 40)
font_width, font_height = font.getsize(text)
draw.rectangle(((0, 0), (font_width, font_height)), fill="black")
draw.text((0, 0), text, font=font, fill="red")
image.save(filename, "PNG")
This generates the following image:
It seems that when writing the text PIL library adds some margin at the top. This margin depends on the font I use.
How can I take this into account when trying to position the text (I want it to be in the middle of a rectangle)?
(Using Python 2.7.6 with Pillow 2.3.0 on Ubuntu 14.04)
I don't understand why, but subtracting font.getoffset(text)[1] from the y co-ordinate fixes it on my computer.
from PIL import Image, ImageDraw, ImageFont
FULL_SIZE = 100
filename = 'font_posn_test.png'
fontname = '/usr/share/fonts/truetype/msttcorefonts/arial.ttf'
textsize = 40
text = "5"
image = Image.new("RGBA", (FULL_SIZE, FULL_SIZE))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(fontname, textsize)
print font.getoffset(text)
print font.font.getsize(text)
font_width, font_height = font.getsize(text)
font_y_offset = font.getoffset(text)[1] # <<<< MAGIC!
draw.rectangle(((0, 0), (font_width, font_height)), fill="black")
draw.text((0, 0 - font_y_offset), text, font=font, fill="red")
image.save(filename, "PNG")

Categories