Documentation Index
Fetch the complete documentation index at: https://docs.pixeltable.com/llms.txt
Use this file to discover all available pages before exploring further.
Problem
You need to add watermarks to hundreds of different images to protect copyright, add branding, or mark drafts.Solution
What’s in this recipe:- Create simple text watermarks
- Test transformations before applying
- Apply to multiple images automatically
ImageDraw (relies on PIL/Pillow). This gives you full control over
watermark placement, font, transparency, and color.
You can iterate on transformations before adding them to your table. Use
.select() with .collect() to preview results on sample
images—nothing is stored in your table. If you want to collect only the
first few rows, use .head(n) instead of .collect(). Once you’re
satisfied, use .add_computed_column() to apply watermarks to all
images in your table.
For more on this workflow, see Get fast feedback on
transformations.
Setup
Load images
Connected to Pixeltable database at: postgresql+psycopg://postgres:@/pixeltable?host=/Users/pjlb/.pixeltable/pgdata
Created directory ‘image_demo’.
<pixeltable.catalog.dir.Dir at 0x30e27cc40>
Created table ‘watermarks’.
Inserting rows into `watermarks`: 0 rows [00:00, ? rows/s]Inserting rows into `watermarks`: 3 rows [00:00, 532.86 rows/s]
Inserted 3 rows with 0 errors.
3 rows inserted, 6 values computed.
Iterate: add watermarks to a few images first
Add: add watermarks to all images in your table
Added 3 column values with 0 errors.
3 rows updated, 3 values computed.
Explanation
How the watermark technique works: The UDF creates a transparent overlay on top of each image. The overlay is created with the same dimensions as the image (Image.new('RGBA', img.size, ...)), so watermarks adapt automatically
whether you’re processing small thumbnails or large photos. The function
draws white text with semi-transparent fill (alpha=200, where 255 is
fully opaque), composites the overlay onto the original image using
Image.alpha_composite(), and converts back to RGB since most image
formats don’t support transparency.
To customize the UDF:
- Position: Change the
(x, y)coordinates in thepositionvariable - Color: Modify the
(R, G, B, Alpha)fill value (0-255 for each) - Size: Adjust the font size parameter in
ImageFont.load_default(size=40) - Font: Use
ImageFont.truetype('path/to/font.ttf', size)for custom fonts
.select() just picks which columns to view.
In Pixeltable, .select() also lets you compute new transformations on
the fly—define new columns without storing them. This makes .select()
perfect for testing transformations before you commit them.
When you use .select(), you’re creating a query that doesn’t execute
until you call .collect(). You must use .collect() to execute the
query and return results—nothing is stored in your table. If you want to
collect only the first few rows, use .head(n) instead of .collect()
to test on a subset before processing your full dataset. Once satisfied,
use .add_computed_column() with the same expression to persist results
permanently.
For more on this workflow, see Get fast feedback on
transformations.