DaddyNote

UK, Irish and EU money and travel rules, worked through step by step, with the date each figure was checked.

AI how-to notes · 2026-09-18 · last run 2026-09-18 · 한국어 원문

Shrinking a PDF — 40x smaller for a photo-heavy file, barely anything for a text one

One line summary

We took a 37.5 MB photo document and a 74 KB text document and tried to shrink them. The photo document came down to 3.7 MB (10.0%) simply by re-saving the images, and to 0.9 MB (2.5%) with the resolution halved. On the text document the best of the methods we tried landed at 95.4%, and turning on one option (clean=True) on its own made it bigger, at 182%. We installed no external program at all.

Who this is for

Test environment

ItemValue
OSWindows 11 Home 25H2 (10.0.26200.9457)
ShellPowerShell 7 / Git Bash
Edge153.0.4234.32
Python3.12.10
Python packagesPyMuPDF 1.28.0, Pillow, NumPy
External programsnone installed (no Ghostscript or anything similar)
Test filesTwo dummy PDFs made for the test. The photos are patterns drawn by a program

The two documents we tested

With file size, the answer depends entirely on what kind of document you have. So we made two.

DocumentPagesSizeWhat's inside
① Photo-heavy document639,305,252 bytes (37.5 MB)12 images, 72 characters
② Text-led document1274,157 bytes (0.07 MB)0 images, 13,359 characters
The two test documents. Two photos per page on top, text only underneath
The two test documents. Two photos per page on top, text only underneath

The first image on page 1 of the photo document is a 1500x1000 pixel PNG. It sits on the page 495 pt wide (about 17.5 cm), which works out to 218 dpi. That one image takes up 3,816,210 bytes.

We measured three things: file size, whether the text can still be extracted, and how much it smears when you zoom in. For the last one we rendered page 1 of the result and page 1 of the original at 200 dpi each and averaged the absolute difference of the pixel values (0–255). That value is what the tables below call "image difference"; 0 means it renders identically to the original.

Where sizes are given in MB, 1 MB is counted as 1,048,576 bytes. For exact figures, read the byte column.

First thing to know — save options alone won't shrink it

We applied PyMuPDF's cleanup options to save one at a time and then in combination. Going by the names, garbage clears out objects nothing uses any more, deflate compresses, and clean tidies the internal structure.

import fitz

d = fitz.open(r"C:\path\source.pdf")
d.save(r"C:\path\result.pdf", garbage=4, deflate=True, clean=True)
d.close()

Here is what they did to the photo document (39,305,252 bytes).

Save methodResult sizevs original
garbage=439,305,252 bytes100.0%
deflate=True39,305,252 bytes100.0%
clean=True39,306,909 bytes100.0%
garbage=4, deflate=True, clean=True39,304,874 bytes100.0%
the above plus deflate_images=True39,304,874 bytes100.0%
the above plus use_objstms=139,301,917 bytes100.0%
ez_save()39,301,959 bytes100.0%

The biggest saving was 3,335 bytes, or 0.01%. All of them finished in 0.1–0.2 seconds. In every case the images stayed at 12 and the first image stayed 1500x1000 pixels, and the image difference was 0.00 throughout. What carries the weight in this document is the images, and the save options we tested leave the images alone.

A photo-heavy document — re-save the images

PyMuPDF has rewrite_images, which picks out the images and saves them again. Going by the names, dpi_threshold decides which images get touched, dpi_target is the level to come down to, and quality is the JPEG quality. How many pixels actually came out is written into the table below exactly as measured.

import fitz

d = fitz.open(r"C:\path\source.pdf")
d.rewrite_images(dpi_threshold=101, dpi_target=96, quality=70)
d.save(r"C:\path\result.pdf", garbage=4, deflate=True, clean=True)
d.close()
SettingResult sizevs originalImage sizeResolution on the pageImage difference
untouched39,305,252 bytes100.0%1500x1000 PNG218 dpi0.00
threshold 151 / target 150 / q 803,915,066 bytes10.0%1500x1000 JPEG218 dpi1.83
threshold 151 / target 110 / q 753,325,472 bytes8.5%1500x1000 JPEG218 dpi1.91
threshold 101 / target 96 / q 70964,328 bytes2.5%750x500 JPEG109 dpi2.50
threshold 101 / target 72 / q 60836,249 bytes2.1%750x500 JPEG109 dpi2.70

All four runs took under 2 seconds (0.6–1.5 s in the log).

The biggest saving came from the image format, not the resolution. At the same 1500x1000 pixels, PNG turning into JPEG took one image from 3,816,210 bytes down to 364,280 bytes. For the whole file that is a tenth. Halve the resolution on top of that and you reach 2.5%.

It stands out that the pixel count went from 1500 to 750, exactly half. Whether we asked for target 110 or 150 it stayed at 1500, and dropping to threshold 101 / target 96 took it to 750 in one step. It does not match the dpi you write. Which setting gives how many pixels is something to settle by examining the result.

We zoomed in to look at the quality. This is the same spot enlarged to 400%.

The same spot on page 1 of the photo document at 400%, four versions, smearing more towards the right
The same spot on page 1 of the photo document at 400%, four versions, smearing more towards the right

The original and target=150, quality=80 have clean circle edges. From target=96 the edges show steps and the pattern starts to smear; target=72 is blurrier still. For drawings, or photos with small print in them, stopping before 96 is the safe call.

Extractable text stayed at 72 characters under every setting.

Bar chart of file size by method for the 6-page photo-heavy PDF
Bar chart of file size by method for the 6-page photo-heavy PDF

A text-led document — there is almost nothing to shrink

We put the same options on the 12-page text document (74,157 bytes). The best performer in this table, ez_save(), is called like this.

import fitz

d = fitz.open(r"C:\path\source.pdf")
d.ez_save(r"C:\path\result.pdf")
d.close()
Save methodResult sizevs original
garbage=474,156 bytes100.0%
deflate=True74,157 bytes100.0%
clean=True135,202 bytes182.3%
garbage=4, deflate=True, clean=True77,034 bytes103.9%
the above plus use_objstms=173,549 bytes99.2%
ez_save()70,712 bytes95.4%
subset_fonts() then garbage=4, deflate, clean77,033 bytes103.9%
rewrite_images(threshold=97, target=96, quality=70)77,139 bytes104.0%

The best, ez_save(), is a 4.6% reduction. And clean=True on its own takes the file to 135,202 bytes, 1.8 times the original. That is the spot where you switch something on to compress and end up inflating instead. We did not dig into why it grows. With garbage=4, deflate=True alongside it, the result was 77,034 bytes, so at least it did not reach 1.8 times.

Putting rewrite_images on a document with no images achieves nothing: 104.0%, slightly bigger rather than smaller. The 13,359 characters came out intact in every case.

We counted what is inside those 74,157 bytes. There are 15 compressed streams, and the two largest are the table mapping glyphs to Unicode (50,346 bytes) and the Korean font (15,619 bytes). Together that is 65,965 bytes, 89.0% of the file. All 13 of the others added together come to 1,290 bytes, and the content streams among them already carry FlateDecode. Take the font out and the text is not visible, so there is nothing left to shrink. The pages that would not shrink in the note on pulling pages out look to be the same streams at work.

Shrinking with nothing installed — printing from Edge

There is one route that needs no Python. Open the photo document in Edge, press Ctrl+P, pick 「Microsoft Print to PDF」 as the printer and print, and you get a new PDF.

ItemOriginalrewrite_images (150/q80)Edge print
Size39,305,252 bytes3,915,066 bytes (10.0%)1,641,339 bytes (4.2%)
Pages666
Images1500x1000 PNG 218 dpi1500x1000 JPEG 218 dpi1500x1000 JPEG 218 dpi
Extractable text72 characters72 characters0 characters
Image difference0.001.833.38
Page size595.0000 x 842.0000 pt595.0000 x 842.0000 pt595.3200 x 841.9200 pt

Of these three files the Edge printout is the smallest. In exchange, extractable text goes to 0. All six pages held 0 characters, 2 images and 8 vector drawings. For something like a photo log, where there was hardly any text to begin with, it is usable; for a report with a body, the loss is large. And since the resolution-lowering target=96 version (964,328 bytes) is smaller anyway, there is no reason to take this route if Python is available.

How much came off — at a glance

DocumentMethodBeforeAfterReduction
6 photo pagessave options only37.5 MB37.5 MB0.01%
6 photo pagesimages to JPEG (q80, resolution kept)37.5 MB3.7 MB90.0%
6 photo pagesimages to JPEG + half resolution (q70)37.5 MB0.9 MB97.5%
6 photo pagesEdge print37.5 MB1.6 MB95.8% (0 extractable characters)
12 text pagesez_save()74,157 bytes70,712 bytes4.6%
12 text pagesclean=True alone74,157 bytes135,202 bytesgrew by 82.3%

To sum up: in this test the photo document shrank a great deal, and the text document shrank 4.6% at best. Which side your file is closer to sorts itself out roughly by opening it and looking for photos. If the document is text only and the size is still a problem, dropping pages or splitting the file is the faster thing to look at.

What failed

>>> d.rewrite_images(dpi_threshold=96, dpi_target=96, quality=70)
ValueError: {dpi_target=} must be less than {dpi_threshold=}

dpi_target has to be smaller than dpi_threshold. The table in this note is the re-run with the threshold set above the target (151/150, 151/110, 101/96, 101/72).

What we did not test

Related notes