VMware ESXi and vSphere Cluster Management
Create Text Watermarks with Pillow
Learn how to add a visible text watermark to an image with Python and Pillow, including fonts, positioning, previewing, saving, and troubleshooting.
A watermark is a visible text or graphic overlay added to an image to identify its source, brand, creator, or ownership. This lesson focuses on a text-based watermark, such as a website name, company name, creator label, or copyright notice.
Pillow is a Python imaging library used to open, modify, draw on, display, and save images. Before starting, you should know basic Python imports and variables, how to install Pillow, and how filesystem paths and the current working directory work.
Pillow modules for a text watermark
A text watermark uses three Pillow modules:
- Image opens the source image and represents it as an image object.
- ImageDraw creates drawing methods, including text drawing.
- ImageFont loads a font and controls its size.
The workflow is: open the image, create a drawing context, load a font, draw the text, then preview or save the modified image.
Open an image and prepare it for drawing
Import the required classes and open an existing image with Image.open. The returned image object is held in memory. Creating an ImageDraw.Draw instance attaches a drawing context to that object.
from PIL import Image, ImageDraw, ImageFont
img = Image.open('input.jpg')
draw = ImageDraw.Draw(img)Drawing operations modify the in-memory img object. They do not automatically create a new file. You must call save when you want to write the result to disk.
Choose the watermark text
Store the label in a string variable. The supplied string becomes the visible text overlay.
text = 'example.com'You could instead use a company name, creator name, or copyright notice:
text = '© 2026 Example Studio'Load a TrueType font
A TrueType font is a font file, commonly ending in .ttf, that Pillow can load. The font path is the filesystem location of that file. The numeric size controls the approximate visible height of the text; larger values produce larger text.
For a visible watermark, use a bold font when one is available:
font = ImageFont.truetype('OpenSans-ExtraBold.ttf', size=50)The filename may work when Pillow can find the font in the current directory or an available font location. If lookup fails, provide the complete path to an installed font file.
Font paths on different operating systems
Font availability varies between machines. A font installed on one computer may not be installed on another.
# Windows: use a raw string so backslashes are treated literally
font = ImageFont.truetype(r'C:\Windows\Fonts\Arial.ttf', size=50)On Linux, fonts are often installed in standard locations such as /usr/share/fonts. You can either install the desired font there and use its actual path, or pass the complete path directly:
font = ImageFont.truetype('/usr/share/fonts/truetype/example/Example-Bold.ttf', size=50)The example Linux path is illustrative: use a path to a font file that actually exists on your system.
Draw a basic bottom-left watermark
Use ImageDraw.text to render the label:
draw.text((10, 1500), text, font=font)
img.show()
img.save('watermarked-output.jpg')The first argument, (10, 1500), is a coordinate tuple: the pair (x, y) identifies the text drawing position. In a typical image coordinate system, x increases from left to right and y increases from top to bottom. A small x value creates a small left margin, while a y value near the image height places the text near the bottom.
For ordinary use, the coordinate is often described as the text's upper-left starting position. More precisely, Pillow's default text anchor and font metrics can offset the rendered glyphs slightly from that reference point. If exact placement matters, specify an anchor and measure the text.
A complete fixed-size example looks like this:
from PIL import Image, ImageDraw, ImageFont
img = Image.open('input.jpg')
draw = ImageDraw.Draw(img)
text = 'example.com'
font = ImageFont.truetype('OpenSans-ExtraBold.ttf', size=50)
draw.text((10, 1500), text, font=font, fill='white')
img.show()
img.save('watermarked-output.jpg')This fixed y coordinate is suitable only when the input image has a known height. On a shorter image, the text could be partly or completely outside the visible area. On a taller image, it might not be close to the bottom.
Position text relative to image size
For images with different dimensions, read the image height and calculate the vertical position. Text measurements are useful when you need a precise bottom margin.
from PIL import Image, ImageDraw, ImageFont
img = Image.open('input.jpg')
draw = ImageDraw.Draw(img)
text = 'example.com'
font = ImageFont.truetype('OpenSans-ExtraBold.ttf', size=50)
left_margin = 10
bottom_margin = 10
# Use an explicit top-left anchor for predictable placement.
bbox = draw.textbbox((0, 0), text, font=font, anchor='lt')
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = left_margin
y = img.height - text_height - bottom_margin
draw.text((x, y), text, font=font, anchor='lt', fill='white')
img.save('watermarked-output.jpg')Here, img.height supplies the actual image height. The text bounding box supplies the rendered text height, and the bottom margin is subtracted so the watermark remains inside the image. The width is also available if you later need right alignment or centering.
For a simple test, use a visibly safe coordinate such as (10, 10). For production placement, combine image dimensions, text measurements, the desired margins, and an explicit anchor.
Preview and save the result
img.show() asks the operating system to display the modified image. It is useful for a quick preview, although the exact behavior depends on your environment.
Save the result to a separate filename:
img.save('watermarked-output.jpg')Keeping the output filename different from the input protects the original image. Do not overwrite the original unless that is intentional and you have a suitable backup.
Font loading and path troubleshooting
Troubleshooting common problems
“Cannot open resource” for the font
- Check that the filename is correct.
- Confirm that the font is installed or present in the directory you expect.
- Provide the complete path to an existing
.ttffile. - On Windows, use a raw string for paths containing backslashes.
- On Linux, check standard font directories such as
/usr/share/fontsor use the full installed path.
The watermark is too high, too low, or outside the image
- Inspect
img.widthandimg.height. - Remember that a fixed coordinate may have been copied from an image with different dimensions.
- Calculate
yfrom the image height and desired bottom margin. - Use
textbboxand an explicit anchor when accurate placement is required.
The watermark is too small or too large
The font size may not match the source image resolution. Adjust the size argument passed to ImageFont.truetype. When processing varied image sizes, choose a font size relative to the image dimensions rather than using one fixed value for every file.
The output appears unchanged
- Confirm that the text coordinate is inside the visible image area.
- Make sure you previewed or saved the modified
imgobject. - Save to a clearly named output file and open that file, not the original.
- Check that the text color contrasts with the image background.
Summary
To create a text watermark with Pillow, open the image with Image.open, attach ImageDraw.Draw, store the label in a string, load a bold TrueType font with ImageFont.truetype, and render it with ImageDraw.text. Use image dimensions and text measurements for reliable placement, preview the result with img.show(), and save to a separate output filename.