decoder: Use glycin for decoding SVGs as well
Part-of: <https://gitlab.gnome.org/GNOME/loupe/-/merge_requests/267>
This commit is contained in:
parent
0aff4ef350
commit
a027513447
9 changed files with 140 additions and 978 deletions
|
|
@ -51,96 +51,84 @@ impl Glycin {
|
|||
|
||||
let (next_frame_send, next_frame_recv) = async_channel::bounded(2);
|
||||
|
||||
glib::MainContext::default().spawn(async move {
|
||||
let update_sender = updater.clone();
|
||||
updater.clone().spawn_error_handled(async move {
|
||||
let mut image_request = glycin::ImageRequest::new(file);
|
||||
|
||||
let result: Result<(), glycin::Error> = async move {
|
||||
let mut image_request = glycin::ImageRequest::new(file);
|
||||
#[cfg(feature = "disable-glycin-sandbox")]
|
||||
image_request.sandbox_mechanism(Some(glycin::SandboxMechanism::NotSandboxed));
|
||||
|
||||
#[cfg(feature = "disable-glycin-sandbox")]
|
||||
image_request.sandbox_mechanism(Some(glycin::SandboxMechanism::NotSandboxed));
|
||||
image_request.cancellable(cancellable_);
|
||||
|
||||
image_request.cancellable(cancellable_);
|
||||
let image = image_request.request().await?;
|
||||
|
||||
let image = image_request.request().await?;
|
||||
|
||||
if let Some(exif_raw) = image.info().exif.clone().into() {
|
||||
updater.send(DecoderUpdate::Metadata(ImageMetadata::from_exif_bytes(
|
||||
exif_raw,
|
||||
)));
|
||||
}
|
||||
|
||||
let dimensions = (image.info().width, image.info().height);
|
||||
tiles.set_original_dimensions(dimensions);
|
||||
|
||||
updater.send(DecoderUpdate::Format(ImageFormat::new(
|
||||
image.mime_type(),
|
||||
image.format_name(),
|
||||
if let Some(exif_raw) = image.info().exif.clone().into() {
|
||||
updater.send(DecoderUpdate::Metadata(ImageMetadata::from_exif_bytes(
|
||||
exif_raw,
|
||||
)));
|
||||
}
|
||||
|
||||
let frame = image.next_frame().await?;
|
||||
let dimensions = (image.info().width, image.info().height);
|
||||
tiles.set_original_dimensions(dimensions);
|
||||
|
||||
if let Some(delay) = frame.delay {
|
||||
updater.send(DecoderUpdate::Animated);
|
||||
updater.send(DecoderUpdate::Format(ImageFormat::new(
|
||||
image.mime_type(),
|
||||
image.format_name(),
|
||||
)));
|
||||
|
||||
let position = (0, 0);
|
||||
let frame = image.next_frame().await?;
|
||||
|
||||
let tile = tiling::Tile {
|
||||
position,
|
||||
zoom_level: tiling::zoom_to_level(1.),
|
||||
bleed: 0,
|
||||
texture: frame.texture,
|
||||
};
|
||||
if let Some(delay) = frame.delay {
|
||||
updater.send(DecoderUpdate::Animated);
|
||||
|
||||
tiles.push_frame(tile, dimensions, delay);
|
||||
updater.send(DecoderUpdate::Redraw);
|
||||
let position = (0, 0);
|
||||
|
||||
loop {
|
||||
if next_frame_recv.recv().await.is_ok() {
|
||||
loop {
|
||||
let frame = image.next_frame().await?;
|
||||
let tile = tiling::Tile {
|
||||
position,
|
||||
zoom_level: tiling::zoom_to_level(1.),
|
||||
bleed: 0,
|
||||
texture: frame.texture,
|
||||
};
|
||||
|
||||
let position = (0, 0);
|
||||
tiles.push_frame(tile, dimensions, delay);
|
||||
updater.send(DecoderUpdate::Redraw);
|
||||
|
||||
let tile = tiling::Tile {
|
||||
position,
|
||||
zoom_level: tiling::zoom_to_level(1.),
|
||||
bleed: 0,
|
||||
texture: frame.texture,
|
||||
};
|
||||
loop {
|
||||
if next_frame_recv.recv().await.is_ok() {
|
||||
loop {
|
||||
let frame = image.next_frame().await?;
|
||||
|
||||
tiles.push_frame(tile, dimensions, delay);
|
||||
let position = (0, 0);
|
||||
|
||||
if tiles.n_frames() >= FRAME_BUFFER {
|
||||
break;
|
||||
}
|
||||
let tile = tiling::Tile {
|
||||
position,
|
||||
zoom_level: tiling::zoom_to_level(1.),
|
||||
bleed: 0,
|
||||
texture: frame.texture,
|
||||
};
|
||||
|
||||
tiles.push_frame(tile, dimensions, delay);
|
||||
|
||||
if tiles.n_frames() >= FRAME_BUFFER {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log::debug!("Animation handler gone");
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
log::debug!("Animation handler gone");
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
let tile = tiling::Tile {
|
||||
position: (0, 0),
|
||||
zoom_level: tiling::zoom_to_level(1.),
|
||||
bleed: 0,
|
||||
texture: frame.texture,
|
||||
};
|
||||
|
||||
tiles.push(tile);
|
||||
}
|
||||
} else {
|
||||
let tile = tiling::Tile {
|
||||
position: (0, 0),
|
||||
zoom_level: tiling::zoom_to_level(1.),
|
||||
bleed: 0,
|
||||
texture: frame.texture,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
tiles.push(tile);
|
||||
}
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
if err.unsupported_format().is_some() {
|
||||
update_sender.send(DecoderUpdate::UnsupportedFormat);
|
||||
}
|
||||
update_sender.send(DecoderUpdate::Error(err.into()));
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
Self {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ impl std::fmt::Display for ImageFormat {
|
|||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum ImageDimensionDetails {
|
||||
Svg((rsvg::Length, rsvg::Length)),
|
||||
Svg(String),
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use crate::deps::*;
|
|||
|
||||
use anyhow::Context;
|
||||
use arc_swap::ArcSwap;
|
||||
use async_channel as mpsc;
|
||||
use gtk::prelude::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
|
@ -33,7 +34,7 @@ pub const RSVG_MAX_SIZE: u32 = 32_767;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Svg {
|
||||
thread_handle: std::thread::JoinHandle<()>,
|
||||
wakeup: mpsc::Sender<()>,
|
||||
current_request: Arc<std::sync::RwLock<Request>>,
|
||||
cancellable: gio::Cancellable,
|
||||
}
|
||||
|
|
@ -54,7 +55,7 @@ impl Request {
|
|||
impl Drop for Svg {
|
||||
fn drop(&mut self) {
|
||||
self.cancellable.cancel();
|
||||
self.thread_handle.thread().unpark();
|
||||
let _ = self.wakeup.try_send(());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,22 +69,37 @@ impl Svg {
|
|||
let request_store = current_request.clone();
|
||||
let cancellable = gio::Cancellable::new();
|
||||
let cancellable_ = cancellable.clone();
|
||||
let (wakeup, wakeup_resv) = mpsc::unbounded();
|
||||
|
||||
let thread_handle = updater.spawn_error_handled(move || {
|
||||
let handle = rsvg::Loader::new().read_file(&file, Some(&cancellable))?;
|
||||
let renderer = rsvg::CairoRenderer::new(&handle);
|
||||
updater.clone().spawn_error_handled(async move {
|
||||
let mut image_request = glycin::ImageRequest::new(file);
|
||||
|
||||
let (original_width, original_height) = svg_dimensions(&renderer);
|
||||
#[cfg(feature = "disable-glycin-sandbox")]
|
||||
image_request.sandbox_mechanism(Some(glycin::SandboxMechanism::NotSandboxed));
|
||||
|
||||
image_request.cancellable(cancellable.clone());
|
||||
|
||||
let image = image_request.request().await?;
|
||||
|
||||
let dimensions = if let Some(string) = image.info().dimensions_text.as_ref() {
|
||||
ImageDimensionDetails::Svg(string.to_string())
|
||||
} else {
|
||||
ImageDimensionDetails::None
|
||||
};
|
||||
|
||||
let intrisic_dimensions = renderer.intrinsic_dimensions();
|
||||
tiles.set_original_dimensions_full(
|
||||
(original_width, original_height),
|
||||
ImageDimensionDetails::Svg((intrisic_dimensions.width, intrisic_dimensions.height)),
|
||||
(image.info().width, image.info().height),
|
||||
dimensions,
|
||||
);
|
||||
|
||||
updater.send(DecoderUpdate::Format(ImageFormat::new(
|
||||
image.mime_type(),
|
||||
image.format_name(),
|
||||
)));
|
||||
|
||||
loop {
|
||||
let tile_request = {
|
||||
let mut request = request_store.write().ok().context("RwLock is poisoned")?;
|
||||
let mut request = request_store.write().unwrap();
|
||||
let value = request.clone();
|
||||
*request = Request::None;
|
||||
value
|
||||
|
|
@ -96,7 +112,10 @@ impl Svg {
|
|||
|
||||
match tile_request {
|
||||
Request::None => {
|
||||
std::thread::park();
|
||||
let result = wakeup_resv.recv().await;
|
||||
if result.is_err() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Request::Tile(tile_request) => {
|
||||
|
|
@ -106,12 +125,7 @@ impl Svg {
|
|||
let relevant_tiles = tiling.relevant_tiles(&tile_request.area);
|
||||
|
||||
for tile_instructions in relevant_tiles {
|
||||
if request_store
|
||||
.read()
|
||||
.ok()
|
||||
.context("RwLock is poisoned")?
|
||||
.is_waiting()
|
||||
{
|
||||
if request_store.read().ok().unwrap().is_waiting() {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -122,44 +136,27 @@ impl Svg {
|
|||
continue;
|
||||
}
|
||||
|
||||
let area = tile_instructions.area_with_bleed();
|
||||
let surface = cairo::ImageSurface::create(
|
||||
cairo::Format::ARgb32,
|
||||
area.width() as i32,
|
||||
area.height() as i32,
|
||||
)?;
|
||||
|
||||
let context = cairo::Context::new(&surface)?;
|
||||
let (total_width, total_height) =
|
||||
tile_instructions.tiling.scaled_dimensions();
|
||||
let area = tile_instructions.area_with_bleed();
|
||||
|
||||
// librsvg does not currently support larger images
|
||||
if total_height > RSVG_MAX_SIZE || total_width > RSVG_MAX_SIZE {
|
||||
continue;
|
||||
}
|
||||
let frame_request = glycin::FrameRequest::new()
|
||||
.scale(total_width, total_height)
|
||||
.clip(
|
||||
area.x() as u32,
|
||||
area.y() as u32,
|
||||
area.width() as u32,
|
||||
area.height() as u32,
|
||||
);
|
||||
|
||||
renderer
|
||||
.render_document(
|
||||
&context,
|
||||
&cairo::Rectangle::new(
|
||||
-area.x() as f64,
|
||||
-area.y() as f64,
|
||||
total_width as f64,
|
||||
total_height as f64,
|
||||
),
|
||||
)
|
||||
.context("Failed to render image")?;
|
||||
drop(context);
|
||||
|
||||
let decoded_image = Decoded { surface };
|
||||
let frame = image.specific_frame(frame_request).await?;
|
||||
|
||||
let position = (
|
||||
tile_instructions.area.x() as u32,
|
||||
tile_instructions.area.y() as u32,
|
||||
);
|
||||
let texture = decoded_image.into_texture()?;
|
||||
|
||||
tiles.push_tile(tiling, position, texture);
|
||||
tiles.push_tile(tiling, position, frame.texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -169,7 +166,7 @@ impl Svg {
|
|||
});
|
||||
|
||||
Self {
|
||||
thread_handle,
|
||||
wakeup,
|
||||
current_request,
|
||||
cancellable: cancellable_,
|
||||
}
|
||||
|
|
@ -182,90 +179,22 @@ impl Svg {
|
|||
.ok()
|
||||
.context("RwLock is poisoned")?;
|
||||
*current_request = Request::Tile(request);
|
||||
self.thread_handle.thread().unpark();
|
||||
self.wakeup.try_send(())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render_print(
|
||||
file: &gio::File,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) -> anyhow::Result<cairo::ImageSurface> {
|
||||
let handle = rsvg::Loader::new().read_file(file, gio::Cancellable::NONE)?;
|
||||
let renderer = rsvg::CairoRenderer::new(&handle);
|
||||
let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, width, height)?;
|
||||
let context = cairo::Context::new(&surface)?;
|
||||
renderer
|
||||
.render_document(
|
||||
&context,
|
||||
&cairo::Rectangle::new(0., 0., width as f64, height as f64),
|
||||
)
|
||||
.context("Failed to render image")?;
|
||||
drop(context);
|
||||
pub fn render_print(file: &gio::File, width: i32, height: i32) -> anyhow::Result<gdk::Texture> {
|
||||
#[allow(unused_mut)]
|
||||
let mut image_request = glycin::ImageRequest::new(file.clone());
|
||||
|
||||
Ok(surface)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn svg_dimensions(renderer: &rsvg::CairoRenderer) -> (u32, u32) {
|
||||
if let Some((width, height)) = renderer.intrinsic_size_in_pixels() {
|
||||
(width.ceil() as u32, height.ceil() as u32)
|
||||
} else {
|
||||
match renderer.intrinsic_dimensions() {
|
||||
rsvg::IntrinsicDimensions {
|
||||
width:
|
||||
rsvg::Length {
|
||||
length: width,
|
||||
unit: rsvg::LengthUnit::Percent,
|
||||
},
|
||||
height:
|
||||
rsvg::Length {
|
||||
length: height,
|
||||
unit: rsvg::LengthUnit::Percent,
|
||||
},
|
||||
vbox: Some(vbox),
|
||||
} => (
|
||||
(width * vbox.width()).ceil() as u32,
|
||||
(height * vbox.height()).ceil() as u32,
|
||||
),
|
||||
dimensions => {
|
||||
log::warn!("Failed to parse SVG dimensions: {dimensions:?}");
|
||||
(300, 300)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Decoded {
|
||||
surface: cairo::ImageSurface,
|
||||
}
|
||||
|
||||
impl Decoded {
|
||||
pub fn into_texture(self) -> anyhow::Result<gdk::Texture> {
|
||||
let memory_format = {
|
||||
#[cfg(target_endian = "little")]
|
||||
{
|
||||
gdk::MemoryFormat::B8g8r8a8
|
||||
}
|
||||
|
||||
#[cfg(target_endian = "big")]
|
||||
{
|
||||
gdk::MemoryFormat::A8r8g8b8
|
||||
}
|
||||
};
|
||||
|
||||
let width = self.surface.width();
|
||||
let height = self.surface.height();
|
||||
let stride = self.surface.stride() as usize;
|
||||
|
||||
let bytes = glib::Bytes::from_owned(
|
||||
self.surface
|
||||
.take_data()
|
||||
.context("Cairo surface already taken")?
|
||||
.to_vec(),
|
||||
);
|
||||
|
||||
Ok(gdk::MemoryTexture::new(width, height, memory_format, &bytes, stride).upcast())
|
||||
#[cfg(feature = "disable-glycin-sandbox")]
|
||||
image_request.sandbox_mechanism(Some(glycin::SandboxMechanism::NotSandboxed));
|
||||
|
||||
let image = async_std::task::block_on(image_request.request())?;
|
||||
let frame_request = glycin::FrameRequest::new().scale(width as u32, height as u32);
|
||||
let frame = async_std::task::block_on(image.specific_frame(frame_request))?;
|
||||
|
||||
Ok(frame.texture)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,14 +80,21 @@ impl UpdateSender {
|
|||
}
|
||||
|
||||
/// Send occuring errors to renderer
|
||||
pub fn spawn_error_handled<F>(&self, f: F) -> std::thread::JoinHandle<()>
|
||||
pub fn spawn_error_handled<F>(&self, f: F) -> glib::JoinHandle<()>
|
||||
where
|
||||
F: FnOnce() -> Result<(), anyhow::Error> + Send + 'static,
|
||||
F: std::future::Future<Output = Result<(), glycin::Error>> + Send + 'static,
|
||||
{
|
||||
let update_sender = self.clone();
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = f() {
|
||||
update_sender.send(DecoderUpdate::Error(err));
|
||||
glib::MainContext::default().spawn(async move {
|
||||
let update_sender = update_sender.clone();
|
||||
|
||||
let result: Result<(), glycin::Error> = f.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
if err.unsupported_format().is_some() {
|
||||
update_sender.send(DecoderUpdate::UnsupportedFormat);
|
||||
}
|
||||
update_sender.send(DecoderUpdate::Error(err.into()));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -847,7 +847,7 @@ impl LpPrint {
|
|||
let texture_scale = self.user_width() / orig_width as f64;
|
||||
let cairo_scale = cairo_dpi / self.dpi();
|
||||
|
||||
let cairo_surface = if image.format().map_or(false, |x| x.is_svg()) {
|
||||
let texture = if image.format().map_or(false, |x| x.is_svg()) {
|
||||
// Render SVG to exact needed sizes
|
||||
// TODO: This should be async
|
||||
decoder::formats::Svg::render_print(
|
||||
|
|
@ -857,8 +857,10 @@ impl LpPrint {
|
|||
)
|
||||
.unwrap()
|
||||
} else {
|
||||
let texture = image.print_data(texture_scale).unwrap();
|
||||
image.print_data(texture_scale).unwrap()
|
||||
};
|
||||
|
||||
let cairo_surface = {
|
||||
let mut downloader = gdk::TextureDownloader::new(&texture);
|
||||
downloader.set_format(gdk::MemoryFormat::B8g8r8a8Premultiplied);
|
||||
|
||||
|
|
|
|||
|
|
@ -306,31 +306,7 @@ impl LpPropertiesView {
|
|||
fn image_size(&self, _image_size: Option<glib::Variant>) -> String {
|
||||
if let Some(image) = self.image() {
|
||||
match image.dimension_details() {
|
||||
ImageDimensionDetails::Svg((width, height))
|
||||
if width.unit != rsvg::LengthUnit::Px
|
||||
&& height.unit != rsvg::LengthUnit::Px =>
|
||||
{
|
||||
// Percent is not stored as percentile
|
||||
let width_factor = if width.unit == rsvg::LengthUnit::Percent {
|
||||
100.
|
||||
} else {
|
||||
1.
|
||||
};
|
||||
let height_factor = if height.unit == rsvg::LengthUnit::Percent {
|
||||
100.
|
||||
} else {
|
||||
1.
|
||||
};
|
||||
|
||||
// Only show two digits
|
||||
let width_n = (width.length * width_factor * 100.).round() / 100.;
|
||||
let height_n = (height.length * height_factor * 100.).round() / 100.;
|
||||
|
||||
let width_unit = width.unit;
|
||||
let height_unit = height.unit;
|
||||
|
||||
format!("{width_n}\u{202F}{width_unit} \u{D7} {height_n}\u{202F}{height_unit}")
|
||||
}
|
||||
ImageDimensionDetails::Svg(string) => string,
|
||||
_ => {
|
||||
let (width, height) = image.image_size();
|
||||
if width > 0 && height > 0 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue