Draw text with background color - python

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")

Related

How to crop and paste an image into an ellipse in pillow

I have a drawn ellipse on an image, I want to crop and place into this ellipse an image thumbnail. Here is how the ellipse is placed in the image:
What I tried to use is paste but that will just cover the ellipse as shown here:
Here is my pillow code so far:
from PIL import ImageFont, ImageDraw, Image
from IPython.display import Image as jpImg
import textwrap
# Start new Image and draw it
image = Image.new('RGB', (1200, 630), color = 'white')
draw = ImageDraw.Draw(image)
# Text content to go into image
txt = "This is the title of this entry This is the title of this entry"
list_sections = ['Some section text will go here', 'Some section text will go here 1', 'Some section text will go here 2','Some section text will go here 3', 'Some section text will go here 4']
category_list = ['category 1', 'category 2', 'category 3', 'category 4']
# Failed attempt to draw a right angle triangle to divide the image rectangle
#draw.polygon([(1200,630), (200, 200), (150,50)], fill = 'yellow')
# Ending up faking it with a [line]
#draw.line((0, 0) + image.size, fill=128)
draw.line((10,960,1700, 75), fill='red', width=600)
# portion of image width you want text width to be
# matching fontsize to width
img_fraction = 0.50
fontsize = 1 # starting font size
font = ImageFont.truetype("/Library/Fonts/Arial.ttf", fontsize)
while font.getsize(txt)[0] < img_fraction*image.size[0]:
# iterate until the text size is just larger than the criteria
fontsize += 1
font = ImageFont.truetype("/Library/Fonts/Arial.ttf", fontsize)
# optionally de-increment to be sure it is less than criteria
fontsize -= 1
font = ImageFont.truetype("/Library/Fonts/Arial.ttf", fontsize)
# Font for title text
font_title = ImageFont.truetype("/Library/Fonts/Arial.ttf", 50)
# Draw sections from list
top = 100
for section in list_sections:
draw.text((35, top), section, font=font, fill="red")
top = top + 40
continue
# Draw Category from list
top = image.size[1] /2
side = image.size[0]-400
for cat in category_list:
draw.text((side, top), cat, font=font, fill="white")
top = top + 40
continue
# Draw parenthesis to surround category list
font_title_parent_0 = ImageFont.truetype("/Library/Fonts/Arial.ttf", 350)
draw.text((side - 100, top-300), '{', font=font_title_parent_0, fill=(255, 255, 255, 128))
draw.text((side + 240, top-300), '}', font=font_title_parent_0, fill="white")
# Draw Ellipse for photo
draw.ellipse((300-20,300-20,30+500,30+500), fill='white',outline="red", width=25)
# Paste an image on the eclipse
# offset = ((bg_w - img_w) // 2, (bg_h - img_h) // 2)
offset = ((300-20) // 2, (30+500) // 2)
avatar = Image.open('data/images/avatar.jpeg', 'r')
#image.paste(avatar, offset)
draw.text((10, 15), textwrap.shorten(txt, width=55, placeholder='..'), font=font_title, fill="red") # put the text on the image
image.save('data/images/final_card.png') # save it
jpImg(filename='data/images/final_card.png')
Is it possible to crop the avatar thumbnail and place it within the ellipse? Thanks.
yes, one way is to load the avatar and mask it then plop it in, as such:
image = Image.new('RGB', (1200, 630), color = 'white')
draw = ImageDraw.Draw(image)
# draw the border
avatar_size = (200, 200)
x_offset, y_offset = 300, 300
border_bounding = [x_offset, y_offset, x_offset+avatar_size[0], y_offset+avatar_size[1]]
draw.ellipse(border_bounding, fill="red")
# make it into a mask, but scale it down slightly so we have a border
border_size = 25
mask = Image.new('L', [ x-border_size for x in avatar_size ], 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.ellipse([0,0,*mask.size], fill=255)
# crop the avatar into the smaller circle
avatar = Image.open('jake.jfif').convert('RGB')
output = ImageOps.fit(avatar, mask.size, centering=(0.5, 0.5))
output.putalpha(mask)
# now drop that in the center of where our cicle is
image.paste(output, (x_offset+(border_size//2), y_offset+(border_size//2)), output)
display(image)
And you get:

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")

Draw underline text with PIL

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()

Categories