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

MethodQualityMulti-page TIFFBest for
img2pdf (Python)Lossless — zero re-encodingYes (each frame = one PDF page)Archival, scan-to-PDF, no quality loss
Pillow (Python)Re-encoded (configurable)YesResize, DPI override, compression control
ImageMagick CLIRe-encodedYesShell scripts, quick one-off conversions
ChangeThisFile APIServer-side PillowYesNo 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

ApproachBest forTradeoff
img2pdfArchival, scan-to-PDF, zero quality lossDoesn't support CMYK or palette-mode TIFFs
PillowAll TIFF modes, multi-page, DPI control, re-encodingRe-compresses pixels; slower than img2pdf for large files
ImageMagickShell scripts, quick one-off CLI conversionsPolicy restrictions on server installs; PDF output may require config
ChangeThisFile APIAny language, no local tools, serverless environmentsNetwork 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=300 parameter 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.