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
|
|
|
///! Tiled renderer
|
|
|
|
|
///!
|
|
|
|
|
///! It is not always feasible or desiareble to store the complete decoded
|
2023-03-15 22:35:59 +01:00
|
|
|
///! image in the VRAM. [`TiledImage`] allows to compose the parts of
|
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
|
|
|
///! the image currently viewed, out of smaller [`Tiles`].
|
|
|
|
|
///!
|
|
|
|
|
///! This is especially important for SVGs where every the image has to
|
|
|
|
|
///! be re-genearted for each zoom level. It can also be used to allow
|
|
|
|
|
///! showing large JPEGs etc where the complete decoded image would not
|
|
|
|
|
///! even fit in the VRAM.
|
2023-03-10 19:08:26 +01:00
|
|
|
use super::{DecoderUpdate, ImageDimensionDetails, UpdateSender};
|
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::*;
|
|
|
|
|
|
|
|
|
|
use arc_swap::ArcSwap;
|
|
|
|
|
use gtk::prelude::*;
|
|
|
|
|
|
|
|
|
|
use std::collections::BTreeMap;
|
2023-03-15 22:35:59 +01:00
|
|
|
use std::collections::VecDeque;
|
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;
|
2023-03-15 22:35:59 +01:00
|
|
|
use std::time::Duration;
|
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
|
|
|
|
|
|
|
|
const ZOOM_SIGNIFICANT_DIGETS: i32 = 6;
|
|
|
|
|
pub const TILE_SIZE: u16 = 4000;
|
|
|
|
|
|
|
|
|
|
pub type ZoomLevel = u32;
|
|
|
|
|
pub type Coordinate = u32;
|
|
|
|
|
pub type Coordinates = (u32, u32);
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
/// Part of the image
|
|
|
|
|
pub struct Tile {
|
|
|
|
|
pub position: Coordinates,
|
|
|
|
|
pub zoom_level: ZoomLevel,
|
|
|
|
|
/// Border that gets cut away to avoid scaling artifacts at the sides
|
|
|
|
|
pub bleed: u8,
|
|
|
|
|
pub texture: gdk::Texture,
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 00:44:07 +01:00
|
|
|
/// Temporary function until HighDPI is sorted out
|
|
|
|
|
///
|
|
|
|
|
/// TODO: Does this has to follow scaling factor?
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn round(r: &graphene::Rect) -> graphene::Rect {
|
|
|
|
|
graphene::Rect::new(
|
|
|
|
|
r.x().round(),
|
|
|
|
|
r.y().round(),
|
|
|
|
|
r.width().round(),
|
|
|
|
|
r.height().round(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
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 Tile {
|
|
|
|
|
pub fn area(&self) -> graphene::Rect {
|
|
|
|
|
let bleed = self.bleed as f32;
|
|
|
|
|
// The texture is rendered slightly larger such that bleed can be cut away
|
|
|
|
|
self.area_with_bleed().inset_r(bleed, bleed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn area_with_bleed(&self) -> graphene::Rect {
|
|
|
|
|
let width = self.texture.width() as f32;
|
|
|
|
|
let height = self.texture.height() as f32;
|
|
|
|
|
let bleed = self.bleed as f32;
|
|
|
|
|
graphene::Rect::new(
|
|
|
|
|
self.position.0 as f32 - bleed,
|
|
|
|
|
self.position.1 as f32 - bleed,
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn zoom(&self) -> f32 {
|
|
|
|
|
level_to_zoom(self.zoom_level) as f32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add tile to snapshot
|
|
|
|
|
///
|
|
|
|
|
/// This is where parts of images actually land on the screen
|
2023-03-27 06:30:02 +02:00
|
|
|
pub fn add_to_snapshot(
|
|
|
|
|
&self,
|
|
|
|
|
snapshot: >k::Snapshot,
|
|
|
|
|
output_zoom: f64,
|
|
|
|
|
options: &RenderOptions,
|
|
|
|
|
) {
|
|
|
|
|
// Multiply by scale factor to get the image scaled to `zoom` in physical pixels
|
|
|
|
|
let zoom = output_zoom as f32 * options.scale_factor as f32 / self.zoom();
|
|
|
|
|
let area = round(&self.area().scale(zoom, zoom));
|
|
|
|
|
let area_with_bleed = round(&self.area_with_bleed().scale(zoom, zoom));
|
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
|
|
|
|
|
|
|
|
// TODO: do not clip outer bounderies of the image
|
|
|
|
|
snapshot.push_clip(&area);
|
2023-04-02 22:22:00 +02:00
|
|
|
if let Some(background_color) = &options.background_color {
|
|
|
|
|
snapshot.append_color(background_color, &area);
|
|
|
|
|
}
|
2023-03-07 00:44:07 +01:00
|
|
|
snapshot.append_scaled_texture(&self.texture, options.scaling_filter, &area_with_bleed);
|
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
|
|
|
snapshot.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn coordinates(&self) -> Coordinates {
|
|
|
|
|
self.position
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-15 22:35:59 +01:00
|
|
|
/// Buffers multiple images for animations
|
|
|
|
|
///
|
|
|
|
|
/// If the image is not animated, this only contains one image
|
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
|
|
|
pub struct FrameBuffer {
|
|
|
|
|
/// The first entry is always the currently shown frame
|
|
|
|
|
pub images: VecDeque<TiledImage>,
|
|
|
|
|
pub update_sender: Option<UpdateSender>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FrameBuffer {
|
|
|
|
|
/// Returns mutable reference to the current image
|
|
|
|
|
///
|
|
|
|
|
/// This is a convenience functions for the case of non-animated images.
|
|
|
|
|
/// If no image exists yet, a new one is inserted an returned.
|
|
|
|
|
pub fn current(&mut self) -> &mut TiledImage {
|
|
|
|
|
if self.images.is_empty() {
|
|
|
|
|
self.images.push_back(TiledImage::default())
|
|
|
|
|
}
|
|
|
|
|
self.images.front_mut().unwrap()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn original_dimensions(&self) -> Option<Coordinates> {
|
|
|
|
|
self.images.front()?.original_dimensions
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_update_sender(&mut self, sender: UpdateSender) {
|
|
|
|
|
self.update_sender = Some(sender);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Render the current image into this snapshot
|
|
|
|
|
pub fn add_to_snapshot(&self, snapshot: >k::Snapshot, zoom: f64, options: &RenderOptions) {
|
|
|
|
|
if let Some(tiling) = self.images.front() {
|
|
|
|
|
tiling.add_to_snapshot(snapshot, zoom, options);
|
|
|
|
|
} else {
|
|
|
|
|
log::error!("Trying to snapshot empty tiling queue");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cleanup(&mut self, zoom: f64, viewport: graphene::Rect) {
|
|
|
|
|
self.current().cleanup(zoom, viewport);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-13 01:19:53 +02:00
|
|
|
/// Delete all stored information
|
|
|
|
|
///
|
|
|
|
|
/// Used when reloading image
|
|
|
|
|
pub fn reset(&mut self) {
|
|
|
|
|
self.update_sender = None;
|
|
|
|
|
self.images.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-15 22:35:59 +01:00
|
|
|
pub fn contains(&self, zoom: f64, coordinates: Coordinates) -> bool {
|
|
|
|
|
self.images
|
|
|
|
|
.front()
|
|
|
|
|
.map_or(false, |x| x.contains(zoom, coordinates))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
/// Store tiles for image
|
2023-03-15 22:35:59 +01:00
|
|
|
///
|
|
|
|
|
/// This is the common representation for image textures.
|
|
|
|
|
/// I many cases there might only be one tile.
|
|
|
|
|
pub struct TiledImage {
|
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 tiles: BTreeMap<ZoomLevel, BTreeMap<Coordinates, Tile>>,
|
|
|
|
|
/// Tile size without bleed
|
|
|
|
|
pub tile_size: u16,
|
|
|
|
|
/// Complete image size
|
|
|
|
|
pub original_dimensions: Option<Coordinates>,
|
2023-03-15 22:35:59 +01:00
|
|
|
/// Delay until to show this frame if animated
|
|
|
|
|
pub delay: std::time::Duration,
|
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
|
|
|
impl Default for TiledImage {
|
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
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new(TILE_SIZE)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct RenderOptions {
|
|
|
|
|
pub scaling_filter: gsk::ScalingFilter,
|
2023-03-27 06:30:02 +02:00
|
|
|
pub scale_factor: i32,
|
2023-04-02 22:22:00 +02:00
|
|
|
pub background_color: Option<gdk::RGBA>,
|
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
|
|
|
impl TiledImage {
|
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(tile_size: u16) -> Self {
|
2023-03-15 22:35:59 +01:00
|
|
|
TiledImage {
|
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: Default::default(),
|
|
|
|
|
tile_size,
|
|
|
|
|
original_dimensions: None,
|
2023-03-15 22:35:59 +01:00
|
|
|
delay: Default::default(),
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Render tiles as image
|
|
|
|
|
///
|
|
|
|
|
/// This is what actually draws the image
|
|
|
|
|
pub fn add_to_snapshot(&self, snapshot: >k::Snapshot, zoom: f64, options: &RenderOptions) {
|
|
|
|
|
let tiles: Vec<_> = self
|
|
|
|
|
.iter_rendering_priority(zoom)
|
|
|
|
|
.flat_map(|(_, tiles)| tiles)
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
log::trace!("Rendering {} tiles", tiles.len());
|
2023-03-27 06:30:02 +02:00
|
|
|
|
|
|
|
|
// Scale from application pixels back to physical pixels
|
|
|
|
|
snapshot.scale(
|
|
|
|
|
1. / options.scale_factor as f32,
|
|
|
|
|
1. / options.scale_factor as f32,
|
|
|
|
|
);
|
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
|
|
|
// reverse to put least fitting tiles in the background
|
|
|
|
|
for (_, tile) in tiles.iter().rev() {
|
2023-03-07 00:44:07 +01:00
|
|
|
tile.add_to_snapshot(snapshot, zoom, options);
|
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-27 06:30:02 +02:00
|
|
|
// reset scale
|
|
|
|
|
snapshot.scale(options.scale_factor as f32, options.scale_factor as f32);
|
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 push(&mut self, tile: Tile) {
|
|
|
|
|
let map = self.tiles.entry(tile.zoom_level).or_default();
|
|
|
|
|
map.insert(tile.coordinates(), tile);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn contains(&self, zoom: f64, coordinates: Coordinates) -> bool {
|
|
|
|
|
let Some(tile_plane) = self.tiles.get(&zoom_to_level(zoom)) else { return false };
|
|
|
|
|
|
|
|
|
|
tile_plane.contains_key(&coordinates)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tiles ordered by how good they are suited for the zoom level
|
|
|
|
|
pub fn iter_rendering_priority(
|
|
|
|
|
&self,
|
|
|
|
|
zoom: f64,
|
|
|
|
|
) -> impl Iterator<Item = (&ZoomLevel, &BTreeMap<Coordinates, Tile>)> {
|
|
|
|
|
let zoom_level = zoom_to_level(zoom);
|
|
|
|
|
self.tiles
|
|
|
|
|
// Tiles at zoom level and with better resolution
|
|
|
|
|
.range(zoom_level..)
|
|
|
|
|
// Afterwards try tiles with worse resolution
|
|
|
|
|
.chain(self.tiles.range(..zoom_level).rev())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Viewport within zoom coordinates
|
|
|
|
|
///
|
|
|
|
|
/// TODO: This is the most buggy function ever
|
|
|
|
|
pub fn cleanup(&mut self, zoom: f64, viewport: graphene::Rect) {
|
|
|
|
|
let Some(original_dimensions) = self.original_dimensions else { log::error!("Too-early cleanup: Original dimension not known"); return; };
|
|
|
|
|
|
|
|
|
|
let mut kept_tiles = Self::new(self.tile_size);
|
|
|
|
|
|
|
|
|
|
// Always work with 100% zoom level for tiles
|
|
|
|
|
let mut missing_tiles = vec![viewport.scale(1. / zoom as f32, 1. / zoom as f32)];
|
|
|
|
|
|
|
|
|
|
// Keep tiles in top layer
|
|
|
|
|
// TODO: Change the top layer if window size changes
|
|
|
|
|
if let Some(top_layer) = self.tiles.iter().next() {
|
|
|
|
|
for tile in top_layer.1.values() {
|
|
|
|
|
kept_tiles.push(tile.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Put all tiles in correct processing order
|
|
|
|
|
let stored_tiles = self.iter_rendering_priority(zoom);
|
|
|
|
|
|
|
|
|
|
for (zoom_level, tiles) in stored_tiles {
|
|
|
|
|
let tile_zoom = level_to_zoom(*zoom_level);
|
|
|
|
|
|
|
|
|
|
let mut next_missing_tiles = vec![];
|
|
|
|
|
|
|
|
|
|
// Tiling for this zoom level and viewport
|
|
|
|
|
let tiling = Tiling {
|
|
|
|
|
tile_size: self.tile_size,
|
|
|
|
|
original_dimensions,
|
|
|
|
|
zoom: tile_zoom,
|
|
|
|
|
bleed: 2,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for mut tile_instruction in tiling.relevant_tiles(&viewport) {
|
|
|
|
|
// scale to 100% reference
|
|
|
|
|
tile_instruction.area = tile_instruction
|
|
|
|
|
.area
|
|
|
|
|
.scale(1. / tile_zoom as f32, 1. / tile_zoom as f32);
|
|
|
|
|
|
|
|
|
|
// Determine if tile of this zoom level has overlap with a missing tile
|
|
|
|
|
let mut intersections = missing_tiles
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|missing| tile_instruction.area.is_intersected(missing))
|
|
|
|
|
.peekable();
|
|
|
|
|
|
|
|
|
|
if intersections.peek().is_some() {
|
|
|
|
|
if let Some(tile) = tiles.get(&tile_instruction.coordinates) {
|
|
|
|
|
// we have this tile and can provide it
|
|
|
|
|
kept_tiles.push(tile.clone());
|
|
|
|
|
} else {
|
|
|
|
|
for missing in missing_tiles.iter() {
|
|
|
|
|
if let Some(overlap) = missing.intersection(&tile_instruction.area) {
|
|
|
|
|
next_missing_tiles.push(overlap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
missing_tiles = next_missing_tiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kept_tiles.tiles.values().flat_map(|x| x.iter()).count();
|
|
|
|
|
|
|
|
|
|
self.tiles = kept_tiles.tiles;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
/// Abstract definition of a tiling
|
|
|
|
|
pub struct Tiling {
|
|
|
|
|
pub tile_size: u16,
|
|
|
|
|
pub original_dimensions: Coordinates,
|
|
|
|
|
pub zoom: f64,
|
|
|
|
|
pub bleed: u8,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Tiling {
|
|
|
|
|
pub fn scaled_dimensions(&self) -> Coordinates {
|
|
|
|
|
let (w, h) = self.original_dimensions;
|
|
|
|
|
(
|
|
|
|
|
(w as f64 * self.zoom) as Coordinate,
|
|
|
|
|
(h as f64 * self.zoom) as Coordinate,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns relevant tiles
|
|
|
|
|
///
|
|
|
|
|
/// This gives what tiles are needed for convering the `preload_area`.
|
|
|
|
|
/// Decoders use the [`TileInstructions`] to know what they have to render.
|
|
|
|
|
pub fn relevant_tiles(&self, preload_area: &graphene::Rect) -> Vec<TileInstructions> {
|
|
|
|
|
let tile_size = self.tile_size as f32;
|
|
|
|
|
|
|
|
|
|
let x0 = (preload_area.x() / tile_size).floor() * tile_size;
|
|
|
|
|
let x1 = ((preload_area.x() + preload_area.width()) / tile_size).ceil() * tile_size;
|
|
|
|
|
let y0 = (preload_area.y() / tile_size).floor() * tile_size;
|
|
|
|
|
let y1 = ((preload_area.y() + preload_area.height()) / tile_size).ceil() * tile_size;
|
|
|
|
|
|
|
|
|
|
let mut tiles = Vec::new();
|
|
|
|
|
for x in (x0 as u32..x1 as u32).step_by(self.tile_size.into()) {
|
|
|
|
|
for y in (y0 as u32..y1 as u32).step_by(self.tile_size.into()) {
|
|
|
|
|
let width = tile_size
|
|
|
|
|
+ f32::min(
|
|
|
|
|
0.,
|
|
|
|
|
self.scaled_dimensions().0 as f32 - (x as f32 + tile_size),
|
|
|
|
|
);
|
|
|
|
|
let height = tile_size
|
|
|
|
|
+ f32::min(
|
|
|
|
|
0.,
|
|
|
|
|
self.scaled_dimensions().1 as f32 - (y as f32 + tile_size),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let area = graphene::Rect::new(x as f32, y as f32, width, height);
|
|
|
|
|
let tile = TileInstructions {
|
|
|
|
|
tiling: *self,
|
|
|
|
|
area,
|
|
|
|
|
coordinates: (x, y),
|
|
|
|
|
};
|
|
|
|
|
tiles.push(tile);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tiles.sort_by_key(|tile| tile.area.center().distance(&preload_area.center()).0 as u32);
|
|
|
|
|
|
|
|
|
|
tiles
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
|
/// Instruction for decoder to generate this tile
|
|
|
|
|
pub struct TileInstructions {
|
|
|
|
|
pub tiling: Tiling,
|
|
|
|
|
pub area: graphene::Rect,
|
|
|
|
|
pub coordinates: Coordinates,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TileInstructions {
|
|
|
|
|
pub fn area_with_bleed(&self) -> graphene::Rect {
|
|
|
|
|
let bleed = self.tiling.bleed as f32;
|
|
|
|
|
self.area.inset_r(-bleed, -bleed)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Integer zoom levels are used because they are hashable
|
|
|
|
|
/// and ignore tiny float calculation errors.
|
|
|
|
|
pub fn zoom_to_level(zoom: f64) -> u32 {
|
|
|
|
|
(zoom * 10_f64.powi(ZOOM_SIGNIFICANT_DIGETS)) as u32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn level_to_zoom(zoom_level: u32) -> f64 {
|
|
|
|
|
zoom_level as f64 * 10_f64.powi(-ZOOM_SIGNIFICANT_DIGETS)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn zoom_normalize(zoom: f64) -> f64 {
|
|
|
|
|
level_to_zoom(zoom_to_level(zoom))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trait RectExt {
|
|
|
|
|
fn is_intersected(&self, b: &graphene::Rect) -> bool;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RectExt for graphene::Rect {
|
|
|
|
|
fn is_intersected(&self, b: &graphene::Rect) -> bool {
|
|
|
|
|
self.intersection(b).is_some()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-02 22:22:00 +02:00
|
|
|
/// We always store [`FrameBuffer`]s in an [`ArcSwap`].
|
|
|
|
|
/// This trait adds convenience functions for this.
|
2023-03-15 22:35:59 +01:00
|
|
|
pub trait 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
|
|
|
fn push(&self, tile: Tile);
|
2023-03-15 22:35:59 +01:00
|
|
|
fn push_frame(&self, tile: Tile, dimensions: Coordinates, delay: Duration);
|
2023-04-13 01:19:53 +02:00
|
|
|
fn reset(&self);
|
2023-03-15 22:35:59 +01:00
|
|
|
/// Return true if the next frame should be shown and removes the outdated frame
|
|
|
|
|
fn frame_timeout(&self, elapsed: Duration) -> bool;
|
|
|
|
|
/// Returns the number of currently buffered frames
|
|
|
|
|
fn n_frames(&self) -> usize;
|
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
|
|
|
fn set_original_dimensions(&self, size: Coordinates);
|
2023-03-10 19:08:26 +01:00
|
|
|
fn set_original_dimensions_full(
|
|
|
|
|
&self,
|
|
|
|
|
size: Coordinates,
|
|
|
|
|
dimension_details: ImageDimensionDetails,
|
|
|
|
|
);
|
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
|
|
|
fn set_update_sender(&self, sender: UpdateSender);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-15 22:35:59 +01:00
|
|
|
impl FrameBufferExt for ArcSwap<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
|
|
|
fn push(&self, tile: Tile) {
|
|
|
|
|
self.rcu(|tiling_store| {
|
|
|
|
|
let mut new_store = (**tiling_store).clone();
|
2023-03-15 22:35:59 +01:00
|
|
|
new_store.current().push(tile.clone());
|
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
|
|
|
Arc::new(new_store)
|
|
|
|
|
});
|
2023-03-15 22:35:59 +01:00
|
|
|
if let Some(updater) = &self.load().update_sender {
|
|
|
|
|
updater.send(DecoderUpdate::Redraw);
|
|
|
|
|
}
|
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
|
|
|
fn push_frame(&self, tile: Tile, dimensions: Coordinates, delay: Duration) {
|
|
|
|
|
let mut store = TiledImage::default();
|
|
|
|
|
store.push(tile);
|
|
|
|
|
store.original_dimensions = Some(dimensions);
|
|
|
|
|
store.delay = delay;
|
|
|
|
|
|
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.rcu(|tiling_store| {
|
|
|
|
|
let mut new_store = (**tiling_store).clone();
|
2023-03-15 22:35:59 +01:00
|
|
|
new_store.images.push_back(store.clone());
|
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
|
|
|
Arc::new(new_store)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-13 01:19:53 +02:00
|
|
|
fn reset(&self) {
|
|
|
|
|
self.rcu(|tiling_store| {
|
|
|
|
|
let mut new_store = (**tiling_store).clone();
|
|
|
|
|
new_store.reset();
|
|
|
|
|
Arc::new(new_store)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-15 22:35:59 +01:00
|
|
|
fn frame_timeout(&self, elapsed: Duration) -> bool {
|
|
|
|
|
if let Some(next_frame) = self.load().images.get(1) {
|
|
|
|
|
if elapsed >= next_frame.delay {
|
|
|
|
|
self.rcu(|tiling_store| {
|
|
|
|
|
let mut new_store = (**tiling_store).clone();
|
|
|
|
|
new_store.images.pop_front();
|
|
|
|
|
Arc::new(new_store)
|
|
|
|
|
});
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn n_frames(&self) -> usize {
|
|
|
|
|
self.load().images.len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_original_dimensions(&self, size: Coordinates) {
|
|
|
|
|
self.set_original_dimensions_full(size, Default::default());
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-10 19:08:26 +01:00
|
|
|
fn set_original_dimensions_full(
|
|
|
|
|
&self,
|
|
|
|
|
size: Coordinates,
|
|
|
|
|
dimension_details: ImageDimensionDetails,
|
|
|
|
|
) {
|
|
|
|
|
self.rcu(|tiling_store| {
|
|
|
|
|
let mut new_store = (**tiling_store).clone();
|
2023-03-15 22:35:59 +01:00
|
|
|
new_store.current().original_dimensions = Some(size);
|
2023-03-10 19:08:26 +01:00
|
|
|
Arc::new(new_store)
|
|
|
|
|
});
|
2023-03-15 22:35:59 +01:00
|
|
|
|
|
|
|
|
if let Some(updater) = &self.load().update_sender {
|
|
|
|
|
updater.send(DecoderUpdate::Dimensions(dimension_details));
|
|
|
|
|
}
|
2023-03-10 19:08:26 +01:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
fn set_update_sender(&self, sender: UpdateSender) {
|
|
|
|
|
self.rcu(|tiling_store| {
|
|
|
|
|
let mut new_store = (**tiling_store).clone();
|
|
|
|
|
new_store.set_update_sender(sender.clone());
|
|
|
|
|
Arc::new(new_store)
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_tiling_cleanup() {
|
2023-03-15 22:35:59 +01:00
|
|
|
let mut store = TiledImage::new(2);
|
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 toplevel = Tile {
|
|
|
|
|
position: (0, 0),
|
|
|
|
|
zoom_level: zoom_to_level(1.),
|
|
|
|
|
bleed: 0,
|
|
|
|
|
texture: gdk::MemoryTexture::new(
|
|
|
|
|
4,
|
|
|
|
|
4,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8,
|
|
|
|
|
&glib::Bytes::from_static(&[0; 4 * 4 * 3]),
|
|
|
|
|
4 * 3,
|
|
|
|
|
)
|
|
|
|
|
.upcast(),
|
|
|
|
|
};
|
|
|
|
|
store.push(toplevel);
|
|
|
|
|
|
|
|
|
|
let right_level = Tile {
|
|
|
|
|
position: (0, 0),
|
|
|
|
|
zoom_level: zoom_to_level(1.5),
|
|
|
|
|
bleed: 0,
|
|
|
|
|
texture: gdk::MemoryTexture::new(
|
|
|
|
|
2,
|
|
|
|
|
2,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8,
|
|
|
|
|
&glib::Bytes::from_static(&[0; 2 * 2 * 3]),
|
|
|
|
|
2 * 3,
|
|
|
|
|
)
|
|
|
|
|
.upcast(),
|
|
|
|
|
};
|
|
|
|
|
store.push(right_level);
|
|
|
|
|
|
|
|
|
|
let low_level = Tile {
|
|
|
|
|
position: (2, 0),
|
|
|
|
|
zoom_level: zoom_to_level(1.6),
|
|
|
|
|
bleed: 0,
|
|
|
|
|
texture: gdk::MemoryTexture::new(
|
|
|
|
|
2,
|
|
|
|
|
2,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8,
|
|
|
|
|
&glib::Bytes::from_static(&[0; 2 * 2 * 3]),
|
|
|
|
|
2 * 3,
|
|
|
|
|
)
|
|
|
|
|
.upcast(),
|
|
|
|
|
};
|
|
|
|
|
store.push(low_level);
|
|
|
|
|
|
|
|
|
|
let overlapped = Tile {
|
|
|
|
|
position: (0, 0),
|
|
|
|
|
zoom_level: zoom_to_level(1.7),
|
|
|
|
|
bleed: 0,
|
|
|
|
|
texture: gdk::MemoryTexture::new(
|
|
|
|
|
2,
|
|
|
|
|
2,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8,
|
|
|
|
|
&glib::Bytes::from_static(&[0; 2 * 2 * 3]),
|
|
|
|
|
2 * 3,
|
|
|
|
|
)
|
|
|
|
|
.upcast(),
|
|
|
|
|
};
|
|
|
|
|
store.push(overlapped);
|
|
|
|
|
|
|
|
|
|
store.cleanup(1.5, graphene::Rect::new(0., 0., 4. * 1.5, 4. * 1.5));
|
|
|
|
|
|
|
|
|
|
assert!(store.tiles.get(&1000000).unwrap().get(&(0, 0)).is_some());
|
|
|
|
|
assert!(store.tiles.get(&1500000).unwrap().get(&(0, 0)).is_some());
|
|
|
|
|
assert!(store.tiles.get(&1600000).unwrap().get(&(2, 0)).is_some());
|
|
|
|
|
// TODO: This should pass, but it does not
|
|
|
|
|
//assert!(store.tiles.get(&1700000).is_none());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_tiling_cleanup_simple() {
|
2023-03-15 22:35:59 +01:00
|
|
|
let mut store = TiledImage::new(2);
|
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 toplevel = Tile {
|
|
|
|
|
position: (0, 0),
|
|
|
|
|
zoom_level: zoom_to_level(1.),
|
|
|
|
|
bleed: 0,
|
|
|
|
|
texture: gdk::MemoryTexture::new(
|
|
|
|
|
4,
|
|
|
|
|
4,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8,
|
|
|
|
|
&glib::Bytes::from_static(&[0; 4 * 4 * 3]),
|
|
|
|
|
4 * 3,
|
|
|
|
|
)
|
|
|
|
|
.upcast(),
|
|
|
|
|
};
|
|
|
|
|
store.push(toplevel);
|
|
|
|
|
|
|
|
|
|
let detailled = Tile {
|
|
|
|
|
position: (2, 2),
|
|
|
|
|
zoom_level: zoom_to_level(2.),
|
|
|
|
|
bleed: 0,
|
|
|
|
|
texture: gdk::MemoryTexture::new(
|
|
|
|
|
4,
|
|
|
|
|
4,
|
|
|
|
|
gdk::MemoryFormat::R8g8b8,
|
|
|
|
|
&glib::Bytes::from_static(&[0; 4 * 4 * 3]),
|
|
|
|
|
4 * 3,
|
|
|
|
|
)
|
|
|
|
|
.upcast(),
|
|
|
|
|
};
|
|
|
|
|
store.push(detailled);
|
|
|
|
|
|
|
|
|
|
store.cleanup(1.5, graphene::Rect::new(0., 0., 20., 20.));
|
|
|
|
|
|
|
|
|
|
assert!(store.tiles.get(&1000000).unwrap().get(&(0, 0)).is_some());
|
|
|
|
|
assert!(store.tiles.get(&2000000).unwrap().get(&(2, 2)).is_some());
|
|
|
|
|
}
|
|
|
|
|
}
|