I wanted to turn a multi-page PDF handout into one tall image, the kind people scroll through on a phone. The converter offered 144, 216 and 300 DPI, so I exported at 216 and then at 300 to see how much sharper it would get. The two files were byte-for-byte identical. This post is the trail from that oddity to the actual cause, which turned out to be a hard size limit in the WebP format.
The setup
My test file was an 8-page A4 handout I made myself: a header bar, a paragraph, a photo or bar chart and a small table on each page, plus a line of 7pt text at the bottom that I use to judge sharpness. The two photos are CC BY-SA 4.0 images from Wikimedia Commons. I used ImgIng (https://imging.ai/) to do the conversion, on an Apple M4 Mac with Chromium 149, with its PDF-to-image tool set to stitch the whole document into one long image as WebP.
At 216 DPI I got a 1446×16376 image of 1,682,308 bytes. At 300 DPI I got the same dimensions and the same byte count. My first guess was some kind of caching, so I ran the 216 export twice more. All three runs produced the same bytes. The interface showed the dimensions on the result card and nothing else, with no hint that anything had been scaled.
Doing the arithmetic
An A4 page is 8.264 inches wide. At 216 DPI that should be 1785 pixels, and eight pages stacked should come to 1785×20208. My image was only 1446 wide. Dividing the width by the page width gives the real resolution: 1446 / 8.264 is about 175 DPI. So the setting I picked never took effect. The whole image had been scaled down proportionally, and the height had stopped at 16376, seven pixels short of 16383.
That number rang a bell. WebP stores width and height in 14 bits each, so neither side can exceed 16383. I checked it with Pillow on my machine:
import PIL.Image
for rows in (16383, 16384):
strip = PIL.Image.new("RGB", (8, rows))
try:
strip.save(f"strip_{rows}.webp")
print(rows, "saved")
except ValueError as e:
print(rows, e)
The first one saved. The second raised a ValueError ending in Image size exceeds WebP limit of 16383 pixels. Working backwards from that limit, eight A4 pages at 216 DPI can't fit, and only six pages or fewer keep the full 216.
One thing I still can't explain: switching the format to JPG or PNG in the same long-image mode gave me 1446×16376 as well. Those formats can store much taller images. My guess is that the long-image mode applies the WebP limit to every format, but I haven't confirmed that.
Browsers don't complain either
If you build long images yourself with a canvas, it gets sneakier. When I asked Chromium 149 for a 1786×20208 canvas as WebP via toBlob, it handed back a 1786×16383 image with no error. Firefox 151 returned null for the same request. WebKit 26.5 gave me a PNG instead. For JPEG, Chromium returned 65500 rows when I asked for 65535. I only measured dimensions in Chromium, so I can't tell you whether the missing rows were cropped or scaled away.
What actually helps
Changing the DPI setting does nothing once you're over the limit. A 20-page version of the handout came out at 578×16380, roughly 70 DPI, whether I picked 144, 216 or 300. Splitting it into four 5-page chunks gave four images of 1785×12630 each, back at the full 216 DPI. The cost was 4.35 MB in total instead of 986 KB for the single image. The tool has no page-range option, so I split the PDF with PyMuPDF first.
My habit now is to read the width of every exported long image and divide it by the page width in inches before sharing it. If the number is far below what I chose, I split the document into chunks of six pages or fewer and export again.
Top comments (0)