Image to PDF: page math
The Image to PDF tool contains no PDF library. A small writer assembles
the file byte by byte, and one pure function, pageGeometry, decides how large each
page is and where its image sits. Because it has no dependencies, this guide calls it: every
point value, offset and scale below was computed at build time by the same code that runs when
you press Create PDF, on illustrative inputs. The quoted fragments of PDF syntax are the
writer's real output on stub JPEGs.
Points, pixels and the DPI field
A PDF measures its pages in points; the writer's own definition of the unit is
1 pt = 1/72 in, and the conversion it applies is
points = pixels × 72 ÷ DPI. The DPI field declares how many of the
image's pixels should span one inch of page, and nothing else: a declaration, not a resample.
The writer refuses a value outside its range with "DPI must be between 24 and 600." Left alone, the field holds the
writer's default of 96 DPI — derived here by building a PDF with no options and reading
the page width back.
Illustratively, a 1920×1080 screenshot at 96 DPI becomes a 1440×810-point page. A 4032×3024 photo becomes 3024×2268 points at the same DPI and 967.68×725.76 points at 300 DPI, while the embedded JPEG declares 4032 by 3024 pixels in both. Raising the DPI makes the page smaller, not the image sharper.
"Match image": the page is the image
In this mode the page and the drawing rectangle are the same rectangle and both offsets are
zero. For the screenshot above the writer emits /MediaBox [0 0 1440 810] on the page object and
this content stream:
q
1440 0 0 810 0 0 cm
/Im0 Do
Q
The six numbers before cm are a transformation matrix — the first and fourth set the
drawn width and height in points, the last two the position of the bottom-left corner — and /Im0 Do paints the image into that rectangle. Numbers are written with at most two decimals and no trailing
zeros.
| Image (px) | DPI | Page (pt) | Page (in) |
|---|---|---|---|
| 1920×1080 | 96 | 1440×810 | 20.00×11.25 |
| 4032×3024 | 96 | 3024×2268 | 42.00×31.50 |
| 4032×3024 | 300 | 967.68×725.76 | 13.44×10.08 |
A4 and Letter: fit inside the margins, then centre
The two fixed page sizes are constants in the writer: A4 is 595.28×841.89 points (8.27×11.69 inches) and Letter is 612×792 points (8.50×11.00 inches). Around each runs a margin of 36 points, 0.5 inch, on every side — the writer's default, which the tool never overrides (its page offers no margin control), recovered here from a width-bound A4 page's horizontal offset. The writer accepts other margins within the bound it states as "Margin must be between 0 and 200 points."
Subtracting the margin twice leaves the available area: 523.28×769.89 points on A4,
540×720 on Letter. The image's size in points is computed as in "match
image", the scale is
min(availableWidth ÷ imageWidth, availableHeight ÷ imageHeight), the drawn
rectangle is the image size times that scale, and each offset is half the space left over, which
centres it.
Worked through for the 4032×3024 photo on A4: at 96 DPI the image measures
3024×2268 points. The width ratio is
0.1730 and the height ratio 0.3395; the smaller wins, so the
scale is 0.1730 and the image is width-bound. It is drawn at
523.28×392.46 points, offset 36 points from
the left — the margin, as width-bound implies — and 224.72 points from the bottom.
In the file this becomes /MediaBox [0 0 595.28 841.89] and 523.28 0 0 392.46 36 224.72 cm.
| Image (px) | Page | Bound by | Drawn (pt) | Offset x, y (pt) |
|---|---|---|---|---|
| 4032×3024 | A4 | width | 523.28×392.46 | 36, 224.72 |
| 1000×4000 | Letter | height | 180×720 | 216, 36 |
| 800×800 | A4 | width | 523.28×523.28 | 36, 159.31 |
| 300×200 | Letter | width | 540×360 | 36, 216 |
A height-bound image mirrors the case above: its vertical offset is the margin and the spare width is split either side. Nothing is cropped or stretched; both dimensions share one scale, so the aspect ratio is preserved by construction.
Why DPI does nothing on A4 and Letter
The image's size in points is proportional to 1 ÷ DPI and the fit scale is proportional to DPI, so their product contains no DPI at all; the drawn rectangle depends only on the aspect ratio and the available area. The build asserts this: the photo fits A4 at 523.28×392.46 points whether 96 or 300 DPI is declared, or this page fails to build. Hence the tool's label, DPI (for "match image").
Small images are scaled up to the margins
The fit scale has no ceiling of one. The illustrative 300×200 image measures 225×150 points at 96 DPI; on Letter it is drawn at 540×360 points, a scale of 2.40. Only the drawing rectangle grows: the image object still declares 300 by 200 pixels, so the same pixels cover more page and no detail is added. To change the pixel count itself, resize first — see the resizing guide.
What the writer puts in the file
The file opens with the header %PDF-1.4 and a binary-marker comment line. Then come
the objects in a fixed layout: a catalog, a page tree, and for every image three more — a page object,
a content stream and an image XObject. The illustrative three-page build therefore contains 11 objects,
3 of them page objects, a page tree declaring /Count 3 and a trailer
declaring /Size 12, one more than the object count for the free entry at
index zero.
The screenshot's image XObject reads << /Type /XObject /Subtype /Image /Width 1920 /Height 1080 /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length 63 >>. Its width and height come from
the JPEG's own frame header, not from the canvas or the page: jpegDimensions
walks the marker stream to the start-of-frame segment and reads 1920×
1080 with 3 components for this stub. The component count selects
the colour space — one component is written as /DeviceGray, four as
/DeviceCMYK, anything else as /DeviceRGB — at
/BitsPerComponent 8, and the filter is always
/DCTDecode. The writer reads nothing else from the JPEG; any density fields it
carries play no part in the page size.
The cross-reference table lists every object's byte offset and startxref gives the table's
own — 564 in the single-page build, where the bytes at that position read xref. The trailer is << /Size 6 /Root 1 0 R >>: an object count and a catalog reference, no
document information dictionary, so the PDF carries no title, author or date. The file ends with %%EOF. Its size is the JPEG bytes plus this structure — 1594 bytes for the three stub fixtures,
an illustrative figure that real JPEGs dwarf.
How your images reach the writer
The writer accepts only JPEG bytes, so the tool's client prepares every image the same way, JPEG
inputs included: decode with createImageBitmap and
imageOrientation: 'from-image', so recorded rotation is applied; draw onto a canvas
over white; encode as JPEG at the slider's quality; hand the bytes to
buildPdf. The planning table every converter consults agrees for a JPEG target: a
PNG source needs a background flatten (true, default colour #ffffff), a GIF source needs one and drops animation to its first frame (true), a WebP source needs one and drops animation to its first frame (true), a BMP source needs one (true), a JPEG source has nothing
to flatten (false). The PDF client flattens onto white
without asking and marks animated inputs on their row; the
transparency guide explains the table.
Because the pixels pass through a canvas, the re-encode strips all embedded metadata — EXIF including GPS location, camera model and timestamps, colour-profile tags, and text chunks. Orientation is applied during decoding, before the metadata is discarded, so uprightness survives. The metadata guide sets out what that means; keep your originals.
What the writer refuses
The writer returns a result rather than throwing, and every refusal carries a plain message: an empty list, "Add at least one image to build a PDF."; a margin outside its range, "Margin must be between 0 and 200 points."; bytes that are not JPEG — here the eight-byte PNG signature — "Not a JPEG file (missing FF D8 FF signature).", which inside a batch names the offending position: "Image 2: Not a JPEG file (missing FF D8 FF signature)." The reader also refuses a marker stream with no frame header or one reporting zero dimensions, so no page is ever sized from a guess.
What is verified, and what the tests pin
The writer is pure bytes in, bytes out, so its tests assert the structure directly: header and EOF marker; object count for one, two and three images; each JPEG present byte for byte; the XObject declaring /DCTDecode with the JPEG's own dimensions, and the grayscale colour space for one-component frames; the exact MediaBox strings for "match image" at the default and a custom DPI and for A4 and Letter; every cross-reference offset landing on its object header; the trailer's size and a content stream's declared length; and the refusals above. When you press Create PDF, the client re-checks header, EOF marker and the trailer's object count before the download appears — the "Verified structure" badge means those checks passed.
What the tool cannot know
It does not know what physical size you intend; the DPI field is your statement, recorded as given. It does not know how a viewer will render the pages; it writes a transformation and an image stream and stops. It does not judge whether an enlarged or reduced image looks acceptable. And it cannot see transparency flattened before the bytes reached it, which is why the tool page states the flattening before you build.
Common misreadings
- "Higher DPI means a sharper PDF." The pixel count is fixed by the encoded JPEG; DPI only changes how large a "match image" page is declared to be.
- "A4 mode resizes my image." The pixels are unchanged; the content stream asks the viewer to draw them into a smaller or larger rectangle.
- "Verbatim means my original file is inside the PDF." The JPEG the tool encoded is embedded unmodified; your original bytes and their metadata are not.
- "The PDF compresses the images." It stores the JPEG streams under /DCTDecode as they are; the file size is their sum plus the structure.
Related guides and tools
Build a document with Image to PDF, or scale pixels first with the Image Resizer. The conversion pipeline guide follows an image from decode to verified output, the guides index lists the rest, and the methodology page places the writer among the site's engines.
Frequently asked questions
Does the DPI setting change the image, its pixels or its quality?
No. DPI only changes the page size declared in "match image" mode. An illustrative 4032×3024 image is a 3024×2268-point page at 96 DPI and a 967.68×725.76-point page at an illustrative 300 DPI, and the embedded JPEG declares /Width 4032 /Height 3024 in both.
Why does an image land at the same size on A4 whatever DPI I type?
Because the fit arithmetic cancels it: the image's size in points shrinks as DPI rises and the scale factor grows by the same ratio. The build checks this — an illustrative 4032×3024 image fits A4 as 523.28×392.46 points at 96 DPI and at 300 DPI alike — which is why the tool's label reads DPI (for "match image").
Can I change the margin on A4 and Letter pages?
Not from the tool page, which has no margin control; the client passes only the page mode and DPI, so the writer's default of 36 points (0.5 inch) applies on every side. The writer itself accepts other values within the bound it states as "Margin must be between 0 and 200 points."
Is my original JPEG embedded untouched?
No. Every image, JPEG inputs included, is decoded, drawn onto a canvas over white and re-encoded as JPEG at the quality set on the slider; that re-encoded JPEG is what the writer embeds byte for byte. "Verbatim" describes the writer, not the whole tool.
Why is a small image enlarged on an A4 or Letter page?
The fit scale has no ceiling, so an image smaller than the area inside the margins is scaled up to meet them. An illustrative 300×200 image on Letter is drawn at 540×360 points, a scale of 2.40. Only the drawing rectangle grows; no detail is added.
Does the PDF compress the images further, or add metadata of its own?
Neither. The JPEG streams are stored under /DCTDecode with no further encoding, so the file is the sum of the JPEGs plus the structure around them, and the trailer holds only the object count and the catalog reference — no title, author or date is written.
Local processing: the images and the PDF this guide describes are decoded, converted, and verified entirely in your browser and never transmitted. How the writer works — and how to verify nothing is uploaded — is on the methodology page.