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:
parent
3dc9e40bcb
commit
dbf662ed3a
5 changed files with 104 additions and 47 deletions
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue