File & PDF Tools

PDF to Images

Render PDF pages as PNG, JPEG, or WebP images.

PDF to images
Render selected pages as PNG, JPEG, or WebP images.
Local processing

PDF to Images

Render selected pages with PDF.js as PNG, JPEG, or WebP. Multiple results are packaged in a ZIP.

Quality

  • Higher scale means more pixels and memory
  • Up to 100 pages and 120 megapixels per run
  • Rendered images lose searchable text and vector structure

Draws selected pages of a PDF onto an HTML canvas with PDF.js and exports them as PNG, JPEG, or WebP at 0.5x to 4x scale, where 1x is 72 dpi. Aimed at turning a report, deck, or signed contract into slide images, thumbnails, or OCR input, not at recovering reusable text.

One PDF point, one pixel: what the scale field multiplies

PDF page geometry lives in user space, where one unit is 1/72 inch, so scale 1 renders at 72 dpi, scale 2 at 144 dpi, and scale 3 at 216 dpi. getViewport takes the page view box — CropBox, falling back to MediaBox — multiplies it by the scale and by /UserUnit, then applies /Rotate, which is why a page carrying /Rotate 90 exports in landscape without any extra setting. Canvas width and height are Math.ceil of that viewport, never rounded down: an A4 page of 595.276 × 841.89 points becomes 893 × 1263 px at the default 1.5 and 1191 × 1684 px at 2. Each page is followed by page.cleanup() so the decoded operator list is released before the next one.

Why 60 A4 pages fail at 2x while 100 pass at 1.5x

Three guards run in a fixed order after you press render. The scale must sit between 0.5 and 4, and values outside that are rejected rather than clamped. The page expression must parse and denote at most 100 pages. Then a running total of ceil(width) × ceil(height) has to stay at or below 120,000,000 px. At the default 1.5, one A4 page costs 1,127,859 px, so a full 100-page run totals 112,785,900 and passes. At 2x a page costs 2,005,644 px: 59 pages total 118,332,996, and the 60th crosses the cap at 120,338,640. At 4x, 8,022,576 px per page allows only 14 pages. The total accumulates in selection order, and a trip discards everything already rendered.

What the raster keeps and what it drops

PDF.js replays the page operator list — glyph draws for text, paths for vectors — onto the canvas, so every glyph is resampled into pixels: the result cannot be selected, searched, or read by a screen reader, and no OCR runs, which is why a 300 dpi render of a scan still carries no text layer. beginDrawing fills the whole canvas with #ffffff before the first operator, so output stays opaque even though the context is created with alpha enabled for PNG and WebP; a page with no background rectangle is not exported as transparent. JPEG has no alpha channel and is encoded at the quality slider value, 0.2 to 1 with a 0.9 default; the slider is hidden for PNG because that encoder ignores it.

Worked example

Exporting pages 1-2 of an A4 report at 2x

Input
report.pdf with A4 pages (595.276 × 841.89 pt), page field 1-2, format PNG, scale 2
Output
report-page-001.png and report-page-002.png, each 1191 × 1684 px, or report-images.zip holding both files when downloading them together

595.276 × 2 is 1190.55 and 841.89 × 2 is 1683.78, and the canvas rounds both up, giving 1191 × 1684 px, or 2,005,644 px per page — 144 dpi, exactly twice the linear resolution of scale 1. The pair totals 4,011,288 px, roughly 3% of the 120-megapixel budget for one run.

Limits and pitfalls

  • Scale is only checked when rendering starts and the field is never clamped, so typing 5 returns an error instead of being quietly reduced to 4.
  • One run allows 100 pages and 120,000,000 output pixels in total; at 2x the pixel cap binds first, so a long A4 document stops at 59 pages even though the page limit is 100.
  • Quality applies to JPEG and WebP only, and the slider disappears for PNG; PNG weight is governed by pixel dimensions and page complexity alone.
  • Files are named after the source page padded to three digits, so anything past page 999 yields page-1000.png, which sorts ahead of page-999.png in name order.
  • Duplicate page numbers collapse (1-3,3 renders three images), the input PDF is rejected above 100 MB, and the multi-page ZIP is written at fflate level 6, which barely shrinks PNG or JPEG members.

Related tools

When it goes wrong

The .jpg I downloaded is rejected by the print shop as "not a JPEG", yet the same picture opens perfectly in the browser tab.

