hyprforge-image 0.1.0

Bounded, orientation-correct picture decoding: measure without decoding, decide how much may safely be decoded, decode that much, and apply EXIF orientation; no GUI toolkit
Documentation

hyprforge-image

Bounded, orientation-correct picture decoding: measure without decoding, decide how much may safely be decoded, decode that much, and apply EXIF orientation; no GUI toolkit.

What a picture is, before anything draws it.

Measure it without decoding it, work out what may safely be decoded, decode that much, and turn it the right way up. No iced, no Wayland, no async runtime, nothing Hyprland-shaped — so a viewer, a preview pane and a test without a window can all ask the same questions.

The finding this crate exists for

iced decodes an image handle at full resolution with no cap, and applies EXIF orientation — but only for two of its three handle kinds. In iced_graphics-0.14.0/src/image.rs, Handle::Path and Handle::Bytes go through image::open / load_from_memory and are then rotated per exif::Tag::Orientation. Handle::Rgba is a pure passthrough of the pixels it is given.

For a wallpaper or a thumbnail that is fine. For a viewer it is not: a 36-megapixel photograph decodes to 144MB and peaks near 300MB once the renderer has its own premultiplied copy — the allocation profile that already cost this suite a lock screen, where a failure inside iced_tiny_skia caches as "no entry" and panics on the next frame.

So a viewer must hand the renderer Handle::Rgba built from pixels it decoded within a budget of its own — and the moment it does, it takes on the orientation work too. Two halves, and getting either one alone is a bug: skip orientation and every portrait phone photograph is sideways; apply it here and use a path handle elsewhere and the same picture is turned twice.

Where to start

measure) first, always — it allocates nothing and everything else depends on its answer. Then budget, which is pure arithmetic and the only place a cap lives. Then decode::decode_to_fit.

Honest about what the budget bounds

What is retained is capped by the viewport: 56MB for a 36-megapixel photograph in a 2560x1600 window, against 137MB uncapped. What is transient cannot be capped here, because image 0.25 has no DCT-scaled decode — a JPEG is decoded whole and then scaled down, and the peak belongs to the source.

That peak was measured rather than assumed, and measuring it changed the code: it began at 509MB and is 214MB, because the obvious downscaler turned out to cost more than the decode it followed. The table and the reasoning are in budget, the line itself in decode.

It also corrected something this doc used to claim: image's own Limits does not bound that peak — the measurement is identical with and without it.

Where this lives

Part of Hyprforge, a suite of native Hyprland desktop applications. This crate is crates/hyprforge-image there; it is published to crates.io so the suite's applications can be built from their own repositories, and its API follows the suite. Issues and pull requests go to the suite repository.

cargo add hyprforge-image

This README is rendered from the crate's //! documentation by tools/crate-readme.py; edit src/lib.rs, not this file.

Licence

MIT. See LICENSE.