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:
parent
8f55668cd6
commit
6db58463bf
5 changed files with 83 additions and 25 deletions
|
|
@ -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 [
|
||||||
|
|
|
||||||
|
|
@ -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"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()));
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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()),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue