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 decoding and caching related types for the run-time library.
6*/
7
8use crate::lengths::{PhysicalPx, ScaleFactor};
9use crate::slice::Slice;
10use crate::{SharedString, SharedVector};
11
12use super::{IntRect, IntSize};
13use crate::items::{ImageFit, ImageHorizontalAlignment, ImageTiling, ImageVerticalAlignment};
14
15#[cfg(feature = "image-decoders")]
16pub mod cache;
17#[cfg(target_arch = "wasm32")]
18mod htmlimage;
19#[cfg(feature = "svg")]
20mod svg;
21
22#[allow(missing_docs)]
23#[vtable::vtable]
24#[repr(C)]
25pub struct OpaqueImageVTable {
26 drop_in_place: fn(VRefMut<OpaqueImageVTable>) -> Layout,
27 dealloc: fn(&OpaqueImageVTable, ptr: *mut u8, layout: Layout),
28 /// Returns the image size
29 size: fn(VRef<OpaqueImageVTable>) -> IntSize,
30 /// Returns a cache key
31 cache_key: fn(VRef<OpaqueImageVTable>) -> ImageCacheKey,
32}
33
34#[cfg(feature = "svg")]
35OpaqueImageVTable_static! {
36 /// VTable for RC wrapped SVG helper struct.
37 pub static PARSED_SVG_VT for svg::ParsedSVG
38}
39
40#[cfg(target_arch = "wasm32")]
41OpaqueImageVTable_static! {
42 /// VTable for RC wrapped HtmlImage helper struct.
43 pub static HTML_IMAGE_VT for htmlimage::HTMLImage
44}
45
46OpaqueImageVTable_static! {
47 /// VTable for RC wrapped SVG helper struct.
48 pub static NINE_SLICE_VT for NineSliceImage
49}
50
51/// SharedPixelBuffer is a container for storing image data as pixels. It is
52/// internally reference counted and cheap to clone.
53///
54/// You can construct a new empty shared pixel buffer with [`SharedPixelBuffer::new`],
55/// or you can clone it from an existing contiguous buffer that you might already have, using
56/// [`SharedPixelBuffer::clone_from_slice`].
57///
58/// See the documentation for [`Image`] for examples how to use this type to integrate
59/// Slint with external rendering functions.
60#[derive(Debug, Clone)]
61#[repr(C)]
62pub struct SharedPixelBuffer<Pixel> {
63 width: u32,
64 height: u32,
65 data: SharedVector<Pixel>,
66}
67
68impl<Pixel> SharedPixelBuffer<Pixel> {
69 /// Returns the width of the image in pixels.
70 pub fn width(&self) -> u32 {
71 self.width
72 }
73
74 /// Returns the height of the image in pixels.
75 pub fn height(&self) -> u32 {
76 self.height
77 }
78
79 /// Returns the size of the image in pixels.
80 pub fn size(&self) -> IntSize {
81 [self.width, self.height].into()
82 }
83}
84
85impl<Pixel: Clone> SharedPixelBuffer<Pixel> {
86 /// Return a mutable slice to the pixel data. If the SharedPixelBuffer was shared, this will make a copy of the buffer.
87 pub fn make_mut_slice(&mut self) -> &mut [Pixel] {
88 self.data.make_mut_slice()
89 }
90}
91
92impl<Pixel: Clone + rgb::Pod> SharedPixelBuffer<Pixel>
93where
94 [Pixel]: rgb::ComponentBytes<u8>,
95{
96 /// Returns the pixels interpreted as raw bytes.
97 pub fn as_bytes(&self) -> &[u8] {
98 use rgb::ComponentBytes;
99 self.data.as_slice().as_bytes()
100 }
101
102 /// Returns the pixels interpreted as raw bytes.
103 pub fn make_mut_bytes(&mut self) -> &mut [u8] {
104 use rgb::ComponentBytes;
105 self.data.make_mut_slice().as_bytes_mut()
106 }
107}
108
109impl<Pixel> SharedPixelBuffer<Pixel> {
110 /// Return a slice to the pixel data.
111 pub fn as_slice(&self) -> &[Pixel] {
112 self.data.as_slice()
113 }
114}
115
116impl<Pixel: Clone + Default> SharedPixelBuffer<Pixel> {
117 /// Creates a new SharedPixelBuffer with the given width and height. Each pixel will be initialized with the value
118 /// that [`Default::default()`] returns for the Pixel type.
119 pub fn new(width: u32, height: u32) -> Self {
120 Self {
121 width,
122 height,
123 data: coreimpl Iterator::iter::repeat(Pixel::default())
124 .take(width as usize * height as usize)
125 .collect(),
126 }
127 }
128}
129
130impl<Pixel: Clone> SharedPixelBuffer<Pixel> {
131 /// Creates a new SharedPixelBuffer by cloning and converting pixels from an existing
132 /// slice. This function is useful when another crate was used to allocate an image
133 /// and you would like to convert it for use in Slint.
134 pub fn clone_from_slice<SourcePixelType>(
135 pixel_slice: &[SourcePixelType],
136 width: u32,
137 height: u32,
138 ) -> Self
139 where
140 [SourcePixelType]: rgb::AsPixels<Pixel>,
141 {
142 use rgb::AsPixels;
143 Self { width, height, data: pixel_slice.as_pixels().into() }
144 }
145}
146
147/// Convenience alias for a pixel with three color channels (red, green and blue), each
148/// encoded as u8.
149pub type Rgb8Pixel = rgb::RGB8;
150/// Convenience alias for a pixel with four color channels (red, green, blue and alpha), each
151/// encoded as u8.
152pub type Rgba8Pixel = rgb::RGBA8;
153
154/// SharedImageBuffer is a container for images that are stored in CPU accessible memory.
155///
156/// The SharedImageBuffer's variants represent the different common formats for encoding
157/// images in pixels.
158#[derive(Clone, Debug)]
159#[repr(C)]
160pub enum SharedImageBuffer {
161 /// This variant holds the data for an image where each pixel has three color channels (red, green,
162 /// and blue) and each channel is encoded as unsigned byte.
163 RGB8(SharedPixelBuffer<Rgb8Pixel>),
164 /// This variant holds the data for an image where each pixel has four color channels (red, green,
165 /// blue and alpha) and each channel is encoded as unsigned byte.
166 RGBA8(SharedPixelBuffer<Rgba8Pixel>),
167 /// This variant holds the data for an image where each pixel has four color channels (red, green,
168 /// blue and alpha) and each channel is encoded as unsigned byte. In contrast to [`Self::RGBA8`],
169 /// this variant assumes that the alpha channel is also already multiplied to each red, green and blue
170 /// component of each pixel.
171 /// Only construct this format if you know that your pixels are encoded this way. It is more efficient
172 /// for rendering.
173 RGBA8Premultiplied(SharedPixelBuffer<Rgba8Pixel>),
174}
175
176impl SharedImageBuffer {
177 /// Returns the width of the image in pixels.
178 #[inline]
179 pub fn width(&self) -> u32 {
180 match self {
181 Self::RGB8(buffer) => buffer.width(),
182 Self::RGBA8(buffer) => buffer.width(),
183 Self::RGBA8Premultiplied(buffer) => buffer.width(),
184 }
185 }
186
187 /// Returns the height of the image in pixels.
188 #[inline]
189 pub fn height(&self) -> u32 {
190 match self {
191 Self::RGB8(buffer) => buffer.height(),
192 Self::RGBA8(buffer) => buffer.height(),
193 Self::RGBA8Premultiplied(buffer) => buffer.height(),
194 }
195 }
196
197 /// Returns the size of the image in pixels.
198 #[inline]
199 pub fn size(&self) -> IntSize {
200 match self {
201 Self::RGB8(buffer) => buffer.size(),
202 Self::RGBA8(buffer) => buffer.size(),
203 Self::RGBA8Premultiplied(buffer) => buffer.size(),
204 }
205 }
206}
207
208impl PartialEq for SharedImageBuffer {
209 fn eq(&self, other: &Self) -> bool {
210 match self {
211 Self::RGB8(lhs_buffer: &SharedPixelBuffer>) => {
212 matches!(other, Self::RGB8(rhs_buffer) if lhs_buffer.data.as_ptr().eq(&rhs_buffer.data.as_ptr()))
213 }
214 Self::RGBA8(lhs_buffer: &SharedPixelBuffer>) => {
215 matches!(other, Self::RGBA8(rhs_buffer) if lhs_buffer.data.as_ptr().eq(&rhs_buffer.data.as_ptr()))
216 }
217 Self::RGBA8Premultiplied(lhs_buffer: &SharedPixelBuffer>) => {
218 matches!(other, Self::RGBA8Premultiplied(rhs_buffer) if lhs_buffer.data.as_ptr().eq(&rhs_buffer.data.as_ptr()))
219 }
220 }
221 }
222}
223
224#[repr(u8)]
225#[derive(Clone, PartialEq, Debug, Copy)]
226/// The pixel format of a StaticTexture
227pub enum PixelFormat {
228 /// red, green, blue. 24bits.
229 Rgb,
230 /// Red, green, blue, alpha. 32bits.
231 Rgba,
232 /// Red, green, blue, alpha. 32bits. The color are premultiplied by alpha
233 RgbaPremultiplied,
234 /// Alpha map. 8bits. Each pixel is an alpha value. The color is specified separately.
235 AlphaMap,
236}
237
238impl PixelFormat {
239 /// The number of bytes in a pixel
240 pub fn bpp(self) -> usize {
241 match self {
242 PixelFormat::Rgb => 3,
243 PixelFormat::Rgba => 4,
244 PixelFormat::RgbaPremultiplied => 4,
245 PixelFormat::AlphaMap => 1,
246 }
247 }
248}
249
250#[repr(C)]
251#[derive(Clone, PartialEq, Debug)]
252/// Some raw pixel data which is typically stored in the binary
253pub struct StaticTexture {
254 /// The position and size of the texture within the image
255 pub rect: IntRect,
256 /// The pixel format of this texture
257 pub format: PixelFormat,
258 /// The color, for the alpha map ones
259 pub color: crate::Color,
260 /// index in the data array
261 pub index: usize,
262}
263
264#[repr(C)]
265#[derive(Clone, PartialEq, Debug)]
266/// A texture is stored in read-only memory and may be composed of sub-textures.
267
268pub struct StaticTextures {
269 /// The total size of the image (this might not be the size of the full image
270 /// as some transparent part are not part of any texture)
271 pub size: IntSize,
272 /// The size of the image before the compiler applied any scaling
273 pub original_size: IntSize,
274 /// The pixel data referenced by the textures
275 pub data: Slice<'static, u8>,
276 /// The list of textures
277 pub textures: Slice<'static, StaticTexture>,
278}
279
280/// ImageCacheKey encapsulates the different ways of indexing images in the
281/// cache of decoded images.
282#[derive(PartialEq, Eq, Debug, Hash, Clone)]
283#[repr(u8)]
284pub enum ImageCacheKey {
285 /// This variant indicates that no image cache key can be created for the image.
286 /// For example this is the case for programmatically created images.
287 Invalid = 0,
288 /// The image is identified by its path on the file system.
289 Path(SharedString) = 1,
290 /// The image is identified by a URL.
291 #[cfg(target_arch = "wasm32")]
292 URL(SharedString) = 2,
293 /// The image is identified by the static address of its encoded data.
294 EmbeddedData(usize) = 3,
295}
296
297impl ImageCacheKey {
298 /// Returns a new cache key if decoded image data can be stored in image cache for
299 /// the given ImageInner.
300 pub fn new(resource: &ImageInner) -> Option<Self> {
301 let key = match resource {
302 ImageInner::None => return None,
303 ImageInner::EmbeddedImage { cache_key, .. } => cache_key.clone(),
304 ImageInner::StaticTextures(textures) => {
305 Self::from_embedded_image_data(textures.data.as_slice())
306 }
307 #[cfg(feature = "svg")]
308 ImageInner::Svg(parsed_svg) => parsed_svg.cache_key(),
309 #[cfg(target_arch = "wasm32")]
310 ImageInner::HTMLImage(htmlimage) => Self::URL(htmlimage.source().into()),
311 ImageInner::BackendStorage(x) => vtable::VRc::borrow(x).cache_key(),
312 #[cfg(not(target_arch = "wasm32"))]
313 ImageInner::BorrowedOpenGLTexture(..) => return None,
314 ImageInner::NineSlice(nine) => vtable::VRc::borrow(nine).cache_key(),
315 };
316 if matches!(key, ImageCacheKey::Invalid) {
317 None
318 } else {
319 Some(key)
320 }
321 }
322
323 /// Returns a cache key for static embedded image data.
324 pub fn from_embedded_image_data(data: &'static [u8]) -> Self {
325 Self::EmbeddedData(data.as_ptr() as usize)
326 }
327}
328
329/// Represent a nine-slice image with the base image and the 4 borders
330pub struct NineSliceImage(pub ImageInner, pub [u16; 4]);
331
332impl NineSliceImage {
333 /// return the backing Image
334 pub fn image(&self) -> Image {
335 Image(self.0.clone())
336 }
337}
338
339impl OpaqueImage for NineSliceImage {
340 fn size(&self) -> IntSize {
341 self.0.size()
342 }
343 fn cache_key(&self) -> ImageCacheKey {
344 ImageCacheKey::new(&self.0).unwrap_or(default:ImageCacheKey::Invalid)
345 }
346}
347
348/// A resource is a reference to binary data, for example images. They can be accessible on the file
349/// system or embedded in the resulting binary. Or they might be URLs to a web server and a downloaded
350/// is necessary before they can be used.
351/// cbindgen:prefix-with-name
352#[derive(Clone, Debug, Default)]
353#[repr(u8)]
354#[allow(missing_docs)]
355pub enum ImageInner {
356 /// A resource that does not represent any data.
357 #[default]
358 None = 0,
359 EmbeddedImage {
360 cache_key: ImageCacheKey,
361 buffer: SharedImageBuffer,
362 } = 1,
363 #[cfg(feature = "svg")]
364 Svg(vtable::VRc<OpaqueImageVTable, svg::ParsedSVG>) = 2,
365 StaticTextures(&'static StaticTextures) = 3,
366 #[cfg(target_arch = "wasm32")]
367 HTMLImage(vtable::VRc<OpaqueImageVTable, htmlimage::HTMLImage>) = 4,
368 BackendStorage(vtable::VRc<OpaqueImageVTable>) = 5,
369 #[cfg(not(target_arch = "wasm32"))]
370 BorrowedOpenGLTexture(BorrowedOpenGLTexture) = 6,
371 NineSlice(vtable::VRc<OpaqueImageVTable, NineSliceImage>) = 7,
372}
373
374impl ImageInner {
375 /// Return or render the image into a buffer
376 ///
377 /// `target_size_for_scalable_source` is the size to use if the image is scalable.
378 /// (when unspecified, will default to the intrinsic size of the image)
379 ///
380 /// Returns None if the image can't be rendered in a buffer or if the image is empty
381 pub fn render_to_buffer(
382 &self,
383 _target_size_for_scalable_source: Option<euclid::Size2D<u32, PhysicalPx>>,
384 ) -> Option<SharedImageBuffer> {
385 match self {
386 ImageInner::EmbeddedImage { buffer, .. } => Some(buffer.clone()),
387 #[cfg(feature = "svg")]
388 ImageInner::Svg(svg) => match svg.render(_target_size_for_scalable_source) {
389 Ok(b) => Some(b),
390 // Ignore error when rendering a 0x0 image, that's just an empty image
391 Err(resvg::usvg::Error::InvalidSize) => None,
392 Err(err) => {
393 eprintln!("Error rendering SVG: {err}");
394 None
395 }
396 },
397 ImageInner::StaticTextures(ts) => {
398 let mut buffer =
399 SharedPixelBuffer::<Rgba8Pixel>::new(ts.size.width, ts.size.height);
400 let stride = buffer.width() as usize;
401 let slice = buffer.make_mut_slice();
402 for t in ts.textures.iter() {
403 let rect = t.rect.to_usize();
404 for y in 0..rect.height() {
405 let slice = &mut slice[(rect.min_y() + y) * stride..][rect.x_range()];
406 let source = &ts.data[t.index + y * rect.width() * t.format.bpp()..];
407 match t.format {
408 PixelFormat::Rgb => {
409 let mut iter = source.chunks_exact(3).map(|p| Rgba8Pixel {
410 r: p[0],
411 g: p[1],
412 b: p[2],
413 a: 255,
414 });
415 slice.fill_with(|| iter.next().unwrap());
416 }
417 PixelFormat::RgbaPremultiplied => {
418 let mut iter = source.chunks_exact(4).map(|p| Rgba8Pixel {
419 r: p[0],
420 g: p[1],
421 b: p[2],
422 a: p[3],
423 });
424 slice.fill_with(|| iter.next().unwrap());
425 }
426 PixelFormat::Rgba => {
427 let mut iter = source.chunks_exact(4).map(|p| {
428 let a = p[3];
429 Rgba8Pixel {
430 r: (p[0] as u16 * a as u16 / 255) as u8,
431 g: (p[1] as u16 * a as u16 / 255) as u8,
432 b: (p[2] as u16 * a as u16 / 255) as u8,
433 a,
434 }
435 });
436 slice.fill_with(|| iter.next().unwrap());
437 }
438 PixelFormat::AlphaMap => {
439 let col = t.color.to_argb_u8();
440 let mut iter = source.iter().map(|p| {
441 let a = *p as u32 * col.alpha as u32;
442 Rgba8Pixel {
443 r: (col.red as u32 * a / (255 * 255)) as u8,
444 g: (col.green as u32 * a / (255 * 255)) as u8,
445 b: (col.blue as u32 * a / (255 * 255)) as u8,
446 a: (a / 255) as u8,
447 }
448 });
449 slice.fill_with(|| iter.next().unwrap());
450 }
451 };
452 }
453 }
454 Some(SharedImageBuffer::RGBA8Premultiplied(buffer))
455 }
456 ImageInner::NineSlice(nine) => nine.0.render_to_buffer(None),
457 _ => None,
458 }
459 }
460
461 /// Returns true if the image is an SVG (either backed by resvg or HTML image wrapper).
462 pub fn is_svg(&self) -> bool {
463 match self {
464 #[cfg(feature = "svg")]
465 Self::Svg(_) => true,
466 #[cfg(target_arch = "wasm32")]
467 Self::HTMLImage(html_image) => html_image.is_svg(),
468 _ => false,
469 }
470 }
471
472 /// Return the image size
473 pub fn size(&self) -> IntSize {
474 match self {
475 ImageInner::None => Default::default(),
476 ImageInner::EmbeddedImage { buffer, .. } => buffer.size(),
477 ImageInner::StaticTextures(StaticTextures { original_size, .. }) => *original_size,
478 #[cfg(feature = "svg")]
479 ImageInner::Svg(svg) => svg.size(),
480 #[cfg(target_arch = "wasm32")]
481 ImageInner::HTMLImage(htmlimage) => htmlimage.size().unwrap_or_default(),
482 ImageInner::BackendStorage(x) => vtable::VRc::borrow(x).size(),
483 #[cfg(not(target_arch = "wasm32"))]
484 ImageInner::BorrowedOpenGLTexture(BorrowedOpenGLTexture { size, .. }) => *size,
485 ImageInner::NineSlice(nine) => nine.0.size(),
486 }
487 }
488}
489
490impl PartialEq for ImageInner {
491 fn eq(&self, other: &Self) -> bool {
492 match (self, other) {
493 (
494 Self::EmbeddedImage { cache_key: l_cache_key: &ImageCacheKey, buffer: l_buffer: &SharedImageBuffer },
495 Self::EmbeddedImage { cache_key: r_cache_key: &ImageCacheKey, buffer: r_buffer: &SharedImageBuffer },
496 ) => l_cache_key == r_cache_key && l_buffer == r_buffer,
497 #[cfg(feature = "svg")]
498 (Self::Svg(l0: &VRc), Self::Svg(r0: &VRc)) => vtable::VRc::ptr_eq(this:l0, other:r0),
499 (Self::StaticTextures(l0: &&StaticTextures), Self::StaticTextures(r0: &&StaticTextures)) => l0 == r0,
500 #[cfg(target_arch = "wasm32")]
501 (Self::HTMLImage(l0), Self::HTMLImage(r0)) => vtable::VRc::ptr_eq(l0, r0),
502 (Self::BackendStorage(l0: &VRc), Self::BackendStorage(r0: &VRc)) => vtable::VRc::ptr_eq(this:l0, other:r0),
503 #[cfg(not(target_arch = "wasm32"))]
504 (Self::BorrowedOpenGLTexture(l0: &BorrowedOpenGLTexture), Self::BorrowedOpenGLTexture(r0: &BorrowedOpenGLTexture)) => l0 == r0,
505 (Self::NineSlice(l: &VRc), Self::NineSlice(r: &VRc)) => l.0 == r.0 && l.1 == r.1,
506 _ => false,
507 }
508 }
509}
510
511impl<'a> From<&'a Image> for &'a ImageInner {
512 fn from(other: &'a Image) -> Self {
513 &other.0
514 }
515}
516
517/// Error generated if an image cannot be loaded for any reasons.
518#[derive(Default, Debug, PartialEq)]
519pub struct LoadImageError(());
520
521impl core::fmt::Display for LoadImageError {
522 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
523 f.write_str(data:"The image cannot be loaded")
524 }
525}
526
527#[cfg(feature = "std")]
528impl std::error::Error for LoadImageError {}
529
530/// An image type that can be displayed by the Image element. You can construct
531/// Image objects from a path to an image file on disk, using [`Self::load_from_path`].
532///
533/// Another typical use-case is to render the image content with Rust code.
534/// For this it's most efficient to create a new SharedPixelBuffer with the known dimensions
535/// and pass the mutable slice to your rendering function. Afterwards you can create an
536/// Image.
537///
538/// The following example creates a 320x200 RGB pixel buffer and calls an external
539/// low_level_render() function to draw a shape into it. Finally the result is
540/// stored in an Image with [`Self::from_rgb8()`]:
541/// ```
542/// # use i_slint_core::graphics::{SharedPixelBuffer, Image, Rgb8Pixel};
543///
544/// fn low_level_render(width: u32, height: u32, buffer: &mut [u8]) {
545/// // render beautiful circle or other shapes here
546/// }
547///
548/// let mut pixel_buffer = SharedPixelBuffer::<Rgb8Pixel>::new(320, 200);
549///
550/// low_level_render(pixel_buffer.width(), pixel_buffer.height(),
551/// pixel_buffer.make_mut_bytes());
552///
553/// let image = Image::from_rgb8(pixel_buffer);
554/// ```
555///
556/// Another use-case is to import existing image data into Slint, by
557/// creating a new Image through cloning of another image type.
558///
559/// The following example uses the popular [image crate](https://docs.rs/image/) to
560/// load a `.png` file from disk, apply brightening filter on it and then import
561/// it into an [`Image`]:
562/// ```no_run
563/// # use i_slint_core::graphics::{SharedPixelBuffer, Image, Rgba8Pixel};
564/// let mut cat_image = image::open("cat.png").expect("Error loading cat image").into_rgba8();
565///
566/// image::imageops::colorops::brighten_in_place(&mut cat_image, 20);
567///
568/// let buffer = SharedPixelBuffer::<Rgba8Pixel>::clone_from_slice(
569/// cat_image.as_raw(),
570/// cat_image.width(),
571/// cat_image.height(),
572/// );
573/// let image = Image::from_rgba8(buffer);
574/// ```
575///
576/// A popular software (CPU) rendering library in Rust is tiny-skia. The following example shows
577/// how to use tiny-skia to render into a [`SharedPixelBuffer`]:
578/// ```
579/// # use i_slint_core::graphics::{SharedPixelBuffer, Image, Rgba8Pixel};
580/// let mut pixel_buffer = SharedPixelBuffer::<Rgba8Pixel>::new(640, 480);
581/// let width = pixel_buffer.width();
582/// let height = pixel_buffer.height();
583/// let mut pixmap = tiny_skia::PixmapMut::from_bytes(
584/// pixel_buffer.make_mut_bytes(), width, height
585/// ).unwrap();
586/// pixmap.fill(tiny_skia::Color::TRANSPARENT);
587///
588/// let circle = tiny_skia::PathBuilder::from_circle(320., 240., 150.).unwrap();
589///
590/// let mut paint = tiny_skia::Paint::default();
591/// paint.shader = tiny_skia::LinearGradient::new(
592/// tiny_skia::Point::from_xy(100.0, 100.0),
593/// tiny_skia::Point::from_xy(400.0, 400.0),
594/// vec![
595/// tiny_skia::GradientStop::new(0.0, tiny_skia::Color::from_rgba8(50, 127, 150, 200)),
596/// tiny_skia::GradientStop::new(1.0, tiny_skia::Color::from_rgba8(220, 140, 75, 180)),
597/// ],
598/// tiny_skia::SpreadMode::Pad,
599/// tiny_skia::Transform::identity(),
600/// ).unwrap();
601///
602/// pixmap.fill_path(&circle, &paint, tiny_skia::FillRule::Winding, Default::default(), None);
603///
604/// let image = Image::from_rgba8_premultiplied(pixel_buffer);
605/// ```
606///
607/// ### Sending Image to a thread
608///
609/// `Image` is not [`Send`], because it uses internal cache that are local to the Slint thread.
610/// If you want to create image data in a thread and send that to slint, construct the
611/// [`SharedPixelBuffer`] in a thread, and send that to Slint's UI thread.
612///
613/// ```rust,no_run
614/// # use i_slint_core::graphics::{SharedPixelBuffer, Image, Rgba8Pixel};
615/// std::thread::spawn(move || {
616/// let mut pixel_buffer = SharedPixelBuffer::<Rgba8Pixel>::new(640, 480);
617/// // ... fill the pixel_buffer with data as shown in the previous example ...
618/// slint::invoke_from_event_loop(move || {
619/// // this will run in the Slint's UI thread
620/// let image = Image::from_rgba8_premultiplied(pixel_buffer);
621/// // ... use the image, eg:
622/// // my_ui_handle.upgrade().unwrap().set_image(image);
623/// });
624/// });
625/// ```
626#[repr(transparent)]
627#[derive(Default, Clone, Debug, PartialEq, derive_more::From)]
628pub struct Image(ImageInner);
629
630impl Image {
631 #[cfg(feature = "image-decoders")]
632 /// Load an Image from a path to a file containing an image
633 pub fn load_from_path(path: &std::path::Path) -> Result<Self, LoadImageError> {
634 self::cache::IMAGE_CACHE.with(|global_cache| {
635 let path: SharedString = path.to_str().ok_or(LoadImageError(()))?.into();
636 global_cache.borrow_mut().load_image_from_path(&path).ok_or(LoadImageError(()))
637 })
638 }
639
640 /// Creates a new Image from the specified shared pixel buffer, where each pixel has three color
641 /// channels (red, green and blue) encoded as u8.
642 pub fn from_rgb8(buffer: SharedPixelBuffer<Rgb8Pixel>) -> Self {
643 Image(ImageInner::EmbeddedImage {
644 cache_key: ImageCacheKey::Invalid,
645 buffer: SharedImageBuffer::RGB8(buffer),
646 })
647 }
648
649 /// Creates a new Image from the specified shared pixel buffer, where each pixel has four color
650 /// channels (red, green, blue and alpha) encoded as u8.
651 pub fn from_rgba8(buffer: SharedPixelBuffer<Rgba8Pixel>) -> Self {
652 Image(ImageInner::EmbeddedImage {
653 cache_key: ImageCacheKey::Invalid,
654 buffer: SharedImageBuffer::RGBA8(buffer),
655 })
656 }
657
658 /// Creates a new Image from the specified shared pixel buffer, where each pixel has four color
659 /// channels (red, green, blue and alpha) encoded as u8 and, in contrast to [`Self::from_rgba8`],
660 /// the alpha channel is also assumed to be multiplied to the red, green and blue channels.
661 ///
662 /// Only construct an Image with this function if you know that your pixels are encoded this way.
663 pub fn from_rgba8_premultiplied(buffer: SharedPixelBuffer<Rgba8Pixel>) -> Self {
664 Image(ImageInner::EmbeddedImage {
665 cache_key: ImageCacheKey::Invalid,
666 buffer: SharedImageBuffer::RGBA8Premultiplied(buffer),
667 })
668 }
669
670 /// Creates a new Image from an existing OpenGL texture. The texture remains borrowed by Slint
671 /// for the duration of being used for rendering, such as when assigned as source property to
672 /// an `Image` element. It's the application's responsibility to delete the texture when it is
673 /// not used anymore.
674 ///
675 /// The texture must be bindable against the `GL_TEXTURE_2D` target, have `GL_RGBA` as format
676 /// for the pixel data.
677 ///
678 /// When Slint renders the texture, it assumes that the origin of the texture is at the top-left.
679 /// This is different from the default OpenGL coordinate system.
680 ///
681 /// # Safety
682 ///
683 /// This function is unsafe because invalid texture ids may lead to undefined behavior in OpenGL
684 /// drivers. A valid texture id is one that was created by the same OpenGL context that is
685 /// current during any of the invocations of the callback set on [`Window::set_rendering_notifier()`](crate::api::Window::set_rendering_notifier).
686 /// OpenGL contexts between instances of [`slint::Window`](crate::api::Window) are not sharing resources. Consequently
687 /// [`slint::Image`](Self) objects created from borrowed OpenGL textures cannot be shared between
688 /// different windows.
689 #[allow(unsafe_code)]
690 #[cfg(not(target_arch = "wasm32"))]
691 #[deprecated(since = "1.2.0", note = "Use BorrowedOpenGLTextureBuilder")]
692 pub unsafe fn from_borrowed_gl_2d_rgba_texture(
693 texture_id: core::num::NonZeroU32,
694 size: IntSize,
695 ) -> Self {
696 BorrowedOpenGLTextureBuilder::new_gl_2d_rgba_texture(texture_id, size).build()
697 }
698
699 /// Creates a new Image from the specified buffer, which contains SVG raw data.
700 #[cfg(feature = "svg")]
701 pub fn load_from_svg_data(buffer: &[u8]) -> Result<Self, LoadImageError> {
702 let cache_key = ImageCacheKey::Invalid;
703 Ok(Image(ImageInner::Svg(vtable::VRc::new(
704 svg::load_from_data(buffer, cache_key).map_err(|_| LoadImageError(()))?,
705 ))))
706 }
707
708 /// Sets the nine-slice edges of the image.
709 ///
710 /// [Nine-slice scaling](https://en.wikipedia.org/wiki/9-slice_scaling) is a method for scaling
711 /// images in such a way that the corners are not distorted.
712 /// The arguments define the pixel sizes of the edges that cut the image into 9 slices.
713 pub fn set_nine_slice_edges(&mut self, top: u16, right: u16, bottom: u16, left: u16) {
714 if top == 0 && left == 0 && right == 0 && bottom == 0 {
715 if let ImageInner::NineSlice(n) = &self.0 {
716 self.0 = n.0.clone();
717 }
718 } else {
719 let array = [top, right, bottom, left];
720 let inner = if let ImageInner::NineSlice(n) = &mut self.0 {
721 n.0.clone()
722 } else {
723 self.0.clone()
724 };
725 self.0 = ImageInner::NineSlice(vtable::VRc::new(NineSliceImage(inner, array)));
726 }
727 }
728
729 /// Returns the size of the Image in pixels.
730 pub fn size(&self) -> IntSize {
731 self.0.size()
732 }
733
734 #[cfg(feature = "std")]
735 /// Returns the path of the image on disk, if it was constructed via [`Self::load_from_path`].
736 ///
737 /// For example:
738 /// ```
739 /// # use std::path::Path;
740 /// # use i_slint_core::graphics::*;
741 /// let path_buf = Path::new(env!("CARGO_MANIFEST_DIR"))
742 /// .join("../../examples/printerdemo/ui/images/cat.jpg");
743 /// let image = Image::load_from_path(&path_buf).unwrap();
744 /// assert_eq!(image.path(), Some(path_buf.as_path()));
745 /// ```
746 pub fn path(&self) -> Option<&std::path::Path> {
747 match &self.0 {
748 ImageInner::EmbeddedImage { cache_key: ImageCacheKey::Path(path), .. } => {
749 Some(std::path::Path::new(path.as_str()))
750 }
751 ImageInner::NineSlice(nine) => match &nine.0 {
752 ImageInner::EmbeddedImage { cache_key: ImageCacheKey::Path(path), .. } => {
753 Some(std::path::Path::new(path.as_str()))
754 }
755 _ => None,
756 },
757 _ => None,
758 }
759 }
760}
761
762/// This enum describes the origin to use when rendering a borrowed OpenGL texture.
763/// Use this with [`BorrowedOpenGLTextureBuilder::origin`].
764#[derive(Copy, Clone, Debug, PartialEq, Default)]
765#[repr(u8)]
766#[non_exhaustive]
767pub enum BorrowedOpenGLTextureOrigin {
768 /// The top-left of the texture is the top-left of the texture drawn on the screen.
769 #[default]
770 TopLeft,
771 /// The bottom-left of the texture is the top-left of the texture draw on the screen,
772 /// flipping it vertically.
773 BottomLeft,
774}
775
776/// Factory to create [`slint::Image`](crate::graphics::Image) from an existing OpenGL texture.
777///
778/// Methods can be chained on it in order to configure it.
779///
780/// * `origin`: Change the texture's origin when rendering (default: TopLeft).
781///
782/// Complete the builder by calling [`Self::build()`] to create a [`slint::Image`](crate::graphics::Image):
783///
784/// ```
785/// # use i_slint_core::graphics::{BorrowedOpenGLTextureBuilder, Image, IntSize, BorrowedOpenGLTextureOrigin};
786/// # let texture_id = core::num::NonZeroU32::new(1).unwrap();
787/// # let size = IntSize::new(100, 100);
788/// let builder = unsafe { BorrowedOpenGLTextureBuilder::new_gl_2d_rgba_texture(texture_id, size) }
789/// .origin(BorrowedOpenGLTextureOrigin::TopLeft);
790///
791/// let image: slint::Image = builder.build();
792/// ```
793#[cfg(not(target_arch = "wasm32"))]
794pub struct BorrowedOpenGLTextureBuilder(BorrowedOpenGLTexture);
795
796#[cfg(not(target_arch = "wasm32"))]
797impl BorrowedOpenGLTextureBuilder {
798 /// Generates the base configuration for a borrowed OpenGL texture.
799 ///
800 /// The texture must be bindable against the `GL_TEXTURE_2D` target, have `GL_RGBA` as format
801 /// for the pixel data.
802 ///
803 /// By default, when Slint renders the texture, it assumes that the origin of the texture is at the top-left.
804 /// This is different from the default OpenGL coordinate system. Use the `mirror_vertically` function
805 /// to reconfigure this.
806 ///
807 /// # Safety
808 ///
809 /// This function is unsafe because invalid texture ids may lead to undefined behavior in OpenGL
810 /// drivers. A valid texture id is one that was created by the same OpenGL context that is
811 /// current during any of the invocations of the callback set on [`Window::set_rendering_notifier()`](crate::api::Window::set_rendering_notifier).
812 /// OpenGL contexts between instances of [`slint::Window`](crate::api::Window) are not sharing resources. Consequently
813 /// [`slint::Image`](Self) objects created from borrowed OpenGL textures cannot be shared between
814 /// different windows.
815 #[allow(unsafe_code)]
816 pub unsafe fn new_gl_2d_rgba_texture(texture_id: core::num::NonZeroU32, size: IntSize) -> Self {
817 Self(BorrowedOpenGLTexture { texture_id, size, origin: Default::default() })
818 }
819
820 /// Configures the texture to be rendered vertically mirrored.
821 pub fn origin(mut self, origin: BorrowedOpenGLTextureOrigin) -> Self {
822 self.0.origin = origin;
823 self
824 }
825
826 /// Completes the process of building a slint::Image that holds a borrowed OpenGL texture.
827 pub fn build(self) -> Image {
828 Image(ImageInner::BorrowedOpenGLTexture(self.0))
829 }
830}
831
832/// Load an image from an image embedded in the binary.
833/// This is called by the generated code.
834#[cfg(feature = "image-decoders")]
835pub fn load_image_from_embedded_data(data: Slice<'static, u8>, format: Slice<'_, u8>) -> Image {
836 self::cache::IMAGE_CACHE.with(|global_cache: &RefCell| {
837 global_cache.borrow_mut().load_image_from_embedded_data(data, format).unwrap_or_default()
838 })
839}
840
841#[test]
842fn test_image_size_from_buffer_without_backend() {
843 {
844 assert_eq!(Image::default().size(), Default::default());
845 }
846 {
847 let buffer: SharedPixelBuffer> = SharedPixelBuffer::<Rgb8Pixel>::new(width:320, height:200);
848 let image: Image = Image::from_rgb8(buffer);
849 assert_eq!(image.size(), [320, 200].into())
850 }
851}
852
853#[cfg(feature = "svg")]
854#[test]
855fn test_image_size_from_svg() {
856 let simple_svg: &str = r#"<svg width="320" height="200" xmlns="http://www.w3.org/2000/svg"></svg>"#;
857 let image: Image = Image::load_from_svg_data(buffer:simple_svg.as_bytes()).unwrap();
858 assert_eq!(image.size(), [320, 200].into());
859}
860
861#[cfg(feature = "svg")]
862#[test]
863fn test_image_invalid_svg() {
864 let invalid_svg: &str = r#"AaBbCcDd"#;
865 let result: Result = Image::load_from_svg_data(buffer:invalid_svg.as_bytes());
866 assert!(result.is_err());
867}
868
869/// The result of the fit function
870#[derive(Debug)]
871pub struct FitResult {
872 /// The clip rect in the source image (in source image coordinate)
873 pub clip_rect: IntRect,
874 /// The scale to apply to go from the source to the target horizontally
875 pub source_to_target_x: f32,
876 /// The scale to apply to go from the source to the target vertically
877 pub source_to_target_y: f32,
878 /// The size of the target
879 pub size: euclid::Size2D<f32, PhysicalPx>,
880 /// The offset in the target in which we draw the image
881 pub offset: euclid::Point2D<f32, PhysicalPx>,
882 /// When Some, it means the image should be tiled instead of stretched to the target
883 /// but still scaled with the source_to_target_x and source_to_target_y factor
884 /// The point is the coordinate within the image's clip_rect of the pixel at the offset
885 pub tiled: Option<euclid::default::Point2D<u32>>,
886}
887
888impl FitResult {
889 fn adjust_for_tiling(
890 self,
891 ratio: f32,
892 alignment: (ImageHorizontalAlignment, ImageVerticalAlignment),
893 tiling: (ImageTiling, ImageTiling),
894 ) -> Self {
895 let mut r = self;
896 let mut tiled = euclid::Point2D::default();
897 let target = r.size;
898 let o = r.clip_rect.size.cast::<f32>();
899 match tiling.0 {
900 ImageTiling::None => {
901 r.size.width = o.width * r.source_to_target_x;
902 if (o.width as f32) > target.width / r.source_to_target_x {
903 let diff = (o.width as f32 - target.width / r.source_to_target_x) as i32;
904 r.clip_rect.size.width -= diff;
905 r.clip_rect.origin.x += match alignment.0 {
906 ImageHorizontalAlignment::Center => diff / 2,
907 ImageHorizontalAlignment::Left => 0,
908 ImageHorizontalAlignment::Right => diff,
909 };
910 r.size.width = target.width;
911 } else if (o.width as f32) < target.width / r.source_to_target_x {
912 r.offset.x += match alignment.0 {
913 ImageHorizontalAlignment::Center => {
914 (target.width - o.width as f32 * r.source_to_target_x) / 2.
915 }
916 ImageHorizontalAlignment::Left => 0.,
917 ImageHorizontalAlignment::Right => {
918 target.width - o.width as f32 * r.source_to_target_x
919 }
920 };
921 }
922 }
923 ImageTiling::Repeat => {
924 tiled.x = match alignment.0 {
925 ImageHorizontalAlignment::Left => 0,
926 ImageHorizontalAlignment::Center => {
927 ((o.width - target.width / ratio) / 2.).rem_euclid(o.width) as u32
928 }
929 ImageHorizontalAlignment::Right => {
930 (-target.width / ratio).rem_euclid(o.width) as u32
931 }
932 };
933 r.source_to_target_x = ratio;
934 }
935 ImageTiling::Round => {
936 if target.width / ratio <= o.width * 1.5 {
937 r.source_to_target_x = target.width / o.width;
938 } else {
939 let mut rem = (target.width / ratio).rem_euclid(o.width);
940 if rem > o.width / 2. {
941 rem -= o.width;
942 }
943 r.source_to_target_x = ratio * target.width / (target.width - rem * ratio);
944 }
945 }
946 }
947
948 match tiling.1 {
949 ImageTiling::None => {
950 r.size.height = o.height * r.source_to_target_y;
951 if (o.height as f32) > target.height / r.source_to_target_y {
952 let diff = (o.height as f32 - target.height / r.source_to_target_y) as i32;
953 r.clip_rect.size.height -= diff;
954 r.clip_rect.origin.y += match alignment.1 {
955 ImageVerticalAlignment::Center => diff / 2,
956 ImageVerticalAlignment::Top => 0,
957 ImageVerticalAlignment::Bottom => diff,
958 };
959 r.size.height = target.height;
960 } else if (o.height as f32) < target.height / r.source_to_target_y {
961 r.offset.y += match alignment.1 {
962 ImageVerticalAlignment::Center => {
963 (target.height - o.height as f32 * r.source_to_target_y) / 2.
964 }
965 ImageVerticalAlignment::Top => 0.,
966 ImageVerticalAlignment::Bottom => {
967 target.height - o.height as f32 * r.source_to_target_y
968 }
969 };
970 }
971 }
972 ImageTiling::Repeat => {
973 tiled.y = match alignment.1 {
974 ImageVerticalAlignment::Top => 0,
975 ImageVerticalAlignment::Center => {
976 ((o.height - target.height / ratio) / 2.).rem_euclid(o.height) as u32
977 }
978 ImageVerticalAlignment::Bottom => {
979 (-target.height / ratio).rem_euclid(o.height) as u32
980 }
981 };
982 r.source_to_target_y = ratio;
983 }
984 ImageTiling::Round => {
985 if target.height / ratio <= o.height * 1.5 {
986 r.source_to_target_y = target.height / o.height;
987 } else {
988 let mut rem = (target.height / ratio).rem_euclid(o.height);
989 if rem > o.height / 2. {
990 rem -= o.height;
991 }
992 r.source_to_target_y = ratio * target.height / (target.height - rem * ratio);
993 }
994 }
995 }
996 let has_tiling = tiling != (ImageTiling::None, ImageTiling::None);
997 r.tiled = has_tiling.then_some(tiled);
998 r
999 }
1000}
1001
1002#[cfg(not(feature = "std"))]
1003trait RemEuclid {
1004 fn rem_euclid(self, b: f32) -> f32;
1005}
1006#[cfg(not(feature = "std"))]
1007impl RemEuclid for f32 {
1008 fn rem_euclid(self, b: f32) -> f32 {
1009 return num_traits::Euclid::rem_euclid(&self, &b);
1010 }
1011}
1012
1013/// Return an FitResult that can be used to render an image in a buffer that matches a given ImageFit
1014pub fn fit(
1015 image_fit: ImageFit,
1016 target: euclid::Size2D<f32, PhysicalPx>,
1017 source_rect: IntRect,
1018 scale_factor: ScaleFactor,
1019 alignment: (ImageHorizontalAlignment, ImageVerticalAlignment),
1020 tiling: (ImageTiling, ImageTiling),
1021) -> FitResult {
1022 let has_tiling = tiling != (ImageTiling::None, ImageTiling::None);
1023 let o = source_rect.size.cast::<f32>();
1024 let ratio = match image_fit {
1025 // If there is any tiling, we ignore image_fit
1026 _ if has_tiling => scale_factor.get(),
1027 ImageFit::Fill => {
1028 return FitResult {
1029 clip_rect: source_rect,
1030 source_to_target_x: target.width / o.width,
1031 source_to_target_y: target.height / o.height,
1032 size: target,
1033 offset: Default::default(),
1034 tiled: None,
1035 }
1036 }
1037 ImageFit::Preserve => scale_factor.get(),
1038 ImageFit::Contain => f32::min(target.width / o.width, target.height / o.height),
1039 ImageFit::Cover => f32::max(target.width / o.width, target.height / o.height),
1040 };
1041
1042 FitResult {
1043 clip_rect: source_rect,
1044 source_to_target_x: ratio,
1045 source_to_target_y: ratio,
1046 size: target,
1047 offset: euclid::Point2D::default(),
1048 tiled: None,
1049 }
1050 .adjust_for_tiling(ratio, alignment, tiling)
1051}
1052
1053/// Generate an iterator of [`FitResult`] for each slice of a nine-slice border image
1054pub fn fit9slice(
1055 source_rect: IntSize,
1056 [t: u16, r: u16, b: u16, l: u16]: [u16; 4],
1057 target: euclid::Size2D<f32, PhysicalPx>,
1058 scale_factor: ScaleFactor,
1059 alignment: (ImageHorizontalAlignment, ImageVerticalAlignment),
1060 tiling: (ImageTiling, ImageTiling),
1061) -> impl Iterator<Item = FitResult> {
1062 let fit_to = |clip_rect: euclid::default::Rect<u16>, target: euclid::Rect<f32, PhysicalPx>| {
1063 (!clip_rect.is_empty() && !target.is_empty()).then(|| {
1064 FitResult {
1065 clip_rect: clip_rect.cast(),
1066 source_to_target_x: target.width() / clip_rect.width() as f32,
1067 source_to_target_y: target.height() / clip_rect.height() as f32,
1068 size: target.size,
1069 offset: target.origin,
1070 tiled: None,
1071 }
1072 .adjust_for_tiling(scale_factor.get(), alignment, tiling)
1073 })
1074 };
1075 use euclid::rect;
1076 let sf = |x| scale_factor.get() * x as f32;
1077 let source = source_rect.cast::<u16>();
1078 if t + b > source.height || l + r > source.width {
1079 [None, None, None, None, None, None, None, None, None]
1080 } else {
1081 [
1082 fit_to(rect(0, 0, l, t), rect(0., 0., sf(l), sf(t))),
1083 fit_to(
1084 rect(l, 0, source.width - l - r, t),
1085 rect(sf(l), 0., target.width - sf(l) - sf(r), sf(t)),
1086 ),
1087 fit_to(rect(source.width - r, 0, r, t), rect(target.width - sf(r), 0., sf(r), sf(t))),
1088 fit_to(
1089 rect(0, t, l, source.height - t - b),
1090 rect(0., sf(t), sf(l), target.height - sf(t) - sf(b)),
1091 ),
1092 fit_to(
1093 rect(l, t, source.width - l - r, source.height - t - b),
1094 rect(sf(l), sf(t), target.width - sf(l) - sf(r), target.height - sf(t) - sf(b)),
1095 ),
1096 fit_to(
1097 rect(source.width - r, t, r, source.height - t - b),
1098 rect(target.width - sf(r), sf(t), sf(r), target.height - sf(t) - sf(b)),
1099 ),
1100 fit_to(rect(0, source.height - b, l, b), rect(0., target.height - sf(b), sf(l), sf(b))),
1101 fit_to(
1102 rect(l, source.height - b, source.width - l - r, b),
1103 rect(sf(l), target.height - sf(b), target.width - sf(l) - sf(r), sf(b)),
1104 ),
1105 fit_to(
1106 rect(source.width - r, source.height - b, r, b),
1107 rect(target.width - sf(r), target.height - sf(b), sf(r), sf(b)),
1108 ),
1109 ]
1110 }
1111 .into_iter()
1112 .flatten()
1113}
1114
1115#[cfg(feature = "ffi")]
1116pub(crate) mod ffi {
1117 #![allow(unsafe_code)]
1118
1119 use super::*;
1120
1121 // Expand Rgb8Pixel so that cbindgen can see it. (is in fact rgb::RGB<u8>)
1122 /// Represents an RGB pixel.
1123 #[cfg(cbindgen)]
1124 #[repr(C)]
1125 struct Rgb8Pixel {
1126 /// red value (between 0 and 255)
1127 r: u8,
1128 /// green value (between 0 and 255)
1129 g: u8,
1130 /// blue value (between 0 and 255)
1131 b: u8,
1132 }
1133
1134 // Expand Rgba8Pixel so that cbindgen can see it. (is in fact rgb::RGBA<u8>)
1135 /// Represents an RGBA pixel.
1136 #[cfg(cbindgen)]
1137 #[repr(C)]
1138 struct Rgba8Pixel {
1139 /// red value (between 0 and 255)
1140 r: u8,
1141 /// green value (between 0 and 255)
1142 g: u8,
1143 /// blue value (between 0 and 255)
1144 b: u8,
1145 /// alpha value (between 0 and 255)
1146 a: u8,
1147 }
1148
1149 #[cfg(feature = "image-decoders")]
1150 #[no_mangle]
1151 pub unsafe extern "C" fn slint_image_load_from_path(path: &SharedString, image: *mut Image) {
1152 core::ptr::write(
1153 image,
1154 Image::load_from_path(std::path::Path::new(path.as_str())).unwrap_or(Image::default()),
1155 )
1156 }
1157
1158 #[cfg(feature = "std")]
1159 #[no_mangle]
1160 pub unsafe extern "C" fn slint_image_load_from_embedded_data(
1161 data: Slice<'static, u8>,
1162 format: Slice<'static, u8>,
1163 image: *mut Image,
1164 ) {
1165 core::ptr::write(image, super::load_image_from_embedded_data(data, format));
1166 }
1167
1168 #[no_mangle]
1169 pub unsafe extern "C" fn slint_image_size(image: &Image) -> IntSize {
1170 image.size()
1171 }
1172
1173 #[no_mangle]
1174 pub extern "C" fn slint_image_path(image: &Image) -> Option<&SharedString> {
1175 match &image.0 {
1176 ImageInner::EmbeddedImage { cache_key, .. } => match cache_key {
1177 ImageCacheKey::Path(path) => Some(path),
1178 _ => None,
1179 },
1180 ImageInner::NineSlice(nine) => match &nine.0 {
1181 ImageInner::EmbeddedImage { cache_key, .. } => match cache_key {
1182 ImageCacheKey::Path(path) => Some(path),
1183 _ => None,
1184 },
1185 _ => None,
1186 },
1187 _ => None,
1188 }
1189 }
1190
1191 #[no_mangle]
1192 pub unsafe extern "C" fn slint_image_from_embedded_textures(
1193 textures: &'static StaticTextures,
1194 image: *mut Image,
1195 ) {
1196 core::ptr::write(image, Image::from(ImageInner::StaticTextures(textures)));
1197 }
1198
1199 #[no_mangle]
1200 pub unsafe extern "C" fn slint_image_compare_equal(image1: &Image, image2: &Image) -> bool {
1201 return image1.eq(image2);
1202 }
1203
1204 /// Call [`Image::set_nine_slice_edges`]
1205 #[no_mangle]
1206 pub extern "C" fn slint_image_set_nine_slice_edges(
1207 image: &mut Image,
1208 top: u16,
1209 right: u16,
1210 bottom: u16,
1211 left: u16,
1212 ) {
1213 image.set_nine_slice_edges(top, right, bottom, left);
1214 }
1215}
1216
1217/// This structure contains fields to identify and render an OpenGL texture that Slint borrows from the application code.
1218/// Use this to embed a native OpenGL texture into a Slint scene.
1219///
1220/// The ownership of the texture remains with the application. It is the application's responsibility to delete the texture
1221/// when it is not used anymore.
1222///
1223/// Note that only 2D RGBA textures are supported.
1224#[derive(Clone, Debug, PartialEq)]
1225#[non_exhaustive]
1226#[cfg(not(target_arch = "wasm32"))]
1227#[repr(C)]
1228pub struct BorrowedOpenGLTexture {
1229 /// The id or name of the texture, as created by [`glGenTextures`](https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGenTextures.xhtml).
1230 pub texture_id: core::num::NonZeroU32,
1231 /// The size of the texture in pixels.
1232 pub size: IntSize,
1233 /// Origin of the texture when rendering.
1234 pub origin: BorrowedOpenGLTextureOrigin,
1235}
1236