Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ png = { version = "0.18.0", optional = true }
flv-rs = { path = "../flv" }
async-channel = { workspace = true }
jpegxr = { git = "https://github.com/ruffle-rs/jpegxr", rev = "2a429b0d71ab416e10b73d4dbdcf34cfe2900395", optional = true }
image = { workspace = true, features = ["tiff"] }
image = { workspace = true, features = ["tiff", "png", "jpeg"] }
enum-map = { workspace = true }
ttf-parser = "0.25"
num-bigint = "0.4"
Expand Down
1 change: 1 addition & 0 deletions core/common/src/avm_string/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ define_common_strings! {
str_error: b"error",
str_extension: b"extension",
str_false: b"false",
str_fastCompression: b"fastCompression",
str_flushed: b"flushed",
str_focusEnabled: b"focusEnabled",
str_fontStyle: b"fontStyle",
Expand Down
6 changes: 6 additions & 0 deletions core/src/avm2/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ pub struct SystemClasses<'gc> {
pub workerdomain: ClassObject<'gc>,
pub messagechannel: ClassObject<'gc>,
pub securitydomain: ClassObject<'gc>,
pub jpegencoder_options: ClassObject<'gc>,
pub pngencoder_options: ClassObject<'gc>,
}

#[derive(Clone, Collect)]
Expand Down Expand Up @@ -357,6 +359,8 @@ impl<'gc> SystemClasses<'gc> {
workerdomain: object,
messagechannel: object,
securitydomain: object,
jpegencoder_options: object,
pngencoder_options: object,
}
}
}
Expand Down Expand Up @@ -696,6 +700,8 @@ pub fn init_native_system_classes(activation: &mut Activation<'_, '_>) {
("flash.display", "Sprite", sprite),
("flash.display", "Stage", stage),
("flash.display", "Stage3D", stage3d),
("flash.display", "JPEGEncoderOptions", jpegencoder_options),
("flash.display", "PNGEncoderOptions", pngencoder_options),
("flash.display3D", "Context3D", context3d),
("flash.display3D", "IndexBuffer3D", indexbuffer3d),
("flash.display3D", "Program3D", program3d),
Expand Down
5 changes: 1 addition & 4 deletions core/src/avm2/globals/flash/display/BitmapData.as
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,6 @@ package flash.display {
}

[API("680")]
public function encode(rect:Rectangle, compressor:Object, byteArray:ByteArray = null):ByteArray {
stub_method("flash.display.BitmapData", "encode");
return null;
}
public native function encode(rect:Rectangle, compressor:Object, byteArray:ByteArray = null):ByteArray;
}
}
157 changes: 157 additions & 0 deletions core/src/avm2/globals/flash/display/bitmap_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
use crate::character::{Character, CompressedBitmap};
use crate::ecma_conversions::round_to_even;
use crate::swf::BlendMode;
use image::ImageEncoder;
use ruffle_macros::istr;
use ruffle_render::backend::RenderBackend;
use ruffle_render::bitmap::PixelRegion;
use ruffle_render::filters::Filter;
use ruffle_render::transform::Transform;
use std::str::FromStr;
Expand Down Expand Up @@ -1623,3 +1627,156 @@

Ok(Value::Undefined)
}

/// Implements `BitmapData.encode`
pub fn encode<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Value<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let this = this.as_object().unwrap();

if let Some(bitmap_data) = this.as_bitmap_data() {
bitmap_data.check_valid(activation)?;

let rectangle = args.get_object(activation, 0, "rect")?;
let (x, y, width, height) = get_rectangle_x_y_width_height(activation, rectangle)?;

let compressor = args.get_object(activation, 1, "compressor")?;

// Get or create the output ByteArray
let output_bytearray = if let Some(ba) = args.try_get_object(2) {
ba.as_bytearray_object().unwrap()
} else {
let storage = ByteArrayStorage::new(activation.context);
ByteArrayObject::from_storage(activation.context, storage)

Check warning on line 1652 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (1651–1652)
};

let jpeg_encoder_class = activation.avm2().classes().jpegencoder_options;
let png_encoder_class = activation.avm2().classes().pngencoder_options;

let options = if compressor.is_of_type(jpeg_encoder_class.inner_class_definition()) {
let quality = Value::from(compressor)
.get_public_property(istr!("quality"), activation)?
.coerce_to_u32(activation)?
.clamp(1, 100) as u8;

Check warning on line 1662 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (1659–1662)

EncodeOptions::Jpeg { quality }

Check warning on line 1664 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1664)
} else if compressor.is_of_type(png_encoder_class.inner_class_definition()) {
let fast_compression = Value::from(compressor)
.get_public_property(istr!("fastCompression"), activation)?
.coerce_to_boolean();

EncodeOptions::Png { fast_compression }
} else {
avm2_stub_method!(

Check warning on line 1672 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1672)
activation,
"flash.display.BitmapData",
"encode",
"with JPEGXREncoderOptions"
);
return Ok(Value::Null);

Check warning on line 1678 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1678)
};

encode_internal(
bitmap_data,
activation.context.renderer,
x,
y,
width,
height,
options,
&mut output_bytearray.storage_mut(),
)
.unwrap();

return Ok(output_bytearray.into());
}

Check warning on line 1694 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1694)

Ok(Value::Null)

Check warning on line 1696 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1696)
}

enum EncodeOptions {
Jpeg { quality: u8 },
Png { fast_compression: bool },
}

#[expect(clippy::too_many_arguments)]
fn encode_internal(
bitmap: BitmapData,
renderer: &mut dyn RenderBackend,
x: i32,
y: i32,
width: i32,
height: i32,
options: EncodeOptions,
bytearray: &mut ByteArrayStorage,
) -> Result<(), image::ImageError> {
let mut region = PixelRegion::for_region_i32(x, y, width, height);

region.clamp(bitmap.width(), bitmap.height());

if region.width() == 0 || region.height() == 0 {
return Ok(());

Check warning on line 1720 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1720)
}

let read = bitmap.read_area(region, renderer);

match options {
EncodeOptions::Jpeg { quality } => {
let mut rgb_data = Vec::with_capacity((region.width() * region.height() * 3) as usize);

Check warning on line 1727 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (1726–1727)

for y in region.y_min..region.y_max {
for x in region.x_min..region.x_max {
let color = read.get_pixel32_raw(x, y).to_un_multiplied_alpha();
rgb_data.push(color.red());
rgb_data.push(color.green());
rgb_data.push(color.blue());
}

Check warning on line 1735 in core/src/avm2/globals/flash/display/bitmap_data.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (1729–1735)
}

let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(bytearray, quality);

encoder.write_image(
&rgb_data,
region.width(),
region.height(),
image::ExtendedColorType::Rgb8,
)?;
}
EncodeOptions::Png { fast_compression } => {
let mut rgba_data = Vec::with_capacity((region.width() * region.height() * 4) as usize);

for y in region.y_min..region.y_max {
for x in region.x_min..region.x_max {
let color = read.get_pixel32_raw(x, y).to_un_multiplied_alpha();
rgba_data.push(color.red());
rgba_data.push(color.green());
rgba_data.push(color.blue());
rgba_data.push(color.alpha());
}
}

let compression = if fast_compression {
image::codecs::png::CompressionType::Fast
} else {
image::codecs::png::CompressionType::Default
};

let encoder = image::codecs::png::PngEncoder::new_with_quality(
bytearray,
compression,
image::codecs::png::FilterType::Adaptive,
);

encoder.write_image(
&rgba_data,
region.width(),
region.height(),
image::ExtendedColorType::Rgba8,
)?;
}
};

Ok(())
}
Loading