decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
///! Decode using image-rs
|
|
|
|
|
use super::*;
|
2023-03-15 22:35:59 +01:00
|
|
|
use crate::decoder::tiling::{self, FrameBufferExt};
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
use crate::deps::*;
|
2023-03-14 00:10:14 +01:00
|
|
|
use crate::i18n::*;
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
|
|
|
|
|
use anyhow::Context;
|
|
|
|
|
use arc_swap::ArcSwap;
|
|
|
|
|
use gtk::prelude::*;
|
2023-03-15 22:35:59 +01:00
|
|
|
use image_rs::AnimationDecoder;
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
|
2023-03-14 00:10:14 +01:00
|
|
|
use std::fs::File;
|
|
|
|
|
use std::io::BufReader;
|
|
|
|
|
use std::path::{Path, PathBuf};
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2023-03-15 22:35:59 +01:00
|
|
|
pub struct ImageRsOther {
|
|
|
|
|
thread_handle: Option<std::thread::JoinHandle<()>>,
|
|
|
|
|
abort: Arc<std::sync::RwLock<bool>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for ImageRsOther {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
if let Some(handle) = &self.thread_handle {
|
|
|
|
|
let mut abort = self.abort.write().unwrap();
|
|
|
|
|
*abort = true;
|
|
|
|
|
handle.thread().unpark();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
|
|
|
|
|
impl ImageRsOther {
|
2023-03-14 00:10:14 +01:00
|
|
|
fn reader(
|
|
|
|
|
path: &Path,
|
|
|
|
|
format: image_rs::ImageFormat,
|
|
|
|
|
) -> anyhow::Result<image_rs::io::Reader<BufReader<File>>> {
|
|
|
|
|
let mut reader = image_rs::io::Reader::open(path).context(i18n("Failed to open image"))?;
|
|
|
|
|
reader.set_format(format);
|
|
|
|
|
Ok(reader)
|
|
|
|
|
}
|
|
|
|
|
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
pub fn new(
|
|
|
|
|
path: PathBuf,
|
2023-03-14 00:10:14 +01:00
|
|
|
format: image_rs::ImageFormat,
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
updater: UpdateSender,
|
2023-03-15 22:35:59 +01:00
|
|
|
tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
) -> Self {
|
2023-03-15 22:35:59 +01:00
|
|
|
let abort: Arc<std::sync::RwLock<bool>> = Default::default();
|
|
|
|
|
|
|
|
|
|
let thread_handle = match format {
|
|
|
|
|
image_rs::ImageFormat::Gif
|
|
|
|
|
| image_rs::ImageFormat::WebP
|
|
|
|
|
| image_rs::ImageFormat::Png => Some(Self::new_animated(
|
|
|
|
|
path,
|
|
|
|
|
format,
|
|
|
|
|
updater,
|
|
|
|
|
tiles,
|
|
|
|
|
abort.clone(),
|
|
|
|
|
)),
|
|
|
|
|
_ => {
|
|
|
|
|
Self::new_static(path, format, updater, tiles);
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ImageRsOther {
|
|
|
|
|
thread_handle,
|
|
|
|
|
abort,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn new_static(
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
format: image_rs::ImageFormat,
|
|
|
|
|
updater: UpdateSender,
|
|
|
|
|
tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
|
|
|
|
|
) {
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
updater.spawn_error_handled(move || {
|
2023-03-14 00:10:14 +01:00
|
|
|
let reader = Self::reader(&path, format)?;
|
|
|
|
|
let dimensions = reader
|
|
|
|
|
.into_dimensions()
|
|
|
|
|
.context("Failed to read image dimensions")?;
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
|
|
|
|
|
tiles.set_original_dimensions(dimensions);
|
|
|
|
|
|
2023-03-14 00:10:14 +01:00
|
|
|
let mut reader = Self::reader(&path, format)?;
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
|
|
|
|
|
reader.limits(image_rs::io::Limits::no_limits());
|
|
|
|
|
|
2023-03-14 00:10:14 +01:00
|
|
|
let dynamic_image = reader.decode().context(i18n("Failed to decode image"))?;
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
|
|
|
|
|
let decoded_image = Decoded { dynamic_image };
|
|
|
|
|
|
|
|
|
|
let tile = tiling::Tile {
|
|
|
|
|
position: (0, 0),
|
|
|
|
|
zoom_level: tiling::zoom_to_level(1.),
|
|
|
|
|
bleed: 0,
|
|
|
|
|
texture: decoded_image.into_texture(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
tiles.push(tile);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
});
|
2023-03-15 22:35:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn new_animated(
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
format: image_rs::ImageFormat,
|
|
|
|
|
updater: UpdateSender,
|
|
|
|
|
tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
|
|
|
|
|
abort: Arc<std::sync::RwLock<bool>>,
|
|
|
|
|
) -> std::thread::JoinHandle<()> {
|
|
|
|
|
updater.clone().spawn_error_handled(move || {
|
|
|
|
|
let mut nth_frame = 1;
|
|
|
|
|
|
|
|
|
|
// 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 (frames, animated_format) = match format {
|
|
|
|
|
image_rs::ImageFormat::Gif => (
|
|
|
|
|
image_rs::codecs::gif::GifDecoder::new(file)?.into_frames(),
|
|
|
|
|
ImageFormat::AnimatedGif,
|
|
|
|
|
),
|
|
|
|
|
image_rs::ImageFormat::WebP => (
|
|
|
|
|
image_rs::codecs::webp::WebPDecoder::new(file)?.into_frames(),
|
|
|
|
|
ImageFormat::AnimatedWebP,
|
|
|
|
|
),
|
|
|
|
|
image_rs::ImageFormat::Png => {
|
|
|
|
|
let decoder = image_rs::codecs::png::PngDecoder::new(file)?;
|
|
|
|
|
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);
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => todo!(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for frame in frames {
|
|
|
|
|
match frame {
|
|
|
|
|
Err(err) => {
|
|
|
|
|
log::warn!("Skipping frame: {err}");
|
|
|
|
|
}
|
|
|
|
|
Ok(frame) => {
|
|
|
|
|
if nth_frame == 2 {
|
|
|
|
|
// We have at least two frames so this is animated
|
|
|
|
|
updater.send(DecoderUpdate::Format(animated_format));
|
|
|
|
|
nth_frame = 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let (delay_num, delay_den) = frame.delay().numer_denom_ms();
|
|
|
|
|
let delay = std::time::Duration::from_micros(f64::round(
|
|
|
|
|
delay_num as f64 * 1000. / delay_den as f64,
|
|
|
|
|
)
|
|
|
|
|
as u64);
|
|
|
|
|
let position = (frame.left(), frame.top());
|
|
|
|
|
let rgb_image = frame.into_buffer();
|
|
|
|
|
let dimensions = (
|
|
|
|
|
rgb_image.width() + position.0,
|
|
|
|
|
rgb_image.height() + position.1,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let dynamic_image = image_rs::DynamicImage::from(rgb_image);
|
|
|
|
|
|
|
|
|
|
let decoded_image = Decoded { dynamic_image };
|
|
|
|
|
|
|
|
|
|
let tile = tiling::Tile {
|
|
|
|
|
position,
|
|
|
|
|
zoom_level: tiling::zoom_to_level(1.),
|
|
|
|
|
bleed: 0,
|
|
|
|
|
texture: decoded_image.into_texture(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
tiles.push_frame(tile, dimensions, delay);
|
|
|
|
|
|
|
|
|
|
if nth_frame == 1 {
|
|
|
|
|
updater.send(DecoderUpdate::Dimensions(Default::default()));
|
|
|
|
|
updater.send(DecoderUpdate::Redraw);
|
|
|
|
|
nth_frame = 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tiles.n_frames() >= 3 {
|
|
|
|
|
std::thread::park();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if *abort.read().unwrap() {
|
|
|
|
|
log::debug!("Terminating decoder thread.");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
|
2023-03-15 22:35:59 +01:00
|
|
|
pub fn fill_frame_buffer(&self) {
|
|
|
|
|
if let Some(handle) = &self.thread_handle {
|
|
|
|
|
handle.thread().unpark();
|
|
|
|
|
} else {
|
|
|
|
|
log::error!("Trying to wake up one-shot decoder");
|
|
|
|
|
}
|
decoder: Use image-rs, libheif-rs, librsvg
This adds proper support for a lot of image formats including SVGs.
It will be the basis for many features to come. The technical core
is an asynchronous renderer that can arrange textures in smaller tiles
which might be generated on demand.
Closes #34, #40, #41, #49
Basis for fixing the following issues #7, #27, #36, #43, #68, #71
2023-03-04 03:14:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct Decoded {
|
|
|
|
|
dynamic_image: image_rs::DynamicImage,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Decoded {
|
|
|
|
|
pub fn into_texture(self) -> gdk::Texture {
|
|
|
|
|
let (n_bytes, memory_format, layout, data) = match self.dynamic_image {
|
|
|
|
|
img @ image_rs::DynamicImage::ImageLuma8(_) => {
|
|
|
|
|
let buffer = img.to_rgb8();
|
|
|
|
|
(
|
|
|
|
|
1,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
buffer.into_raw(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
img @ image_rs::DynamicImage::ImageLumaA8(_) => {
|
|
|
|
|
let buffer = img.to_rgba8();
|
|
|
|
|
(
|
|
|
|
|
1,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8a8,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
buffer.into_raw(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
image_rs::DynamicImage::ImageRgb8(buffer) => (
|
|
|
|
|
1,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
buffer.into_raw(),
|
|
|
|
|
),
|
|
|
|
|
image_rs::DynamicImage::ImageRgba8(buffer) => (
|
|
|
|
|
1,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8a8,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
buffer.into_raw(),
|
|
|
|
|
),
|
|
|
|
|
img @ image_rs::DynamicImage::ImageLuma16(_) => {
|
|
|
|
|
let buffer = img.to_rgb16();
|
|
|
|
|
(
|
|
|
|
|
2,
|
|
|
|
|
gdk::MemoryFormat::R16g16b16,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
image_rs::DynamicImage::from(buffer).into_bytes(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
img @ image_rs::DynamicImage::ImageLumaA16(_) => {
|
|
|
|
|
let buffer = img.to_rgba16();
|
|
|
|
|
(
|
|
|
|
|
2,
|
|
|
|
|
gdk::MemoryFormat::R16g16b16a16,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
image_rs::DynamicImage::from(buffer).into_bytes(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
image_rs::DynamicImage::ImageRgb16(buffer) => (
|
|
|
|
|
2,
|
|
|
|
|
gdk::MemoryFormat::R16g16b16,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
image_rs::DynamicImage::from(buffer).into_bytes(),
|
|
|
|
|
),
|
|
|
|
|
image_rs::DynamicImage::ImageRgba16(buffer) => (
|
|
|
|
|
2,
|
|
|
|
|
gdk::MemoryFormat::R16g16b16a16,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
image_rs::DynamicImage::from(buffer).into_bytes(),
|
|
|
|
|
),
|
|
|
|
|
image_rs::DynamicImage::ImageRgb32F(buffer) => (
|
|
|
|
|
4,
|
|
|
|
|
gdk::MemoryFormat::R32g32b32Float,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
image_rs::DynamicImage::from(buffer).into_bytes(),
|
|
|
|
|
),
|
|
|
|
|
image_rs::DynamicImage::ImageRgba32F(buffer) => (
|
|
|
|
|
4,
|
|
|
|
|
gdk::MemoryFormat::R32g32b32a32Float,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
image_rs::DynamicImage::from(buffer).into_bytes(),
|
|
|
|
|
),
|
|
|
|
|
img => {
|
|
|
|
|
let buffer = img.to_rgba16();
|
|
|
|
|
(
|
|
|
|
|
2,
|
|
|
|
|
gdk::MemoryFormat::R16g16b16a16,
|
|
|
|
|
buffer.sample_layout(),
|
|
|
|
|
safe_transmute::transmute_vec(buffer.into_raw()).unwrap(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let bytes = glib::Bytes::from_owned(data);
|
|
|
|
|
|
|
|
|
|
gdk::MemoryTexture::new(
|
|
|
|
|
layout.width as i32,
|
|
|
|
|
layout.height as i32,
|
|
|
|
|
memory_format,
|
|
|
|
|
&bytes,
|
|
|
|
|
layout.height_stride * n_bytes,
|
|
|
|
|
)
|
|
|
|
|
.upcast()
|
|
|
|
|
}
|
|
|
|
|
}
|