Loupe/src/decoder/formats/image_rs_other.rs
2023-03-18 20:23:08 +00:00

313 lines
10 KiB
Rust

///! Decode using image-rs
use super::*;
use crate::decoder::tiling::{self, FrameBufferExt};
use crate::deps::*;
use crate::i18n::*;
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)]
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();
}
}
}
impl ImageRsOther {
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)
}
pub fn new(
path: PathBuf,
format: image_rs::ImageFormat,
updater: UpdateSender,
tiles: Arc<ArcSwap<tiling::FrameBuffer>>,
) -> Self {
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>>,
) {
updater.spawn_error_handled(move || {
let reader = Self::reader(&path, format)?;
let dimensions = reader
.into_dimensions()
.context("Failed to read image dimensions")?;
tiles.set_original_dimensions(dimensions);
let mut reader = Self::reader(&path, format)?;
reader.limits(image_rs::io::Limits::no_limits());
let dynamic_image = reader.decode().context(i18n("Failed to decode image"))?;
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(())
});
}
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(());
}
}
}
})
}
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");
}
}
}
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()
}
}