mirror of
https://github.com/slint-ui/slint.git
synced 2025-11-01 12:24:16 +00:00
Fix duplicated images when building with embedded images
For embedded images the path is empty but we unconditionally used it to create a TextureCacheKey, which clashes. Instead, preserve and store the ImageCacheKey in the ImageInner variants.
This commit is contained in:
parent
71d6717b68
commit
34dc0a80e7
5 changed files with 69 additions and 42 deletions
|
|
@ -173,6 +173,7 @@ fn gen_corelib(
|
||||||
"SharedString",
|
"SharedString",
|
||||||
"SharedVector",
|
"SharedVector",
|
||||||
"ImageInner",
|
"ImageInner",
|
||||||
|
"ImageCacheKey",
|
||||||
"Image",
|
"Image",
|
||||||
"Color",
|
"Color",
|
||||||
"PathData",
|
"PathData",
|
||||||
|
|
@ -259,6 +260,7 @@ fn gen_corelib(
|
||||||
vec![
|
vec![
|
||||||
"ImageInner",
|
"ImageInner",
|
||||||
"Image",
|
"Image",
|
||||||
|
"ImageCacheKey",
|
||||||
"Size",
|
"Size",
|
||||||
"slint_image_size",
|
"slint_image_size",
|
||||||
"slint_image_path",
|
"slint_image_path",
|
||||||
|
|
@ -354,6 +356,7 @@ fn gen_corelib(
|
||||||
.with_src(crate_dir.join("graphics/path.rs"))
|
.with_src(crate_dir.join("graphics/path.rs"))
|
||||||
.with_src(crate_dir.join("graphics/brush.rs"))
|
.with_src(crate_dir.join("graphics/brush.rs"))
|
||||||
.with_src(crate_dir.join("graphics/image.rs"))
|
.with_src(crate_dir.join("graphics/image.rs"))
|
||||||
|
.with_src(crate_dir.join("graphics/image/cache.rs"))
|
||||||
.with_src(crate_dir.join("animations.rs"))
|
.with_src(crate_dir.join("animations.rs"))
|
||||||
// .with_src(crate_dir.join("input.rs"))
|
// .with_src(crate_dir.join("input.rs"))
|
||||||
.with_src(crate_dir.join("item_rendering.rs"))
|
.with_src(crate_dir.join("item_rendering.rs"))
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,7 @@ pub enum ImageInner {
|
||||||
/// A resource that does not represent any data.
|
/// A resource that does not represent any data.
|
||||||
None,
|
None,
|
||||||
EmbeddedImage {
|
EmbeddedImage {
|
||||||
path: SharedString, // Should be Option, but can't be because of cbindgen, so empty means none.
|
cache_key: cache::ImageCacheKey,
|
||||||
buffer: SharedImageBuffer,
|
buffer: SharedImageBuffer,
|
||||||
},
|
},
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
|
|
@ -291,9 +291,9 @@ impl PartialEq for ImageInner {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(
|
(
|
||||||
Self::EmbeddedImage { path: l_path, buffer: l_buffer },
|
Self::EmbeddedImage { cache_key: l_cache_key, buffer: l_buffer },
|
||||||
Self::EmbeddedImage { path: r_path, buffer: r_buffer },
|
Self::EmbeddedImage { cache_key: r_cache_key, buffer: r_buffer },
|
||||||
) => l_path == r_path && l_buffer == r_buffer,
|
) => l_cache_key == r_cache_key && l_buffer == r_buffer,
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
(Self::Svg(l0), Self::Svg(r0)) => vtable::VRc::ptr_eq(l0, r0),
|
(Self::Svg(l0), Self::Svg(r0)) => vtable::VRc::ptr_eq(l0, r0),
|
||||||
(Self::StaticTextures(l0), Self::StaticTextures(r0)) => l0 == r0,
|
(Self::StaticTextures(l0), Self::StaticTextures(r0)) => l0 == r0,
|
||||||
|
|
@ -414,7 +414,7 @@ impl Image {
|
||||||
/// channels (red, green and blue) encoded as u8.
|
/// channels (red, green and blue) encoded as u8.
|
||||||
pub fn from_rgb8(buffer: SharedPixelBuffer<Rgb8Pixel>) -> Self {
|
pub fn from_rgb8(buffer: SharedPixelBuffer<Rgb8Pixel>) -> Self {
|
||||||
Image(ImageInner::EmbeddedImage {
|
Image(ImageInner::EmbeddedImage {
|
||||||
path: Default::default(),
|
cache_key: cache::ImageCacheKey::Invalid,
|
||||||
buffer: SharedImageBuffer::RGB8(buffer),
|
buffer: SharedImageBuffer::RGB8(buffer),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +423,7 @@ impl Image {
|
||||||
/// channels (red, green, blue and alpha) encoded as u8.
|
/// channels (red, green, blue and alpha) encoded as u8.
|
||||||
pub fn from_rgba8(buffer: SharedPixelBuffer<Rgba8Pixel>) -> Self {
|
pub fn from_rgba8(buffer: SharedPixelBuffer<Rgba8Pixel>) -> Self {
|
||||||
Image(ImageInner::EmbeddedImage {
|
Image(ImageInner::EmbeddedImage {
|
||||||
path: Default::default(),
|
cache_key: cache::ImageCacheKey::Invalid,
|
||||||
buffer: SharedImageBuffer::RGBA8(buffer),
|
buffer: SharedImageBuffer::RGBA8(buffer),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -435,7 +435,7 @@ impl Image {
|
||||||
/// Only construct an Image with this function if you know that your pixels are encoded this way.
|
/// Only construct an Image with this function if you know that your pixels are encoded this way.
|
||||||
pub fn from_rgba8_premultiplied(buffer: SharedPixelBuffer<Rgba8Pixel>) -> Self {
|
pub fn from_rgba8_premultiplied(buffer: SharedPixelBuffer<Rgba8Pixel>) -> Self {
|
||||||
Image(ImageInner::EmbeddedImage {
|
Image(ImageInner::EmbeddedImage {
|
||||||
path: Default::default(),
|
cache_key: cache::ImageCacheKey::Invalid,
|
||||||
buffer: SharedImageBuffer::RGBA8Premultiplied(buffer),
|
buffer: SharedImageBuffer::RGBA8Premultiplied(buffer),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -467,9 +467,10 @@ impl Image {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn path(&self) -> Option<&std::path::Path> {
|
pub fn path(&self) -> Option<&std::path::Path> {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
ImageInner::EmbeddedImage { path, .. } => {
|
ImageInner::EmbeddedImage { cache_key, .. } => match cache_key {
|
||||||
(!path.is_empty()).then(|| std::path::Path::new(path.as_str()))
|
cache::ImageCacheKey::Path(path) => Some(std::path::Path::new(path.as_str())),
|
||||||
}
|
_ => None,
|
||||||
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -551,7 +552,10 @@ pub(crate) mod ffi {
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn slint_image_path(image: &Image) -> Option<&SharedString> {
|
pub unsafe extern "C" fn slint_image_path(image: &Image) -> Option<&SharedString> {
|
||||||
match &image.0 {
|
match &image.0 {
|
||||||
ImageInner::EmbeddedImage { path, .. } => (!path.is_empty()).then(|| path),
|
ImageInner::EmbeddedImage { cache_key, .. } => match cache_key {
|
||||||
|
cache::ImageCacheKey::Path(path) => Some(path),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,31 +12,45 @@ use crate::{slice::Slice, SharedString};
|
||||||
|
|
||||||
/// ImageCacheKey encapsulates the different ways of indexing images in the
|
/// ImageCacheKey encapsulates the different ways of indexing images in the
|
||||||
/// cache of decoded images.
|
/// cache of decoded images.
|
||||||
#[derive(PartialEq, Eq, Hash, Debug, derive_more::From)]
|
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
pub enum ImageCacheKey {
|
pub enum ImageCacheKey {
|
||||||
|
/// This variant indicates that no image cache key can be created for the image.
|
||||||
|
/// For example this is the case for programmatically created images.
|
||||||
|
Invalid,
|
||||||
/// The image is identified by its path on the file system.
|
/// The image is identified by its path on the file system.
|
||||||
Path(SharedString),
|
Path(SharedString),
|
||||||
/// The image is identified by a URL.
|
/// The image is identified by a URL.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
URL(String),
|
URL(SharedString),
|
||||||
/// The image is identified by the static address of its encoded data.
|
/// The image is identified by the static address of its encoded data.
|
||||||
EmbeddedData(by_address::ByAddress<&'static [u8]>),
|
EmbeddedData(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageCacheKey {
|
impl ImageCacheKey {
|
||||||
/// Returns a new cache key if decoded image data can be stored in image cache for
|
/// Returns a new cache key if decoded image data can be stored in image cache for
|
||||||
/// the given ImageInner.
|
/// the given ImageInner.
|
||||||
pub fn new(resource: &ImageInner) -> Option<Self> {
|
pub fn new(resource: &ImageInner) -> Option<Self> {
|
||||||
Some(match resource {
|
let key = match resource {
|
||||||
ImageInner::None => return None,
|
ImageInner::None => return None,
|
||||||
ImageInner::EmbeddedImage { path, .. } => path.clone().into(),
|
ImageInner::EmbeddedImage { cache_key, .. } => cache_key.clone(),
|
||||||
ImageInner::StaticTextures(textures) => {
|
ImageInner::StaticTextures(textures) => {
|
||||||
by_address::ByAddress(textures.data.as_slice()).into()
|
Self::from_embedded_image_data(textures.data.as_slice())
|
||||||
}
|
}
|
||||||
ImageInner::Svg(parsed_svg) => parsed_svg.path().clone().into(),
|
ImageInner::Svg(parsed_svg) => parsed_svg.cache_key(),
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
ImageInner::HTMLImage(htmlimage) => htmlimage.source().into(),
|
ImageInner::HTMLImage(htmlimage) => Self::URL(htmlimage.source().into()),
|
||||||
})
|
};
|
||||||
|
if matches!(key, ImageCacheKey::Invalid) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a cache key for static embedded image data.
|
||||||
|
pub fn from_embedded_image_data(data: &'static [u8]) -> Self {
|
||||||
|
Self::EmbeddedData(data.as_ptr() as usize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,14 +66,14 @@ impl ImageCache {
|
||||||
fn lookup_image_in_cache_or_create(
|
fn lookup_image_in_cache_or_create(
|
||||||
&mut self,
|
&mut self,
|
||||||
cache_key: ImageCacheKey,
|
cache_key: ImageCacheKey,
|
||||||
image_create_fn: impl Fn() -> Option<ImageInner>,
|
image_create_fn: impl Fn(ImageCacheKey) -> Option<ImageInner>,
|
||||||
) -> Option<Image> {
|
) -> Option<Image> {
|
||||||
Some(Image(match self.0.entry(cache_key) {
|
Some(Image(match self.0.entry(cache_key.clone()) {
|
||||||
std::collections::hash_map::Entry::Occupied(existing_entry) => {
|
std::collections::hash_map::Entry::Occupied(existing_entry) => {
|
||||||
existing_entry.get().clone()
|
existing_entry.get().clone()
|
||||||
}
|
}
|
||||||
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
|
std::collections::hash_map::Entry::Vacant(vacant_entry) => {
|
||||||
let new_image = image_create_fn()?;
|
let new_image = image_create_fn(cache_key)?;
|
||||||
vacant_entry.insert(new_image.clone());
|
vacant_entry.insert(new_image.clone());
|
||||||
new_image
|
new_image
|
||||||
}
|
}
|
||||||
|
|
@ -70,19 +84,19 @@ impl ImageCache {
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let cache_key = ImageCacheKey::from(path.clone());
|
let cache_key = ImageCacheKey::Path(path.clone());
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
return self.lookup_image_in_cache_or_create(cache_key, || {
|
return self.lookup_image_in_cache_or_create(cache_key, |_| {
|
||||||
return Some(ImageInner::HTMLImage(vtable::VRc::new(
|
return Some(ImageInner::HTMLImage(vtable::VRc::new(
|
||||||
super::htmlimage::HTMLImage::new(&path),
|
super::htmlimage::HTMLImage::new(&path),
|
||||||
)));
|
)));
|
||||||
});
|
});
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
return self.lookup_image_in_cache_or_create(cache_key, || {
|
return self.lookup_image_in_cache_or_create(cache_key, |cache_key| {
|
||||||
if cfg!(feature = "svg") {
|
if cfg!(feature = "svg") {
|
||||||
if path.ends_with(".svg") || path.ends_with(".svgz") {
|
if path.ends_with(".svg") || path.ends_with(".svgz") {
|
||||||
return Some(ImageInner::Svg(vtable::VRc::new(
|
return Some(ImageInner::Svg(vtable::VRc::new(
|
||||||
super::svg::load_from_path(path).map_or_else(
|
super::svg::load_from_path(path, cache_key).map_or_else(
|
||||||
|err| {
|
|err| {
|
||||||
eprintln!("Error loading SVG from {}: {}", &path, err);
|
eprintln!("Error loading SVG from {}: {}", &path, err);
|
||||||
None
|
None
|
||||||
|
|
@ -100,7 +114,7 @@ impl ImageCache {
|
||||||
},
|
},
|
||||||
|image| {
|
|image| {
|
||||||
Some(ImageInner::EmbeddedImage {
|
Some(ImageInner::EmbeddedImage {
|
||||||
path: path.clone(),
|
cache_key,
|
||||||
buffer: dynamic_image_to_shared_image_buffer(image),
|
buffer: dynamic_image_to_shared_image_buffer(image),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -113,12 +127,12 @@ impl ImageCache {
|
||||||
data: Slice<'static, u8>,
|
data: Slice<'static, u8>,
|
||||||
format: Slice<'static, u8>,
|
format: Slice<'static, u8>,
|
||||||
) -> Option<Image> {
|
) -> Option<Image> {
|
||||||
let cache_key = ImageCacheKey::from(by_address::ByAddress(data.as_slice()));
|
let cache_key = ImageCacheKey::from_embedded_image_data(data.as_slice());
|
||||||
self.lookup_image_in_cache_or_create(cache_key, || {
|
self.lookup_image_in_cache_or_create(cache_key, |cache_key| {
|
||||||
#[cfg(feature = "svg")]
|
#[cfg(feature = "svg")]
|
||||||
if format.as_slice() == b"svg" || format.as_slice() == b"svgz" {
|
if format.as_slice() == b"svg" || format.as_slice() == b"svgz" {
|
||||||
return Some(ImageInner::Svg(vtable::VRc::new(
|
return Some(ImageInner::Svg(vtable::VRc::new(
|
||||||
super::svg::load_from_data(data.as_slice()).map_or_else(
|
super::svg::load_from_data(data.as_slice(), cache_key).map_or_else(
|
||||||
|svg_err| {
|
|svg_err| {
|
||||||
eprintln!("Error loading SVG: {}", svg_err);
|
eprintln!("Error loading SVG: {}", svg_err);
|
||||||
None
|
None
|
||||||
|
|
@ -139,7 +153,7 @@ impl ImageCache {
|
||||||
|
|
||||||
match maybe_image {
|
match maybe_image {
|
||||||
Ok(image) => Some(ImageInner::EmbeddedImage {
|
Ok(image) => Some(ImageInner::EmbeddedImage {
|
||||||
path: Default::default(),
|
cache_key,
|
||||||
buffer: dynamic_image_to_shared_image_buffer(image),
|
buffer: dynamic_image_to_shared_image_buffer(image),
|
||||||
}),
|
}),
|
||||||
Err(decode_err) => {
|
Err(decode_err) => {
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,14 @@
|
||||||
|
|
||||||
#![cfg(feature = "svg")]
|
#![cfg(feature = "svg")]
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use crate::SharedString;
|
use crate::SharedString;
|
||||||
|
|
||||||
use super::SharedPixelBuffer;
|
use super::SharedPixelBuffer;
|
||||||
|
|
||||||
pub struct ParsedSVG {
|
pub struct ParsedSVG {
|
||||||
svg_tree: usvg::Tree,
|
svg_tree: usvg::Tree,
|
||||||
path: SharedString,
|
cache_key: crate::graphics::cache::ImageCacheKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::OpaqueRc for ParsedSVG {}
|
impl super::OpaqueRc for ParsedSVG {}
|
||||||
|
|
@ -26,8 +27,8 @@ impl ParsedSVG {
|
||||||
[size.width(), size.height()].into()
|
[size.width(), size.height()].into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> &SharedString {
|
pub fn cache_key(&self) -> crate::graphics::cache::ImageCacheKey {
|
||||||
&self.path
|
self.cache_key.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders the SVG with the specified size.
|
/// Renders the SVG with the specified size.
|
||||||
|
|
@ -71,19 +72,24 @@ fn with_svg_options<T>(callback: impl FnOnce(usvg::OptionsRef<'_>) -> T) -> T {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn load_from_path(path: &SharedString) -> Result<ParsedSVG, std::io::Error> {
|
pub fn load_from_path(
|
||||||
|
path: &SharedString,
|
||||||
|
cache_key: crate::graphics::cache::ImageCacheKey,
|
||||||
|
) -> Result<ParsedSVG, std::io::Error> {
|
||||||
let svg_data = std::fs::read(std::path::Path::new(&path.as_str()))?;
|
let svg_data = std::fs::read(std::path::Path::new(&path.as_str()))?;
|
||||||
|
|
||||||
with_svg_options(|options| {
|
with_svg_options(|options| {
|
||||||
usvg::Tree::from_data(&svg_data, &options)
|
usvg::Tree::from_data(&svg_data, &options)
|
||||||
.map(|svg| ParsedSVG { svg_tree: svg, path: path.clone() })
|
.map(|svg| ParsedSVG { svg_tree: svg, cache_key })
|
||||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_from_data(slice: &[u8]) -> Result<ParsedSVG, usvg::Error> {
|
pub fn load_from_data(
|
||||||
|
slice: &[u8],
|
||||||
|
cache_key: crate::graphics::cache::ImageCacheKey,
|
||||||
|
) -> Result<ParsedSVG, usvg::Error> {
|
||||||
with_svg_options(|options| {
|
with_svg_options(|options| {
|
||||||
usvg::Tree::from_data(slice, &options)
|
usvg::Tree::from_data(slice, &options).map(|svg| ParsedSVG { svg_tree: svg, cache_key })
|
||||||
.map(|svg| ParsedSVG { svg_tree: svg, path: Default::default() })
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ use vtable::VRef;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
pub struct ValueOpaque([usize; 7]);
|
pub struct ValueOpaque([usize; 8]);
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
#[repr(align(8))]
|
#[repr(align(8))]
|
||||||
pub struct ValueOpaque([usize; 10]);
|
pub struct ValueOpaque([usize; 11]);
|
||||||
/// Asserts that ValueOpaque is as large as Value and has the same alignment, to make transmute safe.
|
/// Asserts that ValueOpaque is as large as Value and has the same alignment, to make transmute safe.
|
||||||
const _: [(); std::mem::size_of::<ValueOpaque>()] = [(); std::mem::size_of::<Value>()];
|
const _: [(); std::mem::size_of::<ValueOpaque>()] = [(); std::mem::size_of::<Value>()];
|
||||||
const _: [(); std::mem::align_of::<ValueOpaque>()] = [(); std::mem::align_of::<Value>()];
|
const _: [(); std::mem::align_of::<ValueOpaque>()] = [(); std::mem::align_of::<Value>()];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue