diff --git a/data/gtk/window.ui b/data/gtk/window.ui
index f172102..7e42abc 100644
--- a/data/gtk/window.ui
+++ b/data/gtk/window.ui
@@ -59,7 +59,7 @@
Loupe
-
+
image_view
diff --git a/src/application.rs b/src/application.rs
index 3624dbc..f64d93a 100644
--- a/src/application.rs
+++ b/src/application.rs
@@ -73,7 +73,7 @@ mod imp {
let application = self.obj();
for file in files {
let win = LpWindow::new(&*application);
- win.set_image_from_file(&file);
+ win.set_image_from_file(file);
}
}
}
diff --git a/src/decoder/formats/heif.rs b/src/decoder/formats/heif.rs
index eca2d86..d9bea2f 100644
--- a/src/decoder/formats/heif.rs
+++ b/src/decoder/formats/heif.rs
@@ -3,14 +3,14 @@ use super::*;
use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*;
use crate::util::gettext::*;
-use gtk::prelude::*;
+use crate::util::ToBufRead;
use anyhow::Context;
use arc_swap::ArcSwap;
+use gtk::prelude::*;
use libheif_rs::{ColorSpace, HeifContext, LibHeif, Plane, RgbChroma};
use once_cell::sync::Lazy;
-use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug)]
@@ -21,14 +21,24 @@ static LIBHEIF: Lazy = Lazy::new(LibHeif::new);
impl Heif {
pub fn new(
- path: PathBuf,
+ file: gio::File,
updater: UpdateSender,
tiles: Arc>,
) -> Self {
log::debug!("Loading HEIF");
updater.spawn_error_handled(move || {
- let ctx = HeifContext::read_from_file(&path.display().to_string())
- .context(gettext("Failed to read image"))?;
+ let file_size = file
+ .query_info(
+ gio::FILE_ATTRIBUTE_STANDARD_SIZE,
+ gio::FileQueryInfoFlags::NONE,
+ gio::Cancellable::NONE,
+ )
+ .context(gettext("Failed to read image file information"))?
+ .size();
+ let buf_reader = file.to_buf_read()?;
+ let stream_reader = libheif_rs::StreamReader::new(buf_reader, file_size as u64);
+ let ctx = HeifContext::read_from_reader(Box::new(stream_reader))
+ .context(gettext("Failed to decode image"))?;
let handle = ctx.primary_image_handle()?;
tiles.set_original_dimensions((handle.width(), handle.height()));
diff --git a/src/decoder/formats/image_rs_other.rs b/src/decoder/formats/image_rs_other.rs
index b81349f..5abe86d 100644
--- a/src/decoder/formats/image_rs_other.rs
+++ b/src/decoder/formats/image_rs_other.rs
@@ -3,15 +3,13 @@ use super::*;
use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*;
use crate::util::gettext::*;
+use crate::util::{BufReadSeek, ToBufRead};
use anyhow::Context;
use arc_swap::ArcSwap;
use gtk::prelude::*;
use image_rs::AnimationDecoder;
-use std::fs::File;
-use std::io::BufReader;
-use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug)]
@@ -32,17 +30,17 @@ impl Drop for ImageRsOther {
impl ImageRsOther {
fn reader(
- path: &Path,
+ file: &gio::File,
format: image_rs::ImageFormat,
- ) -> anyhow::Result>> {
- let mut reader =
- image_rs::io::Reader::open(path).context(gettext("Failed to open image"))?;
+ ) -> anyhow::Result>> {
+ let buf_read = file.to_buf_read()?;
+ let mut reader = image_rs::io::Reader::new(buf_read);
reader.set_format(format);
Ok(reader)
}
pub fn new(
- path: PathBuf,
+ file: gio::File,
format: image_rs::ImageFormat,
updater: UpdateSender,
tiles: Arc>,
@@ -53,14 +51,14 @@ impl ImageRsOther {
image_rs::ImageFormat::Gif
| image_rs::ImageFormat::WebP
| image_rs::ImageFormat::Png => Some(Self::new_animated(
- path,
+ file,
format,
updater,
tiles,
abort.clone(),
)),
_ => {
- Self::new_static(path, format, updater, tiles);
+ Self::new_static(file, format, updater, tiles);
None
}
};
@@ -72,20 +70,20 @@ impl ImageRsOther {
}
fn new_static(
- path: PathBuf,
+ file: gio::File,
format: image_rs::ImageFormat,
updater: UpdateSender,
tiles: Arc>,
) {
updater.spawn_error_handled(move || {
- let reader = Self::reader(&path, format)?;
+ let reader = Self::reader(&file, format)?;
let dimensions = reader
.into_dimensions()
.context("Failed to read image dimensions")?;
tiles.set_original_dimensions(dimensions);
- let mut reader = Self::reader(&path, format)?;
+ let mut reader = Self::reader(&file, format)?;
reader.limits(image_rs::io::Limits::no_limits());
@@ -107,7 +105,7 @@ impl ImageRsOther {
}
fn new_animated(
- path: PathBuf,
+ file: gio::File,
format: image_rs::ImageFormat,
updater: UpdateSender,
tiles: Arc>,
@@ -119,30 +117,30 @@ impl ImageRsOther {
// We are currently decoding for each repetition of the animation
// TODO: Check if/how that can be solved differently
loop {
- let file = std::fs::File::open(&path)?;
+ let buf_reader = file.to_buf_read()?;
let (frames, animated_format) = match format {
image_rs::ImageFormat::Gif => (
- image_rs::codecs::gif::GifDecoder::new(file)?.into_frames(),
+ image_rs::codecs::gif::GifDecoder::new(buf_reader)?.into_frames(),
ImageFormat::AnimatedGif,
),
image_rs::ImageFormat::WebP => {
- let decoder = image_rs::codecs::webp::WebPDecoder::new(file)?;
+ let decoder = image_rs::codecs::webp::WebPDecoder::new(buf_reader)?;
if decoder.has_animation() {
(decoder.into_frames(), ImageFormat::AnimatedWebP)
} else {
// Static WebP images need a different decoder
- Self::new_static(path, format, updater, tiles);
+ Self::new_static(file, format, updater, tiles);
return Ok(());
}
}
image_rs::ImageFormat::Png => {
- let decoder = image_rs::codecs::png::PngDecoder::new(file)?;
+ let decoder = image_rs::codecs::png::PngDecoder::new(buf_reader)?;
if decoder.is_apng() {
(decoder.apng().into_frames(), ImageFormat::AnimatedPng)
} else {
// Static PNG images need a different decoder
- Self::new_static(path, format, updater, tiles);
+ Self::new_static(file, format, updater, tiles);
return Ok(());
}
}
diff --git a/src/decoder/formats/svg.rs b/src/decoder/formats/svg.rs
index 9288b45..e11502c 100644
--- a/src/decoder/formats/svg.rs
+++ b/src/decoder/formats/svg.rs
@@ -8,7 +8,6 @@ use anyhow::Context;
use arc_swap::ArcSwap;
use gtk::prelude::*;
-use std::path::PathBuf;
use std::sync::Arc;
/// Current librsvg limit on maximum dimensions. See
@@ -45,7 +44,7 @@ impl Drop for Svg {
impl Svg {
pub fn new(
- path: PathBuf,
+ file: gio::File,
updater: UpdateSender,
tiles: Arc>,
) -> Self {
@@ -53,7 +52,7 @@ impl Svg {
let request_store = current_request.clone();
let thread_handle = updater.spawn_error_handled(move || {
- let handle = rsvg::Loader::new().read_path(path)?;
+ let handle = rsvg::Loader::new().read_file(&file, gio::Cancellable::NONE)?;
let renderer = rsvg::CairoRenderer::new(&handle);
let (original_width, original_height) = svg_dimensions(&renderer);
@@ -85,7 +84,7 @@ impl Svg {
tile_size: tiling::TILE_SIZE,
original_dimensions: (original_width, original_height),
zoom: tile_request.zoom,
- bleed: 2,
+ bleed: 0,
};
let relevant_tiles = tiling.relevant_tiles(&tile_request.viewport);
diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs
index 8f9e23f..82fa480 100644
--- a/src/decoder/mod.rs
+++ b/src/decoder/mod.rs
@@ -7,16 +7,16 @@ pub use formats::ImageDimensionDetails;
use crate::deps::*;
use crate::image_metadata::ImageMetadata;
use crate::util::gettext::*;
+use crate::util::ToBufRead;
use formats::*;
use tiling::FrameBufferExt;
-use anyhow::{anyhow, bail, Context};
+use anyhow::{anyhow, bail, Context, Result};
use arc_swap::ArcSwap;
use futures::channel::mpsc;
use gio::prelude::*;
use std::io::Read;
-use std::path::PathBuf;
use std::sync::Arc;
pub use formats::{ImageFormat, RSVG_MAX_SIZE};
@@ -95,13 +95,12 @@ impl Decoder {
file: gio::File,
tiles: Arc>,
) -> anyhow::Result<(Self, mpsc::UnboundedReceiver)> {
- let path = file.path().context("Need a file path")?;
let (sender, receiver) = mpsc::unbounded();
let update_sender = UpdateSender { sender };
tiles.set_update_sender(update_sender.clone());
- let decoder = gio::spawn_blocking(move || Self::format_decoder(update_sender, path, tiles))
+ let decoder = gio::spawn_blocking(move || Self::format_decoder(update_sender, file, tiles))
.await
.map_err(|_| anyhow!("Constructing the FormatDecoder failed unexpectedly"))??;
@@ -110,31 +109,23 @@ impl Decoder {
fn format_decoder(
update_sender: UpdateSender,
- path: PathBuf,
+ file: gio::File,
tiles: Arc>,
) -> anyhow::Result {
- update_sender.send(DecoderUpdate::Metadata(ImageMetadata::load(&path)));
+ update_sender.send(DecoderUpdate::Metadata(ImageMetadata::load(&file)));
- let mut buf = Vec::new();
- let file = std::fs::File::open(&path).context(gettext("Could not open image"))?;
- file.take(64)
- .read_to_end(&mut buf)
- .context(gettext("Could not read image"))?;
+ let format = Self::guess_format(&file)?;
- // Try magic bytes first and than file name extension
- let format =
- image_rs::guess_format(&buf).or_else(|_| image_rs::ImageFormat::from_path(&path));
-
- if let Ok(format) = format {
+ if let Some(format) = format {
match format {
image_rs::ImageFormat::Avif => {
update_sender.send(DecoderUpdate::Format(ImageFormat::Heif));
- return Ok(FormatDecoder::Heif(Heif::new(path, update_sender, tiles)));
+ return Ok(FormatDecoder::Heif(Heif::new(file, update_sender, tiles)));
}
format => {
update_sender.send(DecoderUpdate::Format(ImageFormat::ImageRs(format)));
return Ok(FormatDecoder::ImageRsOther(ImageRsOther::new(
- path,
+ file,
format,
update_sender,
tiles,
@@ -142,7 +133,7 @@ impl Decoder {
}
}
} else {
- let file_info = gio::File::for_path(&path)
+ let file_info = file
.query_info(
gio::FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
gio::FileQueryInfoFlags::NONE,
@@ -159,12 +150,12 @@ impl Decoder {
// - image/svg+xml-compressed
if content_type.split('+').next() == Some("image/svg") {
update_sender.send(DecoderUpdate::Format(ImageFormat::Svg));
- return Ok(FormatDecoder::Svg(Svg::new(path, update_sender, tiles)));
+ return Ok(FormatDecoder::Svg(Svg::new(file, update_sender, tiles)));
} else if ["image/avif", "image/heif", "image/heic"]
.contains(&content_type.as_str())
{
update_sender.send(DecoderUpdate::Format(ImageFormat::Heif));
- return Ok(FormatDecoder::Heif(Heif::new(path, update_sender, tiles)));
+ return Ok(FormatDecoder::Heif(Heif::new(file, update_sender, tiles)));
} else {
bail!(gettext_f(
"Unknown image format: {}",
@@ -193,4 +184,25 @@ impl Decoder {
log::error!("Trying to fill frame buffer for decoder without animation support");
}
}
+
+ fn guess_format(file: &gio::File) -> anyhow::Result