DEV Community

AsyncMonk
AsyncMonk

Posted on

Pulling assets out of a client's brochure PDF: where raw extraction stops being enough

A typical freelance message: "Here's our brochure as a PDF, can you put these pictures on the site?" Pulling out the images stored in the file is the cheapest answer, and it hands back original pixels instead of a screenshot. Before sending anyone a hero image I wanted to know when that is enough and when the extracted file isn't the picture the brochure shows. I have no client file to show, so I used a public brochure (Creative Commons Indonesia's "Brosur Creative Commons Indonesia", CC BY 4.0, via Wikimedia Commons) and a one-page PDF I wrote byte by byte with four images whose answer I know. Desktop Chromium 149 on an M4 Mac, 1 KB = 1024 bytes, one sample each, default settings.

The brochure was the easy case

The brochure is two A4 landscape pages. I opened it in ImgIng (https://imging.ai/) in the desktop browser, which reported 8 results, 419 KB, with cards marked page 1 and page 2, and the saved ZIP was 430,633 bytes. The cover came out at 624×882. A Retina screenshot at 100% zoom would be 399×565 (simulated, not a real screenshot).

Catches I'd tell a client up front: it takes one PDF per run (I dragged in two files and it read the first, with no message about the second); a page with no images gives 0 results; and ZIP files are numbered image-001 and so on, so page numbers exist only on the on-screen cards.

The brochure couldn't tell me if anything was cropped

PyMuPDF lists 9 image placements and 20 clip entries across the brochure's two pages. The list doesn't say which clip sits on which image and I never matched them, so I can't say whether any of the 8 pictures is partly hidden. I built a file where I know.

Four images, three agreeing tools

The hand-built page has four lossless images. Each has coloured corners and a big letter so I can tell its direction. The page draws A through a clip rectangle that keeps the left half, B rotated 90° counterclockwise, C mirrored, and D as a 16-colour palette image with a /Decode [15 0] entry. I ran pdfimages 26.08.0, PyMuPDF 1.26.5 and the decoded bitmap from pdf.js 4.10.38 (it has no public extract call, so I took the bitmap in Chromium).

Four cases: what three extraction tools return

Compare "Visible on the page" with "Extracted": A shows its left half but comes back whole at 240×160, B is portrait on the page and landscape in the file, and C is mirrored on the page but not in the file. The last column says "yes" because pdfimages, PyMuPDF and pdf.js returned identical pixel hashes, and on D all three also matched the page. ImgIng is not in this picture.

I ran ImgIng on the same file. On A, B and C it returned the stored image like the other three: whole, not rotated, not mirrored. On D it did not match the page: it saved the palette colours as stored, so the red bar sat on the opposite side, where the other three had applied the /Decode. I only tried this 4-bit [15 0] case, not an 8-bit palette.

What I look at before I hand over an extracted file

The pixels can't say how the page uses them, but PyMuPDF gives each image's transform.

import fitz
pdf = fitz.open("four_images.pdf")
for tag, im in zip("ABCD", pdf[0].get_image_info(xrefs=True)):
    a, b, c, d = im["transform"][:4]
    print(tag, "rotated" if b or c else "-", "mirrored" if a < 0 else "-")
Enter fullscreen mode Exit fullscreen mode
A - -
B rotated -
C - mirrored
D - -
Enter fullscreen mode Exit fullscreen mode

On this file that is enough: B has (0, -180, 120, 0) and C has a negative first number. That reading comes from the two transforms in this file, so the rule is a starting point.

Clipping is trickier. The bbox for A is 180 pt wide, the whole drawn picture, though the page shows 90 pt. Two ways out: render the page and crop, or read the clip rectangle and crop the extracted file. On this file the second matches a 96 dpi page render exactly (average error 0.00 on a 0 to 255 scale), because the image is stored at 96 pixels per inch. At other render sizes the error is 0.98 to 2.51, from resampling, and a 600 dpi render only upscales the stored 240 pixels.

What I haven't tested

A mirror combined with a clip, rotations that aren't multiples of 90° and three or more nested clips are untested. I also don't know how the brochure's InDesign output compares with a hand-built PDF; I'd guess it lays images out the same way, but that is a guess. My routine for a client file is now: extract, run the transform check on every image, then put the extracted cover next to a render of its page before I rename anything.

Top comments (0)