Skip to main content

ferroui_base/media/imaging/
bitmap.rs

1use crate::media::imaging::bitmap_memory::BitmapMemory;
2use crate::media::imaging::pixel_format_transcoder::PixelFormatTranscoder;
3use crate::media::imaging::{BitmapEncoderOptions, BitmapInterpolationMode, IBitmap};
4use crate::media::{DrawingContext, IImage, IImageBrushSource};
5use crate::platform::{self, AlphaFormat, IBitmapImpl, ILockedFramebuffer, PixelFormat};
6use crate::utilities::{RefCountable, RefCounted};
7use crate::{PixelRect, PixelSize, Rect, Size, Vector};
8use std::any::Any;
9use std::io::{Read, Write};
10
11/// Holds a bitmap image.
12///
13/// The types deriving from it ([`WriteableBitmap`](super::WriteableBitmap),
14/// [`RenderTargetBitmap`](super::RenderTargetBitmap)) dereference to
15/// `Bitmap`; the members they override are reached through the deriving type
16/// or through [`IBitmap`]/[`IImage`] handles.
17pub struct Bitmap {
18    is_transcoded: bool,
19    platform_impl: RefCounted<crate::platform::SharedBitmapImpl>,
20}
21
22/// Wraps a platform bitmap in a counted reference that disposes it when the
23/// last reference is released.
24pub(crate) fn create_bitmap_ref(platform_impl: std::sync::Arc<crate::platform::SharedBitmapImpl>) -> RefCounted<crate::platform::SharedBitmapImpl> {
25    let item = platform_impl.clone();
26    RefCountable::create(platform_impl, move || item.dispose())
27}
28
29impl Bitmap {
30    /// Loads a bitmap from a stream, decoded to the specified width
31    /// maintaining aspect ratio.
32    ///
33    /// `interpolation_mode` is used should a resize be necessary; upstream
34    /// defaults it to high quality.
35    pub fn decode_to_width(
36        stream: &mut dyn Read,
37        width: i32,
38        interpolation_mode: BitmapInterpolationMode,
39    ) -> std::io::Result<Bitmap> {
40        Ok(Bitmap::from_impl(platform::render_interface().load_bitmap_to_width(stream, width, interpolation_mode)?))
41    }
42
43    /// Loads a bitmap from a stream, decoded to the specified height
44    /// maintaining aspect ratio.
45    pub fn decode_to_height(
46        stream: &mut dyn Read,
47        height: i32,
48        interpolation_mode: BitmapInterpolationMode,
49    ) -> std::io::Result<Bitmap> {
50        Ok(Bitmap::from_impl(platform::render_interface().load_bitmap_to_height(stream, height, interpolation_mode)?))
51    }
52
53    /// Creates a bitmap scaled to the specified size from the current
54    /// bitmap.
55    pub fn create_scaled_bitmap(
56        &self,
57        destination_size: PixelSize,
58        interpolation_mode: BitmapInterpolationMode,
59    ) -> Bitmap {
60        Bitmap::from_impl(platform::render_interface().resize_bitmap(
61            &*self.platform_impl.item(),
62            destination_size,
63            interpolation_mode,
64        ))
65    }
66
67    /// Loads a bitmap from a file.
68    pub fn from_file(file_name: &str) -> std::io::Result<Bitmap> {
69        Ok(Bitmap::from_impl(platform::render_interface().load_bitmap_from_file(file_name)?))
70    }
71
72    /// Loads a bitmap from a stream.
73    pub fn from_stream(stream: &mut dyn Read) -> std::io::Result<Bitmap> {
74        Ok(Bitmap::from_impl(platform::render_interface().load_bitmap(stream)?))
75    }
76
77    /// Creates a bitmap sharing a platform bitmap: takes another reference
78    /// to it.
79    pub fn from_ref(platform_impl: &RefCounted<crate::platform::SharedBitmapImpl>) -> Bitmap {
80        Bitmap { is_transcoded: false, platform_impl: platform_impl.clone_ref() }
81    }
82
83    /// Creates a bitmap owning a platform bitmap.
84    pub fn from_impl(platform_impl: std::sync::Arc<crate::platform::SharedBitmapImpl>) -> Bitmap {
85        Bitmap { is_transcoded: false, platform_impl: create_bitmap_ref(platform_impl) }
86    }
87
88    /// Creates a bitmap from pixel data: `stride * size.height` bytes of
89    /// `format`/`alpha_format` pixels.
90    ///
91    /// A format the backend cannot hold is converted to RGBA8888.
92    pub fn from_pixels(
93        format: PixelFormat,
94        alpha_format: AlphaFormat,
95        data: &[u8],
96        size: PixelSize,
97        dpi: Vector,
98        stride: i32,
99    ) -> Bitmap {
100        let factory = platform::render_interface();
101        if factory.is_supported_bitmap_pixel_format(format) {
102            Bitmap::from_impl(factory.load_bitmap_from_pixels(format, alpha_format, data, size, dpi, stride))
103        } else {
104            let mut transcoded = BitmapMemory::new(PixelFormat::RGBA8888, AlphaFormat::Unpremul, size);
105            let transcoded_alpha_format = if format.has_alpha() { alpha_format } else { AlphaFormat::Opaque };
106            let (row_bytes, transcoded_format) = (transcoded.row_bytes(), transcoded.format());
107
108            PixelFormatTranscoder::transcode(
109                data,
110                size,
111                stride,
112                format,
113                alpha_format,
114                transcoded.data_mut(),
115                row_bytes,
116                transcoded_format,
117                transcoded_alpha_format,
118            );
119
120            let platform_impl = factory.load_bitmap_from_pixels(
121                PixelFormat::RGBA8888,
122                transcoded_alpha_format,
123                transcoded.data(),
124                size,
125                dpi,
126                row_bytes,
127            );
128            transcoded.dispose();
129
130            Bitmap { is_transcoded: true, platform_impl: create_bitmap_ref(platform_impl) }
131        }
132    }
133
134    /// Releases the bitmap's reference to the platform bitmap.
135    pub fn dispose(&self) {
136        self.platform_impl.dispose();
137    }
138
139    /// The dots per inch (DPI) of the image.
140    pub fn dpi(&self) -> Vector {
141        self.platform_impl.item().dpi()
142    }
143
144    /// The size of the image, in device independent pixels.
145    pub fn size(&self) -> Size {
146        self.platform_impl.item().pixel_size().to_size_with_dpi_vector(self.dpi())
147    }
148
149    /// The size of the bitmap, in device pixels.
150    pub fn pixel_size(&self) -> PixelSize {
151        self.platform_impl.item().pixel_size()
152    }
153
154    /// The platform-specific bitmap implementation.
155    pub fn platform_impl(&self) -> &RefCounted<crate::platform::SharedBitmapImpl> {
156        &self.platform_impl
157    }
158
159    /// Saves the bitmap to a file.
160    pub fn save_to_file(&self, file_name: &str, options: &BitmapEncoderOptions) -> std::io::Result<()> {
161        let mut stream = std::fs::File::create(file_name)?;
162        self.save(&mut stream, options)
163    }
164
165    /// Saves the bitmap to a stream.
166    pub fn save(&self, stream: &mut dyn Write, options: &BitmapEncoderOptions) -> std::io::Result<()> {
167        self.platform_impl.item().save(stream, options)
168    }
169
170    /// The pixel format of the bitmap, if its pixels can be read.
171    pub fn format(&self) -> Option<PixelFormat> {
172        self.platform_impl.item().as_readable_bitmap().and_then(|readable| readable.format())
173    }
174
175    /// The alpha format of the bitmap, if its pixels can be read.
176    pub fn alpha_format(&self) -> Option<AlphaFormat> {
177        self.platform_impl.item().as_readable_bitmap().and_then(|readable| readable.alpha_format())
178    }
179
180    fn validate_source_rect(&self, mut source_rect: PixelRect) -> PixelRect {
181        let pixel_size = self.pixel_size();
182
183        if (source_rect.width <= 0 || source_rect.height <= 0) && (source_rect.x != 0 || source_rect.y != 0) {
184            panic!("source_rect is out of range: {source_rect}");
185        }
186
187        if source_rect.x < 0 || source_rect.y < 0 {
188            panic!("source_rect is out of range: {source_rect}");
189        }
190
191        if source_rect.width <= 0 {
192            source_rect = source_rect.with_width(pixel_size.width);
193        }
194        if source_rect.height <= 0 {
195            source_rect = source_rect.with_height(pixel_size.height);
196        }
197
198        if source_rect.right() > pixel_size.width || source_rect.bottom() > pixel_size.height {
199            panic!("source_rect is out of range: {source_rect}");
200        }
201
202        source_rect
203    }
204
205    /// Copies the rows of `source_rect` from `source` (rows
206    /// `source_row_bytes` apart, pixels of `source_format`) into `buffer`
207    /// (rows `stride` apart).
208    ///
209    /// Panics when `stride` is too small for a row or `buffer` too small for
210    /// the rectangle.
211    pub(crate) fn copy_pixels_between(
212        source_rect: PixelRect,
213        source: &[u8],
214        source_row_bytes: i32,
215        source_format: PixelFormat,
216        buffer: &mut [u8],
217        stride: i32,
218    ) {
219        let bits_per_pixel = source_format.bits_per_pixel() as i32;
220        let min_stride = source_rect
221            .width
222            .checked_mul(bits_per_pixel)
223            .and_then(|bits| bits.checked_add(7))
224            .expect("arithmetic overflow")
225            / 8;
226        if stride < min_stride {
227            panic!("stride is out of range: {stride}");
228        }
229
230        // 64-bit to avoid overflowing the guard for very large
231        // strides/heights.
232        let min_buffer_size = stride as i64 * source_rect.height as i64;
233        if min_buffer_size > buffer.len() as i64 {
234            panic!("buffer size is out of range: {}", buffer.len());
235        }
236
237        let offset_x =
238            source_rect.x.checked_mul(bits_per_pixel).and_then(|bits| bits.checked_add(7)).expect("arithmetic overflow")
239                / 8;
240
241        // Fast-path: when the source and destination layouts are identical,
242        // tightly-packed and forward (no row padding, no X offset, positive
243        // stride), the whole region is contiguous in both buffers and can be
244        // copied with a single blit. Requiring stride == min_stride also
245        // guarantees the source is not read past its last row.
246        if offset_x == 0 && source_row_bytes == stride && stride == min_stride {
247            let start = (source_row_bytes as i64 * source_rect.y as i64) as usize;
248            let length = min_buffer_size as usize;
249            buffer[..length].copy_from_slice(&source[start..start + length]);
250            return;
251        }
252
253        let min_stride = min_stride as usize;
254        for y in 0..source_rect.height {
255            let src_address =
256                (source_row_bytes as i64 * (source_rect.y + y) as i64 + offset_x as i64) as usize;
257            let dst_address = (stride as i64 * y as i64) as usize;
258            buffer[dst_address..dst_address + min_stride]
259                .copy_from_slice(&source[src_address..src_address + min_stride]);
260        }
261    }
262
263    pub(crate) fn copy_pixels_core(
264        &self,
265        source_rect: PixelRect,
266        buffer: &mut [u8],
267        stride: i32,
268        fb: &dyn ILockedFramebuffer,
269    ) {
270        let source_rect = self.validate_source_rect(source_rect);
271        let (row_bytes, format) = (fb.row_bytes(), fb.format());
272        fb.with_data(&mut |source| {
273            Self::copy_pixels_between(source_rect, source, row_bytes, format, buffer, stride);
274        });
275    }
276
277    /// Copies the pixels of `source_rect` into `buffer`, whose rows are
278    /// `stride` bytes apart. An empty `source_rect` selects the whole
279    /// bitmap.
280    ///
281    /// Panics when the pixels of the bitmap cannot be read, when the bitmap
282    /// was converted from another pixel format on creation, or when the
283    /// rectangle, stride or buffer are out of range.
284    pub fn copy_pixels(&self, source_rect: PixelRect, buffer: &mut [u8], stride: i32) {
285        let item = self.platform_impl.item();
286        let readable = match (self.format(), item.as_readable_bitmap()) {
287            (Some(format), Some(readable)) if Some(format) == readable.format() => readable,
288            _ => panic!("CopyPixels is not supported for this bitmap type"),
289        };
290
291        if self.is_transcoded {
292            panic!("CopyPixels is not supported for transcoded bitmaps");
293        }
294
295        let fb = readable.lock();
296        self.copy_pixels_core(source_rect, buffer, stride, &*fb);
297        fb.dispose();
298    }
299
300    /// Copies the pixels of the bitmap into a locked framebuffer, converting
301    /// them to its pixel and alpha formats.
302    ///
303    /// Panics when the pixels of the bitmap cannot be read.
304    pub fn copy_pixels_to_framebuffer(&self, buffer: &dyn ILockedFramebuffer) {
305        let item = self.platform_impl.item();
306        let readable = match item.as_readable_bitmap() {
307            Some(readable) if readable.format().is_some() && readable.alpha_format().is_some() => readable,
308            // The pixels cannot be read: the bitmap has to be drawn into a
309            // compatible bitmap first, which needs a drawing context.
310            _ => panic!("CopyPixels is not supported for this bitmap type"),
311        };
312
313        if Some(buffer.format()) != readable.format() || Some(buffer.alpha_format()) != readable.alpha_format() {
314            let fb = readable.lock();
315            let (size, row_bytes, format, alpha_format) = (fb.size(), fb.row_bytes(), fb.format(), fb.alpha_format());
316            let (dest_row_bytes, dest_format, dest_alpha_format) =
317                (buffer.row_bytes(), buffer.format(), buffer.alpha_format());
318            fb.with_data(&mut |source| {
319                buffer.with_data(&mut |dest| {
320                    PixelFormatTranscoder::transcode(
321                        source,
322                        size,
323                        row_bytes,
324                        format,
325                        alpha_format,
326                        dest,
327                        dest_row_bytes,
328                        dest_format,
329                        dest_alpha_format,
330                    );
331                });
332            });
333            fb.dispose();
334        } else {
335            let fb = readable.lock();
336            let (source_rect, stride) = (PixelRect::from_size(fb.size()), fb.row_bytes());
337            let buffer_size = (buffer.row_bytes() as i64 * buffer.size().height as i64).max(0) as usize;
338            buffer.with_data(&mut |dest| {
339                let length = buffer_size.min(dest.len());
340                self.copy_pixels_core(source_rect, &mut dest[..length], stride, &*fb);
341            });
342            fb.dispose();
343        }
344    }
345}
346
347impl IImage for Bitmap {
348    fn size(&self) -> Size {
349        Bitmap::size(self)
350    }
351
352    fn draw(&self, context: &mut DrawingContext<'_>, source_rect: Rect, dest_rect: Rect) {
353        context.draw_bitmap(&self.platform_impl, 1.0, source_rect, dest_rect);
354    }
355
356    fn as_any(&self) -> &dyn Any {
357        self
358    }
359
360    fn as_bitmap(&self) -> Option<&dyn IBitmap> {
361        Some(self)
362    }
363}
364
365impl IBitmap for Bitmap {
366    fn dpi(&self) -> Vector {
367        Bitmap::dpi(self)
368    }
369
370    fn pixel_size(&self) -> PixelSize {
371        Bitmap::pixel_size(self)
372    }
373
374    fn platform_impl(&self) -> &RefCounted<crate::platform::SharedBitmapImpl> {
375        &self.platform_impl
376    }
377
378    fn save(&self, stream: &mut dyn Write, options: &BitmapEncoderOptions) -> std::io::Result<()> {
379        Bitmap::save(self, stream, options)
380    }
381
382    fn dispose(&self) {
383        Bitmap::dispose(self)
384    }
385}
386
387impl IImageBrushSource for Bitmap {
388    fn bitmap(&self) -> Option<&RefCounted<crate::platform::SharedBitmapImpl>> {
389        if !self.platform_impl.is_alive() {
390            return None;
391        }
392        Some(&self.platform_impl)
393    }
394}
395
396/// Bitmaps compare by identity (reference equality), so that their handles can be
397/// held in untyped values.
398impl PartialEq for Bitmap {
399    fn eq(&self, other: &Self) -> bool {
400        std::ptr::eq(self, other)
401    }
402}