metadata: Move file info and format to metadata

This commit is contained in:
Sophie Herold 2023-12-04 12:52:40 +01:00
parent c36e7f4eb6
commit c0f435386d
16 changed files with 327 additions and 339 deletions

View file

@ -34,17 +34,19 @@ use crate::deps::*;
use crate::decoder::tiling::FrameBufferExt;
use crate::decoder::{self, tiling, Decoder, DecoderUpdate};
use crate::image_metadata::LpImageMetadata;
use crate::metadata::{self, Metadata};
use crate::util::Gesture;
use adw::prelude::*;
use adw::subclass::prelude::*;
use arc_swap::ArcSwap;
use futures::prelude::*;
use glib::subclass::Signal;
use once_cell::sync::Lazy;
use std::cell::{Cell, OnceCell, RefCell};
use std::marker::PhantomData;
use std::ops::Deref;
use std::sync::Arc;
/// Default background color around images and behind transparent images
@ -123,8 +125,7 @@ mod imp {
/// Set if an error has occurred, shown on error_page
#[property(get)]
pub(super) error: RefCell<Option<String>>,
#[property(name = "format-name", type = Option<String>, get = Self::format_name)]
pub(super) format: RefCell<Option<decoder::ImageFormat>>,
//pub(super) format: RefCell<Option<decoder::ImageFormat>>,
pub(super) background_color: RefCell<Option<gdk::RGBA>>,
/// Track changes to this image
@ -177,8 +178,7 @@ mod imp {
pub(super) _vscroll_policy: PhantomData<gtk::ScrollablePolicy>,
/// Currently EXIF data
#[property(name = "metadata", type=LpImageMetadata, get)]
pub(super) image_metadata: RefCell<LpImageMetadata>,
pub(super) metadata: RefCell<Metadata>,
/// Image dimension details for SVGs
pub(super) dimension_details: RefCell<decoder::ImageDimensionDetails>,
@ -229,6 +229,12 @@ mod imp {
self.derived_property(id, pspec)
}
fn signals() -> &'static [Signal] {
static SIGNALS: Lazy<Vec<Signal>> =
Lazy::new(|| vec![Signal::builder("metadata-changed").build()]);
SIGNALS.as_ref()
}
fn constructed(&self) {
self.parent_constructed();
@ -604,11 +610,6 @@ mod imp {
pub fn scroll_policy(&self) -> gtk::ScrollablePolicy {
gtk::ScrollablePolicy::Minimum
}
/// Image format displayable name
pub fn format_name(&self) -> Option<String> {
self.format.borrow().as_ref().map(|x| x.to_string())
}
}
impl WidgetImpl for LpImage {
@ -651,18 +652,18 @@ mod imp {
// called when the widget content should be re-rendered
fn snapshot(&self, snapshot: &gtk::Snapshot) {
let widget = self.obj();
let widget_width = widget.width() as f64;
let widget_height = widget.height() as f64;
let display_width = widget.image_displayed_width();
let display_height = widget.image_displayed_height();
let obj = self.obj();
let widget_width = obj.width() as f64;
let widget_height = obj.height() as f64;
let display_width = obj.image_displayed_width();
let display_height = obj.image_displayed_height();
// make sure the scrollbars are correct
widget.configure_adjustments();
obj.configure_adjustments();
let applicable_zoom = widget.applicable_zoom();
let applicable_zoom = obj.applicable_zoom();
let scaling_filter = if self.format.borrow().as_ref().map_or(false, |x| x.is_svg()) {
let scaling_filter = if obj.metadata().format().map_or(false, |x| x.is_svg()) {
// Looks better in SVG animations and avoids rendering issues
gsk::ScalingFilter::Linear
} else if applicable_zoom < 1. {
@ -675,8 +676,8 @@ mod imp {
let render_options = tiling::RenderOptions {
scaling_filter,
scale_factor: widget.scale_factor(),
background_color: Some(widget.background_color()),
scale_factor: obj.scale_factor(),
background_color: Some(obj.background_color()),
};
// Operations on snapshots are coordinate transformations
@ -685,34 +686,34 @@ mod imp {
// Add background
snapshot.append_color(
&widget.background_color(),
&obj.background_color(),
&graphene::Rect::new(0., 0., widget_width as f32, widget_height as f32),
);
// Apply the scrolling position to the image
let hadj: gtk::Adjustment = widget.hadjustment();
let hadj: gtk::Adjustment = obj.hadjustment();
let x = -(hadj.value() - (hadj.upper() - display_width) / 2.);
snapshot.translate(&graphene::Point::new(widget.round_f64(x) as f32, 0.));
snapshot.translate(&graphene::Point::new(obj.round_f64(x) as f32, 0.));
let vadj = widget.vadjustment();
let vadj = obj.vadjustment();
let y = -(vadj.value() - (vadj.upper() - display_height) / 2.);
snapshot.translate(&graphene::Point::new(0., widget.round_f64(y) as f32));
snapshot.translate(&graphene::Point::new(0., obj.round_f64(y) as f32));
// Centering in widget when no scrolling (black bars around image)
let x = widget.round_f64(f64::max((widget_width - display_width) / 2.0, 0.));
let y = widget.round_f64(f64::max((widget_height - display_height) / 2.0, 0.));
let x = obj.round_f64(f64::max((widget_width - display_width) / 2.0, 0.));
let y = obj.round_f64(f64::max((widget_height - display_height) / 2.0, 0.));
// Round to pixel values to not have a half pixel offset to physical pixels
// The offset would leading to a blurry output
snapshot.translate(&graphene::Point::new(
widget.round_f64(x) as f32,
widget.round_f64(y) as f32,
obj.round_f64(x) as f32,
obj.round_f64(y) as f32,
));
// Apply rotation and mirroring
widget.snapshot_rotate_mirror(
obj.snapshot_rotate_mirror(
snapshot,
widget.rotation() as f32,
widget.mirrored(),
obj.rotation() as f32,
obj.mirrored(),
applicable_zoom,
);
@ -813,9 +814,19 @@ glib::wrapper! {
impl LpImage {
pub async fn load(&self, file: &gio::File) {
let imp = self.imp();
log::debug!("Loading file {}", file.uri());
self.set_file(file);
imp.metadata.replace(Metadata::default());
self.emmit_metadata_changed();
let file_info = metadata::FileInfo::new(file).await;
match file_info {
Ok(file_info) => imp.metadata.borrow_mut().set_file_info(file_info),
Err(err) => log::warn!("Failed to load file information: {err}"),
}
self.emmit_metadata_changed();
let tiles = &self.imp().frame_buffer;
// Delete all stored textures for reloads
@ -823,7 +834,8 @@ impl LpImage {
// Reset background color for reloads
self.set_background_color(None);
let (decoder, mut decoder_update) = Decoder::new(file.clone(), tiles.clone()).await;
let (decoder, mut decoder_update) =
Decoder::new(file.clone(), self.metadata().mime_type(), tiles.clone()).await;
let weak_obj = self.downgrade();
glib::spawn_future_local(async move {
@ -835,7 +847,7 @@ impl LpImage {
log::debug!("Stopped listening to decoder since sender is gone");
});
self.imp().decoder.replace(Some(Arc::new(decoder)));
imp.decoder.replace(Some(Arc::new(decoder)));
}
/// Called when decoder sends update
@ -845,8 +857,8 @@ impl LpImage {
match update {
DecoderUpdate::Metadata(metadata) => {
log::debug!("Received metadata");
imp.image_metadata.replace(LpImageMetadata::from(metadata));
self.notify_metadata();
imp.metadata.borrow_mut().merge(metadata);
self.emmit_metadata_changed();
self.reset_rotation();
}
@ -880,8 +892,8 @@ impl LpImage {
self.set_error(Some(err));
}
DecoderUpdate::Format(format) => {
imp.format.replace(Some(format));
self.notify_format_name();
imp.metadata.borrow_mut().set_format(format);
self.emmit_metadata_changed();
}
DecoderUpdate::Animated => {
let callback_id = self
@ -1117,7 +1129,7 @@ impl LpImage {
/// Maximal zoom allowed for this image
fn max_zoom(&self) -> f64 {
if self.format().map_or(false, |x| x.is_svg()) {
if self.metadata().format().map_or(false, |x| x.is_svg()) {
let (width, height) = self.original_dimensions();
// Avoid division by 0
let long_side = f64::max(1., i32::max(width, height) as f64);
@ -1392,7 +1404,7 @@ impl LpImage {
/// Image size of original image with EXIF rotation applied
pub fn image_size(&self) -> (i32, i32) {
let orientation = self.imp().image_metadata.borrow().orientation();
let orientation = self.imp().metadata.borrow().orientation();
if orientation.rotation.abs() == 90. || orientation.rotation.abs() == 270. {
let (x, y) = self.original_dimensions();
(y, x)
@ -1599,11 +1611,6 @@ impl LpImage {
Some(gdk::ContentProvider::for_value(&list.to_value()))
}
/// Image format
pub fn format(&self) -> Option<decoder::ImageFormat> {
self.imp().format.borrow().clone()
}
fn set_error(&self, err: Option<anyhow::Error>) {
log::debug!("Decoding error: {err:?}");
self.imp()
@ -1696,6 +1703,7 @@ impl LpImage {
pub async fn background_color_guess(&self) -> Option<gdk::RGBA> {
// Shortcut for formats that don't support transparency
if !self
.metadata()
.format()
.map_or(true, |x| x.is_potentially_transparent())
{
@ -1809,4 +1817,19 @@ impl LpImage {
.await
.ok()?
}
pub fn metadata(&self) -> impl Deref<Target = Metadata> + '_ {
self.imp().metadata.borrow()
}
pub fn connect_changed(&self, f: impl Fn() + 'static) {
self.connect_local("metadata-changed", false, move |_| {
f();
None
});
}
fn emmit_metadata_changed(&self) {
self.emit_by_name::<()>("metadata-changed", &[]);
}
}

View file

@ -856,7 +856,7 @@ impl LpPrint {
let texture_scale = self.user_width() / orig_width as f64;
let cairo_scale = cairo_dpi / self.dpi();
let texture = if image.format().map_or(false, |x| x.is_svg()) {
let texture = if image.metadata().format().map_or(false, |x| x.is_svg()) {
// Render SVG to exact needed sizes
// TODO: This should be async
decoder::formats::Svg::render_print(

View file

@ -44,6 +44,8 @@ mod imp {
#[template_child]
folder: TemplateChild<adw::ActionRow>,
#[template_child]
uri: TemplateChild<adw::ActionRow>,
#[template_child]
image_size: TemplateChild<adw::ActionRow>,
@ -52,6 +54,8 @@ mod imp {
#[template_child]
file_size: TemplateChild<adw::ActionRow>,
#[template_child]
dates: TemplateChild<adw::PreferencesGroup>,
#[template_child]
file_created: TemplateChild<adw::ActionRow>,
#[template_child]
@ -108,11 +112,13 @@ mod imp {
image_signals.connect_notify_local(
Some("image-size-available"),
glib::clone!(@weak obj => move |_,_| obj.imp().update()),
glib::clone!(@weak obj => move |_, _| obj.imp().update()),
);
image_signals.connect_notify_local(
Some("metadata"),
glib::clone!(@weak obj => move |_,_| obj.imp().update()),
image_signals.connect_local(
"metadata-changed",
false,
glib::clone!(@weak obj => @default-return None, move |_| { obj.imp().update(); None }),
);
self.update();
@ -156,28 +162,43 @@ mod imp {
return;
};
let metadata = image.metadata();
// Folder
Self::update_row(&self.folder, self.folder_name());
let has_folder = Self::update_row(&self.folder, self.folder_name());
// The portal only supports opening folders of files that have a path
obj.action_set_enabled("properties.open-folder", file.path().is_some());
// Only show URI if now folder available
let uri = if !has_folder {
image.file().map(|x| x.uri())
} else {
None
};
Self::update_row(&self.uri, uri);
// Image info
Self::update_row(&self.image_size, self.image_size());
Self::update_row(&self.image_format, image.format().map(|x| x.to_string()));
//Self::update_row(&self.file_size, self.image_size());
Self::update_row(&self.image_format, metadata.format_name());
Self::update_row(&self.file_size, metadata.file_size());
// Sizes
// Dates
let has_dates = [
Self::update_row(&self.file_created, metadata.file_created()),
Self::update_row(&self.file_modified, dbg!(metadata.file_modified())),
]
.into_iter()
.any(|x| x);
self.dates.set_visible(has_dates);
// Details
let metadata = image.metadata();
let meta = metadata.src();
// Details (EXIF)
let has_details = [
Self::update_row(&self.location, meta.gps_location().map(|x| x.display())),
Self::update_row(&self.originally_created, meta.originally_created()),
Self::update_row(&self.aperture, meta.f_number()),
Self::update_row(&self.exposure, meta.exposure_time()),
Self::update_row(&self.iso, meta.iso()),
Self::update_row(&self.focal_length, meta.focal_length()),
Self::update_row(&self.maker_model, meta.maker_model()),
Self::update_row(&self.location, metadata.gps_location().map(|x| x.display())),
Self::update_row(&self.originally_created, metadata.originally_created()),
Self::update_row(&self.aperture, metadata.f_number()),
Self::update_row(&self.exposure, metadata.exposure_time()),
Self::update_row(&self.iso, metadata.iso()),
Self::update_row(&self.focal_length, metadata.focal_length()),
Self::update_row(&self.maker_model, metadata.maker_model()),
]
.iter()
.any(|x| *x);
@ -211,7 +232,7 @@ mod imp {
_ => {
let (width, height) = image.image_size();
if width > 0 && height > 0 {
return Some(format!("{width}\u{202F}\u{D7}\u{202F}{height}"));
Some(format!("{width}\u{202F}\u{D7}\u{202F}{height}"))
} else {
None
}
@ -265,34 +286,3 @@ glib::wrapper! {
@extends gtk::Widget, adw::Bin,
@implements gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
}
impl LpPropertiesView {
/* fn set_file(&self, file: Option<&gio::File>) {
let imp = self.imp();
self.action_set_enabled(
"properties.open-folder",
file.map_or(false, |x| x.is_native()),
);
if let Some(file) = file {
if let Some(current_file) = self.file() {
if current_file.equal(file) {
return;
}
}
// Cancel the file info future. See `build_file_info()` for details.
if let Some(handle) = imp.info_handle.take() {
handle.abort();
}
self.build_file_info(file);
}
let weak = file.map(|x| x.downgrade());
imp.file.replace(weak);
self.notify("file");
}
*/
}

View file

@ -34,6 +34,16 @@
</style>
</object>
</child>
<child>
<object class="AdwActionRow" id="uri">
<property name="title" translatable="yes">URI</property>
<property name="subtitle-selectable">True</property>
<property name="subtitle-lines">1</property>
<style>
<class name="property" />
</style>
</object>
</child>
</object>
</child>
<child>
@ -68,7 +78,7 @@
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<object class="AdwPreferencesGroup" id="dates">
<child>
<object class="AdwActionRow" id="file_created">
<property name="title" translatable="yes">File Created</property>