TIFF-to-PDF conversion has one critical split: lossless vs re-encoded. img2pdf embeds TIFF pixels directly into PDF without re-compressing — pixel-perfect and fast. Pillow re-encodes, which gives you control over compression and DPI but loses the original pixel values. ImageMagick is the standard CLI tool for quick conversions and shell scripts. The ChangeThisFile API handles multi-page TIFFs and supports all three approaches server-side.
TL;DR
| Method | Quality | Multi-page TIFF | Best for |
|---|---|---|---|
| img2pdf (Python) | Lossless — zero re-encoding | Yes (each frame = one PDF page) | Archival, scan-to-PDF, no quality loss |
| Pillow (Python) | Re-encoded (configurable) | Yes | Resize, DPI override, compression control |
| ImageMagick CLI | Re-encoded | Yes | Shell scripts, quick one-off conversions |
| ChangeThisFile API | Server-side Pillow | Yes | No local libs, serverless, any language |
Method 1: img2pdf (Python — lossless, fastest)
img2pdf embeds image pixels directly into a PDF without re-compressing. The output is bit-for-bit identical to the input — critical for scanned documents and archival workflows.
pip install img2pdf
import img2pdf
from pathlib import Path
def tiff_to_pdf(tiff_path: str, pdf_path: str) -> None:
with open(tiff_path, "rb") as f_in, open(pdf_path, "wb") as f_out:
f_out.write(img2pdf.convert(f_in))
tiff_to_pdf("scan.tiff", "scan.pdf")
print("Done")
# With explicit page size (A4 at 300 DPI)
def tiff_to_pdf_a4(tiff_path: str, pdf_path: str) -> None:
a4_layout = img2pdf.get_layout_fun(img2pdf.mm_to_pt(210, 297)) # A4 in mm
with open(tiff_path, "rb") as f_in, open(pdf_path, "wb") as f_out:
f_out.write(img2pdf.convert(f_in, layout_fun=a4_layout))
# Multiple TIFFs → one PDF (one page per image)
def tiffs_to_pdf(tiff_paths: list[str], pdf_path: str) -> None:
with open(pdf_path, "wb") as f_out:
f_out.write(img2pdf.convert(*tiff_paths))
tiffs_to_pdf(["page1.tiff", "page2.tiff", "page3.tiff"], "document.pdf")
img2pdf is significantly faster than Pillow for large TIFF files because it skips the decode-and-re-encode step — it reads the raw pixel data and wraps it in a PDF envelope. The output file size is larger than a re-compressed PDF but pixel-perfect.
Limitation: img2pdf does not support CMYK or palette-mode TIFFs. For those, use Pillow (Method 2).
Method 2: Pillow (Python — flexible, handles all modes)
Pillow handles CMYK, palette, and multi-page TIFFs that img2pdf rejects. Re-encodes pixels but gives you control over compression and DPI metadata.
pip install Pillow
from PIL import Image
from pathlib import Path
def tiff_to_pdf_pillow(tiff_path: str, pdf_path: str, dpi: int = 300) -> None:
"""
Convert TIFF to PDF. Handles multi-page TIFFs.
All frames are combined into a single PDF.
"""
with Image.open(tiff_path) as img:
frames = []
try:
while True:
frame = img.copy()
# PDF requires RGB or Grayscale — convert CMYK, P, RGBA
if frame.mode == "CMYK":
frame = frame.convert("RGB")
elif frame.mode == "P":
frame = frame.convert("RGB")
elif frame.mode == "RGBA":
# Flatten alpha onto white background
bg = Image.new("RGB", frame.size, (255, 255, 255))
bg.paste(frame, mask=frame.split()[3])
frame = bg
elif frame.mode != "RGB" and frame.mode != "L":
frame = frame.convert("RGB")
frames.append(frame)
img.seek(img.tell() + 1)
except EOFError:
pass # All frames extracted
if not frames:
raise ValueError(f"No frames found in {tiff_path}")
frames[0].save(
pdf_path,
save_all=True,
append_images=frames[1:],
resolution=dpi,
)
tiff_to_pdf_pillow("scan.tiff", "scan.pdf", dpi=300)
print("Done")
The multi-frame loop is required for multi-page TIFFs — Pillow doesn't expose all frames automatically. EOFError signals the end of frames. Each frame becomes one PDF page when using save_all=True.
Method 3: ImageMagick CLI (quick, scriptable)
ImageMagick's convert command handles TIFF-to-PDF in a single command. Good for shell scripts and quick one-off conversions.
# Install
apt install imagemagick # Ubuntu/Debian
brew install imagemagick # macOS
# Single TIFF to PDF
convert scan.tiff scan.pdf
# With explicit DPI and quality
convert -density 300 scan.tiff -quality 85 scan.pdf
# Multi-page TIFF to PDF
convert multipage.tiff output.pdf
# Multiple TIFFs to a single PDF (one page per image)
convert page1.tiff page2.tiff page3.tiff document.pdf
# Batch convert all TIFFs in a directory
for f in *.tiff; do convert "$f" "${f%.tiff}.pdf"; done
Security note: ImageMagick's policy file often restricts PDF output by default on server installations. If you get a not authorized error, edit /etc/ImageMagick-6/policy.xml and change the PDF policy line from none to readwrite:
<!-- Change this: -->
<policy domain="coder" rights="none" pattern="PDF" />
<!-- To this: -->
<policy domain="coder" rights="readwrite" pattern="PDF" />
The restriction exists because Ghostscript (used by ImageMagick for PDF) has had historical vulnerabilities. Only relax the policy on servers where you control the input files.
Method 4: ChangeThisFile API (any language, no local tools)
POST the TIFF to the API, receive PDF. Source is auto-detected from the filename — pass only target=pdf. Multi-page TIFFs are handled automatically. Free tier: 1,000 conversions/month, no card needed.
# Test with curl
curl -X POST https://changethisfile.com/v1/convert \
-H "Authorization: Bearer ctf_sk_your_key" \
-F "file=@scan.tiff" \
-F "target=pdf" \
--output scan.pdf
# Python
import requests
from pathlib import Path
API_KEY = "ctf_sk_your_key_here"
def tiff_to_pdf_api(tiff_path: str, pdf_path: str) -> None:
with open(tiff_path, "rb") as f:
response = requests.post(
"https://changethisfile.com/v1/convert",
headers={"Authorization": f"Bearer {API_KEY}"},
files={"file": (Path(tiff_path).name, f, "image/tiff")},
data={"target": "pdf"},
timeout=120,
)
response.raise_for_status()
Path(pdf_path).write_bytes(response.content)
tiff_to_pdf_api("scan.tiff", "scan.pdf")
// Node.js 18+
const fs = require('fs');
const path = require('path');
const API_KEY = 'ctf_sk_your_key_here';
async function tiffToPdfApi(tiffPath, pdfPath) {
const fileBuffer = fs.readFileSync(tiffPath);
const blob = new Blob([fileBuffer], { type: 'image/tiff' });
const form = new FormData();
form.append('file', blob, path.basename(tiffPath));
form.append('target', 'pdf');
const res = await fetch('https://changethisfile.com/v1/convert', {
method: 'POST',
headers: { 'Authorization': `Bearer ${API_KEY}` },
body: form,
});
if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`);
fs.writeFileSync(pdfPath, Buffer.from(await res.arrayBuffer()));
}
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| img2pdf | Archival, scan-to-PDF, zero quality loss | Doesn't support CMYK or palette-mode TIFFs |
| Pillow | All TIFF modes, multi-page, DPI control, re-encoding | Re-compresses pixels; slower than img2pdf for large files |
| ImageMagick | Shell scripts, quick one-off CLI conversions | Policy restrictions on server installs; PDF output may require config |
| ChangeThisFile API | Any language, no local tools, serverless environments | Network call; 25MB file limit on free tier |
Production tips
- Use img2pdf for scanned documents — it's lossless. Scanned TIFFs from document scanners are already compressed at the scanner's native DPI. Re-encoding with Pillow or ImageMagick introduces additional quality loss. img2pdf is pixel-perfect and faster.
- Set DPI metadata correctly. Pillow's
resolution=300parameter embeds DPI metadata in the PDF — without it, PDF viewers assume 72 DPI and the document appears enormous. TIFF files usually contain their own DPI metadata; Pillow inherits it automatically. - Handle multi-page TIFFs explicitly. Not all libraries handle multi-page TIFFs (each frame as a separate PDF page) automatically. Test with your actual files — a single-frame TIFF and a 100-page TIFF behave differently across tools.
- Watch for CMYK TIFFs from print workflows. CMYK TIFFs from print design software (InDesign, Photoshop) cannot be converted by img2pdf. Use Pillow with .convert('RGB') or let the ChangeThisFile API handle the conversion.
- For batch conversions, prefer img2pdf's multi-file API. Calling img2pdf.convert(*paths) converts multiple TIFFs into a single PDF in one call — faster than converting individually and merging.
For archival scan-to-PDF workflows, img2pdf is the best choice — lossless, fast, and zero re-encoding. For CMYK or palette-mode TIFFs, use Pillow. For shell scripts, ImageMagick. The ChangeThisFile API is the right call when you want no local dependencies. Free tier: 1,000 conversions/month.