diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index f3c1c5a24..756e4a17c 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -253,10 +253,6 @@ fn attach_bitmap<'gc>( .coerce_to_i32(activation)? .wrapping_add(AVM_DEPTH_BIAS); - let bitmap_handle = bitmap_data - .write(activation.context.gc_context) - .bitmap_handle(activation.context.renderer); - // TODO: Implement pixel snapping let _pixel_snapping = args .get(2) @@ -268,29 +264,20 @@ fn attach_bitmap<'gc>( .unwrap_or(&Value::Undefined) .as_bool(activation.swf_version()); - if let Some(bitmap_handle) = bitmap_handle { - //TODO: do attached BitmapDatas have character ids? - let display_object = Bitmap::new_with_bitmap_data( - &mut activation.context, - 0, - Some(bitmap_handle), - bitmap_data.read().width() as u16, - bitmap_data.read().height() as u16, - Some(bitmap_data), - smoothing, - ); - movie_clip.replace_at_depth( - &mut activation.context, - display_object.into(), - depth, - ); - display_object.post_instantiation( - &mut activation.context, - None, - Instantiator::Avm1, - true, - ); - } + //TODO: do attached BitmapDatas have character ids? + let display_object = Bitmap::new_with_bitmap_data( + &mut activation.context, + 0, + bitmap_data, + smoothing, + ); + movie_clip.replace_at_depth(&mut activation.context, display_object.into(), depth); + display_object.post_instantiation( + &mut activation.context, + None, + Instantiator::Avm1, + true, + ); } } } diff --git a/core/src/avm2/globals/flash/display/bitmap.rs b/core/src/avm2/globals/flash/display/bitmap.rs index 3676741ce..984bd0c99 100644 --- a/core/src/avm2/globals/flash/display/bitmap.rs +++ b/core/src/avm2/globals/flash/display/bitmap.rs @@ -10,6 +10,7 @@ use crate::avm2::Error; use crate::avm2::Multiname; use crate::avm2::Namespace; use crate::avm2::QName; + use crate::bitmap::bitmap_data::BitmapData; use crate::character::Character; use crate::display_object::{Bitmap, TDisplayObject}; @@ -43,96 +44,80 @@ pub fn instance_init<'gc>( .coerce_to_boolean(); if let Some(bitmap) = this.as_display_object().and_then(|dobj| dobj.as_bitmap()) { - if bitmap.bitmap_data().is_none() { - //We are being initialized by the movie. This means that we - //need to create bitmap data right away, since all AVM2 bitmaps - //hold bitmap data. + //We are being initialized by the movie. This means that we + //need to create bitmap data right away, since all AVM2 bitmaps + //hold bitmap data. - let bd_object = if let Some(bd_class) = bitmap.avm2_bitmapdata_class() { - bd_class.construct(activation, &[])? - } else if let Some(b_class) = bitmap.avm2_bitmap_class() { - // Instantiating Bitmap from a Flex-style bitmap asset. - // Contrary to the above comment, this code path DOES - // trigger from AVM2, since the DisplayObject instantiation - // logic does its job in this case. - if let Some((movie, symbol_id)) = activation + let bd_object = if let Some(bd_class) = bitmap.avm2_bitmapdata_class() { + bd_class.construct(activation, &[])? + } else if let Some(b_class) = bitmap.avm2_bitmap_class() { + // Instantiating Bitmap from a Flex-style bitmap asset. + // Contrary to the above comment, this code path DOES + // trigger from AVM2, since the DisplayObject instantiation + // logic does its job in this case. + if let Some((movie, symbol_id)) = activation + .context + .library + .avm2_class_registry() + .class_symbol(b_class) + { + if let Some(Character::Bitmap { + bitmap, + initial_data, + }) = activation .context .library - .avm2_class_registry() - .class_symbol(b_class) + .library_for_movie_mut(movie) + .character_by_id(symbol_id) + .cloned() { - if let Some(Character::Bitmap { - bitmap, - initial_data, - }) = activation - .context - .library - .library_for_movie_mut(movie) - .character_by_id(symbol_id) - .cloned() - { - let new_bitmap_data = GcCell::allocate( - activation.context.gc_context, - BitmapData::default(), - ); + let new_bitmap_data = + GcCell::allocate(activation.context.gc_context, BitmapData::default()); - fill_bitmap_data_from_symbol( - activation, - bitmap, - new_bitmap_data, - initial_data, - ); - BitmapDataObject::from_bitmap_data( - activation, - new_bitmap_data, - activation.context.avm2.classes().bitmapdata, - )? - } else { - //Class association not to a Bitmap - return Err("Attempted to instantiate Bitmap from timeline with symbol class associated to non-Bitmap!".into()); - } + fill_bitmap_data_from_symbol( + activation, + bitmap, + new_bitmap_data, + initial_data, + ); + BitmapDataObject::from_bitmap_data( + activation, + new_bitmap_data, + activation.context.avm2.classes().bitmapdata, + )? } else { - //Class association not bidirectional - return Err("Cannot instantiate Bitmap from timeline without bidirectional symbol class association".into()); + //Class association not to a Bitmap + return Err("Attempted to instantiate Bitmap from timeline with symbol class associated to non-Bitmap!".into()); } } else { - // No class association - return Err( - "Cannot instantiate Bitmap from timeline without associated symbol class" - .into(), - ); - }; + //Class association not bidirectional + return Err("Cannot instantiate Bitmap from timeline without bidirectional symbol class association".into()); + } + } else { + // No class association + return Err( + "Cannot instantiate Bitmap from timeline without associated symbol class" + .into(), + ); + }; - this.set_property( - &Multiname::public("bitmapData"), - bd_object.into(), - activation, - )?; - } + this.set_property( + &Multiname::public("bitmapData"), + bd_object.into(), + activation, + )?; bitmap.set_smoothing(activation.context.gc_context, smoothing); } else { //We are being initialized by AVM2 (and aren't associated with a //Bitmap subclass). - let bitmap_handle = if let Some(bd) = bitmap_data { - bd.write(activation.context.gc_context) - .bitmap_handle(activation.context.renderer) - } else { - None - }; - let width = bitmap_data.map(|bd| bd.read().width()).unwrap_or(0) as u16; - let height = bitmap_data.map(|bd| bd.read().height()).unwrap_or(0) as u16; + let bitmap_data = bitmap_data.unwrap_or_else(|| { + GcCell::allocate(activation.context.gc_context, BitmapData::dummy()) + }); - let bitmap = Bitmap::new_with_bitmap_data( - &mut activation.context, - 0, - bitmap_handle, - width, - height, - bitmap_data, - smoothing, - ); + let bitmap = + Bitmap::new_with_bitmap_data(&mut activation.context, 0, bitmap_data, smoothing); this.init_display_object(activation.context.gc_context, bitmap.into()); } @@ -160,10 +145,13 @@ pub fn bitmap_data<'gc>( .and_then(|this| this.as_display_object()) .and_then(|dobj| dobj.as_bitmap()) { - return Ok(bitmap - .bitmap_data() - .map(|bd| bd.read().object2()) - .unwrap_or(Value::Null)); + let mut value = bitmap.bitmap_data().read().object2(); + + // AS3 expects an unset BitmapData to be null, not 'undefined' + if matches!(value, Value::Undefined) { + value = Value::Null; + } + return Ok(value); } Ok(Value::Undefined) @@ -179,13 +167,15 @@ pub fn set_bitmap_data<'gc>( .and_then(|this| this.as_display_object()) .and_then(|dobj| dobj.as_bitmap()) { - let bitmap_data = args - .get(0) - .cloned() - .unwrap_or(Value::Null) - .as_object() - .and_then(|bd| bd.as_bitmap_data()); - + let bitmap_data = args.get(0).unwrap_or(&Value::Null); + let bitmap_data = if matches!(bitmap_data, Value::Null) { + GcCell::allocate(activation.context.gc_context, BitmapData::dummy()) + } else { + bitmap_data + .coerce_to_object(activation)? + .as_bitmap_data() + .ok_or_else(|| Error::RustError("Argument was not a BitmapData".into()))? + }; bitmap.set_bitmap_data(&mut activation.context, bitmap_data); } diff --git a/core/src/bitmap/bitmap_data.rs b/core/src/bitmap/bitmap_data.rs index 79097ca8d..e515b7618 100644 --- a/core/src/bitmap/bitmap_data.rs +++ b/core/src/bitmap/bitmap_data.rs @@ -200,6 +200,23 @@ impl fmt::Debug for BitmapData<'_> { } impl<'gc> BitmapData<'gc> { + // Creates a dummy BitmapData with no pixels or handle, marked as disposed. + // This is used for AS3 `Bitmap` instances without a corresponding AS3 `BitmapData` instance. + // Marking it as disposed skips rendering, and the unset `avm2_object` will cause this to + // be inaccessible to AS3 code. + pub fn dummy() -> Self { + BitmapData { + pixels: Vec::new(), + dirty: false, + width: 0, + height: 0, + transparency: false, + disposed: true, + bitmap_handle: None, + avm2_object: None, + } + } + pub fn init_pixels(&mut self, width: u32, height: u32, transparency: bool, fill_color: i32) { self.width = width; self.height = height; diff --git a/core/src/display_object/bitmap.rs b/core/src/display_object/bitmap.rs index c5594e21e..2d622ed1e 100644 --- a/core/src/display_object/bitmap.rs +++ b/core/src/display_object/bitmap.rs @@ -10,8 +10,8 @@ use crate::display_object::{DisplayObjectBase, DisplayObjectPtr, TDisplayObject} use crate::prelude::*; use crate::vminterface::Instantiator; use core::fmt; -use gc_arena::{Collect, Gc, GcCell, MutationContext}; -use ruffle_render::bitmap::BitmapHandle; +use gc_arena::{Collect, GcCell, MutationContext}; +use ruffle_render::bitmap::BitmapFormat; use ruffle_render::commands::CommandHandler; use std::cell::{Ref, RefMut}; @@ -64,19 +64,10 @@ impl fmt::Debug for Bitmap<'_> { #[collect(no_drop)] pub struct BitmapData<'gc> { base: DisplayObjectBase<'gc>, - static_data: Gc<'gc, BitmapStatic>, + id: CharacterId, /// The current bitmap data object. - bitmap_data: Option>>, - - /// The current bitmap handle. - /// - /// This needs to be cached separately from the associated bitmap data so - /// that it can be accessed without a mutation context. - /// - /// If this is `None`, then the bitmap does not render anything. - #[collect(require_static)] - bitmap_handle: Option, + bitmap_data: GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>, /// Whether or not bitmap smoothing is enabled. smoothing: bool, @@ -102,10 +93,7 @@ impl<'gc> Bitmap<'gc> { pub fn new_with_bitmap_data( context: &mut UpdateContext<'_, 'gc, '_>, id: CharacterId, - bitmap_handle: Option, - width: u16, - height: u16, - bitmap_data: Option>>, + bitmap_data: GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>, smoothing: bool, ) -> Self { //NOTE: We do *not* solicit a handle from the `bitmap_data` at this @@ -115,9 +103,8 @@ impl<'gc> Bitmap<'gc> { context.gc_context, BitmapData { base: Default::default(), - static_data: Gc::allocate(context.gc_context, BitmapStatic { id, width, height }), + id, bitmap_data, - bitmap_handle, smoothing, avm2_object: None, avm2_bitmap_class: BitmapClass::NoSubclass, @@ -131,45 +118,43 @@ impl<'gc> Bitmap<'gc> { id: CharacterId, bitmap: ruffle_render::bitmap::Bitmap, ) -> Result { - let width = bitmap.width() as u16; - let height = bitmap.height() as u16; - let bitmap_handle = context.renderer.register_bitmap(bitmap)?; - let bitmap_data = None; + let width = bitmap.width(); + let height = bitmap.height(); + let pixels: Vec<_> = bitmap + .as_colors() + .map(crate::bitmap::bitmap_data::Color::from) + .collect(); + let mut bitmap_data = crate::bitmap::bitmap_data::BitmapData::default(); + bitmap_data.set_pixels( + width, + height, + match bitmap.format() { + BitmapFormat::Rgba => true, + BitmapFormat::Rgb => false, + }, + pixels, + ); + let bitmap_data = GcCell::allocate(context.gc_context, bitmap_data); + let smoothing = true; Ok(Self::new_with_bitmap_data( context, id, - Some(bitmap_handle), - width, - height, bitmap_data, smoothing, )) } - #[allow(dead_code)] - pub fn bitmap_handle(self) -> Option { - self.0.read().bitmap_handle.clone() - } - pub fn width(self) -> u16 { - let read = self.0.read(); - - read.bitmap_data - .map(|bd| bd.read().width() as u16) - .unwrap_or_else(|| read.static_data.width) + self.0.read().bitmap_data.read().width() as u16 } pub fn height(self) -> u16 { - let read = self.0.read(); - - read.bitmap_data - .map(|bd| bd.read().height() as u16) - .unwrap_or_else(|| read.static_data.height) + self.0.read().bitmap_data.read().height() as u16 } /// Retrieve the bitmap data associated with this `Bitmap`. - pub fn bitmap_data(self) -> Option>> { + pub fn bitmap_data(self) -> GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>> { self.0.read().bitmap_data } @@ -184,25 +169,9 @@ impl<'gc> Bitmap<'gc> { pub fn set_bitmap_data( self, context: &mut UpdateContext<'_, 'gc, '_>, - bitmap_data: Option>>, + bitmap_data: GcCell<'gc, crate::bitmap::bitmap_data::BitmapData<'gc>>, ) { - if let Some(bitmap_data) = bitmap_data { - let bitmap_handle = bitmap_data - .write(context.gc_context) - .bitmap_handle(context.renderer); - - let mut write = self.0.write(context.gc_context); - - write.bitmap_data = Some(bitmap_data); - if let Some(bitmap_handle) = bitmap_handle { - write.bitmap_handle = Some(bitmap_handle); - } - } else { - let mut write = self.0.write(context.gc_context); - - write.bitmap_data = None; - write.bitmap_handle = None; - } + self.0.write(context.gc_context).bitmap_data = bitmap_data; } pub fn avm2_bitmapdata_class(self) -> Option> { @@ -262,7 +231,7 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> { } fn id(&self) -> CharacterId { - self.0.read().static_data.id + self.0.read().id } fn self_bounds(&self) -> BoundingBox { @@ -317,20 +286,24 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> { } let bitmap_data = self.0.read(); - if let Some(bitmap_handle) = &bitmap_data.bitmap_handle { - if let Some(inner_bitmap_data) = bitmap_data.bitmap_data { - if let Ok(mut bd) = inner_bitmap_data.try_write(context.gc_context) { - bd.update_dirty_texture(context); - } else { - return; // bail, this is caused by recursive render attempt. TODO: support this. - }; + let inner_bitmap_data = bitmap_data.bitmap_data.try_write(context.gc_context); + if let Ok(mut inner_bitmap_data) = inner_bitmap_data { + if inner_bitmap_data.disposed() { + return; } + inner_bitmap_data.update_dirty_texture(context); + let handle = inner_bitmap_data + .bitmap_handle(context.renderer) + .expect("Missing bitmap handle"); + context.commands.render_bitmap( - &bitmap_handle, + &handle, context.transform_stack.transform(), bitmap_data.smoothing, ); + } else { + //this is caused by recursive render attempt. TODO: support this. } } @@ -350,12 +323,3 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> { Some(self) } } - -/// Static data shared between all instances of a bitmap. -#[derive(Clone, Collect)] -#[collect(no_drop)] -struct BitmapStatic { - id: CharacterId, - width: u16, - height: u16, -} diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index d44afaa75..d56b1a8a0 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -5,6 +5,7 @@ use crate::avm2::{ use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, DisplayObjectPtr, TDisplayObject}; use crate::drawing::Drawing; +use crate::library::MovieLibrarySource; use crate::prelude::*; use crate::tag_utils::SwfMovie; use crate::vminterface::Instantiator; @@ -47,11 +48,13 @@ impl<'gc> Graphic<'gc> { let static_data = GraphicStatic { id: swf_shape.id, bounds: (&swf_shape.shape_bounds).into(), - render_handle: Some( - context - .renderer - .register_shape((&swf_shape).into(), library), - ), + render_handle: Some(context.renderer.register_shape( + (&swf_shape).into(), + &MovieLibrarySource { + library, + gc_context: context.gc_context, + }, + )), shape: swf_shape, movie: Some(movie), }; diff --git a/core/src/display_object/morph_shape.rs b/core/src/display_object/morph_shape.rs index 84526bf05..99c0f1d46 100644 --- a/core/src/display_object/morph_shape.rs +++ b/core/src/display_object/morph_shape.rs @@ -1,11 +1,11 @@ use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, DisplayObjectPtr, TDisplayObject}; -use crate::library::Library; +use crate::library::{Library, MovieLibrarySource}; use crate::prelude::*; use crate::tag_utils::SwfMovie; use core::fmt; use gc_arena::{Collect, Gc, GcCell, MutationContext}; -use ruffle_render::backend::{RenderBackend, ShapeHandle}; +use ruffle_render::backend::ShapeHandle; use ruffle_render::commands::CommandHandler; use std::cell::{Ref, RefCell, RefMut}; use std::sync::Arc; @@ -102,7 +102,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> { let this = self.0.read(); let ratio = this.ratio; let static_data = this.static_data; - let shape_handle = static_data.get_shape(context.renderer, context.library, ratio); + let shape_handle = static_data.get_shape(context, context.library, ratio); context .commands .render_shape(shape_handle, context.transform_stack.transform()); @@ -183,10 +183,10 @@ impl MorphShapeStatic { /// Retrieves the `ShapeHandle` for the given ratio. /// Lazily intializes and tessellates the shape if it does not yet exist. - fn get_shape( + fn get_shape<'gc>( &self, - renderer: &'_ mut dyn RenderBackend, - library: &Library<'_>, + context: &mut RenderContext<'_, 'gc, '_>, + library: &Library<'gc>, ratio: u16, ) -> ShapeHandle { let mut frame = self.get_frame(ratio); @@ -194,7 +194,13 @@ impl MorphShapeStatic { handle } else { let library = library.library_for_movie(self.movie.clone()).unwrap(); - let handle = renderer.register_shape((&frame.shape).into(), library); + let handle = context.renderer.register_shape( + (&frame.shape).into(), + &MovieLibrarySource { + library, + gc_context: context.gc_context, + }, + ); frame.shape_handle = Some(handle); handle } diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 07e68b9e7..0b9ca5a4f 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -2979,7 +2979,7 @@ impl<'gc, 'a> MovieClipData<'gc> { ) -> Result<(), Error> { let define_bits_lossless = reader.read_define_bits_lossless(version)?; let bitmap = ruffle_render::utils::decode_define_bits_lossless(&define_bits_lossless)?; - let initial_data: Vec = bitmap.clone().into(); + let initial_data: Vec = bitmap.as_colors().collect(); let bitmap = Bitmap::new(context, define_bits_lossless.id, bitmap)?; context .library @@ -3103,7 +3103,7 @@ impl<'gc, 'a> MovieClipData<'gc> { .jpeg_tables(); let jpeg_data = ruffle_render::utils::glue_tables_to_jpeg(jpeg_data, jpeg_tables); let bitmap = ruffle_render::utils::decode_define_bits_jpeg(&jpeg_data, None)?; - let initial_data: Vec = bitmap.clone().into(); + let initial_data: Vec = bitmap.as_colors().collect(); let bitmap = Bitmap::new(context, id, bitmap)?; context .library @@ -3127,7 +3127,7 @@ impl<'gc, 'a> MovieClipData<'gc> { let id = reader.read_u16()?; let jpeg_data = reader.read_slice_to_end(); let bitmap = ruffle_render::utils::decode_define_bits_jpeg(jpeg_data, None)?; - let initial_data: Vec = bitmap.clone().into(); + let initial_data: Vec = bitmap.as_colors().collect(); let bitmap = Bitmap::new(context, id, bitmap)?; context .library @@ -3157,7 +3157,7 @@ impl<'gc, 'a> MovieClipData<'gc> { let jpeg_data = reader.read_slice(jpeg_len)?; let alpha_data = reader.read_slice_to_end(); let bitmap = ruffle_render::utils::decode_define_bits_jpeg(jpeg_data, Some(alpha_data))?; - let initial_data: Vec = bitmap.clone().into(); + let initial_data: Vec = bitmap.as_colors().collect(); let bitmap = Bitmap::new(context, id, bitmap)?; context .library diff --git a/core/src/library.rs b/core/src/library.rs index a2f9f9d2f..4a59595b0 100644 --- a/core/src/library.rs +++ b/core/src/library.rs @@ -2,6 +2,7 @@ use crate::avm1::PropertyMap as Avm1PropertyMap; use crate::avm2::{ClassObject as Avm2ClassObject, Domain as Avm2Domain}; use crate::backend::audio::SoundHandle; use crate::character::Character; + use crate::display_object::{Bitmap, Graphic, MorphShape, TDisplayObject, Text}; use crate::font::{Font, FontDescriptor}; use crate::prelude::*; @@ -11,6 +12,7 @@ use gc_arena::{Collect, MutationContext}; use ruffle_render::backend::RenderBackend; use ruffle_render::bitmap::BitmapHandle; use ruffle_render::utils::remove_invalid_jpeg_data; + use std::collections::HashMap; use std::sync::{Arc, Weak}; use swf::CharacterId; @@ -319,17 +321,30 @@ impl<'gc> MovieLibrary<'gc> { } } -impl<'gc> ruffle_render::bitmap::BitmapSource for MovieLibrary<'gc> { +pub struct MovieLibrarySource<'a, 'gc, 'gc_context> { + pub library: &'a MovieLibrary<'gc>, + pub gc_context: MutationContext<'gc, 'gc_context>, +} + +impl<'a, 'gc, 'gc_context> ruffle_render::bitmap::BitmapSource + for MovieLibrarySource<'a, 'gc, 'gc_context> +{ fn bitmap_size(&self, id: u16) -> Option { - self.get_bitmap(id) + self.library + .get_bitmap(id) .map(|bitmap| ruffle_render::bitmap::BitmapSize { width: bitmap.width(), height: bitmap.height(), }) } - fn bitmap_handle(&self, id: u16, _backend: &mut dyn RenderBackend) -> Option { - self.get_bitmap(id) - .and_then(|bitmap| bitmap.bitmap_handle()) + + fn bitmap_handle(&self, id: u16, backend: &mut dyn RenderBackend) -> Option { + self.library.get_bitmap(id).and_then(|bitmap| { + bitmap + .bitmap_data() + .write(self.gc_context) + .bitmap_handle(backend) + }) } } diff --git a/render/src/bitmap.rs b/render/src/bitmap.rs index e6de76135..31120a775 100644 --- a/render/src/bitmap.rs +++ b/render/src/bitmap.rs @@ -102,33 +102,19 @@ impl Bitmap { pub fn data_mut(&mut self) -> &mut [u8] { &mut self.data } -} -impl From for Vec { - fn from(bitmap: Bitmap) -> Self { - match bitmap.format { - BitmapFormat::Rgb => bitmap - .data - .chunks_exact(3) - .map(|chunk| { - let red = chunk[0]; - let green = chunk[1]; - let blue = chunk[2]; - i32::from_le_bytes([blue, green, red, 0xFF]) - }) - .collect(), - BitmapFormat::Rgba => bitmap - .data - .chunks_exact(4) - .map(|chunk| { - let red = chunk[0]; - let green = chunk[1]; - let blue = chunk[2]; - let alpha = chunk[3]; - i32::from_le_bytes([blue, green, red, alpha]) - }) - .collect(), - } + pub fn as_colors(&self) -> impl Iterator + '_ { + let chunks = match self.format { + BitmapFormat::Rgb => self.data.chunks_exact(3), + BitmapFormat::Rgba => self.data.chunks_exact(4), + }; + chunks.map(|chunk| { + let red = chunk[0]; + let green = chunk[1]; + let blue = chunk[2]; + let alpha = chunk.get(3).copied().unwrap_or(0xFF); + i32::from_le_bytes([blue, green, red, alpha]) + }) } }