diff --git a/core/src/avm1/globals/bitmap_data.rs b/core/src/avm1/globals/bitmap_data.rs index 05840a425..8c9942004 100644 --- a/core/src/avm1/globals/bitmap_data.rs +++ b/core/src/avm1/globals/bitmap_data.rs @@ -7,41 +7,11 @@ use crate::avm1::object::bitmap_data::BitmapDataObject; use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::{Object, TObject, Value}; use crate::bitmap::bitmap_data::{BitmapData, ChannelOptions, Color}; +use crate::bitmap::is_size_valid; use crate::character::Character; use crate::display_object::TDisplayObject; use gc_arena::{GcCell, MutationContext}; -fn is_size_valid(swf_version: u8, width: u32, height: u32) -> bool { - // From https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html: - // "In AIR 1.5 and Flash Player 10, the maximum size for a BitmapData object is 8,191 pixels in - // width or height, and the total number of pixels cannot exceed 16,777,215 pixels. (So, if a - // BitmapData object is 8,191 pixels wide, it can only be 2,048 pixels high.) In Flash Player 9 - // and earlier and AIR 1.1 and earlier, the limitation is 2,880 pixels in height and 2,880 in width. - // Starting with AIR 3 and Flash player 11, the size limits for a BitmapData object have been removed. - // The maximum size of a bitmap is now dependent on the operating system." - // - // In addition, width and height of 0 are invalid in all versions. - if width == 0 || height == 0 { - return false; - } - if swf_version <= 9 { - if width > 2880 || height > 2880 { - return false; - } - } else if swf_version <= 12 { - if width >= 0x2000 || height >= 0x2000 || width * height >= 0x1000000 { - return false; - } - } else { - // These limits are undocumented, but seem to be reliable. - // TODO: Do they vary across different machines? - if width > 0x6666666 || height > 0x6666666 || width as u64 * height as u64 >= 0x20000000 { - return false; - } - } - true -} - const PROTO_DECLS: &[Declaration] = declare_properties! { "height" => property(height); "width" => property(width); diff --git a/core/src/avm2/globals/flash/display/bitmapdata.rs b/core/src/avm2/globals/flash/display/bitmapdata.rs index ada1e4d83..154a21442 100644 --- a/core/src/avm2/globals/flash/display/bitmapdata.rs +++ b/core/src/avm2/globals/flash/display/bitmapdata.rs @@ -4,19 +4,109 @@ use crate::avm2::activation::Activation; use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::method::Method; use crate::avm2::names::{Namespace, QName}; -use crate::avm2::object::{bitmapdata_allocator, Object}; +use crate::avm2::object::{bitmapdata_allocator, Object, TObject}; use crate::avm2::value::Value; use crate::avm2::Error; +use crate::bitmap::bitmap_data::BitmapData; +use crate::bitmap::is_size_valid; +use crate::character::Character; use gc_arena::{GcCell, MutationContext}; /// Implements `flash.display.BitmapData`'s instance constructor. pub fn instance_init<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: Option>, - _args: &[Value<'gc>], + args: &[Value<'gc>], ) -> Result, Error> { if let Some(this) = this { activation.super_init(this, &[])?; + + let name = this + .as_class_object() + .and_then(|t| t.as_class()) + .map(|c| c.read().name().clone()); + let character = this + .as_class_object() + .and_then(|t| { + activation + .context + .library + .avm2_class_registry() + .class_symbol(t) + }) + .and_then(|(movie, chara_id)| { + activation + .context + .library + .library_for_movie_mut(movie) + .character_by_id(chara_id) + .cloned() + }); + + let new_bitmap_data = + GcCell::allocate(activation.context.gc_context, BitmapData::default()); + + if let Some(Character::Bitmap(bd)) = character { + let bitmap_handle = bd.bitmap_handle(); + + if let Some(bitmap_pixels) = + activation.context.renderer.get_bitmap_pixels(bitmap_handle) + { + let bitmap_pixels: Vec = bitmap_pixels.data.into(); + new_bitmap_data + .write(activation.context.gc_context) + .set_pixels( + bd.width().into(), + bd.height().into(), + true, + bitmap_pixels.into_iter().map(|p| p.into()).collect(), + ); + } else { + log::warn!( + "Could not read bitmap data associated with class {:?}", + name + ); + } + } else { + if character.is_some() { + //TODO: Determine if mismatched symbols will still work as a + //regular BitmapData subclass, or if this should throw + log::warn!( + "BitmapData subclass {:?} is associated with a non-bitmap symbol", + name + ); + } + + let width = args + .get(0) + .unwrap_or(&Value::Undefined) + .coerce_to_i32(activation)? as u32; + let height = args + .get(1) + .unwrap_or(&Value::Undefined) + .coerce_to_i32(activation)? as u32; + let transparency = args + .get(2) + .unwrap_or(&Value::Bool(true)) + .coerce_to_boolean(); + let fill_color = args + .get(3) + .unwrap_or(&Value::Unsigned(0xFFFFFFFF)) + .coerce_to_u32(activation)?; + + if !is_size_valid(activation.context.swf.version(), width, height) { + return Err("Bitmap size is not valid".into()); + } + + new_bitmap_data + .write(activation.context.gc_context) + .init_pixels(width, height, transparency, fill_color as i32); + } + + new_bitmap_data + .write(activation.context.gc_context) + .init_object2(this); + this.init_bitmap_data(activation.context.gc_context, new_bitmap_data); } Ok(Value::Undefined) @@ -34,7 +124,7 @@ pub fn class_init<'gc>( /// Construct `BitmapData`'s class. pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { let class = Class::new( - QName::new(Namespace::package("flash.media"), "BitmapData"), + QName::new(Namespace::package("flash.display"), "BitmapData"), Some(QName::new(Namespace::package(""), "Object").into()), Method::from_builtin(instance_init, "", mc), Method::from_builtin(class_init, "", mc), diff --git a/core/src/bitmap.rs b/core/src/bitmap.rs index 61764192e..b02f315df 100644 --- a/core/src/bitmap.rs +++ b/core/src/bitmap.rs @@ -1,3 +1,49 @@ pub mod bitmap_data; pub mod color_transform_params; pub mod turbulence; + +/// Determine if a particular bitmap data size is valid. +/// +/// This enforces limits on BitmapData as specified in the Flash documentation. +/// Specifically, from https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/BitmapData.html: +/// +/// "In AIR 1.5 and Flash Player 10, the maximum size for a BitmapData object +/// is 8,191 pixels in width or height, and the total number of pixels cannot +/// exceed 16,777,215 pixels. (So, if a BitmapData object is 8,191 pixels wide, +/// it can only be 2,048 pixels high.) In Flash Player 9 and earlier and +/// AIR 1.1 and earlier, the limitation is 2,880 pixels in height and 2,880 in +/// width. Starting with AIR 3 and Flash player 11, the size limits for a +/// BitmapData object have been removed. The maximum size of a bitmap is now +/// dependent on the operating system." +/// +/// In addition, we found the following undocumented limits: +/// +/// - Width and height of 0 are invalid in all versions +/// - Widths and heights exceeding 0x666666 are invalid in all versions +/// - Pixel counts (of any width/height) exceeding 0x20000000 pixels +/// +/// All of these are curently enforced. +pub fn is_size_valid(swf_version: u8, width: u32, height: u32) -> bool { + // From : + // + // In addition, width and height of 0 are invalid in all versions. + if width == 0 || height == 0 { + return false; + } + if swf_version <= 9 { + if width > 2880 || height > 2880 { + return false; + } + } else if swf_version <= 12 { + if width >= 0x2000 || height >= 0x2000 || width * height >= 0x1000000 { + return false; + } + } else { + // These limits are undocumented, but seem to be reliable. + // TODO: Do they vary across different machines? + if width > 0x6666666 || height > 0x6666666 || width as u64 * height as u64 >= 0x20000000 { + return false; + } + } + true +}