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
4use crate::PhysicalSize;
5#[cfg(skia_backend_opengl)]
6use i_slint_core::graphics::BorrowedOpenGLTexture;
7use i_slint_core::graphics::{
8 cache as core_cache, Image, ImageCacheKey, ImageInner, IntRect, IntSize, OpaqueImage,
9 OpaqueImageVTable, SharedImageBuffer,
10};
11use i_slint_core::items::ImageFit;
12use i_slint_core::lengths::{LogicalSize, ScaleFactor};
13
14struct SkiaCachedImage {
15 image: skia_safe::Image,
16 cache_key: ImageCacheKey,
17}
18
19i_slint_core::OpaqueImageVTable_static! {
20 static SKIA_CACHED_IMAGE_VT for SkiaCachedImage
21}
22
23impl OpaqueImage for SkiaCachedImage {
24 fn size(&self) -> IntSize {
25 IntSize::new(self.image.width() as u32, self.image.height() as u32)
26 }
27
28 fn cache_key(&self) -> ImageCacheKey {
29 self.cache_key.clone()
30 }
31}
32
33pub(crate) fn as_skia_image(
34 image: Image,
35 target_size_fn: &dyn Fn() -> LogicalSize,
36 image_fit: ImageFit,
37 scale_factor: ScaleFactor,
38 canvas: &skia_safe::Canvas,
39) -> Option<skia_safe::Image> {
40 let image_inner: &ImageInner = (&image).into();
41 match image_inner {
42 ImageInner::None => None,
43 ImageInner::EmbeddedImage { buffer, cache_key } => {
44 let result = image_buffer_to_skia_image(buffer);
45 if let Some(img) = result.as_ref() {
46 core_cache::replace_cached_image(
47 cache_key.clone(),
48 ImageInner::BackendStorage(vtable::VRc::into_dyn(vtable::VRc::new(
49 SkiaCachedImage { image: img.clone(), cache_key: cache_key.clone() },
50 ))),
51 )
52 }
53 result
54 }
55 ImageInner::Svg(svg) => {
56 // Query target_width/height here again to ensure that changes will invalidate the item rendering cache.
57 let svg_size = svg.size();
58 let fit = i_slint_core::graphics::fit(
59 image_fit,
60 target_size_fn() * scale_factor,
61 IntRect::from_size(svg_size.cast()),
62 scale_factor,
63 Default::default(), // We only care about the size, so alignments don't matter
64 Default::default(),
65 );
66 let target_size = PhysicalSize::new(
67 svg_size.cast::<f32>().width * fit.source_to_target_x,
68 svg_size.cast::<f32>().height * fit.source_to_target_y,
69 );
70 let pixels = match svg.render(Some(target_size.cast())).ok()? {
71 SharedImageBuffer::RGB8(_) => unreachable!(),
72 SharedImageBuffer::RGBA8(_) => unreachable!(),
73 SharedImageBuffer::RGBA8Premultiplied(pixels) => pixels,
74 };
75
76 let image_info = skia_safe::ImageInfo::new(
77 skia_safe::ISize::new(pixels.width() as i32, pixels.height() as i32),
78 skia_safe::ColorType::RGBA8888,
79 skia_safe::AlphaType::Premul,
80 None,
81 );
82
83 skia_safe::images::raster_from_data(
84 &image_info,
85 skia_safe::Data::new_copy(pixels.as_bytes()),
86 pixels.width() as usize * 4,
87 )
88 }
89 ImageInner::StaticTextures(_) => todo!(),
90 ImageInner::BackendStorage(x) => {
91 vtable::VRc::borrow(x).downcast::<SkiaCachedImage>().map(|x| x.image.clone())
92 }
93 #[cfg(skia_backend_opengl)]
94 ImageInner::BorrowedOpenGLTexture(BorrowedOpenGLTexture {
95 texture_id,
96 size,
97 origin,
98 ..
99 }) => unsafe {
100 let mut texture_info = skia_safe::gpu::gl::TextureInfo::from_target_and_id(
101 glow::TEXTURE_2D,
102 texture_id.get(),
103 );
104 texture_info.format = glow::RGBA8;
105 let backend_texture = skia_safe::gpu::backend_textures::make_gl(
106 (size.width as _, size.height as _),
107 skia_safe::gpu::Mipmapped::No,
108 texture_info,
109 "Borrowed GL texture",
110 );
111 skia_safe::image::Image::from_texture(
112 canvas.recording_context().as_mut().unwrap(),
113 &backend_texture,
114 match origin {
115 i_slint_core::graphics::BorrowedOpenGLTextureOrigin::TopLeft => {
116 skia_safe::gpu::SurfaceOrigin::TopLeft
117 }
118 i_slint_core::graphics::BorrowedOpenGLTextureOrigin::BottomLeft => {
119 skia_safe::gpu::SurfaceOrigin::BottomLeft
120 }
121 _ => unimplemented!(
122 "internal error: missing implementation for BorrowedOpenGLTextureOrigin"
123 ),
124 },
125 skia_safe::ColorType::RGBA8888,
126 skia_safe::AlphaType::Unpremul,
127 None,
128 )
129 },
130 #[cfg(not(skia_backend_opengl))]
131 ImageInner::BorrowedOpenGLTexture(..) => None,
132 ImageInner::NineSlice(n) => {
133 as_skia_image(n.image(), target_size_fn, ImageFit::Preserve, scale_factor, canvas)
134 }
135 }
136}
137
138fn image_buffer_to_skia_image(buffer: &SharedImageBuffer) -> Option<skia_safe::Image> {
139 let (data, bpl, size, color_type, alpha_type) = match buffer {
140 SharedImageBuffer::RGB8(pixels) => {
141 // RGB888 with one byte per component is not supported by Skia right now. Convert once to RGBA8 :-(
142 let rgba = pixels
143 .as_bytes()
144 .chunks(3)
145 .flat_map(|rgb| IntoIterator::into_iter([rgb[0], rgb[1], rgb[2], 255]))
146 .collect::<Vec<u8>>();
147 (
148 skia_safe::Data::new_copy(&*rgba),
149 pixels.width() as usize * 4,
150 pixels.size(),
151 skia_safe::ColorType::RGBA8888,
152 skia_safe::AlphaType::Unpremul,
153 )
154 }
155 SharedImageBuffer::RGBA8(pixels) => (
156 skia_safe::Data::new_copy(pixels.as_bytes()),
157 pixels.width() as usize * 4,
158 pixels.size(),
159 skia_safe::ColorType::RGBA8888,
160 skia_safe::AlphaType::Unpremul,
161 ),
162 SharedImageBuffer::RGBA8Premultiplied(pixels) => (
163 skia_safe::Data::new_copy(pixels.as_bytes()),
164 pixels.width() as usize * 4,
165 pixels.size(),
166 skia_safe::ColorType::RGBA8888,
167 skia_safe::AlphaType::Premul,
168 ),
169 };
170 let image_info = skia_safe::ImageInfo::new(
171 skia_safe::ISize::new(size.width as i32, size.height as i32),
172 color_type,
173 alpha_type,
174 None,
175 );
176 skia_safe::images::raster_from_data(&image_info, data, bpl)
177}
178