1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4/*!
5This module contains image and caching related types for the run-time library.
6*/
7
8use super::{Image, ImageCacheKey, ImageInner, SharedImageBuffer, SharedPixelBuffer};
9use crate::{slice::Slice, SharedString};
10
11struct ImageWeightInBytes;
12
13impl clru::WeightScale<ImageCacheKey, ImageInner> for ImageWeightInBytes {
14 fn weight(&self, key: &ImageCacheKey, value: &ImageInner) -> usize {
15 match value {
16 ImageInner::None => 0,
17 ImageInner::EmbeddedImage { buffer: &SharedImageBuffer, .. } => match buffer {
18 SharedImageBuffer::RGB8(pixels: &SharedPixelBuffer>) => pixels.as_bytes().len(),
19 SharedImageBuffer::RGBA8(pixels: &SharedPixelBuffer>) => pixels.as_bytes().len(),
20 SharedImageBuffer::RGBA8Premultiplied(pixels: &SharedPixelBuffer>) => pixels.as_bytes().len(),
21 },
22 #[cfg(feature = "svg")]
23 ImageInner::Svg(_) => 512, // Don't know how to measure the size of the parsed SVG tree...
24 #[cfg(target_arch = "wasm32")]
25 ImageInner::HTMLImage(_) => 512, // Something... the web browser maintainers its own cache. The purpose of this cache is to reduce the amount of DOM elements.
26 ImageInner::StaticTextures(_) => 0,
27 ImageInner::BackendStorage(x: &VRc) => vtable::VRc::borrow(this:x).size().area() as usize,
28 #[cfg(not(target_arch = "wasm32"))]
29 ImageInner::BorrowedOpenGLTexture(..) => 0, // Assume storage in GPU memory
30 ImageInner::NineSlice(nine: &VRc) => self.weight(key, &nine.0),
31 }
32 }
33}
34
35/// Cache used to avoid repeatedly decoding images from disk.
36pub(crate) struct ImageCache(
37 clru::CLruCache<
38 ImageCacheKey,
39 ImageInner,
40 std::collections::hash_map::RandomState,
41 ImageWeightInBytes,
42 >,
43);
44
45thread_local!(pub(crate) static IMAGE_CACHE: core::cell::RefCell<ImageCache> =
46 core::cell::RefCell::new(
47 ImageCache(
48 clru::CLruCache::with_config(
49 clru::CLruCacheConfig::new(core::num::NonZeroUsize::new(5 * 1024 * 1024).unwrap())
50 .with_scale(ImageWeightInBytes)
51 )
52 )
53 )
54);
55
56impl ImageCache {
57 // Look up the given image cache key in the image cache and upgrade the weak reference to a strong one if found,
58 // otherwise a new image is created/loaded from the given callback.
59 fn lookup_image_in_cache_or_create(
60 &mut self,
61 cache_key: ImageCacheKey,
62 image_create_fn: impl Fn(ImageCacheKey) -> Option<ImageInner>,
63 ) -> Option<Image> {
64 Some(Image(if let Some(entry) = self.0.get(&cache_key) {
65 entry.clone()
66 } else {
67 let new_image = image_create_fn(cache_key.clone())?;
68 self.0.put_with_weight(cache_key, new_image.clone()).ok();
69 new_image
70 }))
71 }
72
73 pub(crate) fn load_image_from_path(&mut self, path: &SharedString) -> Option<Image> {
74 if path.is_empty() {
75 return None;
76 }
77 let cache_key = ImageCacheKey::Path(path.clone());
78 #[cfg(target_arch = "wasm32")]
79 return self.lookup_image_in_cache_or_create(cache_key, |_| {
80 return Some(ImageInner::HTMLImage(vtable::VRc::new(
81 super::htmlimage::HTMLImage::new(&path),
82 )));
83 });
84 #[cfg(not(target_arch = "wasm32"))]
85 return self.lookup_image_in_cache_or_create(cache_key, |cache_key| {
86 if cfg!(feature = "svg") && (path.ends_with(".svg") || path.ends_with(".svgz")) {
87 return Some(ImageInner::Svg(vtable::VRc::new(
88 super::svg::load_from_path(path, cache_key).map_or_else(
89 |err| {
90 eprintln!("Error loading SVG from {}: {}", &path, err);
91 None
92 },
93 Some,
94 )?,
95 )));
96 }
97
98 image::open(std::path::Path::new(&path.as_str())).map_or_else(
99 |decode_err| {
100 eprintln!("Error loading image from {}: {}", &path, decode_err);
101 None
102 },
103 |image| {
104 Some(ImageInner::EmbeddedImage {
105 cache_key,
106 buffer: dynamic_image_to_shared_image_buffer(image),
107 })
108 },
109 )
110 });
111 }
112
113 pub(crate) fn load_image_from_embedded_data(
114 &mut self,
115 data: Slice<'static, u8>,
116 format: Slice<'_, u8>,
117 ) -> Option<Image> {
118 let cache_key = ImageCacheKey::from_embedded_image_data(data.as_slice());
119 self.lookup_image_in_cache_or_create(cache_key, |cache_key| {
120 #[cfg(feature = "svg")]
121 if format.as_slice() == b"svg" || format.as_slice() == b"svgz" {
122 return Some(ImageInner::Svg(vtable::VRc::new(
123 super::svg::load_from_data(data.as_slice(), cache_key).map_or_else(
124 |svg_err| {
125 eprintln!("Error loading SVG: {}", svg_err);
126 None
127 },
128 Some,
129 )?,
130 )));
131 }
132
133 let format = std::str::from_utf8(format.as_slice())
134 .ok()
135 .and_then(image::ImageFormat::from_extension);
136 let maybe_image = if let Some(format) = format {
137 image::load_from_memory_with_format(data.as_slice(), format)
138 } else {
139 image::load_from_memory(data.as_slice())
140 };
141
142 match maybe_image {
143 Ok(image) => Some(ImageInner::EmbeddedImage {
144 cache_key,
145 buffer: dynamic_image_to_shared_image_buffer(image),
146 }),
147 Err(decode_err) => {
148 eprintln!("Error decoding embedded image: {}", decode_err);
149 None
150 }
151 }
152 })
153 }
154}
155
156fn dynamic_image_to_shared_image_buffer(dynamic_image: image::DynamicImage) -> SharedImageBuffer {
157 if dynamic_image.color().has_alpha() {
158 let rgba8image: ImageBuffer, Vec<…>> = dynamic_image.to_rgba8();
159 SharedImageBuffer::RGBA8(SharedPixelBuffer::clone_from_slice(
160 pixel_slice:rgba8image.as_raw(),
161 rgba8image.width(),
162 rgba8image.height(),
163 ))
164 } else {
165 let rgb8image: ImageBuffer, Vec<…>> = dynamic_image.to_rgb8();
166 SharedImageBuffer::RGB8(SharedPixelBuffer::clone_from_slice(
167 pixel_slice:rgb8image.as_raw(),
168 rgb8image.width(),
169 rgb8image.height(),
170 ))
171 }
172}
173
174/// Replace the cached image key with the given value
175pub fn replace_cached_image(key: ImageCacheKey, value: ImageInner) {
176 if key == ImageCacheKey::Invalid {
177 return;
178 }
179 let _ =
180 IMAGE_CACHE.with(|global_cache: &RefCell| global_cache.borrow_mut().0.put_with_weight(key, value));
181}
182

Provided by KDAB

Privacy Policy