i_slint_core/graphics/
image.rs

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