| 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 | use crate::PhysicalSize; |
| 5 | #[cfg (skia_backend_opengl)] |
| 6 | use i_slint_core::graphics::BorrowedOpenGLTexture; |
| 7 | use i_slint_core::graphics::{ |
| 8 | cache as core_cache, Image, ImageCacheKey, ImageInner, IntRect, IntSize, OpaqueImage, |
| 9 | OpaqueImageVTable, SharedImageBuffer, |
| 10 | }; |
| 11 | use i_slint_core::items::ImageFit; |
| 12 | use i_slint_core::lengths::{LogicalSize, ScaleFactor}; |
| 13 | |
| 14 | struct SkiaCachedImage { |
| 15 | image: skia_safe::Image, |
| 16 | cache_key: ImageCacheKey, |
| 17 | } |
| 18 | |
| 19 | i_slint_core::OpaqueImageVTable_static! { |
| 20 | static SKIA_CACHED_IMAGE_VT for SkiaCachedImage |
| 21 | } |
| 22 | |
| 23 | impl 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 | |
| 33 | pub(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 | |
| 138 | fn 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 | |