You ran the command everyone recommends for Ghostscript PDF compression:
gs -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -dNOPAUSE -dQUIET -dBATCH -sOutputFile=out.pdf in.pdf
Ghostscript finished without errors. You check the output — and it’s exactly the same size as the original. Sometimes it’s even larger. If yours came back larger rather than unchanged, that is a different mechanism and I measured it separately in why Ghostscript made your PDF bigger.
This isn’t a bug, and your file isn’t broken. It’s a threshold rule that Ghostscript’s own documentation explains, but that almost every practical how-to guide leaves out — and nobody publishes the numbers for. Below is the rule, plus measurements from my own test runs showing exactly when /ebook works and when it does nothing.
The rule in one line
Ghostscript only downsamples an image when the source resolution is more than 1.5× the target resolution.
That 1.5 is the default ImageDownsampleThreshold, and none of the four presets override it. So the preset alone doesn’t decide anything — the ratio between your source DPI and the preset’s target DPI does.
| Preset | Target DPI (color & grayscale) | Downsamples a 300dpi scan? | Downsamples a 150dpi scan? |
|---|---|---|---|
/screen |
72 | Yes (ratio 4.2) | Yes (ratio 2.1) |
/ebook |
150 | Yes (ratio 2.0) | No (ratio 1.0) |
/printer |
300 | No (ratio 1.0) | No |
/prepress |
300 | No (ratio 1.0) | No |
So if your scanner produced 150dpi images and you reach for /ebook (target 150), the ratio is 1.0 — below the threshold — and Ghostscript rewrites the file without touching a single image.
One important exception: 1-bit (bilevel) scans follow a separate path with different targets — /screen and /ebook aim at 300dpi, /printer and /prepress at 1200dpi, governed by MonoImageDownsampleThreshold. If your scan is black-and-white rather than grayscale, use those numbers instead of the table above.
Measured: the same command, two different sources
I generated two grayscale scan-style PDFs — one at 150dpi, one at 300dpi — and ran each preset against both. Same command, same Ghostscript build. Only the source resolution differs.
300dpi source (1.69 MB)
| Preset | Output | Reduction |
|---|---|---|
/screen |
0.10 MB | 93.9% |
/ebook |
0.26 MB | 84.7% |
/printer |
1.69 MB | 0% (output marginally larger) |
150dpi source (1.83 MB)
| Preset | Output | Reduction |
|---|---|---|
/screen |
0.27 MB | 85.1% |
/ebook |
1.83 MB | 0% (output marginally larger) |
/printer |
1.83 MB | 0% (output marginally larger) |
/ebook cut the 300dpi file by 84.7% and the 150dpi file by nothing at all. The preset didn’t change. The ratio did.
One detail worth knowing: downsampling lands on integer decimation factors, so you don’t always hit the target exactly. /screen on my 150dpi file produced 75 ppi, not 72 — it halved rather than scaling precisely to the target.
What doesn’t fix it — and why the usual advice is wrong
Two flags get recommended constantly in forum threads about this problem. Neither moved the number on my 150dpi file, and it’s worth being precise about why, because the two failure modes are completely different.
| Attempt | Result |
|---|---|
/ebook alone |
1.83 MB — 0% |
+ -dRemoveUnusedResources=true (not a real parameter) |
1.83 MB — 0% |
+ -dSubsetFonts=true (already the default) |
1.83 MB — 0% |
+ lowered threshold |
0.64 MB — 65% |
-dSubsetFonts=true does nothing because it is already the default. The distiller parameter table ships with /SubsetFonts //true. You are switching on a switch that was never off.
-dRemoveUnusedResources=true does nothing because Ghostscript has no such parameter. It appears nowhere in the distiller parameter table or the official documentation. And here is the part that traps people: Ghostscript silently accepts any unrecognised -d token. Passing -dTotallyFakeParameter=true also completes with exit code 0 and no warning. So “the command ran without errors” is never evidence that a flag did anything at all.
The real reason the byte count barely moves is PassThroughJPEGImages, which defaults to true: a JPEG that doesn’t clear the downsample threshold is copied straight through without re-encoding. On a scanned document the images are effectively the entire file, so nothing else you strip is large enough to matter. The chain is: threshold not met → downsampling skipped → original JPEG passed through untouched → output size unchanged.
The fix
Option 1 — pick a preset that’s actually below your source
Simplest path. If your source is 150dpi, /screen clears the threshold and gave me an 85% reduction:
gs -sDEVICE=pdfwrite -dPDFSETTINGS=/screen -dNOPAUSE -dQUIET -dBATCH -sOutputFile=out.pdf in.pdf
Option 2 — lower the threshold and set your own DPI
When /screen is too aggressive but the preset above it does nothing, drop the threshold to 1.0 so any image above your target gets downsampled:
gs -sDEVICE=pdfwrite -dPDFSETTINGS=/ebook -dDownsampleGrayImages=true -dGrayImageResolution=120 -dGrayImageDownsampleThreshold=1.0 -dNOPAUSE -dQUIET -dBATCH -sOutputFile=out.pdf in.pdf
On the 150dpi test file this produced 0.64 MB — a 65% reduction, sitting between the untouched /ebook output and the heavy-handed /screen result.
For color images use the color equivalents: -dDownsampleColorImages=true, -dColorImageResolution, -dColorImageDownsampleThreshold.
Check your source resolution first
Every decision above depends on one number you probably haven’t checked: the actual DPI of the images inside your PDF. The standard tool is pdfimages, which ships in poppler-utils (apt install poppler-utils on Debian/Ubuntu, brew install poppler on macOS):
pdfimages -list input.pdf
Read the x-ppi and y-ppi columns — that is your source resolution. Also check bpc: if it reads 8 you are on the color/grayscale path and the table above applies; if it reads 1, you have a bilevel scan and the mono targets apply instead.
Compare that resolution against the preset targets. If the ratio is not comfortably above 1.5, no image will be downsampled — but that is a statement about images, not about the file. Ghostscript also subsets embedded fonts, and on a text-heavy source with no qualifying images at all that alone took 70,775 B to 14,847 B (-79.0%) under /ebook. Check pdffonts before concluding there is nothing to win.
Two related cases worth knowing
- Text-only PDFs have nothing to downsample — but that is not the same as nothing to gain. If the source embeds its fonts without subsetting them, pdfwrite subsets them and the file collapses: a 10-page text-only PDF with a fully embedded C059-Roman face went 70,775 B to 14,847 B (-79.0%) with
/ebookalone (pdffonts: sub: no becomes yes, prefix ACAQMC+), while qpdf, which cannot subset, managed only -4.4%. The bad case below is specifically a file whose fonts are not embedded. Running a 10-page text document (8.2 KB) through the presets gave me 9.3 KB with/ebookand 16.0 KB with/printer. The higher presets are dangerous only when the source leaves its fonts unembedded:/printerand/prepressset NeverEmbed to empty and CompatibilityLevel to 1.7, so base-14 fonts get embedded for the first time and the file grows — measured 9,039 B to 14,054 B (+55.5%). When the fonts are already embedded, the same preset shrinks the file instead, because pdfwrite subsets them: 70,775 B to 17,680 B (-75.0%). That was true of older releases, but not of the version measured here: since Ghostscript 10.03 (2024) pdfwrite writes object streams and compressed cross-reference streams by default (-dWriteObjStmsand-dWriteXRefStmare both true), and they are disabled only when the output stays below PDF 1.5 — for example under-dCompatibilityLevel=1.4, which on my text file cost 1,483 bytes (16,330 B instead of 14,847 B). A source that already used object streams did not grow when rewritten: 67,673 B in, 14,847 B out. Text and fonts are already losslessly compressed; a downsampler has nothing to work with. - Re-compressing an already-compressed file gains nothing. Running
/ebooka second time over its own output produced 0% additional reduction. Once the images have been downsampled and re-encoded, repeating the pass only risks extra generation loss.
Quick reference
| Symptom | Cause | Action |
|---|---|---|
| Output identical to input | Source DPI ÷ target DPI ≤ 1.5 | Use a lower preset, or set threshold to 1.0 |
| Output larger than input | Source fonts were not embedded, and a higher preset (/printer, /prepress) embedded them for the first time |
Stay on /screen or /ebook. First run pdffonts: if fonts are already embedded with sub: no, Ghostscript will subset them and shrink the file sharply instead |
| Quality too low | Preset target far below source | Move up one preset, or set an explicit DPI |
| Second pass does nothing | Images already downsampled | Keep the first output |
| Black-and-white scan behaves oddly | Bilevel images use mono targets | Compare against 300 / 1200dpi, not 72 / 150 |
Test setup. Measured 19 July 2026 · Ghostscript 10.05.1 · Linux. Test files were synthetic grayscale scan-style PDFs generated at 150dpi (3 pages, 1.83 MB) and 300dpi (1 page, 1.69 MB), plus a 10-page text-only PDF (8.2 KB). Figures are the output files produced by each command. Your own numbers will vary with image content and color depth — the threshold behavior itself is deterministic and reproducible.
References: Ghostscript — Optimizing PDFs · Ghostscript VectorDevices reference (image downsampling parameters)