avm2: Fix rounding behavior in BitmapData rectangle operations

Flash Player performs `x + width` and `y + height` as floating
point operations before `round_to_even`. This affects the extent
covered by a `Rectangle` in various BitmapData methods, as the sum
of two values might be large enough to be rounded up to a larger
value (when rounding `x` and `width` individually would have
produced a smaller overall extent).
This commit is contained in:
Aaron Hill 2023-05-28 16:53:46 -05:00
parent 2ae15b05e8
commit 0955ab40e6
6 changed files with 74 additions and 5 deletions

View File

@ -17,30 +17,45 @@ use crate::bitmap::{is_size_valid, operations};
use crate::character::Character;
use crate::display_object::Bitmap;
use crate::display_object::TDisplayObject;
use crate::ecma_conversions::round_to_even;
use crate::swf::BlendMode;
use gc_arena::GcCell;
use ruffle_render::filters::Filter;
use ruffle_render::transform::Transform;
use std::str::FromStr;
// Computes the integer x,y,width,height values from
// the given `Rectangle`. This method performs `x + width`
// and `y + height` as floating point operations before
// `round_to_even`, which is needed to match Flash Player's
// rounding behavior.
fn get_rectangle_x_y_width_height<'gc>(
activation: &mut Activation<'_, 'gc>,
rectangle: Object<'gc>,
) -> Result<(i32, i32, i32, i32), Error<'gc>> {
let x = rectangle
.get_public_property("x", activation)?
.coerce_to_i32(activation)?;
.coerce_to_number(activation)?;
let y = rectangle
.get_public_property("y", activation)?
.coerce_to_i32(activation)?;
.coerce_to_number(activation)?;
let width = rectangle
.get_public_property("width", activation)?
.coerce_to_i32(activation)?;
.coerce_to_number(activation)?;
let height = rectangle
.get_public_property("height", activation)?
.coerce_to_i32(activation)?;
.coerce_to_number(activation)?;
Ok((x, y, width, height))
let x_max = round_to_even(x + width);
let y_max = round_to_even(y + height);
let x_int = round_to_even(x);
let y_int = round_to_even(y);
let width_int = x_max - x_int;
let height_int = y_max - y_int;
Ok((x_int, y_int, width_int, height_int))
}
/// Copy the static data from a given Bitmap into a new BitmapData.

View File

@ -0,0 +1,37 @@
package {
import flash.display.BitmapData;
import flash.geom.Rectangle;
public class Test {
public function Test() {
var otherData = new BitmapData(5, 5, true, 0);
otherData.fillRect(new Rectangle(1.6, 1.6, 1.8, 2), 0xFFFFFFFF);
trace("First fillRect");
print(otherData);
otherData.fillRect(new Rectangle(0.1, 0, 1.8, 2.5), 0xFFaabbcc);
trace("Second fillRect");
print(otherData);
var bytes = otherData.getPixels(new Rectangle(0.5, 0.5, 2.5, 2.5));
bytes.position = 0;
trace("Bytes len: " + bytes.length);
var byteString = "";
while (bytes.bytesAvailable != 0) {
byteString += bytes.readUnsignedInt().toString(16) + " ";
}
trace("Bytes: " + byteString);
}
private function print(data: BitmapData) {
for (var y = 0; y < data.height; y++) {
var line = "";
for (var x = 0; x < data.width; x++) {
line += data.getPixel32(x, y).toString(16) + " "
}
trace(line)
}
trace()
}
}
}

View File

@ -0,0 +1,16 @@
First fillRect
0 0 0 0 0
0 0 0 0 0
0 0 ffffffff 0 0
0 0 ffffffff 0 0
0 0 0 0 0
Second fillRect
ffaabbcc ffaabbcc 0 0 0
ffaabbcc ffaabbcc 0 0 0
0 0 ffffffff 0 0
0 0 ffffffff 0 0
0 0 0 0 0
Bytes len: 36
Bytes: ffaabbcc ffaabbcc 0 ffaabbcc ffaabbcc 0 0 0 ffffffff

View File

@ -0,0 +1 @@
num_frames = 1