This documentation page is also available as an interactive notebook. You can launch the notebook in
Kaggle or Colab, or download it for use with an IDE or local Jupyter installation, by clicking one of the
above links.
You add watermarks to images using a custom UDF that wraps Pillow’s
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.
import pixeltable as pxtfrom PIL import Image, ImageDraw, ImageFont# Create a fresh directory (drop existing if present)pxt.drop_dir('image_demo', force=True)pxt.create_dir('image_demo')
Connected to Pixeltable database at: postgresql+psycopg://postgres:@/pixeltable?host=/Users/pjlb/.pixeltable/pgdata
Created directory ‘image_demo’.
<pixeltable.catalog.dir.Dir at 0x30e27cc40>
t = pxt.create_table('image_demo/watermarks', {'image': pxt.Image})
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 the position variable
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
The Pixeltable workflow:In traditional databases, .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.