Skip to content

Commit f89ae1c

Browse files
committed
Move image/svg handling into iced_graphics
The `TextureStore` trait is implemented by the atlas, and can also be implemented in the glow renderer or in a software renderer. The API here may be improved in the future, but API stability is presumably not a huge issue since these types will only be used by renderer backends.
1 parent d222b5c commit f89ae1c

10 files changed

Lines changed: 281 additions & 191 deletions

File tree

graphics/Cargo.toml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,33 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
1111
categories = ["gui"]
1212

1313
[features]
14+
svg = ["resvg", "usvg", "tiny-skia"]
15+
image = ["png", "jpeg", "jpeg_rayon", "gif", "webp", "bmp"]
16+
png = ["image_rs/png"]
17+
jpeg = ["image_rs/jpeg"]
18+
jpeg_rayon = ["image_rs/jpeg_rayon"]
19+
gif = ["image_rs/gif"]
20+
webp = ["image_rs/webp"]
21+
pnm = ["image_rs/pnm"]
22+
ico = ["image_rs/ico"]
23+
bmp = ["image_rs/bmp"]
24+
hdr = ["image_rs/hdr"]
25+
dds = ["image_rs/dds"]
26+
farbfeld = ["image_rs/farbfeld"]
1427
canvas = ["lyon"]
1528
qr_code = ["qrcode", "canvas"]
1629
font-source = ["font-kit"]
1730
font-fallback = []
1831
font-icons = []
1932
opengl = []
33+
image_rs = ["kamadak-exif"]
2034

2135
[dependencies]
2236
glam = "0.21.3"
37+
log = "0.4"
2338
raw-window-handle = "0.5"
2439
thiserror = "1.0"
40+
bitflags = "1.2"
2541

2642
[dependencies.bytemuck]
2743
version = "1.4"
@@ -48,6 +64,28 @@ default-features = false
4864
version = "0.10"
4965
optional = true
5066

67+
[dependencies.image_rs]
68+
version = "0.23"
69+
package = "image"
70+
default-features = false
71+
optional = true
72+
73+
[dependencies.resvg]
74+
version = "0.18"
75+
optional = true
76+
77+
[dependencies.usvg]
78+
version = "0.18"
79+
optional = true
80+
81+
[dependencies.tiny-skia]
82+
version = "0.6"
83+
optional = true
84+
85+
[dependencies.kamadak-exif]
86+
version = "0.5"
87+
optional = true
88+
5189
[package.metadata.docs.rs]
5290
rustdoc-args = ["--cfg", "docsrs"]
5391
all-features = true

