Lesson 8.2Lesson 8.2 · Automation & the Everyday Toolkit
Batch-Processing Images and PDFs
Pillow for whole folders of images and pypdf for document sets - resize, watermark, convert, merge, split and extract at scale
Two hundred 8-megabyte renders, resized, watermarked and web-ready - from one loop you wrote once.
Design produces media at volume: renders, site photos, moodboard images, scanned drawings, PDF issue sets. Preparing it - resizing for the web, stamping a studio watermark, converting formats, stitching PDFs into an issue - is slow, repetitive handwork in Photoshop or Acrobat, done the same way every time.
This is textbook automation. Two friendly libraries cover most of it: Pillow (imported as PIL) opens, transforms and saves images, and pypdf reads and rearranges PDF pages. Both install in seconds and both follow the batch shape you already know - loop over a folder, apply the same transform to each file, write the results out. This lesson turns your media chores into scripts you run and forget.
Pillow: open/transform/save. pypdf: reader.pages -> writer.add_page -> write. New files only.
Pillow - an image is a grid of pixels you can open and save
Pillow is the standard Python imaging library; you install it with pip install Pillow but import it as PIL. Its core object is Image, and the whole library revolves around three verbs: open a file into an Image, transform it, and save it back out - possibly in a different format. A single image first, to see the shape:
from PIL import Image
img = Image.open("render.png")
print(img.size, img.mode) # (4000, 2250) RGBA
small = img.resize((1600, 900))
small = small.convert("RGB")
small.save("render_web.jpg", quality=82)img.size is a (width, height) tuple in pixels; img.mode tells you the colour model (RGB, RGBA with transparency, L for greyscale). resize takes a new (width, height). The convert("RGB") matters: JPEG cannot hold transparency, so a PNG with an alpha channel must be flattened to RGB before saving as .jpg, or Pillow will error. Saving as .jpg with quality=82 is the sweet spot for web - visually clean at a fraction of the file size. Notice you changed the format simply by giving the output a different extension; Pillow infers it. That is the entire model - and to keep proportions when you only care about a maximum size, img.thumbnail((1600, 1600)) resizes in place, preserving aspect ratio and never enlarging.
Why Pillow rather than driving Photoshop from code? Because it is small, free, installs in seconds and is built precisely for this batch work - no application to open, no licence, no interface in the way. It also exposes an image's metadata: img.info and, for photographs, the EXIF data via img.getexif() can report the camera orientation, which matters because phone photos are often stored sideways with a rotation flag rather than actually rotated. Pillow's ImageOps.exif_transpose(img) applies that flag so your resized copies come out the right way up - one small call that saves a confusing folder of sideways thumbnails.
The batch loop - a whole folder at once
One image is a demo; the value is in the folder. Combine last lesson's pathlib + glob with Pillow's open-transform-save, and write results to a separate output folder so originals are never touched:
from pathlib import Path
from PIL import Image
src = Path("photos")
out = Path("web")
out.mkdir(exist_ok=True)
for file in src.glob("*.jpg"):
with Image.open(file) as img:
img.thumbnail((1600, 1600))
img.save(out / file.name, quality=82, optimize=True)
print(f"done {file.name}")The with Image.open(file) as img: form is worth adopting - it closes the file cleanly even if something goes wrong mid-loop, which matters when you open hundreds. thumbnail shrinks each image to fit within 1600x1600 while keeping its proportions; optimize=True squeezes a little more off the file size. The print on each iteration is a progress signal - on a big batch, silence is unnerving, and a running log tells you it is alive and where it is. One robustness habit: real folders contain surprises - a corrupt file, a stray non-image. Wrap the body in try / except so one bad file logs a warning and the loop carries on rather than crashing at photo 147 of 200. That single pattern - process each item, catch its errors, keep going - turns a fragile script into a dependable one.
For a long batch it is worth ending with a summary rather than only a wall of per-file lines. Keep a simple counter - increment a done variable on success and append to a failed list in the except - and print f"processed {done}, skipped {len(failed)}" at the end. On two hundred files that one line tells you at a glance whether the run was clean or whether a few problem files need a look, without scrolling through everything. This habit of reporting the outcome scales to every batch script you write: automation whose result you cannot see is automation you cannot quite trust.
open -> transform -> save, to a NEW folder. try/except each file so one bad photo doesn't kill the batch.
Watermarking and cropping - drawing onto images
Beyond resizing, Pillow can draw on an image through its ImageDraw module - text, lines, rectangles - which is how you stamp a studio watermark across a set of renders. Text needs a font; ImageFont.truetype loads a .ttf file at a chosen size, and you position the text with an (x, y) coordinate measured from the top-left corner:
from PIL import Image, ImageDraw, ImageFont
img = Image.open("render_web.jpg").convert("RGB")
draw = ImageDraw.Draw(img)
font = ImageFont.truetype("arial.ttf", 40)
w, h = img.size
draw.text((w - 360, h - 60), "STUDIO MATRX",
fill=(255, 255, 255), font=font)
img.save("render_marked.jpg", quality=82)Coordinates in Pillow run from (0, 0) at the top-left, x rightwards and y downwards - a small thing to remember when placing the mark. Here (w - 360, h - 60) tucks the text into the bottom-right corner regardless of image size, because it is measured back from the width and height. Cropping is equally direct: img.crop((left, top, right, bottom)) returns the rectangle between those pixel coordinates, handy for trimming borders off scanned drawings in bulk.
For a subtler mark than solid text, build the watermark on a separate transparent layer and blend it in. Create an RGBA overlay the same size as the image, draw semi-transparent text onto it (the fourth colour value is opacity, 0 to 255), and composite it with Image.alpha_composite before flattening back to RGB to save. That produces the faint, professional diagonal watermark you see on preview renders rather than an opaque stamp - and because it is all code, the placement and opacity are identical across every image in the folder. Fold either of these into the batch loop from the previous section and you have a folder-wide watermarker or auto-cropper. The lesson is that images are addressable data - a grid of pixels with a coordinate system - so anything you would do by hand to one, a loop does to a thousand.
PDFs with pypdf - merge, split, extract
PDF handling looks intimidating and is not, because the useful operations are all page-shuffling, not re-drawing. The modern library is pypdf (pip install pypdf). Its two objects mirror each other: a PdfReader opens an existing file and exposes its .pages, and a PdfWriter collects pages and writes a new file. Every operation is: read pages from one place, add the ones you want to a writer, write it out. Merging a folder of PDFs into a single issue set:
from pathlib import Path
from pypdf import PdfReader, PdfWriter
writer = PdfWriter()
for pdf in sorted(Path("sheets").glob("*.pdf")):
reader = PdfReader(pdf)
for page in reader.pages:
writer.add_page(page)
with open("issue_set.pdf", "wb") as f:
writer.write(f)Splitting and extracting are the same idea in reverse. To pull pages 1 to 3 into their own file, create a fresh PdfWriter, loop for page in reader.pages[0:3] (Python slices are zero-based and the end is exclusive, so [0:3] is the first three pages), add_page each, and write. To extract a single page, add just reader.pages[4] for the fifth page. Because you always write to a new file, the source PDFs are never altered - the same safety principle as the image batches. pypdf can also rotate and re-order pages - page.rotate(90) before adding it straightens a landscape sheet scanned portrait - so a mixed, wrongly-oriented set can be normalised in the very same pass that merges it. One honest limitation: pypdf rearranges and combines pages beautifully but does not reliably edit the content inside a page or extract clean text from scanned (image-only) PDFs. For page logistics - the merge/split/extract chores that eat an afternoon before an issue - it is exactly the right tool.
Formats, quality and knowing what to hand off
A quick map of image formats saves a lot of grief. JPEG (.jpg) is lossy and small - the right default for photographs and renders headed for the web or email. PNG is lossless and supports transparency - right for diagrams, screenshots, logos and anything with crisp edges or an alpha channel. WEBP, which Pillow both reads and writes, gives noticeably smaller files than JPEG at similar quality and is now widely supported - worth reaching for when file size really matters. Because Pillow infers the format from the output extension, choosing between them is a one-line change, and a single loop can even emit several formats per source image:
from pathlib import Path
from PIL import Image
out = Path("web")
out.mkdir(exist_ok=True)
for file in Path("src").glob("*.png"):
with Image.open(file) as img:
rgb = img.convert("RGB")
rgb.save(out / f"{file.stem}.jpg", quality=82)
rgb.save(out / f"{file.stem}.webp", quality=80)A word on quality that beginners often miss: repeatedly opening and re-saving a JPEG recompresses it each time and slowly degrades it, so always work from the original high-resolution file, do your resize once, and save a single web copy. If you need perfect fidelity - an archival master, a diagram - save PNG instead, which is lossless.
The same honesty from the previous lesson applies to media. A script is the right tool when the operation is uniform across many files: the same resize, the same watermark, the same conversion. It is the wrong tool for the one hero render that needs retouching by eye, or the single document that needs a bespoke layout. The craft is in the split - automate the repetitive ninety, hand-finish the meaningful ten. And keep every batch non-destructive: write to a new folder, never over your source library, because the one truly irreversible act in all of this is deleting an original you cannot regenerate.
Pillow (PIL)
Open, transform and save images
Install as Pillow, import as PIL. Image.open / resize / thumbnail / convert / save. The standard Python imaging toolkit.
Image.thumbnail
Resize keeping aspect ratio
Shrinks in place to fit a max box, never enlarges, preserves proportions. Safer than resize() when you only care about a size cap.
ImageDraw / ImageFont
Draw text and shapes on an image
How watermarking works. Coordinates run from top-left, y downwards. Needs a .ttf font file for text.
pypdf
Read and rearrange PDF pages
PdfReader.pages -> PdfWriter.add_page -> write. Merge/split/extract are lossless page-shuffling; it does not edit page content or OCR scans.
Workshop — the web-ready media prep tool
Build a two-part script that prepares a project's media for sharing: batch-resize and watermark a folder of images, then merge a folder of PDFs into one issue set. Everything writes to new files, leaving originals safe.
Python 3, `pip install Pillow pypdf`, a `.ttf` font file for the watermark, and copies of an image folder and a PDF folder to practise on.
Goal: batch-resize + watermark images, then merge PDFs Inputs: a folder of images and a folder of PDFs (copies) Time: ~50 minutes
- 1Install the libraries:
pip install Pillow pypdf. Confirm withfrom PIL import Imageandfrom pypdf import PdfReaderrunning without error. - 2Write the image loop: create an output folder, glob the images, and for each
open->thumbnail((1600,1600))->save(..., quality=82). Wrap the per-file body intry/exceptthat prints a warning and continues. - 3Add watermarking: with
ImageDrawand a.ttffont, stamp your studio name into the bottom-right corner (positioned fromimg.size) before saving. - 4Write the PDF merge: make a
PdfWriter, loop the sorted PDFs adding every page, andwritetoissue_set.pdf. Open the result and confirm the page order and count. - 5Add a split: from one merged PDF, extract pages 1 to 3 into
cover_set.pdfusing a slicereader.pages[0:3]. Verify the extracted file opens correctly.
You’ll walk away with
A script that turns a folder of full-size images into watermarked, web-ready copies and merges a folder of PDFs into a single issue set (plus a split-out range) - robust to a bad file, with all originals preserved.
Three altitudes on the same idea
Read the band that fits you — or all three.
Issue day is a media-logistics day. Renders need resizing and watermarking before they leave the office; sheet PDFs need merging into a single issue set, or a range split out for a consultant. Scripting these means the presentation set is generated identically every time, at the right size, with the right stamp, in the time it takes to fetch a coffee. Point the same tools at incoming consultant PDFs to split or extract exactly the pages you need to coordinate.
Product and moodboard imagery is your daily media load. Supplier photos arrive huge and inconsistent; a Pillow batch resizes and converts a whole folder to web-ready JPGs for a client deck in seconds, optionally watermarked. Spec and FF&E PDFs from different suppliers merge into one tidy document per room or per package with pypdf. These two scripts alone remove hours of Photoshop-and-Acrobat handwork from every project.
Portfolios and submissions are image-and-PDF marathons. Compressing a folder of high-res renders so a portfolio PDF is emailable, watermarking work-in-progress, or stitching a submission set from many exports are all one short script each - and they teach you Pillow and pypdf on deadlines you actually face. The habit of automating your own output is exactly what a computational-design or visualization role wants to see.
“Editing images and PDFs properly needs Photoshop and Acrobat - a script can only do crude, low-quality versions.”
Do it yourself
Think through what each transform does before running it.
- 1Why must a transparent PNG be
convert("RGB")before it can be saved as a.jpg? - 2What is the difference between
resize((1600, 900))andthumbnail((1600, 1600))? - 3In Pillow, where is the coordinate
(0, 0)and which way does the y-axis increase? - 4Describe the read-write pattern pypdf uses to merge several PDFs into one.
- 5Why wrap each file's processing in
try/exceptinside a batch loop?
The one line to carry out
Peer-reviewed journals & authoritative standards
- 01The Python Standard Library — Python documentation, 2026.
- 02Python Tutorial — Python documentation, 2026.
- 03Python (programming language) — Wikipedia, 2026.
- 04Library (computing) — Wikipedia, 2026.
Images and PDFs are the visual deliverables. The other half of a project's paperwork is structured: schedules, BOQs, cost sheets and reports. Next we automate those - reading and writing Excel with openpyxl and generating Word documents with python-docx.
The author
Amogh N P
Architect, interior designer, and creative polymath. Studio Matrx began in his notebooks — his vision of design made honest, useful, and open to everyone. Its Academy is written and taught in his memory, and free, forever.
More about Amogh →