CauseThe pixels are produced by a render, the suffix is produced by a download. extension() reads format at the instant you click download, but the Blob itself was encoded by canvas.toBlob(type, quality) during the last run, with whatever format was selected then. Switching PNG to JPEG after rendering only relabels the existing PNG bytes; nothing is re-encoded.

FixSet the format first, then press Render pages again so a new pass encodes fresh Blobs. To confirm a file, open it in a hex viewer: a JPEG starts with FF D8, a PNG with 89 50 4E 47.

My .webp downloads open as PNG in Preview, and the file is exactly as heavy as the PNG export I made a minute earlier.

CausetoBlob falls back to image/png when the canvas has no encoder for the requested MIME type, so the produced Blob carries type: image/png. The component never inspects blob.type: extension() just splits the selected format string, and downloadAll writes that suffix onto the ZIP entry name too.

FixCheck the header of any suspicious file (PNG begins 89 50 4E 47). If you get PNG bytes, that browser cannot encode WebP — export PNG deliberately and compress it with the Image Compressor, or rerun in a Chromium-based browser.

I typed 12-8 so the appendix would show up first, and the result grid really did come out backwards: page 12, then 11, then 10.

CauseparsePageSelection accepts a reversed pair: when start is greater than end it sets the step to -1 and pushes those pages in that direction. rendered is filled in loop order, and downloadAll inserts ZIP entries in the same order, so the archive is internally descending as well — only the padded names page-012, page-011 let a file manager sort it back.

FixWrite 8-12 when you want page order. If the point is "appendix first", reorder after unzipping; the only ordering control this tool has is the page expression itself.

I wiped the prefilled range to type one page number, got interrupted, hit Render pages, and it died with "Output exceeds 120 megapixels".

CauseAn empty field means all pages, not none: parsePageSelection returns early with every page index of the document because defaultAll defaults to true and this component passes no options. The whole book is queued, and the running totalPixels counter aborts the run the moment ceil(width) × ceil(height) crosses 120,000,000.

FixType the page number rather than clearing the box; it is refilled with 1-N on every file selection. For a genuinely long document, lower the scale so the arithmetic fits, or split it into two runs — the counter throws instead of quietly rendering what fits.

Rendering a plotter-size drawing at 4x fails with "Image conversion failed. Try a PNG, JPEG, or WebP image", although my input is a PDF and I never picked an image.

CausetoBlob invokes its callback with null when the canvas is beyond what the browser encoder can handle (a dimension or total area limit), the promise rejects with IMAGE_ENCODE_FAILED, and friendlyFileError maps that code to a sentence written for the image-conversion tools — advice about an input format that does not exist on this page.

FixRead the width × height printed under a rendered thumbnail, then step the scale down until that drawing encodes, or crop the sheet to the region you need before rendering.

One concrete scenario

One signed contract, two passes, because scale is per run

Thirty-four A4 pages (595.276 × 841.89 pt) of a signed contract have to leave as images: three signature pages at 300 dpi for the archive, the other thirty-one only for a review meeting. At 1.5 each page is 893 × 1263 px, that is 1,127,859 px, so the review pass totals 34,963,629 px. Scale is one value for the whole run, so the signature pages need their own pass: at 4 they are 2382 × 3368 px, 8,022,576 px each, 24,067,728 px for the three — about 288 dpi, close enough to the 300 the archive asks for. Both passes stay far under the 120,000,000 px ceiling, yet each costs its own Render click, and each ZIP has to be downloaded before the next pass replaces the list: review set as JPEG at quality 0.9, archive set as PNG.

Frequently Asked Questions

What determines the exported image’s sharpness?
The scale factor: 1x ≈ the page at 72 dpi, 2x ≈ 144 dpi, 4x ≈ 288 dpi approaching print preview. 2x suffices for screens; start at 3x for printing or OCR. Higher scales cost time and memory.
Why do multi-page exports arrive as a ZIP?
Browsers will not fire many consecutive downloads, so multi-page results are packaged into one ZIP — open it with any unzip tool. Single-page exports download the image directly.
Is text still selectable in the exported images?
No. Rasterizing turns text into pixels and loses the text layer — exactly the difference from "extract text". Use images for social posts and slides; use the text extractor to reuse wording.

Content reviewed: