feat(wallpaper): apply EXIF orientation to wallpapers (#2075)

Same fix as pop-os/cosmic-bg#142, which was asked for there.

`open_image` has two decoding paths, and neither applies the Exif
orientation:
`ImageReader::decode` for the common formats, and
`DynamicImage::from_decoder` in
`decode_jpegxl`. Cameras store portrait photos with the pixels laid out
landscape
and record the rotation in the Exif metadata, so such a photo is shown
rotated in
the wallpaper page, both in the preview and in the thumbnails.

Both paths now read the orientation from the decoder and apply it to the
decoded
image. `into_decoder` does not check the allocation limit against the
size of the
decoded image the way `decode` does, so that check is kept explicitly.

Tested with the 8 Exif orientation values, with images that carry no
Exif metadata
(unchanged), and with the JPEG XL wallpapers from gnome-backgrounds,
which decode
identically.

cosmic-greeter needs a different change: it does not decode images
itself. It
passes the raw file bytes to `widget::image::Handle::from_bytes`
(src/common.rs),
and those are decoded by `iced_graphics` with `image::load_from_memory`,
which
does not apply the orientation either. So the orientation has to be
applied before
the handle is built, or in the toolkit.

- [x] I have disclosed use of any AI generated code in my commit
messages.
- [x] I understand these changes in full and will be able to respond to
review comments.
- [x] My change is accurately described in the commit message.
- [x] My contribution is tested and working as described.
- [x] I have read the [Developer Certificate of
Origin](https://developercertificate.org/) and certify my contribution
under its conditions.

Signed-off-by: Leandro Pérez G. <leandropatodo@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leandro Pérez G. 2026-07-24 09:24:25 -06:00 • committed by GitHub
parent 044fc11b2f
commit 65d0f0c7c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -4,7 +4,7 @@ use fast_image_resize::SrcCropping;
use futures_lite::Stream;
use futures_util::StreamExt;
use image::imageops::FilterType;
use image::{DynamicImage, ImageBuffer, Rgba, RgbaImage};
use image::{DynamicImage, ImageBuffer, ImageDecoder, ImageResult, Limits, Rgba, RgbaImage};
use jxl_oxide::integration::JxlDecoder;
use std::borrow::Cow;
use std::collections::HashMap;
@ -304,7 +304,7 @@ fn open_image(input_buffer: &mut Vec<u8>, path: &Path) -> Option<DynamicImage> {
return None;
};
match image_decoder.decode() {
match decode(image_decoder) {
Ok(image) => Some(image),
Err(why) => {
tracing::error!(?path, ?why, "image decode failed");
@ -313,6 +313,24 @@ fn open_image(input_buffer: &mut Vec<u8>, path: &Path) -> Option<DynamicImage> {
}
}
fn decode(
mut reader: image::ImageReader<std::io::Cursor<&mut Vec<u8>>>,
) -> ImageResult<DynamicImage> {
let mut limits = Limits::default();
reader.limits(limits.clone());
let mut decoder = reader.into_decoder()?;
let orientation = decoder.orientation()?;
limits.reserve(decoder.total_bytes())?;
decoder.set_limits(limits)?;
let mut image = DynamicImage::from_decoder(decoder)?;
image.apply_orientation(orientation);
Ok(image)
}
// https://users.rust-lang.org/t/how-to-trim-image-to-circle-image-without-jaggy/70374/2
fn round(img: &mut image::ImageBuffer<image::Rgba<u8>, Vec<u8>>, radius: [u32; 4]) {
let (width, height) = img.dimensions();
@ -434,11 +452,19 @@ fn border_radius(
pub fn decode_jpegxl(path: &std::path::Path) -> eyre::Result<DynamicImage> {
let file = File::open(path).map_err(|why| eyre!("failed to open jxl image file: {why}"))?;
let decoder =
let mut decoder =
JxlDecoder::new(file).map_err(|why| eyre!("failed to read jxl image header: {why}"))?;
image::DynamicImage::from_decoder(decoder)
.map_err(|why| eyre!("failed to decode jxl image: {why}"))
let orientation = decoder
.orientation()
.map_err(|why| eyre!("failed to read jxl image orientation: {why}"))?;
let mut image = image::DynamicImage::from_decoder(decoder)
.map_err(|why| eyre!("failed to decode jxl image: {why}"))?;
image.apply_orientation(orientation);
Ok(image)
}
/// Use `fast-image-resize` crate for faster thumbnail generation.