imageView: Skip unsupported formats when sliding

If an image with unsupported format is opened directly,
the error is shown.

Closes #92

Part-of: <https://gitlab.gnome.org/Incubator/loupe/-/merge_requests/212>
This commit is contained in:
Sophie Herold 2023-04-28 00:44:04 +02:00 • committed by Marge Bot
parent 8f55668cd6
commit 6db58463bf
5 changed files with 83 additions and 25 deletions

View file

@ -23,7 +23,7 @@ use crate::util;
use crate::util::gettext::*; use crate::util::gettext::*;
use crate::util::ToBufRead; use crate::util::ToBufRead;
use anyhow::Context; use anyhow::{anyhow, bail, Context};
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use gtk::prelude::*; use gtk::prelude::*;
use libheif_rs::{ColorProfile, ColorSpace, HeifContext, LibHeif, Plane, RgbChroma}; use libheif_rs::{ColorProfile, ColorSpace, HeifContext, LibHeif, Plane, RgbChroma};
@ -56,7 +56,7 @@ impl Heif {
let cancellable = gio::Cancellable::new(); let cancellable = gio::Cancellable::new();
let cancellable_ = cancellable.clone(); let cancellable_ = cancellable.clone();
updater.spawn_error_handled(move || { updater.clone().spawn_error_handled(move || {
let file_size = file let file_size = file
.query_info( .query_info(
gio::FILE_ATTRIBUTE_STANDARD_SIZE, gio::FILE_ATTRIBUTE_STANDARD_SIZE,
@ -99,9 +99,17 @@ impl Heif {
RgbChroma::Rgb RgbChroma::Rgb
}; };
let mut image = LIBHEIF let image_result = LIBHEIF.decode(&handle, ColorSpace::Rgb(rgb_chroma), None);
.decode(&handle, ColorSpace::Rgb(rgb_chroma), None)
.context(gettext("Failed to decode image"))?; let mut image = match image_result {
Err(err)
if matches!(err.sub_code, libheif_rs::HeifErrorSubCode::UnsupportedCodec) =>
{
updater.send(DecoderUpdate::UnsupportedFormat);
bail!(anyhow!(err).context(gettext("Unknown image format")));
}
image => image.context(gettext("Failed to decode image"))?,
};
let icc_profile = if let Some(profile) = handle.color_profile_raw() { let icc_profile = if let Some(profile) = handle.color_profile_raw() {
if [ if [

View file

@ -63,6 +63,8 @@ pub enum DecoderUpdate {
Metadata(ImageMetadata), Metadata(ImageMetadata),
/// Image format determined /// Image format determined
Format(ImageFormat), Format(ImageFormat),
/// Image format not supported or unknown
UnsupportedFormat,
/// New image data available, redraw /// New image data available, redraw
Redraw, Redraw,
/// And error occured during decoding /// And error occured during decoding
@ -113,13 +115,13 @@ impl Decoder {
pub async fn new( pub async fn new(
file: gio::File, file: gio::File,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>, tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) -> anyhow::Result<(Self, mpsc::UnboundedReceiver<DecoderUpdate>)> { ) -> (Result<Self, ()>, mpsc::UnboundedReceiver<DecoderUpdate>) {
let (sender, receiver) = mpsc::unbounded(); let (sender, receiver) = mpsc::unbounded();
let update_sender = UpdateSender { sender }; let update_sender = UpdateSender { sender };
tiles.set_update_sender(update_sender.clone()); tiles.set_update_sender(update_sender.clone());
let decoder = gio::spawn_blocking( let format_decoder = gio::spawn_blocking(
glib::clone!(@strong update_sender => move || Self::format_decoder( glib::clone!(@strong update_sender => move || Self::format_decoder(
update_sender, update_sender,
file, file,
@ -127,15 +129,20 @@ impl Decoder {
)), )),
) )
.await .await
.map_err(|_| anyhow!("Constructing the FormatDecoder failed unexpectedly"))??; .map_err(|_| anyhow!("Constructing the FormatDecoder failed unexpectedly"));
Ok(( let decoder = match format_decoder {
Self { Ok(Ok(decoder)) => Ok(Self {
decoder, decoder,
update_sender, update_sender,
}, }),
receiver, Err(err) | Ok(Err(err)) => {
)) update_sender.send(DecoderUpdate::Error(err));
Err(())
}
};
(decoder, receiver)
} }
fn format_decoder( fn format_decoder(
@ -186,11 +193,13 @@ impl Decoder {
update_sender.send(DecoderUpdate::Format(ImageFormat::Heif)); update_sender.send(DecoderUpdate::Format(ImageFormat::Heif));
return Ok(FormatDecoder::Heif(Heif::new(file, update_sender, tiles))); return Ok(FormatDecoder::Heif(Heif::new(file, update_sender, tiles)));
} else { } else {
update_sender.send(DecoderUpdate::UnsupportedFormat);
bail!(gettext_f("Unknown image format: {}", &[mime_type.as_str()])); bail!(gettext_f("Unknown image format: {}", &[mime_type.as_str()]));
} }
} }
} }
update_sender.send(DecoderUpdate::UnsupportedFormat);
bail!(gettext("Unknown image format")) bail!(gettext("Unknown image format"))
} }

View file

@ -222,6 +222,10 @@ impl LpFileModel {
self.imp().files.borrow().last().map(|x| x.1).cloned() self.imp().files.borrow().last().map(|x| x.1).cloned()
} }
pub fn remove(&self, file: &gio::File) -> Option<gio::File> {
self.imp().files.borrow_mut().remove(&file.uri())
}
/// Currently sorts by name /// Currently sorts by name
fn sort(files: &mut IndexMap<GString, gio::File>) { fn sort(files: &mut IndexMap<GString, gio::File>) {
files.sort_by(|_, x, _, y| util::compare_by_name(&x.uri(), &y.uri())); files.sort_by(|_, x, _, y| util::compare_by_name(&x.uri(), &y.uri()));

View file

@ -109,6 +109,7 @@ mod imp {
pub struct LpImage { pub struct LpImage {
pub(super) file: RefCell<Option<gio::File>>, pub(super) file: RefCell<Option<gio::File>>,
pub(super) is_deleted: Cell<bool>, pub(super) is_deleted: Cell<bool>,
pub(super) is_unsupported: Cell<bool>,
/// Track changes to this image /// Track changes to this image
pub(super) file_monitor: RefCell<Option<gio::FileMonitor>>, pub(super) file_monitor: RefCell<Option<gio::FileMonitor>>,
pub(super) frame_buffer: Arc<ArcSwap<tiling::FrameBuffer>>, pub(super) frame_buffer: Arc<ArcSwap<tiling::FrameBuffer>>,
@ -200,6 +201,9 @@ mod imp {
glib::ParamSpecBoolean::builder("is-loaded") glib::ParamSpecBoolean::builder("is-loaded")
.read_only() .read_only()
.build(), .build(),
glib::ParamSpecBoolean::builder("is-unsupported")
.read_only()
.build(),
glib::ParamSpecString::builder("error").read_only().build(), glib::ParamSpecString::builder("error").read_only().build(),
glib::ParamSpecObject::builder::<LpImageMetadata>("metadata") glib::ParamSpecObject::builder::<LpImageMetadata>("metadata")
.read_only() .read_only()
@ -241,6 +245,7 @@ mod imp {
"file" => obj.file().to_value(), "file" => obj.file().to_value(),
"is-deleted" => obj.is_deleted().to_value(), "is-deleted" => obj.is_deleted().to_value(),
"is-loaded" => obj.is_loaded().to_value(), "is-loaded" => obj.is_loaded().to_value(),
"is-unsupported" => obj.is_unsupported().to_value(),
"error" => obj.error().to_value(), "error" => obj.error().to_value(),
"metadata" => obj.metadata().to_value(), "metadata" => obj.metadata().to_value(),
"format-name" => obj.format_name().to_value(), "format-name" => obj.format_name().to_value(),
@ -759,13 +764,7 @@ impl LpImage {
// Reset background color for reloads // Reset background color for reloads
self.set_background_color(None); self.set_background_color(None);
let (decoder, mut decoder_update) = match Decoder::new(file.clone(), tiles.clone()).await { let (decoder_res, mut decoder_update) = Decoder::new(file.clone(), tiles.clone()).await;
Ok(x) => x,
Err(err) => {
self.set_error(err);
return;
}
};
let weak_obj = self.downgrade(); let weak_obj = self.downgrade();
spawn(async move { spawn(async move {
@ -777,8 +776,10 @@ impl LpImage {
log::debug!("Stopped listening to decoder since sender is gone"); log::debug!("Stopped listening to decoder since sender is gone");
}); });
let decoder = Arc::new(decoder); if let Ok(decoder) = decoder_res {
self.imp().decoder.replace(Some(decoder)); let decoder = Arc::new(decoder);
self.imp().decoder.replace(Some(decoder));
}
} }
/// Called when decoder sends update /// Called when decoder sends update
@ -837,6 +838,12 @@ impl LpImage {
imp.tick_callback.replace(Some(callback_id)); imp.tick_callback.replace(Some(callback_id));
} }
} }
DecoderUpdate::UnsupportedFormat => {
if !self.is_unsupported() {
imp.is_unsupported.set(true);
self.notify("is-unsupported");
}
}
} }
} }
@ -874,6 +881,10 @@ impl LpImage {
self.imp().is_deleted.get() self.imp().is_deleted.get()
} }
pub fn is_unsupported(&self) -> bool {
self.imp().is_unsupported.get()
}
/// Zoom level that makes the image fit in widget /// Zoom level that makes the image fit in widget
/// ///
/// During image rotation the image does not actually fit into widget. /// During image rotation the image does not actually fit into widget.

View file

@ -256,7 +256,7 @@ impl LpImageView {
if let Some(first) = files.first().cloned() { if let Some(first) = files.first().cloned() {
let model = LpFileModel::from_files(files); let model = LpFileModel::from_files(files);
self.set_model(model); self.set_model(model);
let page = LpImagePage::from_file(&first); let page = self.new_image_page(&first);
sliding_view.clear(); sliding_view.clear();
sliding_view.append(&page); sliding_view.append(&page);
@ -356,7 +356,7 @@ impl LpImageView {
let page = if let Some(page) = sliding_view.get(new_file) { let page = if let Some(page) = sliding_view.get(new_file) {
page page
} else { } else {
let page = LpImagePage::from_file(new_file); let page = self.new_image_page(new_file);
if new_index > current_index { if new_index > current_index {
sliding_view.append(&page); sliding_view.append(&page);
@ -397,7 +397,7 @@ impl LpImageView {
if let Some(page) = existing.get(&file.uri()) { if let Some(page) = existing.get(&file.uri()) {
sliding_view.move_to(page, position); sliding_view.move_to(page, position);
} else { } else {
sliding_view.insert(&LpImagePage::from_file(file), position); sliding_view.insert(&self.new_image_page(file), position);
} }
} }
@ -438,6 +438,32 @@ impl LpImageView {
self.imp().model.borrow().clone() self.imp().model.borrow().clone()
} }
/// Create new image that communicates updates to file model
fn new_image_page(&self, file: &gio::File) -> LpImagePage {
let page = LpImagePage::from_file(file);
page.image().connect_notify_local(
Some("is-unsupported"),
glib::clone!(@weak self as obj => move |image, _| {
if image.is_unsupported() {
if obj.current_image().as_ref() == Some(image) {
log::debug!(
"Image format unsupported but not removing since current image"
);
return;
}
if let Some(file) = image.file() {
log::debug!("Removing image with unsupported format {:?}", file.uri());
obj.model().remove(&file);
}
}
}),
);
page
}
fn set_model(&self, model: LpFileModel) { fn set_model(&self, model: LpFileModel) {
model.connect_changed( model.connect_changed(
glib::clone!(@weak self as obj => move || obj.model_content_changed_cb()), glib::clone!(@weak self as obj => move || obj.model_content_changed_cb()),