Skip to main content

ferroui_skia/
immutable_bitmap.rs

1use crate::gpu::{drawable_image, needs_mipmaps, ISkiaGrContext};
2use crate::helpers::image_saving_helper;
3use crate::i_drawable_bitmap_impl::IDrawableBitmapImpl;
4use crate::locked_framebuffer::LockedFramebuffer;
5use crate::skia_sharp_extensions::{
6    to_alpha_format, to_pixel_format_opt, to_sk_alpha_type, to_sk_color_type_or_panic, to_sk_sampling_options_scaled,
7};
8use ferroui_base::media::imaging::{BitmapEncoderOptions, BitmapInterpolationMode};
9use ferroui_base::platform::{AlphaFormat, IBitmapImpl, ILockedFramebuffer, IReadableBitmapImpl, PixelFormat};
10use ferroui_base::{PixelSize, Vector};
11use skia_safe::canvas::SrcRectConstraint;
12use skia_safe::{
13    AlphaType, Bitmap, Canvas, Codec, ColorType, Data, Image, ImageInfo, Paint, Rect, SamplingOptions,
14};
15use std::any::Any;
16use std::io::{self, Read, Write};
17use std::rc::Rc;
18use std::sync::Mutex;
19
20pub(crate) fn load_error() -> io::Error {
21    io::Error::new(io::ErrorKind::InvalidData, "Unable to load bitmap from provided data")
22}
23
24fn read_stream(stream: &mut dyn Read) -> io::Result<Data> {
25    let mut bytes = Vec::new();
26    stream.read_to_end(&mut bytes)?;
27    Ok(Data::new_copy(&bytes))
28}
29
30/// Allocates a bitmap for `info` and decodes the codec's image into it.
31fn decode_with_info(codec: &mut Codec, info: &ImageInfo) -> Option<Bitmap> {
32    let mut bitmap = Bitmap::new();
33    if !bitmap.try_alloc_pixels_info(info, None) {
34        return None;
35    }
36
37    let row_bytes = bitmap.row_bytes();
38    let mut pixmap = bitmap.peek_pixels()?;
39    let pixels = pixmap.bytes_mut()?;
40
41    match codec.get_pixels_with_options(info, pixels, row_bytes, None) {
42        skia_safe::codec::Result::Success | skia_safe::codec::Result::IncompleteInput => Some(bitmap),
43        _ => None,
44    }
45}
46
47/// Decodes an encoded image at its natural size.
48pub(crate) fn decode_bitmap(stream: &mut dyn Read) -> io::Result<Bitmap> {
49    let data = read_stream(stream)?;
50    let mut codec = Codec::from_data(data).ok_or_else(load_error)?;
51
52    let mut info = codec.info();
53    if info.alpha_type() == AlphaType::Unpremul {
54        info = info.with_alpha_type(AlphaType::Premul);
55    }
56    let info = info.with_color_space(None);
57
58    decode_with_info(&mut codec, &info).ok_or_else(load_error)
59}
60
61/// Decodes an encoded image so that its width (`horizontal`) or height is
62/// `decode_size`, keeping the aspect ratio.
63pub(crate) fn decode_bitmap_to_size(
64    stream: &mut dyn Read,
65    decode_size: i32,
66    horizontal: bool,
67    interpolation_mode: BitmapInterpolationMode,
68) -> io::Result<Bitmap> {
69    let data = read_stream(stream)?;
70    let mut codec = Codec::from_data(data).ok_or_else(load_error)?;
71    let info = codec.info();
72
73    // Get the scale that is nearest to what we want (e.g. a jpg returns 512).
74    let supported_scale = codec.get_scaled_dimensions(if horizontal {
75        decode_size as f32 / info.width() as f32
76    } else {
77        decode_size as f32 / info.height() as f32
78    });
79
80    // Decode the bitmap at the nearest size.
81    let nearest = ImageInfo::new_n32_premul((supported_scale.width, supported_scale.height), None);
82    let bitmap = decode_with_info(&mut codec, &nearest).ok_or_else(load_error)?;
83
84    // Now scale that to the size that we want.
85    let real_scale = if horizontal {
86        info.height() as f64 / info.width() as f64
87    } else {
88        info.width() as f64 / info.height() as f64
89    };
90
91    let desired = if horizontal {
92        ImageInfo::new_n32_premul((decode_size, (real_scale * decode_size as f64) as i32), None)
93    } else {
94        ImageInfo::new_n32_premul(((real_scale * decode_size as f64) as i32, decode_size), None)
95    };
96
97    if bitmap.width() != desired.width() || bitmap.height() != desired.height() {
98        let is_upscaling = desired.width() > bitmap.width() || desired.height() > bitmap.height();
99
100        let mut scaled = Bitmap::new();
101        if !scaled.try_alloc_pixels_info(&desired, None) {
102            return Err(load_error());
103        }
104
105        let mut dst = scaled.peek_pixels().ok_or_else(load_error)?;
106        let src = bitmap.peek_pixels().ok_or_else(load_error)?;
107        if !src.scale_pixels(&mut dst, to_sk_sampling_options_scaled(interpolation_mode, is_upscaling)) {
108            return Err(load_error());
109        }
110
111        return Ok(scaled);
112    }
113
114    Ok(bitmap)
115}
116
117/// A Skia bitmap held by an object that is shared between threads.
118pub(crate) struct SendBitmap(pub Bitmap);
119
120// SAFETY: a Skia bitmap owns its pixels through an atomically counted
121// reference and has no affinity to the thread that created it; its holders
122// keep it under a lock, so it is used by one thread at a time.
123unsafe impl Send for SendBitmap {}
124
125impl std::ops::Deref for SendBitmap {
126    type Target = Bitmap;
127
128    fn deref(&self) -> &Bitmap {
129        &self.0
130    }
131}
132
133/// Immutable Skia bitmap.
134pub struct ImmutableBitmap {
135    image: Mutex<Option<Image>>,
136    bitmap: Mutex<Option<SendBitmap>>,
137    custom_image_dispose: Mutex<Option<Box<dyn FnOnce(Image) + Send>>>,
138    dpi: Vector,
139    pixel_size: PixelSize,
140}
141
142impl ImmutableBitmap {
143    fn from_bitmap(mut bitmap: Bitmap, pixel_size: Option<PixelSize>, dpi: Vector) -> io::Result<Self> {
144        bitmap.set_immutable();
145        let image = skia_safe::images::raster_from_bitmap(&bitmap).ok_or_else(load_error)?;
146        let pixel_size = pixel_size.unwrap_or_else(|| PixelSize::new(image.width(), image.height()));
147
148        Ok(Self {
149            image: Mutex::new(Some(image)),
150            bitmap: Mutex::new(Some(SendBitmap(bitmap))),
151            custom_image_dispose: Mutex::new(None),
152            dpi,
153            pixel_size,
154        })
155    }
156
157    /// Creates an immutable bitmap from the given stream.
158    pub fn from_stream(stream: &mut dyn Read) -> io::Result<Self> {
159        // Skia doesn't have an API for DPI.
160        Self::from_bitmap(decode_bitmap(stream)?, None, Vector::new(96.0, 96.0))
161    }
162
163    /// Wraps a Skia image. `custom_image_dispose` replaces releasing the
164    /// image when the bitmap is disposed: it is handed the image, to release
165    /// it where that has to happen (with a graphics context current).
166    pub fn from_image(image: Image, custom_image_dispose: Option<Box<dyn FnOnce(Image) + Send>>) -> Self {
167        let pixel_size = PixelSize::new(image.width(), image.height());
168
169        Self {
170            image: Mutex::new(Some(image)),
171            bitmap: Mutex::new(None),
172            custom_image_dispose: Mutex::new(custom_image_dispose),
173            dpi: Vector::new(96.0, 96.0),
174            pixel_size,
175        }
176    }
177
178    /// Creates a resized copy of `src`.
179    pub fn resized(
180        src: &ImmutableBitmap,
181        destination_size: PixelSize,
182        interpolation_mode: BitmapInterpolationMode,
183    ) -> io::Result<Self> {
184        let is_upscaling =
185            destination_size.width > src.pixel_size.width || destination_size.height > src.pixel_size.height;
186        let info = ImageInfo::new(
187            (destination_size.width, destination_size.height),
188            ColorType::BGRA8888,
189            AlphaType::Premul,
190            None,
191        );
192
193        let mut bitmap = Bitmap::new();
194        if !bitmap.try_alloc_pixels_info(&info, None) {
195            return Err(load_error());
196        }
197
198        let pixmap = bitmap.peek_pixels().ok_or_else(load_error)?;
199        if !src.image().scale_pixels(&pixmap, to_sk_sampling_options_scaled(interpolation_mode, is_upscaling), None)
200        {
201            return Err(load_error());
202        }
203
204        Self::from_bitmap(bitmap, None, Vector::new(96.0, 96.0))
205    }
206
207    /// Creates an immutable bitmap from a stream, decoded so that its width
208    /// (`horizontal`) or height is `decode_size`.
209    pub fn from_stream_to_size(
210        stream: &mut dyn Read,
211        decode_size: i32,
212        horizontal: bool,
213        interpolation_mode: BitmapInterpolationMode,
214    ) -> io::Result<Self> {
215        let bitmap = decode_bitmap_to_size(stream, decode_size, horizontal, interpolation_mode)?;
216        Self::from_bitmap(bitmap, None, Vector::new(96.0, 96.0))
217    }
218
219    /// Creates an immutable bitmap from raw pixel data, which is copied.
220    ///
221    /// `data` holds `|stride| * size.height` bytes. A negative stride means
222    /// the rows are stored bottom-up: the first row of the image is the last
223    /// row of `data`.
224    pub fn from_pixels(
225        size: PixelSize,
226        dpi: Vector,
227        stride: i32,
228        format: PixelFormat,
229        alpha_format: AlphaFormat,
230        data: &[u8],
231    ) -> io::Result<Self> {
232        let create_error =
233            || io::Error::new(io::ErrorKind::InvalidInput, "Unable to create bitmap from provided data");
234
235        let info = ImageInfo::new(
236            (size.width, size.height),
237            to_sk_color_type_or_panic(format),
238            to_sk_alpha_type(alpha_format),
239            None,
240        );
241
242        let mut bitmap = Bitmap::new();
243        if !bitmap.try_alloc_pixels_info(&info, None) {
244            return Err(create_error());
245        }
246
247        let row_bytes = bitmap.row_bytes();
248        let height = size.height.max(0) as usize;
249        let abs_stride = stride.unsigned_abs() as usize;
250        let copy_bytes = info.min_row_bytes().min(abs_stride);
251
252        if data.len() < abs_stride * height.saturating_sub(1) + if height > 0 { copy_bytes } else { 0 } {
253            return Err(create_error());
254        }
255
256        if height > 0 {
257            let mut pixmap = bitmap.peek_pixels().ok_or_else(create_error)?;
258            let pixels = pixmap.bytes_mut().ok_or_else(create_error)?;
259
260            for row in 0..height {
261                let source_row = if stride < 0 { height - 1 - row } else { row };
262                let source = &data[source_row * abs_stride..source_row * abs_stride + copy_bytes];
263                pixels[row * row_bytes..row * row_bytes + copy_bytes].copy_from_slice(source);
264            }
265        }
266
267        Self::from_bitmap(bitmap, Some(size), dpi)
268    }
269
270    /// The Skia image.
271    ///
272    /// # Panics
273    /// Panics when the bitmap has been disposed.
274    pub fn image(&self) -> Image {
275        self.image.lock().unwrap().clone().expect("ImmutableBitmap has been disposed")
276    }
277}
278
279impl IBitmapImpl for ImmutableBitmap {
280    fn dpi(&self) -> Vector {
281        self.dpi
282    }
283
284    fn pixel_size(&self) -> PixelSize {
285        self.pixel_size
286    }
287
288    fn version(&self) -> i32 {
289        1
290    }
291
292    fn save(&self, stream: &mut dyn Write, options: &BitmapEncoderOptions) -> io::Result<()> {
293        image_saving_helper::save_image(&self.image(), stream, options)
294    }
295
296    fn dispose(&self) {
297        let image = self.image.lock().unwrap().take();
298        let custom_image_dispose = self.custom_image_dispose.lock().unwrap().take();
299        match (custom_image_dispose, image) {
300            (Some(custom_image_dispose), Some(image)) => custom_image_dispose(image),
301            (_, image) => drop(image),
302        }
303        self.bitmap.lock().unwrap().take();
304    }
305
306    fn as_any(&self) -> &dyn Any {
307        self
308    }
309
310    fn as_readable_bitmap(&self) -> Option<&dyn IReadableBitmapImpl> {
311        Some(self)
312    }
313}
314
315impl IDrawableBitmapImpl for ImmutableBitmap {
316    fn draw(
317        &self,
318        gr_context: Option<&dyn ISkiaGrContext>,
319        canvas: &Canvas,
320        source_rect: &Rect,
321        dest_rect: &Rect,
322        sampling_options: SamplingOptions,
323        paint: &Paint,
324    ) {
325        canvas.draw_image_rect_with_sampling_options(
326            drawable_image(gr_context, self.image(), needs_mipmaps(&sampling_options)),
327            Some((source_rect, SrcRectConstraint::Fast)),
328            dest_rect,
329            sampling_options,
330            paint,
331        );
332    }
333}
334
335impl IReadableBitmapImpl for ImmutableBitmap {
336    fn format(&self) -> Option<PixelFormat> {
337        self.bitmap.lock().unwrap().as_ref().and_then(|bitmap| to_pixel_format_opt(bitmap.color_type()))
338    }
339
340    fn alpha_format(&self) -> Option<AlphaFormat> {
341        self.bitmap.lock().unwrap().as_ref().map(|bitmap| to_alpha_format(bitmap.alpha_type()))
342    }
343
344    fn lock(&self) -> Rc<dyn ILockedFramebuffer> {
345        let bitmap = self.bitmap.lock().unwrap();
346        let bitmap = bitmap.as_ref().unwrap_or_else(|| panic!("A bitmap is needed for locking"));
347
348        let format = to_pixel_format_opt(bitmap.color_type())
349            .unwrap_or_else(|| panic!("Unsupported format {:?}", bitmap.color_type()));
350        let alpha_format = to_alpha_format(bitmap.alpha_type());
351
352        Rc::new(LockedFramebuffer::new(
353            bitmap.pixmap().addr() as *mut u8,
354            self.pixel_size,
355            bitmap.row_bytes() as i32,
356            self.dpi,
357            format,
358            alpha_format,
359            None,
360        ))
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367
368    fn constructor_from_pixels_copies_source_data(width: i32, height: i32, negative_stride: bool) {
369        let size = PixelSize::new(width, height);
370        let row_bytes = (width * 4) as usize;
371        let abs_stride = row_bytes;
372        let byte_size = abs_stride * height as usize;
373
374        // Logical pixel byte: deterministic function of (row, byte index within row).
375        let expected = |row: usize, x: usize| ((row * row_bytes + x) * 7 + 1) as u8;
376
377        // Lay the logical rows out in physical memory. For a negative stride
378        // the rows are stored bottom-up.
379        let mut source = vec![0u8; byte_size];
380        for row in 0..height as usize {
381            let physical_row = if negative_stride { height as usize - 1 - row } else { row };
382            for x in 0..row_bytes {
383                source[physical_row * abs_stride + x] = expected(row, x);
384            }
385        }
386
387        let stride = if negative_stride { -(abs_stride as i32) } else { abs_stride as i32 };
388
389        let bitmap = ImmutableBitmap::from_pixels(
390            size,
391            Vector::new(96.0, 96.0),
392            stride,
393            PixelFormat::BGRA8888,
394            AlphaFormat::Premul,
395            &source,
396        )
397        .unwrap();
398
399        // The constructor must take its own copy: corrupting the source
400        // afterwards must not affect the bitmap's pixels.
401        source.fill(0xCD);
402        drop(source);
403
404        assert_eq!(size, bitmap.pixel_size());
405
406        let locked = bitmap.lock();
407        assert_eq!(size, locked.size());
408
409        let locked_row_bytes = locked.row_bytes() as usize;
410        // SAFETY: the locked framebuffer is `row_bytes * height` bytes long
411        // and stays valid while `bitmap` is alive.
412        let dst = unsafe { std::slice::from_raw_parts(locked.address(), locked_row_bytes * height as usize) };
413
414        for row in 0..height as usize {
415            for x in 0..row_bytes {
416                assert_eq!(expected(row, x), dst[row * locked_row_bytes + x]);
417            }
418        }
419
420        locked.dispose();
421        bitmap.dispose();
422    }
423
424    #[test]
425    fn constructor_from_pixels_copies_source_data_cases() {
426        for (width, height) in [(1, 1), (3, 5), (64, 64)] {
427            constructor_from_pixels_copies_source_data(width, height, false);
428            constructor_from_pixels_copies_source_data(width, height, true);
429        }
430    }
431}