graphics/src/image.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//! Image loading and caching
2+
3+
use std::fmt::Debug;
4+
5+
#[cfg(feature = "image_rs")]
6+
pub mod raster;
7+
8+
#[cfg(feature = "svg")]
9+
pub mod vector;
10+
11+
/// Entry in the texture store
12+
pub trait TextureStoreEntry: Debug {
13+
/// Width and height of the entry
14+
fn size(&self) -> (u32, u32);
15+
}
16+
17+
/// Stores cached image data for use in rendering
18+
pub trait TextureStore {
19+
/// Entry in the texture store
20+
type Entry: TextureStoreEntry;
21+
/// State passed to upload/remove
22+
type State<'a>;
23+
24+
/// Upload image data
25+
fn upload(
26+
&mut self,
27+
width: u32,
28+
height: u32,
29+
data: &[u8],
30+
state: &mut Self::State<'_>,
31+
) -> Option<Self::Entry>;
32+
/// Remome image from store
33+
fn remove(&mut self, entry: &Self::Entry, state: &mut Self::State<'_>);
34+
}
Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
1-
use crate::image::atlas::{self, Atlas};
1+
//! Raster image loading and caching
2+
23
use iced_native::image;
34
use std::collections::{HashMap, HashSet};
45

56
use bitflags::bitflags;
67

8+
use super::{TextureStore, TextureStoreEntry};
9+
10+
/// Entry in cache corresponding to an image handle
711
#[derive(Debug)]
8-
pub enum Memory {
9-
Host(::image_rs::ImageBuffer<::image_rs::Bgra<u8>, Vec<u8>>),
10-
Device(atlas::Entry),
12+
pub enum Memory<T: TextureStore> {
13+
/// Image data on host
14+
Host(::image_rs::ImageBuffer<::image_rs::Rgba<u8>, Vec<u8>>),
15+
/// Texture store entry
16+
Device(T::Entry),
17+
/// Image not found
1118
NotFound,
19+
/// Invalid image data
1220
Invalid,
1321
}
1422

15-
impl Memory {
23+
impl<T: TextureStore> Memory<T> {
24+
/// Width and height of image
1625
pub fn dimensions(&self) -> (u32, u32) {
1726
match self {
1827
Memory::Host(image) => image.dimensions(),
@@ -23,21 +32,16 @@ impl Memory {
2332
}
2433
}
2534

35+
/// Caches image raster data
2636
#[derive(Debug)]
27-
pub struct Cache {
28-
map: HashMap<u64, Memory>,
37+
pub struct Cache<T: TextureStore> {
38+
map: HashMap<u64, Memory<T>>,
2939
hits: HashSet<u64>,
3040
}
3141

32-
impl Cache {
33-
pub fn new() -> Self {
34-
Self {
35-
map: HashMap::new(),
36-
hits: HashSet::new(),
37-
}
38-
}
39-
40-
pub fn load(&mut self, handle: &image::Handle) -> &mut Memory {
42+
impl<T: TextureStore> Cache<T> {
43+
/// Load image
44+
pub fn load(&mut self, handle: &image::Handle) -> &mut Memory<T> {
4145
if self.contains(handle) {
4246
return self.get(handle).unwrap();
4347
}
@@ -53,7 +57,7 @@ impl Cache {
5357
})
5458
.unwrap_or_else(Operation::empty);
5559

56-
Memory::Host(operation.perform(image.to_bgra8()))
60+
Memory::Host(operation.perform(image.to_rgba8()))
5761
} else {
5862
Memory::NotFound
5963
}
@@ -65,7 +69,7 @@ impl Cache {
6569
.ok()
6670
.unwrap_or_else(Operation::empty);
6771

68-
Memory::Host(operation.perform(image.to_bgra8()))
72+
Memory::Host(operation.perform(image.to_rgba8()))
6973
} else {
7074
Memory::Invalid
7175
}
@@ -91,19 +95,19 @@ impl Cache {
9195
self.get(handle).unwrap()
9296
}
9397

98+
/// Load image and upload raster data
9499
pub fn upload(
95100
&mut self,
96101
handle: &image::Handle,
97-
device: &wgpu::Device,
98-
encoder: &mut wgpu::CommandEncoder,
99-
atlas: &mut Atlas,
100-
) -> Option<&atlas::Entry> {
102+
state: &mut T::State<'_>,
103+
store: &mut T,
104+
) -> Option<&T::Entry> {
101105
let memory = self.load(handle);
102106

103107
if let Memory::Host(image) = memory {
104108
let (width, height) = image.dimensions();
105109

106-
let entry = atlas.upload(width, height, image, device, encoder)?;
110+
let entry = store.upload(width, height, image, state)?;
107111

108112
*memory = Memory::Device(entry);
109113
}
@@ -115,15 +119,16 @@ impl Cache {
115119
}
116120
}
117121

118-
pub fn trim(&mut self, atlas: &mut Atlas) {
122+
/// Trim cache misses from cache
123+
pub fn trim(&mut self, store: &mut T, state: &mut T::State<'_>) {
119124
let hits = &self.hits;
120125

121126
self.map.retain(|k, memory| {
122127
let retain = hits.contains(k);
123128

124129
if !retain {
125130
if let Memory::Device(entry) = memory {
126-
atlas.remove(entry);
131+
store.remove(entry, state);
127132
}
128133
}
129134

@@ -133,13 +138,13 @@ impl Cache {
133138
self.hits.clear();
134139
}
135140

136-
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory> {
141+
fn get(&mut self, handle: &image::Handle) -> Option<&mut Memory<T>> {
137142
let _ = self.hits.insert(handle.id());
138143

139144
self.map.get_mut(&handle.id())
140145
}
141146

142-
fn insert(&mut self, handle: &image::Handle, memory: Memory) {
147+
fn insert(&mut self, handle: &image::Handle, memory: Memory<T>) {
143148
let _ = self.map.insert(handle.id(), memory);
144149
}
145150

@@ -148,6 +153,15 @@ impl Cache {
148153
}
149154
}
150155

156+
impl<T: TextureStore> Default for Cache<T> {
157+
fn default() -> Self {
158+
Self {
159+
map: HashMap::new(),
160+
hits: HashSet::new(),
161+
}
162+
}
163+
}
164+
151165
bitflags! {
152166
struct Operation: u8 {
153167
const FLIP_HORIZONTALLY = 0b001;
Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
use crate::image::atlas::{self, Atlas};
1+
//! Vector image loading and caching
22
33
use iced_native::svg;
44

55
use std::collections::{HashMap, HashSet};
66
use std::fs;
77

8+
use super::TextureStore;
9+
10+
/// Entry in cache corresponding to an svg handle
811
pub enum Svg {
12+
/// Parsed svg
913
Loaded(usvg::Tree),
14+
/// Svg not found or failed to parse
1015
NotFound,
1116
}
1217

1318
impl Svg {
19+
/// Viewport width and height
1420
pub fn viewport_dimensions(&self) -> (u32, u32) {
1521
match self {
1622
Svg::Loaded(tree) => {
@@ -23,24 +29,17 @@ impl Svg {
2329
}
2430
}
2531

32+
/// Caches svg vector and raster data
2633
#[derive(Debug)]
27-
pub struct Cache {
34+
pub struct Cache<T: TextureStore> {
2835
svgs: HashMap<u64, Svg>,
29-
rasterized: HashMap<(u64, u32, u32), atlas::Entry>,
36+
rasterized: HashMap<(u64, u32, u32), T::Entry>,
3037
svg_hits: HashSet<u64>,
3138
rasterized_hits: HashSet<(u64, u32, u32)>,
3239
}
3340

34-
impl Cache {
35-
pub fn new() -> Self {
36-
Self {
37-
svgs: HashMap::new(),
38-
rasterized: HashMap::new(),
39-
svg_hits: HashSet::new(),
40-
rasterized_hits: HashSet::new(),
41-
}
42-
}
43-
41+
impl<T: TextureStore> Cache<T> {
42+
/// Load svg
4443
pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
4544
if self.svgs.contains_key(&handle.id()) {
4645
return self.svgs.get(&handle.id()).unwrap();
@@ -73,15 +72,15 @@ impl Cache {
7372
self.svgs.get(&handle.id()).unwrap()
7473
}
7574

75+
/// Load svg and upload raster data
7676
pub fn upload(
7777
&mut self,
7878
handle: &svg::Handle,
7979
[width, height]: [f32; 2],
8080
scale: f32,
81-
device: &wgpu::Device,
82-
encoder: &mut wgpu::CommandEncoder,
83-
texture_atlas: &mut Atlas,
84-
) -> Option<&atlas::Entry> {
81+
state: &mut T::State<'_>,
82+
texture_store: &mut T,
83+
) -> Option<&T::Entry> {
8584
let id = handle.id();
8685

8786
let (width, height) = (
@@ -125,12 +124,11 @@ impl Cache {
125124
let mut rgba = img.take();
126125
rgba.chunks_exact_mut(4).for_each(|rgba| rgba.swap(0, 2));
127126

128-
let allocation = texture_atlas.upload(
127+
let allocation = texture_store.upload(
129128
width,
130129
height,
131130
bytemuck::cast_slice(rgba.as_slice()),
132-
device,
133-
encoder,
131+
state,
134132
)?;
135133
log::debug!("allocating {} {}x{}", id, width, height);
136134

@@ -144,7 +142,8 @@ impl Cache {
144142
}
145143
}
146144

147-
pub fn trim(&mut self, atlas: &mut Atlas) {
145+
/// Load svg and upload raster data
146+
pub fn trim(&mut self, texture_store: &mut T, state: &mut T::State<'_>) {
148147
let svg_hits = &self.svg_hits;
149148
let rasterized_hits = &self.rasterized_hits;
150149

@@ -153,7 +152,7 @@ impl Cache {
153152
let retain = rasterized_hits.contains(k);
154153

155154
if !retain {
156-
atlas.remove(entry);
155+
texture_store.remove(entry, state);
157156
}
158157

159158
retain
@@ -163,6 +162,17 @@ impl Cache {
163162
}
164163
}
165164

165+
impl<T: TextureStore> Default for Cache<T> {
166+
fn default() -> Self {
167+
Self {
168+
svgs: HashMap::new(),
169+
rasterized: HashMap::new(),
170+
svg_hits: HashSet::new(),
171+
rasterized_hits: HashSet::new(),
172+
}
173+
}
174+
}
175+
166176
impl std::fmt::Debug for Svg {
167177
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168178
match self {

graphics/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ mod viewport;
3030
pub mod backend;
3131
pub mod font;
3232
pub mod gradient;
33+
pub mod image;
3334
pub mod layer;
3435
pub mod overlay;
3536
pub mod renderer;

0 commit comments

Comments
 (0)