From dbf662ed3a3273ee609fdbf497a72508bbc0637a Mon Sep 17 00:00:00 2001 From: Sophie Herold Date: Thu, 4 May 2023 02:13:39 +0200 Subject: [PATCH] heif: Ignore EXIF rotation info for now Ideally we would check if HEIF has transformations but that will only work when libheif 1.16 is supported Closes #157 --- src/decoder/formats/heif.rs | 38 ++++++++++ src/decoder/formats/image_rs_other.rs | 7 +- src/decoder/mod.rs | 2 - src/image_metadata/mod.rs | 102 +++++++++++++++----------- src/image_metadata/obj.rs | 2 +- 5 files changed, 104 insertions(+), 47 deletions(-) diff --git a/src/decoder/formats/heif.rs b/src/decoder/formats/heif.rs index 2239a92..05227c5 100644 --- a/src/decoder/formats/heif.rs +++ b/src/decoder/formats/heif.rs @@ -19,6 +19,7 @@ use super::*; use crate::decoder::tiling::{self, FrameBufferExt}; use crate::deps::*; +use crate::image_metadata::ImageMetadata; use crate::util; use crate::util::gettext::*; use crate::util::ToBufRead; @@ -71,6 +72,13 @@ impl Heif { .context(gettext("Failed to decode image"))?; let handle = ctx.primary_image_handle()?; + let mut metadata = Self::exif(&handle, &file); + + // TODO: Later use libheif 1.16 to get info if there is a transformation + metadata.heif_transform = true; + + updater.send(DecoderUpdate::Metadata(metadata)); + tiles.set_original_dimensions((handle.width(), handle.height())); let rgb_chroma = if handle.luma_bits_per_pixel() > 8 { @@ -155,6 +163,36 @@ impl Heif { cancellable: cancellable_, } } + + fn exif(handle: &libheif_rs::ImageHandle, file: &gio::File) -> ImageMetadata { + let mut meta_ids = vec![0]; + handle.metadata_block_ids(&mut meta_ids, b"Exif"); + + if let Some(meta_id) = meta_ids.first() { + match handle.metadata(*meta_id) { + Ok(mut exif_bytes) => { + if let Some(skip) = exif_bytes + .get(0..4) + .map(|x| u32::from_be_bytes(x.try_into().unwrap()) as usize) + { + if exif_bytes.len() > skip + 4 { + exif_bytes.drain(0..skip + 4); + return ImageMetadata::from_exif_bytes(exif_bytes); + } else { + log::warn!("EXIF data has far too few bytes in {}", file.uri()); + } + } else { + log::warn!("EXIF data has far too few bytes in {}", file.uri()); + } + } + Err(err) => { + log::warn!("Unable to decode EXIF data for {}: {err}", file.uri()); + } + } + } + + ImageMetadata::default() + } } pub struct Decoded<'a> { diff --git a/src/decoder/formats/image_rs_other.rs b/src/decoder/formats/image_rs_other.rs index 53ac93c..d8a521d 100644 --- a/src/decoder/formats/image_rs_other.rs +++ b/src/decoder/formats/image_rs_other.rs @@ -19,6 +19,7 @@ use super::*; use crate::decoder::tiling::{self, FrameBufferExt}; use crate::deps::*; +use crate::image_metadata::ImageMetadata; use crate::util; use crate::util::gettext::*; use crate::util::{BufReadSeek, ToBufRead}; @@ -95,7 +96,9 @@ impl ImageRsOther { tiles: Arc>, cancellable: gio::Cancellable, ) { - updater.spawn_error_handled(move || { + updater.clone().spawn_error_handled(move || { + updater.send(DecoderUpdate::Metadata(ImageMetadata::load(&file))); + let reader = Self::reader(&file, format, &cancellable)?; let dimensions = reader .into_dimensions() @@ -157,6 +160,8 @@ impl ImageRsOther { cancellable: gio::Cancellable, ) -> std::thread::JoinHandle<()> { updater.clone().spawn_error_handled(move || { + updater.send(DecoderUpdate::Metadata(ImageMetadata::load(&file))); + let mut nth_frame = 1; // We are currently decoding for each repetition of the animation diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 841dd97..198e64e 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -150,8 +150,6 @@ impl Decoder { file: gio::File, tiles: Arc>, ) -> anyhow::Result { - update_sender.send(DecoderUpdate::Metadata(ImageMetadata::load(&file))); - let format = Self::guess_format(&file)?; if let Some(format) = format { diff --git a/src/image_metadata/mod.rs b/src/image_metadata/mod.rs index 51e2d00..52f9898 100644 --- a/src/image_metadata/mod.rs +++ b/src/image_metadata/mod.rs @@ -33,29 +33,27 @@ use crate::deps::*; use gio::prelude::*; #[derive(Default)] -pub enum ImageMetadata { - Exif(exif::Exif), - #[default] - None, +pub struct ImageMetadata { + pub exif: Option, + pub heif_transform: bool, } impl std::fmt::Debug for ImageMetadata { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - Self::Exif(exif) => { - let list = exif.fields().map(|f| { - let mut value = f.display_value().to_string(); - // Remove long values - if value.len() > 100 { - value = String::from("…"); - } + if let Some(exif) = &self.exif { + let list = exif.fields().map(|f| { + let mut value = f.display_value().to_string(); + // Remove long values + if value.len() > 100 { + value = String::from("…"); + } - (f.ifd_num.to_string(), f.tag.to_string(), value) - }); - fmt.write_str("Exif")?; - fmt.debug_list().entries(list).finish() - } - Self::None => fmt.write_str("None"), + (f.ifd_num.to_string(), f.tag.to_string(), value) + }); + fmt.write_str("Exif")?; + fmt.debug_list().entries(list).finish() + } else { + fmt.write_str("Empty") } } } @@ -67,36 +65,54 @@ impl ImageMetadata { if let Ok(mut bufreader) = file.to_buf_read(&gio::Cancellable::new()) { let exifreader = exif::Reader::new(); - if let Ok(exif) = exifreader.read_from_container(&mut bufreader) { - return Self::Exif(exif); - } + let exif = exifreader.read_from_container(&mut bufreader).ok(); + return Self { + exif, + ..Default::default() + }; } - Self::None + Self::default() } - pub fn is_none(&self) -> bool { - matches!(self, Self::None) + pub fn from_exif_bytes(bytes: Vec) -> Self { + let reader = exif::Reader::new(); + match reader.read_raw(bytes) { + Ok(exif) => Self { + exif: Some(exif), + ..Default::default() + }, + Err(err) => { + log::warn!("Failed to decoder EXIF bytes: {err}"); + Self::default() + } + } + } + + pub fn has_information(&self) -> bool { + self.exif.is_some() } pub fn orientation(&self) -> Orientation { - match self { - Self::Exif(exif) => { - if let Some(orientation) = exif - .get_field(exif::Tag::Orientation, exif::In::PRIMARY) - .and_then(|x| x.value.get_uint(0)) - { - Orientation::from(orientation) - } else { - Orientation::default() - } + if self.heif_transform { + // HEIF library already does it's transformations on its own + Orientation::default() + } else if let Some(exif) = &self.exif { + if let Some(orientation) = exif + .get_field(exif::Tag::Orientation, exif::In::PRIMARY) + .and_then(|x| x.value.get_uint(0)) + { + Orientation::from(orientation) + } else { + Orientation::default() } - Self::None => Orientation::default(), + } else { + Orientation::default() } } pub fn originally_created(&self) -> Option { - if let Self::Exif(exif) = self { + if let Some(exif) = &self.exif { if let Some(field) = exif .get_field(exif::Tag::DateTimeOriginal, exif::In::PRIMARY) .or_else(|| exif.get_field(exif::Tag::DateTime, exif::In::PRIMARY)) @@ -126,7 +142,7 @@ impl ImageMetadata { } pub fn f_number(&self) -> Option { - if let Self::Exif(exif) = self { + if let Some(exif) = &self.exif { if let Some(field) = exif.get_field(exif::Tag::FNumber, exif::In::PRIMARY) { return Some(format!("ƒ\u{2215}{}", field.display_value())); } @@ -136,7 +152,7 @@ impl ImageMetadata { } pub fn exposure_time(&self) -> Option { - if let Self::Exif(exif) = self { + if let Some(exif) = &self.exif { let field = exif.get_field(exif::Tag::ExposureTime, exif::In::PRIMARY)?; if let exif::Value::Rational(rational) = &field.value { let exposure = format!("{:.0}", 1. / rational.first()?.to_f32()); @@ -150,7 +166,7 @@ impl ImageMetadata { } pub fn iso(&self) -> Option { - if let Self::Exif(exif) = self { + if let Some(exif) = &self.exif { if let Some(field) = exif.get_field(exif::Tag::PhotographicSensitivity, exif::In::PRIMARY) { @@ -162,7 +178,7 @@ impl ImageMetadata { } pub fn focal_length(&self) -> Option { - if let Self::Exif(exif) = self { + if let Some(exif) = &self.exif { let field = exif.get_field(exif::Tag::FocalLength, exif::In::PRIMARY)?; if let exif::Value::Rational(rational) = &field.value { let length = format!("{:.0}", rational.first()?.to_f32()); @@ -193,7 +209,7 @@ impl ImageMetadata { } pub fn model(&self) -> Option { - if let Self::Exif(exif) = self { + if let Some(exif) = &self.exif { if let Some(field) = exif.get_field(exif::Tag::Model, exif::In::PRIMARY) { if let exif::Value::Ascii(value) = &field.value { if let Some(entry) = value.first() { @@ -207,7 +223,7 @@ impl ImageMetadata { } pub fn maker(&self) -> Option { - if let Self::Exif(exif) = self { + if let Some(exif) = &self.exif { if let Some(field) = exif.get_field(exif::Tag::Make, exif::In::PRIMARY) { if let exif::Value::Ascii(value) = &field.value { if let Some(entry) = value.first() { @@ -221,7 +237,7 @@ impl ImageMetadata { } pub fn gps_location(&self) -> Option { - if let Self::Exif(exif) = self { + if let Some(exif) = &self.exif { if let (Some(latitude), Some(latitude_ref), Some(longitude), Some(longitude_ref)) = ( exif.get_field(exif::Tag::GPSLatitude, exif::In::PRIMARY), exif.get_field(exif::Tag::GPSLatitudeRef, exif::In::PRIMARY), diff --git a/src/image_metadata/obj.rs b/src/image_metadata/obj.rs index ae4e5ae..8d52f12 100644 --- a/src/image_metadata/obj.rs +++ b/src/image_metadata/obj.rs @@ -108,7 +108,7 @@ mod imp { fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { let metadata = self.metadata.borrow(); match pspec.name() { - "has-information" => (!metadata.is_none()).to_value(), + "has-information" => (metadata.has_information()).to_value(), "originally-created" => metadata.originally_created().to_value(), "location" => metadata.gps_location().map(|x| x.display()).to_value(), "f-number" => metadata.f_number().to_value(),