Skip to content

Let a PNG declare the resolution it was rendered at - #1118

Closed
oetiker wants to merge 1 commit into
linebender:mainfrom
oetiker:feat/png-dpi
Closed

Let a PNG declare the resolution it was rendered at#1118
oetiker wants to merge 1 commit into
linebender:mainfrom
oetiker:feat/png-dpi

Conversation

@oetiker

@oetiker oetiker commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Adds resvg::encode_png_with_dpi and resvg::save_png_with_dpi, plus a --png-dpi flag
on the CLI.

Why

A PNG without a pHYs chunk carries no resolution, so whatever opens it has to guess one,
and most guess 96 DPI. An image rendered for print or for a high resolution display then
shows up at the wrong physical size. This matters particularly for SVG output, since the
resolution is also what decides how large the text in the image ends up: rendering a
document at 300 DPI and then having it displayed as if it were 96 undoes exactly the
sizing the document asked for.

tiny_skia::Pixmap::encode_png writes no pHYs chunk and offers no way to add one, so
this cannot be done by the caller without re-encoding the image by hand.

Which resolution

The value is the resolution of the rendered image, which is only the same as
usvg::Options::dpi when the tree was rendered without scaling. --zoom, --width and
--height all change it. Deriving it automatically would also have to take a position on
what an SVG user unit means physically, which --dpi and the CSS definition of an inch
disagree about for documents sized in px. The CLI therefore takes the value explicitly
rather than guessing, and the docs state what it refers to.

Implementation

The pixel data is produced exactly as tiny-skia produces it, by demultiplying through its
PremultipliedColorU8::demultiply rather than repeating the arithmetic, so the two paths
cannot drift apart. png becomes a regular dependency of resvg instead of a
dev-dependency only; it is already in the tree through tiny-skia, and png::EncodingError
is already part of resvg's public API surface through tiny-skia's own signatures.

Tests

Four tests: the chunk is written with the expected pixels-per-meter, a plain encoding
still declares nothing, the file-writing wrapper round-trips, and the decoded image is
byte for byte the same as the one from Pixmap::encode_png, so adding the chunk cannot
change the picture.

A PNG without a `pHYs` chunk carries no resolution, so a viewer or a
word processor has to guess one, and most guess 96 DPI. An image
rendered for print or for a high resolution display then appears at the
wrong physical size, which matters all the more because the resolution
also decides how large the text in it ends up.

The pixel data is produced exactly as tiny-skia produces it, by
demultiplying through its own pixel type rather than repeating the
arithmetic, and a test compares the decoded result of both paths.

The resolution is the one of the rendered image, which differs from
`--dpi` as soon as `--zoom` or `--width` scale the output, so the CLI
takes it as its own `--png-dpi` value rather than guessing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LaurenzV

LaurenzV commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Hmm, sorry, but I think I would consider this out-of-scope. If you want to encode the PNG in a special way, you can just render into the pixmap yourself using the existing API and then encode the raw bytes yourself into a PNG with the specific settings. For the CLI, I think this is a bit too overkill.

@oetiker

oetiker commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

well, there are applications which do care for the dpi info and if the png carries non, then there is additional work for the user. but yes, I agree it can certainly be worked around. It becomes especially pertinent in connection with font rendering since the font designs care about the resolution.

@RazrFalcon

Copy link
Copy Markdown
Collaborator

Honestly, I still have no idea how SVG suppose to handle DPI. And if SVGs DPI is the same as PNG one.
If you know any SVG libraries/apps that also write PNG DPI, then we can try using them as a reference, otherwise it's just a shot in the dark.

Right now, SVG DPI is basically a hint for non-pixel coordinates resolving, not the actual "DPI".

@LaurenzV

LaurenzV commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

I'm not saying your use case is invalid! My point is: The task of resvg is to render an SVG into a pixmap. The fact that there already exists a way to convert to PNG is a "side-effect" of the fact that tiny-skia offers this API, and it's useful because many people do want to convert into PNG.

However, I'd like to avoid adding more API to resvg for very specific image encoding requirements. Otherwise, people might start asking for other things like "add an API to convert to JPEG" or "add an API for another specific type of PNG", etc., I hope you get my point. 😄 If people want something more specific than just a generic PNG, I think that should be implemented on their side.

@oetiker

oetiker commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Fair enough, closing. For the record on the question of a reference: Inkscape does write a pHYs chunk carrying its export DPI, and since SVG defines a user unit as 1/96 inch, the value it writes works out to 96 × the render scale rather than being a shot in the dark. But that only answers whether the number is well defined, not whether the encoding belongs in resvg, and I take the point that it doesn't. Easy enough to do on the calling side.

@oetiker oetiker closed this Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants