Loupe/src/decoder.rs

218 lines
6.4 KiB
Rust
Raw Normal View History

2026-02-27 21:03:02 +01:00
// Copyright (c) 2023-2026 Sophie Herold
2023-11-29 21:15:36 +01:00
// Copyright (c) 2023 Julian Hofer
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//! Decodes several image formats
pub mod formats;
pub mod tiling;
use std::sync::Arc;
use anyhow::Result;
2024-09-27 02:40:21 +02:00
use async_channel as mpsc;
2024-01-01 22:41:56 +01:00
pub use formats::RSVG_MAX_SIZE;
use formats::*;
use self::tiling::SharedFrameBuffer;
use crate::deps::*;
2024-01-01 22:41:56 +01:00
use crate::metadata::Metadata;
#[derive(Clone, Copy, Debug)]
/// Renderer requests new tiles
///
/// This happens for initial loading or when zoom level or viewing area changes.
pub struct TileRequest {
pub viewport: graphene::Rect,
pub area: graphene::Rect,
pub zoom: f64,
}
#[derive(Debug, Clone)]
/// Signals update to the renderer
pub struct UpdateSender {
2024-09-27 02:40:21 +02:00
sender: mpsc::Sender<DecoderUpdate>,
}
#[derive(Debug)]
/// Signals for renderer (LpImage)
pub enum DecoderUpdate {
/// Dimensions of image in `TilingSore` available/updated
2024-01-01 22:41:56 +01:00
Dimensions,
/// Metadata available
2024-01-01 22:41:56 +01:00
Metadata(Box<Metadata>),
/// New image data available, redraw
Redraw,
/// Start animation
Animated,
Error(Option<DecoderError>),
2024-07-19 23:19:34 +02:00
}
#[derive(Debug, Clone)]
2024-07-19 23:19:34 +02:00
pub enum DecoderError {
/// Image format not supported or unknown
2026-06-25 16:52:58 +02:00
UnsupportedFormat(String, glycin::Error),
2024-07-19 23:19:34 +02:00
/// No glycin-loaders installed
2026-06-25 16:52:58 +02:00
NoLoadersConfigured(glycin::Error),
/// Memory limit exceeded
2026-06-25 16:52:58 +02:00
OutOfMemory(glycin::Error),
/// Failed to load image
2026-06-25 16:52:58 +02:00
ImageSource(glib::Error, glycin::Error),
2024-07-19 23:19:34 +02:00
2026-06-25 16:52:58 +02:00
Generic(glycin::Error),
2024-07-19 23:19:34 +02:00
}
impl UpdateSender {
pub fn send(&self, update: DecoderUpdate) {
2024-09-27 02:40:21 +02:00
let result = self.sender.force_send(update);
2024-09-27 02:40:21 +02:00
match result {
2025-12-18 23:15:46 +01:00
Err(err) => tracing::error!("Failed to send update: {err}"),
Ok(Some(msg)) => tracing::error!("Unexpectedly replaced the message {msg:?}"),
2024-09-27 02:40:21 +02:00
_ => {}
}
}
2023-11-30 21:58:26 +01:00
/// Send occurring errors to renderer
pub fn spawn_error_handled<F>(&self, f: F) -> glib::JoinHandle<()>
where
2026-06-25 16:52:58 +02:00
F: std::future::Future<Output = Result<(), glycin::Error>> + Send + 'static,
{
let update_sender = self.clone();
glib::spawn_future(async move {
let update_sender = update_sender.clone();
2026-06-25 16:52:58 +02:00
let result: Result<(), glycin::Error> = f.await;
if let Err(err) = result {
update_sender.send_error(err);
}
})
}
2026-06-25 16:52:58 +02:00
pub fn send_error(&self, err: glycin::Error) {
if let Some(mime_type) = err.unsupported_format() {
let mut metadata = Metadata::default();
metadata.set_mime_type(mime_type.clone());
self.send(DecoderUpdate::Metadata(Box::new(metadata)));
self.send(DecoderUpdate::Error(Some(DecoderError::UnsupportedFormat(
mime_type, err,
))));
} else if err.is_out_of_memory() {
self.send(DecoderUpdate::Error(Some(DecoderError::OutOfMemory(err))));
2026-06-25 16:52:58 +02:00
} else if err.has_no_processor_configured() {
self.send(DecoderUpdate::Error(Some(
DecoderError::NoLoadersConfigured(err),
)));
2026-06-25 16:52:58 +02:00
} else if let Some(glib_err) = err.failed_image_source() {
self.send(DecoderUpdate::Error(Some(DecoderError::ImageSource(
2026-06-25 16:52:58 +02:00
glib_err, err,
))));
} else {
self.send(DecoderUpdate::Error(Some(DecoderError::Generic(err))));
}
}
}
#[derive(Debug)]
pub struct Decoder {
decoder: FormatDecoder,
}
#[derive(Debug)]
enum FormatDecoder {
Glycin(Glycin),
Svg(Svg),
Failed,
}
impl Decoder {
/// Get new image decoder
///
/// The textures will be stored in the passed `TiledImage`.
/// The renderer should listen to updates from the returned receiver.
pub async fn new(
file: gio::File,
tiles: Arc<SharedFrameBuffer>,
2024-09-27 02:40:21 +02:00
) -> (Self, mpsc::Receiver<DecoderUpdate>) {
let (sender, receiver) = mpsc::unbounded();
let update_sender = UpdateSender { sender };
tiles.set_update_sender(update_sender.clone());
2025-12-18 23:15:46 +01:00
tracing::trace!("Setting up loader");
let mut loader = glycin::Loader::new(file);
loader.apply_transformations(false);
let image = match loader.load().await {
Ok(image) => image,
Err(err) => {
update_sender.send_error(err);
return (
Self {
decoder: FormatDecoder::Failed,
},
receiver,
);
}
};
let format_decoder = Self::format_decoder(update_sender.clone(), image, tiles);
let decoder = Self {
decoder: format_decoder,
};
(decoder, receiver)
}
fn format_decoder(
update_sender: UpdateSender,
image: glycin::Image,
tiles: Arc<SharedFrameBuffer>,
) -> FormatDecoder {
let mime_type = image.mime_type().to_string();
// Known things we want to match here are
// - image/svg+xml
// - image/svg+xml-compressed
if mime_type.split('+').next() == Some("image/svg") {
2025-12-04 05:35:00 +01:00
FormatDecoder::Svg(Svg::new(image, update_sender, tiles))
} else {
FormatDecoder::Glycin(Glycin::new(image, update_sender, tiles))
}
}
/// Request missing tiles
pub fn request(&self, tile_request: TileRequest) {
2026-01-29 19:04:26 +01:00
if let FormatDecoder::Svg(svg) = &self.decoder
&& let Err(err) = svg.request(tile_request)
{
tracing::error!("Failed to request new SVG tiles: {err}");
}
}
pub fn fill_frame_buffer(&self) {
if let FormatDecoder::Glycin(decoder) = &self.decoder {
decoder.fill_frame_buffer();
} else {
2025-12-18 23:15:46 +01:00
tracing::error!("Trying to fill frame buffer for decoder without animation support");
}
}
}