Skip to content

Commit 9eaa9c3

Browse files
committed
Image: data url support
Data url format is useful when one tries slintpad with images. Image { source: @image-url("data:image/png;base64,iV....="); }
1 parent d6f83a2 commit 9eaa9c3

File tree

10 files changed

+130
-14
lines changed

10 files changed

+130
-14
lines changed

api/cpp/cbindgen.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ fn gen_corelib(
387387
"slint_image_size",
388388
"slint_image_path",
389389
"slint_image_load_from_path",
390+
"slint_image_load_from_data_url",
390391
"slint_image_load_from_embedded_data",
391392
"slint_image_from_embedded_textures",
392393
"slint_image_compare_equal",
@@ -501,6 +502,7 @@ fn gen_corelib(
501502
"slint_image_size",
502503
"slint_image_path",
503504
"slint_image_load_from_path",
505+
"slint_image_load_from_data_url",
504506
"slint_image_load_from_embedded_data",
505507
"slint_image_from_embedded_textures",
506508
"slint_image_compare_equal",
@@ -593,6 +595,7 @@ fn gen_corelib(
593595
"slint_image_size",
594596
"slint_image_path",
595597
"slint_image_load_from_path",
598+
"slint_image_load_from_data_url",
596599
"slint_image_load_from_embedded_data",
597600
"slint_image_set_nine_slice_edges",
598601
"slint_image_to_rgb8",

api/cpp/include/slint_image.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ struct Image
130130
cbindgen_private::types::slint_image_load_from_path(&file_path, &img.data);
131131
return img;
132132
}
133+
134+
/// Load an image from data url
135+
[[nodiscard]] static Image load_from_data_url(const SharedString &data_url)
136+
{
137+
Image img;
138+
cbindgen_private::types::slint_image_load_from_data_url(&data_url, &img.data);
139+
return img;
140+
}
133141
#endif
134142

135143
/// Constructs a new Image from an existing OpenGL texture. The texture remains borrowed by

internal/compiler/generator/cpp.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3249,7 +3249,13 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
32493249
Expression::ImageReference { resource_ref, nine_slice } => {
32503250
let image = match resource_ref {
32513251
crate::expression_tree::ImageReference::None => r#"slint::Image()"#.to_string(),
3252-
crate::expression_tree::ImageReference::AbsolutePath(path) => format!(r#"slint::Image::load_from_path(slint::SharedString(u8"{}"))"#, escape_string(path.as_str())),
3252+
crate::expression_tree::ImageReference::AbsolutePath(path) => {
3253+
if path.starts_with("data:") {
3254+
format!(r#"slint::Image::load_from_data_url(u8"{}")"#, escape_string(path.as_str()))
3255+
} else {
3256+
format!(r#"slint::Image::load_from_path(u8"{}")"#, escape_string(path.as_str()))
3257+
}
3258+
}
32533259
crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => {
32543260
let symbol = format!("slint_embedded_resource_{}", resource_id);
32553261
format!(r#"slint::private_api::load_image_from_embedded_data({symbol}, "{}")"#, escape_string(extension))

internal/compiler/generator/rust.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2417,7 +2417,11 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
24172417
}
24182418
crate::expression_tree::ImageReference::AbsolutePath(path) => {
24192419
let path = path.as_str();
2420-
quote!(sp::Image::load_from_path(::std::path::Path::new(#path)).unwrap_or_default())
2420+
if path.starts_with("data:") {
2421+
quote!(sp::Image::load_from_data_url(#path).unwrap_or_default())
2422+
} else {
2423+
quote!(sp::Image::load_from_path(::std::path::Path::new(#path)).unwrap_or_default())
2424+
}
24212425
}
24222426
crate::expression_tree::ImageReference::EmbeddedData { resource_id, extension } => {
24232427
let symbol = format_ident!("SLINT_EMBEDDED_RESOURCE_{}", resource_id);

internal/compiler/passes/resolving.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,14 @@ impl Expression {
361361
fn from_at_image_url_node(node: syntax_nodes::AtImageUrl, ctx: &mut LookupCtx) -> Self {
362362
let s = match node
363363
.child_text(SyntaxKind::StringLiteral)
364-
.and_then(|x| crate::literals::unescape_string(&x))
364+
.and_then(|x|
365+
if x.starts_with("\"data:") {
366+
// Remove quotes here because unescape_string() doesn't support \n yet.
367+
let x = x.strip_prefix('"')?;
368+
let x = x.strip_suffix('"')?;
369+
Some(SmolStr::new(x))
370+
} else { crate::literals::unescape_string(&x) }
371+
)
365372
{
366373
Some(s) => s,
367374
None => {

internal/core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ web-sys = { workspace = true, features = [ "HtmlImageElement" ] }
105105

106106
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
107107
fontdb = { workspace = true, optional = true, default-features = true }
108+
dataurl = "0.1.2"
108109

109110
[dev-dependencies]
110111
slint = { path = "../../api/rs/slint", default-features = false, features = ["std", "compat-1-2"] }

internal/core/graphics/image.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,14 @@ impl Image {
671671
})
672672
}
673673

674+
#[cfg(feature = "image-decoders")]
675+
/// Load an Image from a data url
676+
pub fn load_from_data_url(data_url: &str) -> Result<Self, LoadImageError> {
677+
self::cache::IMAGE_CACHE.with(|global_cache| {
678+
global_cache.borrow_mut().load_image_from_data_url(&data_url).ok_or(LoadImageError(()))
679+
})
680+
}
681+
674682
/// Creates a new Image from the specified shared pixel buffer, where each pixel has three color
675683
/// channels (red, green and blue) encoded as u8.
676684
pub fn from_rgb8(buffer: SharedPixelBuffer<Rgb8Pixel>) -> Self {
@@ -1274,6 +1282,15 @@ pub(crate) mod ffi {
12741282
)
12751283
}
12761284

1285+
#[cfg(feature = "image-decoders")]
1286+
#[no_mangle]
1287+
pub unsafe extern "C" fn slint_image_load_from_data_url(data_url: &SharedString, image: *mut Image) {
1288+
core::ptr::write(
1289+
image,
1290+
Image::load_from_data_url(data_url.as_str()).unwrap_or(Image::default()),
1291+
)
1292+
}
1293+
12771294
#[cfg(feature = "std")]
12781295
#[no_mangle]
12791296
pub unsafe extern "C" fn slint_image_load_from_embedded_data(

internal/core/graphics/image/cache.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ This module contains image and caching related types for the run-time library.
77

88
use super::{CachedPath, Image, ImageCacheKey, ImageInner, SharedImageBuffer, SharedPixelBuffer};
99
use crate::{slice::Slice, SharedString};
10+
#[cfg(not(target_arch = "wasm32"))]
11+
use dataurl::DataUrl;
1012

1113
struct ImageWeightInBytes;
1214

@@ -110,6 +112,59 @@ impl ImageCache {
110112
});
111113
}
112114

115+
pub(crate) fn load_image_from_data_url(&mut self, str: &str) -> Option<Image> {
116+
let cache_key = ImageCacheKey::Path(CachedPath::new(str));
117+
#[cfg(target_arch = "wasm32")]
118+
return self.lookup_image_in_cache_or_create(cache_key, |_| {
119+
return Some(ImageInner::HTMLImage(vtable::VRc::new(
120+
super::htmlimage::HTMLImage::new(&str),
121+
)));
122+
});
123+
#[cfg(not(target_arch = "wasm32"))]
124+
return self.lookup_image_in_cache_or_create(cache_key, |cache_key| {
125+
let data_url = DataUrl::parse(&str).unwrap();
126+
let media_type = data_url.get_media_type();
127+
if !media_type.starts_with("image/") {
128+
eprintln!("Unsupported media type: {}", media_type);
129+
return None;
130+
}
131+
let media_type = media_type.split('/').nth(1).unwrap_or("");
132+
133+
let text = data_url.get_text();
134+
let data = if data_url.get_is_base64_encoded() {
135+
data_url.get_data()
136+
} else {
137+
text.as_bytes()
138+
};
139+
140+
if cfg!(feature = "svg") && (media_type == ("svg+xml") || media_type == "svgz+xml") {
141+
return Some(ImageInner::Svg(vtable::VRc::new(
142+
super::svg::load_from_data(data, cache_key).map_or_else(
143+
|err| {
144+
eprintln!("Error loading SVG from {}: {}", &str, err);
145+
None
146+
},
147+
Some,
148+
)?,
149+
)));
150+
}
151+
152+
let format = image::ImageFormat::from_extension(media_type);
153+
image::load_from_memory_with_format(data, format.unwrap()).map_or_else(
154+
|decode_err| {
155+
eprintln!("Error loading image from {}: {}", &str, decode_err);
156+
None
157+
},
158+
|image| {
159+
Some(ImageInner::EmbeddedImage {
160+
cache_key,
161+
buffer: dynamic_image_to_shared_image_buffer(image),
162+
})
163+
},
164+
)
165+
});
166+
}
167+
113168
pub(crate) fn load_image_from_embedded_data(
114169
&mut self,
115170
data: Slice<'static, u8>,

internal/interpreter/eval.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -272,17 +272,21 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon
272272
Ok(Default::default())
273273
}
274274
i_slint_compiler::expression_tree::ImageReference::AbsolutePath(path) => {
275-
let path = std::path::Path::new(path);
276-
if path.starts_with("builtin:/") {
277-
i_slint_compiler::fileaccess::load_file(path).and_then(|virtual_file| virtual_file.builtin_contents).map(|virtual_file| {
278-
let extension = path.extension().unwrap().to_str().unwrap();
279-
corelib::graphics::load_image_from_embedded_data(
280-
corelib::slice::Slice::from_slice(virtual_file),
281-
corelib::slice::Slice::from_slice(extension.as_bytes())
282-
)
283-
}).ok_or_else(Default::default)
275+
if path.starts_with("data:") {
276+
corelib::graphics::Image::load_from_data_url(path)
284277
} else {
285-
corelib::graphics::Image::load_from_path(path)
278+
let path = std::path::Path::new(path);
279+
if path.starts_with("builtin:/") {
280+
i_slint_compiler::fileaccess::load_file(path).and_then(|virtual_file| virtual_file.builtin_contents).map(|virtual_file| {
281+
let extension = path.extension().unwrap().to_str().unwrap();
282+
corelib::graphics::load_image_from_embedded_data(
283+
corelib::slice::Slice::from_slice(virtual_file),
284+
corelib::slice::Slice::from_slice(extension.as_bytes())
285+
)
286+
}).ok_or_else(Default::default)
287+
} else {
288+
corelib::graphics::Image::load_from_path(path)
289+
}
286290
}
287291
}
288292
i_slint_compiler::expression_tree::ImageReference::EmbeddedData { .. } => {

tests/cases/elements/image.slint

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,23 @@ TestCase := Rectangle {
1717
source: @image-url("image.slint");
1818
}
1919

20+
2021
out property <image> with-border: @image-url("dog.jpg", nine-slice(12 13 14 15));
2122

23+
out property <image> data-url-plain-text: @image-url("data:,Hello%2C%20World%21");
24+
25+
// slint-logo-small-light.png
26+
out property <image> data-url-image-png-base64: @image-url("data:image/png;base64,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");
27+
28+
out property <image> data-url-image-svg: @image-url("data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%0A%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%2295%22%20viewBox%3D%220%200%20100%2095%22%3E%0A%3Cpolygon%20fill%3D%22%23231815%22%20points%3D%2250%2C0%2065.451%2C31.271%20100%2C36.287%2075%2C60.629%2080.902%2C95%2050%2C78.771%2019.098%2C95%2025%2C60.629%200%2C36.287%20%0A%0934.549%2C31.271%20%22%2F%3E%0A%3C%2Fsvg%3E");
29+
2230
property <length> img_width: img.width;
2331
property <length> img_height: img.height;
32+
property <bool> data-url: data-url-plain-text.width == 0 && data-url-plain-text.height == 0 &&
33+
data-url-image-png-base64.width == 128 && data-url-image-png-base64.height == 128 &&
34+
data-url-image-svg.width > 0 && data-url-image-svg.height > 0;
2435
property <bool> test: img2.source-clip-height * 1px == img2.height && img2.source-clip-width * 1px == img2.width &&
25-
img2.width/1px == img2.source.width - 20 && img3.source.width == 0 && img3.source.height == 0;
36+
img2.width/1px == img2.source.width - 20 && img3.source.width == 0 && img3.source.height == 0 && data-url;
2637
}
2738

2839
/*

0 commit comments

Comments
 (0)