Watermarks, labels, charts on the fly and generated social images all rely on drawing text precisely.
ImageDraw.Draw(im) returns a drawing context that modifies `im` in place. Its methods take coordinates as (x, y) tuples or bounding boxes [x0, y0, x1, y1]:
line,rectangle,rounded_rectangle,ellipse,polygon,arc,pieslicefill=for the interior colour,outline=andwidth=for the border
For text, load a font with ImageFont.truetype("DejaVuSans.ttf", size); a path or a font name your system can find. ImageFont.load_default(size=...) gives a built-in scalable font in recent versions, good for quick labels.
draw.text((x, y), text, font=font, fill=...) draws text; anchor= changes what (x, y) refers to, for example "mm" centres the text on the point and "ls" puts it on the left baseline. Multi-line strings work, with spacing= and align=.
To lay out text, measure it: draw.textbbox((x, y), text, font=font) returns the box (left, top, right, bottom) the text would occupy, and draw.textlength(text, font=font) the advance width. The old textsize method was removed in Pillow 10.
For semi-transparent overlays (watermarks), draw on a separate transparent RGBA layer and combine it with Image.alpha_composite; drawing a translucent fill straight onto an RGB image replaces pixels rather than blending.
Example
from PIL import Image, ImageDraw, ImageFont
im = Image.new("RGB", (400, 200), "white")
draw = ImageDraw.Draw(im) # draws on im in place
draw.rectangle([20, 20, 180, 120], outline="navy", width=3)
draw.ellipse([220, 30, 320, 130], fill="tomato")
draw.line([(0, 190), (399, 150)], fill="gray", width=2)
font = ImageFont.load_default(size=28) # or ImageFont.truetype(path, 28)
text = "Hello Pillow"
box = draw.textbbox((0, 0), text, font=font)
w, h = box[2] - box[0], box[3] - box[1]
print(w > 0 and h > 0) # True: measured before drawing
draw.text((200, 170), text, font=font, fill="black", anchor="mm") # centred
print(im.getpixel((270, 80))) # (255, 99, 71) tomato
print(draw.textlength("ab", font=font) > draw.textlength("a", font=font)) # TrueRemember
- ImageDraw.Draw draws in place on the image
- Load fonts with ImageFont.truetype or load_default(size=...)
- Measure text with textbbox/textlength; textsize is gone
anchor='mm'centres text on a point
Watch out for
- Calling the removed
draw.textsize()from old tutorials; usetextbboxinstead.