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
This commit is contained in:
Sophie Herold 2023-05-04 02:13:39 +02:00
parent 3dc9e40bcb
commit dbf662ed3a
5 changed files with 104 additions and 47 deletions

View file

@ -19,6 +19,7 @@
use super::*; use super::*;
use crate::decoder::tiling::{self, FrameBufferExt}; use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*; use crate::deps::*;
use crate::image_metadata::ImageMetadata;
use crate::util; use crate::util;
use crate::util::gettext::*; use crate::util::gettext::*;
use crate::util::ToBufRead; use crate::util::ToBufRead;
@ -71,6 +72,13 @@ impl Heif {
.context(gettext("Failed to decode image"))?; .context(gettext("Failed to decode image"))?;
let handle = ctx.primary_image_handle()?; 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())); tiles.set_original_dimensions((handle.width(), handle.height()));
let rgb_chroma = if handle.luma_bits_per_pixel() > 8 { let rgb_chroma = if handle.luma_bits_per_pixel() > 8 {
@ -155,6 +163,36 @@ impl Heif {
cancellable: cancellable_, 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> { pub struct Decoded<'a> {

View file

@ -19,6 +19,7 @@
use super::*; use super::*;
use crate::decoder::tiling::{self, FrameBufferExt}; use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*; use crate::deps::*;
use crate::image_metadata::ImageMetadata;
use crate::util; use crate::util;
use crate::util::gettext::*; use crate::util::gettext::*;
use crate::util::{BufReadSeek, ToBufRead}; use crate::util::{BufReadSeek, ToBufRead};
@ -95,7 +96,9 @@ impl ImageRsOther {
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
cancellable: gio::Cancellable, 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 reader = Self::reader(&file, format, &cancellable)?;
let dimensions = reader let dimensions = reader
.into_dimensions() .into_dimensions()
@ -157,6 +160,8 @@ impl ImageRsOther {
cancellable: gio::Cancellable, cancellable: gio::Cancellable,
) -> std::thread::JoinHandle<()> { ) -> std::thread::JoinHandle<()> {
updater.clone().spawn_error_handled(move || { updater.clone().spawn_error_handled(move || {
updater.send(DecoderUpdate::Metadata(ImageMetadata::load(&file)));
let mut nth_frame = 1; let mut nth_frame = 1;
// We are currently decoding for each repetition of the animation // We are currently decoding for each repetition of the animation

View file

@ -150,8 +150,6 @@ impl Decoder {
file: gio::File, file: gio::File,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) -> anyhow::Result<FormatDecoder> { ) -> anyhow::Result<FormatDecoder> {
update_sender.send(DecoderUpdate::Metadata(ImageMetadata::load(&file)));
let format = Self::guess_format(&file)?; let format = Self::guess_format(&file)?;
if let Some(format) = format { if let Some(format) = format {

View file

@ -33,29 +33,27 @@ use crate::deps::*;
use gio::prelude::*; use gio::prelude::*;
#[derive(Default)] #[derive(Default)]
pub enum ImageMetadata { pub struct ImageMetadata {
Exif(exif::Exif), pub exif: Option<exif::Exif>,
#[default] pub heif_transform: bool,
None,
} }
impl std::fmt::Debug for ImageMetadata { impl std::fmt::Debug for ImageMetadata {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self { if let Some(exif) = &self.exif {
Self::Exif(exif) => { let list = exif.fields().map(|f| {
let list = exif.fields().map(|f| { let mut value = f.display_value().to_string();
let mut value = f.display_value().to_string(); // Remove long values
// Remove long values if value.len() > 100 {
if value.len() > 100 { value = String::from("…");
value = String::from("…"); }
}
(f.ifd_num.to_string(), f.tag.to_string(), value) (f.ifd_num.to_string(), f.tag.to_string(), value)
}); });
fmt.write_str("Exif")?; fmt.write_str("Exif")?;
fmt.debug_list().entries(list).finish() fmt.debug_list().entries(list).finish()
} } else {
Self::None => fmt.write_str("None"), fmt.write_str("Empty")
} }
} }
} }
@ -67,36 +65,54 @@ impl ImageMetadata {
if let Ok(mut bufreader) = file.to_buf_read(&gio::Cancellable::new()) { if let Ok(mut bufreader) = file.to_buf_read(&gio::Cancellable::new()) {
let exifreader = exif::Reader::new(); let exifreader = exif::Reader::new();
if let Ok(exif) = exifreader.read_from_container(&mut bufreader) { let exif = exifreader.read_from_container(&mut bufreader).ok();
return Self::Exif(exif); return Self {
} exif,
..Default::default()
};
} }
Self::None Self::default()
} }
pub fn is_none(&self) -> bool { pub fn from_exif_bytes(bytes: Vec<u8>) -> Self {
matches!(self, Self::None) 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 { pub fn orientation(&self) -> Orientation {
match self { if self.heif_transform {
Self::Exif(exif) => { // HEIF library already does it's transformations on its own
if let Some(orientation) = exif Orientation::default()
.get_field(exif::Tag::Orientation, exif::In::PRIMARY) } else if let Some(exif) = &self.exif {
.and_then(|x| x.value.get_uint(0)) if let Some(orientation) = exif
{ .get_field(exif::Tag::Orientation, exif::In::PRIMARY)
Orientation::from(orientation) .and_then(|x| x.value.get_uint(0))
} else { {
Orientation::default() Orientation::from(orientation)
} } else {
Orientation::default()
} }
Self::None => Orientation::default(), } else {
Orientation::default()
} }
} }
pub fn originally_created(&self) -> Option<String> { pub fn originally_created(&self) -> Option<String> {
if let Self::Exif(exif) = self { if let Some(exif) = &self.exif {
if let Some(field) = exif if let Some(field) = exif
.get_field(exif::Tag::DateTimeOriginal, exif::In::PRIMARY) .get_field(exif::Tag::DateTimeOriginal, exif::In::PRIMARY)
.or_else(|| exif.get_field(exif::Tag::DateTime, 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<String> { pub fn f_number(&self) -> Option<String> {
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) { if let Some(field) = exif.get_field(exif::Tag::FNumber, exif::In::PRIMARY) {
return Some(format!("ƒ\u{2215}{}", field.display_value())); return Some(format!("ƒ\u{2215}{}", field.display_value()));
} }
@ -136,7 +152,7 @@ impl ImageMetadata {
} }
pub fn exposure_time(&self) -> Option<String> { pub fn exposure_time(&self) -> Option<String> {
if let Self::Exif(exif) = self { if let Some(exif) = &self.exif {
let field = exif.get_field(exif::Tag::ExposureTime, exif::In::PRIMARY)?; let field = exif.get_field(exif::Tag::ExposureTime, exif::In::PRIMARY)?;
if let exif::Value::Rational(rational) = &field.value { if let exif::Value::Rational(rational) = &field.value {
let exposure = format!("{:.0}", 1. / rational.first()?.to_f32()); let exposure = format!("{:.0}", 1. / rational.first()?.to_f32());
@ -150,7 +166,7 @@ impl ImageMetadata {
} }
pub fn iso(&self) -> Option<String> { pub fn iso(&self) -> Option<String> {
if let Self::Exif(exif) = self { if let Some(exif) = &self.exif {
if let Some(field) = if let Some(field) =
exif.get_field(exif::Tag::PhotographicSensitivity, exif::In::PRIMARY) exif.get_field(exif::Tag::PhotographicSensitivity, exif::In::PRIMARY)
{ {
@ -162,7 +178,7 @@ impl ImageMetadata {
} }
pub fn focal_length(&self) -> Option<String> { pub fn focal_length(&self) -> Option<String> {
if let Self::Exif(exif) = self { if let Some(exif) = &self.exif {
let field = exif.get_field(exif::Tag::FocalLength, exif::In::PRIMARY)?; let field = exif.get_field(exif::Tag::FocalLength, exif::In::PRIMARY)?;
if let exif::Value::Rational(rational) = &field.value { if let exif::Value::Rational(rational) = &field.value {
let length = format!("{:.0}", rational.first()?.to_f32()); let length = format!("{:.0}", rational.first()?.to_f32());
@ -193,7 +209,7 @@ impl ImageMetadata {
} }
pub fn model(&self) -> Option<String> { pub fn model(&self) -> Option<String> {
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 Some(field) = exif.get_field(exif::Tag::Model, exif::In::PRIMARY) {
if let exif::Value::Ascii(value) = &field.value { if let exif::Value::Ascii(value) = &field.value {
if let Some(entry) = value.first() { if let Some(entry) = value.first() {
@ -207,7 +223,7 @@ impl ImageMetadata {
} }
pub fn maker(&self) -> Option<String> { pub fn maker(&self) -> Option<String> {
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 Some(field) = exif.get_field(exif::Tag::Make, exif::In::PRIMARY) {
if let exif::Value::Ascii(value) = &field.value { if let exif::Value::Ascii(value) = &field.value {
if let Some(entry) = value.first() { if let Some(entry) = value.first() {
@ -221,7 +237,7 @@ impl ImageMetadata {
} }
pub fn gps_location(&self) -> Option<GPSLocation> { pub fn gps_location(&self) -> Option<GPSLocation> {
if let Self::Exif(exif) = self { if let Some(exif) = &self.exif {
if let (Some(latitude), Some(latitude_ref), Some(longitude), Some(longitude_ref)) = ( 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::GPSLatitude, exif::In::PRIMARY),
exif.get_field(exif::Tag::GPSLatitudeRef, exif::In::PRIMARY), exif.get_field(exif::Tag::GPSLatitudeRef, exif::In::PRIMARY),

View file

@ -108,7 +108,7 @@ mod imp {
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
let metadata = self.metadata.borrow(); let metadata = self.metadata.borrow();
match pspec.name() { match pspec.name() {
"has-information" => (!metadata.is_none()).to_value(), "has-information" => (metadata.has_information()).to_value(),
"originally-created" => metadata.originally_created().to_value(), "originally-created" => metadata.originally_created().to_value(),
"location" => metadata.gps_location().map(|x| x.display()).to_value(), "location" => metadata.gps_location().map(|x| x.display()).to_value(),
"f-number" => metadata.f_number().to_value(), "f-number" => metadata.f_number().to_value(),