avm2: Implement `BitmapData`'s constructor.

This includes support for both embedded bitmap data (resolved via the SymbolClass mechanism) as well as empty bitmaps configured via arguments.
This commit is contained in:
David Wendt 2021-09-05 17:32:14 -04:00 committed by Mike Welsh
parent e5151d147d
commit 335aec5be0
3 changed files with 140 additions and 34 deletions

View File

@ -7,41 +7,11 @@ use crate::avm1::object::bitmap_data::BitmapDataObject;
use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::property_decl::{define_properties_on, Declaration};
use crate::avm1::{Object, TObject, Value}; use crate::avm1::{Object, TObject, Value};
use crate::bitmap::bitmap_data::{BitmapData, ChannelOptions, Color}; use crate::bitmap::bitmap_data::{BitmapData, ChannelOptions, Color};
use crate::bitmap::is_size_valid;
use crate::character::Character; use crate::character::Character;
use crate::display_object::TDisplayObject; use crate::display_object::TDisplayObject;
use gc_arena::{GcCell, MutationContext}; 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! { const PROTO_DECLS: &[Declaration] = declare_properties! {
"height" => property(height); "height" => property(height);
"width" => property(width); "width" => property(width);

View File

@ -4,19 +4,109 @@ use crate::avm2::activation::Activation;
use crate::avm2::class::{Class, ClassAttributes}; use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::method::Method; use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName}; 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::value::Value;
use crate::avm2::Error; 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}; use gc_arena::{GcCell, MutationContext};
/// Implements `flash.display.BitmapData`'s instance constructor. /// Implements `flash.display.BitmapData`'s instance constructor.
pub fn instance_init<'gc>( pub fn instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>, this: Option<Object<'gc>>,
_args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> { ) -> Result<Value<'gc>, Error> {
if let Some(this) = this { if let Some(this) = this {
activation.super_init(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<i32> = 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) Ok(Value::Undefined)
@ -34,7 +124,7 @@ pub fn class_init<'gc>(
/// Construct `BitmapData`'s class. /// Construct `BitmapData`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
let class = Class::new( 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()), Some(QName::new(Namespace::package(""), "Object").into()),
Method::from_builtin(instance_init, "<BitmapData instance initializer>", mc), Method::from_builtin(instance_init, "<BitmapData instance initializer>", mc),
Method::from_builtin(class_init, "<BitmapData class initializer>", mc), Method::from_builtin(class_init, "<BitmapData class initializer>", mc),

View File

@ -1,3 +1,49 @@
pub mod bitmap_data; pub mod bitmap_data;
pub mod color_transform_params; pub mod color_transform_params;
pub mod turbulence; 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
}