core,avm1: Move BitmapData implementation into core::bitmap from core::avm1::object
This enables exposing it to AVM2 as well in the future.
This commit is contained in:
parent
27e06af003
commit
2048bb9887
|
@ -1,11 +1,12 @@
|
|||
//! flash.display.BitmapData object
|
||||
|
||||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::error::Error;
|
||||
use crate::avm1::function::{Executable, FunctionObject};
|
||||
use crate::avm1::object::bitmap_data::{BitmapDataObject, ChannelOptions, Color};
|
||||
use crate::avm1::object::bitmap_data::BitmapDataObject;
|
||||
use crate::avm1::property_decl::{define_properties_on, Declaration};
|
||||
use crate::avm1::{activation::Activation, object::bitmap_data::BitmapData};
|
||||
use crate::avm1::{Object, TObject, Value};
|
||||
use crate::bitmap::bitmap_data::{BitmapData, ChannelOptions, Color};
|
||||
use crate::character::Character;
|
||||
use crate::display_object::TDisplayObject;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
|
|
|
@ -3,850 +3,8 @@ use crate::avm1::{Object, ScriptObject, TObject};
|
|||
use crate::impl_custom_object;
|
||||
use gc_arena::{Collect, GcCell, MutationContext};
|
||||
|
||||
use crate::avm1::object::color_transform_object::ColorTransformObject;
|
||||
use crate::backend::render::{BitmapHandle, RenderBackend};
|
||||
use crate::bitmap::turbulence::Turbulence;
|
||||
use bitflags::bitflags;
|
||||
use downcast_rs::__std::fmt::Formatter;
|
||||
use crate::bitmap::bitmap_data::BitmapData;
|
||||
use std::fmt;
|
||||
use std::ops::Range;
|
||||
|
||||
/// An implementation of the Lehmer/Park-Miller random number generator
|
||||
/// Uses the fixed parameters m = 2,147,483,647 and a = 16,807
|
||||
pub struct LehmerRng {
|
||||
x: u32,
|
||||
}
|
||||
|
||||
impl LehmerRng {
|
||||
pub fn with_seed(seed: u32) -> Self {
|
||||
Self { x: seed }
|
||||
}
|
||||
|
||||
/// Generate the next value in the sequence via the following formula
|
||||
/// X_(k+1) = a * X_k mod m
|
||||
pub fn gen(&mut self) -> u32 {
|
||||
self.x = ((self.x as u64).overflowing_mul(16_807).0 % 2_147_483_647) as u32;
|
||||
self.x
|
||||
}
|
||||
|
||||
pub fn gen_range(&mut self, rng: Range<u8>) -> u8 {
|
||||
rng.start + (self.gen() % ((rng.end - rng.start) as u32 + 1)) as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct Color(i32);
|
||||
|
||||
impl Color {
|
||||
pub fn blue(&self) -> u8 {
|
||||
(self.0 & 0xFF) as u8
|
||||
}
|
||||
|
||||
pub fn green(&self) -> u8 {
|
||||
((self.0 >> 8) & 0xFF) as u8
|
||||
}
|
||||
|
||||
pub fn red(&self) -> u8 {
|
||||
((self.0 >> 16) & 0xFF) as u8
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> u8 {
|
||||
((self.0 >> 24) & 0xFF) as u8
|
||||
}
|
||||
|
||||
pub fn to_premultiplied_alpha(self, transparency: bool) -> Self {
|
||||
// This has some accuracy issues with some alpha values
|
||||
|
||||
let old_alpha = if transparency { self.alpha() } else { 255 };
|
||||
|
||||
let a = old_alpha as f64 / 255.0;
|
||||
|
||||
let r = (self.red() as f64 * a).round() as u8;
|
||||
let g = (self.green() as f64 * a).round() as u8;
|
||||
let b = (self.blue() as f64 * a).round() as u8;
|
||||
|
||||
Self::argb(old_alpha, r, g, b)
|
||||
}
|
||||
|
||||
pub fn to_un_multiplied_alpha(self) -> Self {
|
||||
let a = self.alpha() as f64 / 255.0;
|
||||
|
||||
let r = (self.red() as f64 / a).round() as u8;
|
||||
let g = (self.green() as f64 / a).round() as u8;
|
||||
let b = (self.blue() as f64 / a).round() as u8;
|
||||
|
||||
Self::argb(self.alpha(), r, g, b)
|
||||
}
|
||||
|
||||
pub fn argb(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
|
||||
Self(i32::from_le_bytes([blue, green, red, alpha]))
|
||||
}
|
||||
|
||||
pub fn with_alpha(&self, alpha: u8) -> Self {
|
||||
Self::argb(alpha, self.red(), self.green(), self.blue())
|
||||
}
|
||||
|
||||
pub fn blend_over(&self, source: &Self) -> Self {
|
||||
let sa = source.alpha();
|
||||
|
||||
let r = source.red() + ((self.red() as u16 * (255 - sa as u16)) >> 8) as u8;
|
||||
let g = source.green() + ((self.green() as u16 * (255 - sa as u16)) >> 8) as u8;
|
||||
let b = source.blue() + ((self.blue() as u16 * (255 - sa as u16)) >> 8) as u8;
|
||||
let a = source.alpha() + ((self.alpha() as u16 * (255 - sa as u16)) >> 8) as u8;
|
||||
Self::argb(a, r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format!("{:#x}", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for i32 {
|
||||
fn from(c: Color) -> Self {
|
||||
c.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for u32 {
|
||||
fn from(c: Color) -> Self {
|
||||
c.0 as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Color {
|
||||
fn from(i: i32) -> Self {
|
||||
Color(i)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ChannelOptions: u8 {
|
||||
const RED = 1 << 0;
|
||||
const GREEN = 1 << 1;
|
||||
const BLUE = 1 << 2;
|
||||
const ALPHA = 1 << 3;
|
||||
const RGB = Self::RED.bits | Self::GREEN.bits | Self::BLUE.bits;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Collect, Default, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub struct BitmapData {
|
||||
/// The pixels in the bitmap, stored as a array of pre-multiplied ARGB colour values
|
||||
pub pixels: Vec<Color>,
|
||||
dirty: bool,
|
||||
width: u32,
|
||||
height: u32,
|
||||
transparency: bool,
|
||||
|
||||
bitmap_handle: Option<BitmapHandle>,
|
||||
}
|
||||
|
||||
impl BitmapData {
|
||||
pub fn init_pixels(&mut self, width: u32, height: u32, transparency: bool, fill_color: i32) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self.transparency = transparency;
|
||||
self.pixels = vec![
|
||||
Color(fill_color).to_premultiplied_alpha(self.transparency());
|
||||
width as usize * height as usize
|
||||
];
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn dispose(&mut self) {
|
||||
self.width = 0;
|
||||
self.height = 0;
|
||||
self.pixels.clear();
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn bitmap_handle(&mut self, renderer: &mut dyn RenderBackend) -> Option<BitmapHandle> {
|
||||
if self.bitmap_handle.is_none() {
|
||||
let bitmap_handle =
|
||||
renderer.register_bitmap_raw(self.width(), self.height(), self.pixels_rgba());
|
||||
if let Err(e) = &bitmap_handle {
|
||||
log::warn!("Failed to register raw bitmap for BitmapData: {:?}", e);
|
||||
}
|
||||
self.bitmap_handle = bitmap_handle.ok();
|
||||
}
|
||||
|
||||
self.bitmap_handle
|
||||
}
|
||||
|
||||
pub fn transparency(&self) -> bool {
|
||||
self.transparency
|
||||
}
|
||||
|
||||
pub fn set_transparency(&mut self, transparency: bool) {
|
||||
self.transparency = transparency;
|
||||
}
|
||||
|
||||
pub fn dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
pub fn set_dirty(&mut self, dirty: bool) {
|
||||
self.dirty = dirty;
|
||||
}
|
||||
|
||||
pub fn pixels(&self) -> &[Color] {
|
||||
&self.pixels
|
||||
}
|
||||
|
||||
pub fn set_pixels(&mut self, width: u32, height: u32, transparency: bool, pixels: Vec<Color>) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self.transparency = transparency;
|
||||
self.pixels = pixels;
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn pixels_rgba(&self) -> Vec<u8> {
|
||||
// TODO: This could have been implemented as follows:
|
||||
//
|
||||
// self.pixels
|
||||
// .iter()
|
||||
// .flat_map(|p| [p.red(), p.green(), p.blue(), p.alpha()])
|
||||
// .collect()
|
||||
//
|
||||
// But currently Rust emits suboptimal code in that case. For now we use
|
||||
// `Vec::with_capacity` manually to avoid unnecessary re-allocations.
|
||||
|
||||
let mut output = Vec::with_capacity(self.pixels.len() * 4);
|
||||
for p in &self.pixels {
|
||||
output.extend_from_slice(&[p.red(), p.green(), p.blue(), p.alpha()])
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {
|
||||
x >= 0 && x < self.width() as i32 && y >= 0 && y < self.height() as i32
|
||||
}
|
||||
|
||||
pub fn get_pixel_raw(&self, x: u32, y: u32) -> Option<Color> {
|
||||
if x >= self.width() || y >= self.height() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.pixels.get((x + y * self.width()) as usize).copied()
|
||||
}
|
||||
|
||||
pub fn get_pixel32(&self, x: i32, y: i32) -> Color {
|
||||
self.get_pixel_raw(x as u32, y as u32)
|
||||
.map(|f| f.to_un_multiplied_alpha())
|
||||
.unwrap_or_else(|| 0.into())
|
||||
}
|
||||
|
||||
pub fn get_pixel(&self, x: i32, y: i32) -> i32 {
|
||||
if self.is_point_in_bounds(x, y) {
|
||||
self.get_pixel32(x, y).with_alpha(0x0).into()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_pixel(&mut self, x: u32, y: u32, color: Color) {
|
||||
let current_alpha = self.get_pixel_raw(x, y).map(|p| p.alpha()).unwrap_or(0);
|
||||
self.set_pixel32(x as i32, y as i32, color.with_alpha(current_alpha));
|
||||
}
|
||||
|
||||
pub fn set_pixel32_raw(&mut self, x: u32, y: u32, color: Color) {
|
||||
let width = self.width();
|
||||
self.pixels[(x + y * width) as usize] = color;
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn set_pixel32(&mut self, x: i32, y: i32, color: Color) {
|
||||
if self.is_point_in_bounds(x, y) {
|
||||
self.set_pixel32_raw(
|
||||
x as u32,
|
||||
y as u32,
|
||||
color.to_premultiplied_alpha(self.transparency()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_rect(&mut self, x: u32, y: u32, width: u32, height: u32, color: Color) {
|
||||
for x_offset in 0..width {
|
||||
for y_offset in 0..height {
|
||||
self.set_pixel32((x + x_offset) as i32, (y + y_offset) as i32, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flood_fill(&mut self, x: u32, y: u32, replace_color: Color) {
|
||||
let expected_color = self.get_pixel_raw(x, y).unwrap_or_else(|| 0.into());
|
||||
|
||||
let mut pending = vec![(x, y)];
|
||||
|
||||
while !pending.is_empty() {
|
||||
if let Some((x, y)) = pending.pop() {
|
||||
if let Some(old_color) = self.get_pixel_raw(x, y) {
|
||||
if old_color == expected_color {
|
||||
if x > 0 {
|
||||
pending.push((x - 1, y));
|
||||
}
|
||||
if y > 0 {
|
||||
pending.push((x, y - 1));
|
||||
}
|
||||
if x < self.width() - 1 {
|
||||
pending.push((x + 1, y))
|
||||
}
|
||||
if y < self.height() - 1 {
|
||||
pending.push((x, y + 1));
|
||||
}
|
||||
self.set_pixel32_raw(x, y, replace_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn noise(
|
||||
&mut self,
|
||||
seed: i32,
|
||||
low: u8,
|
||||
high: u8,
|
||||
channel_options: ChannelOptions,
|
||||
gray_scale: bool,
|
||||
) {
|
||||
let true_seed = if seed <= 0 {
|
||||
(-seed + 1) as u32
|
||||
} else {
|
||||
seed as u32
|
||||
};
|
||||
|
||||
let mut rng = LehmerRng::with_seed(true_seed);
|
||||
|
||||
for y in 0..self.height() {
|
||||
for x in 0..self.width() {
|
||||
let pixel_color = if gray_scale {
|
||||
let gray = rng.gen_range(low..high);
|
||||
let alpha = if channel_options.contains(ChannelOptions::ALPHA) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
255
|
||||
};
|
||||
|
||||
Color::argb(alpha, gray, gray, gray)
|
||||
} else {
|
||||
let r = if channel_options.contains(ChannelOptions::RED) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let g = if channel_options.contains(ChannelOptions::GREEN) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let b = if channel_options.contains(ChannelOptions::BLUE) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let a = if channel_options.contains(ChannelOptions::ALPHA) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
255
|
||||
};
|
||||
|
||||
Color::argb(a, r, g, b)
|
||||
};
|
||||
|
||||
self.set_pixel32_raw(x, y, pixel_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_channel(
|
||||
&mut self,
|
||||
dest_point: (u32, u32),
|
||||
src_rect: (u32, u32, u32, u32),
|
||||
source_bitmap: &Self,
|
||||
source_channel: i32,
|
||||
dest_channel: i32,
|
||||
) {
|
||||
let (min_x, min_y) = dest_point;
|
||||
let (src_min_x, src_min_y, src_max_x, src_max_y) = src_rect;
|
||||
|
||||
for x in src_min_x.max(0)..src_max_x.min(source_bitmap.width()) {
|
||||
for y in src_min_y.max(0)..src_max_y.min(source_bitmap.height()) {
|
||||
if self.is_point_in_bounds((x + min_x) as i32, (y + min_y) as i32) {
|
||||
let original_color: u32 = self
|
||||
.get_pixel_raw((x + min_x) as u32, (y + min_y) as u32)
|
||||
.unwrap_or_else(|| 0.into())
|
||||
.into();
|
||||
let source_color: u32 = source_bitmap
|
||||
.get_pixel_raw(x, y)
|
||||
.unwrap_or_else(|| 0.into())
|
||||
.into();
|
||||
|
||||
let channel_shift: u32 = match source_channel {
|
||||
// red
|
||||
1 => 16,
|
||||
// green
|
||||
2 => 8,
|
||||
// blue
|
||||
4 => 0,
|
||||
// alpha
|
||||
8 => 24,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
let source_part = (source_color >> channel_shift) & 0xFF;
|
||||
|
||||
let result_color: u32 = match dest_channel {
|
||||
// red
|
||||
1 => (original_color & 0xFF00FFFF) | source_part << 16,
|
||||
// green
|
||||
2 => (original_color & 0xFFFF00FF) | source_part << 8,
|
||||
// blue
|
||||
4 => (original_color & 0xFFFFFF00) | source_part,
|
||||
// alpha
|
||||
8 => (original_color & 0x00FFFFFF) | source_part << 24,
|
||||
_ => original_color,
|
||||
};
|
||||
|
||||
self.set_pixel32_raw(
|
||||
(x + min_x) as u32,
|
||||
(y + min_y) as u32,
|
||||
(result_color as i32).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color_transform(
|
||||
&mut self,
|
||||
min_x: u32,
|
||||
min_y: u32,
|
||||
end_x: u32,
|
||||
end_y: u32,
|
||||
color_transform: ColorTransformObject,
|
||||
) {
|
||||
for x in min_x..end_x.min(self.width()) {
|
||||
for y in min_y..end_y.min(self.height()) {
|
||||
let color = self
|
||||
.get_pixel_raw(x, y)
|
||||
.unwrap_or_else(|| 0.into())
|
||||
.to_un_multiplied_alpha();
|
||||
|
||||
let alpha = ((color.alpha() as f32 * color_transform.get_alpha_multiplier() as f32)
|
||||
+ color_transform.get_alpha_offset() as f32) as u8;
|
||||
let red = ((color.red() as f32 * color_transform.get_red_multiplier() as f32)
|
||||
+ color_transform.get_red_offset() as f32) as u8;
|
||||
let green = ((color.green() as f32 * color_transform.get_green_multiplier() as f32)
|
||||
+ color_transform.get_green_offset() as f32) as u8;
|
||||
let blue = ((color.blue() as f32 * color_transform.get_blue_multiplier() as f32)
|
||||
+ color_transform.get_blue_offset() as f32) as u8;
|
||||
|
||||
self.set_pixel32_raw(
|
||||
x,
|
||||
y,
|
||||
Color::argb(alpha, red, green, blue)
|
||||
.to_premultiplied_alpha(self.transparency()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color_bounds_rect(
|
||||
&self,
|
||||
find_color: bool,
|
||||
mask: i32,
|
||||
color: i32,
|
||||
) -> (u32, u32, u32, u32) {
|
||||
let mut min_x = self.width();
|
||||
let mut max_x = 0;
|
||||
let mut min_y = self.height();
|
||||
let mut max_y = 0;
|
||||
|
||||
for x in 0..self.width() {
|
||||
for y in 0..self.height() {
|
||||
let pixel_raw: i32 = self.get_pixel_raw(x, y).unwrap().into();
|
||||
let color_matches = if find_color {
|
||||
(pixel_raw & mask) == color
|
||||
} else {
|
||||
(pixel_raw & mask) != color
|
||||
};
|
||||
|
||||
if color_matches {
|
||||
min_x = min_x.min(x);
|
||||
max_x = max_x.max(x);
|
||||
min_y = min_y.min(y);
|
||||
max_y = max_y.max(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flash treats a match of (0, 0) alone as none.
|
||||
if max_x > 0 || max_y > 0 {
|
||||
let x = min_x;
|
||||
let y = min_y;
|
||||
let w = max_x - min_x + 1;
|
||||
let h = max_y - min_y + 1;
|
||||
(x, y, w, h)
|
||||
} else {
|
||||
(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_pixels(
|
||||
&mut self,
|
||||
source_bitmap: &Self,
|
||||
src_rect: (i32, i32, i32, i32),
|
||||
dest_point: (i32, i32),
|
||||
alpha_source: Option<(&Self, (i32, i32), bool)>,
|
||||
) {
|
||||
let (src_min_x, src_min_y, src_width, src_height) = src_rect;
|
||||
let (dest_min_x, dest_min_y) = dest_point;
|
||||
|
||||
for src_y in src_min_y..(src_min_y + src_height) {
|
||||
for src_x in src_min_x..(src_min_x + src_width) {
|
||||
let dest_x = src_x - src_min_x + dest_min_x;
|
||||
let dest_y = src_y - src_min_y + dest_min_y;
|
||||
|
||||
if !source_bitmap.is_point_in_bounds(src_x, src_y)
|
||||
|| !self.is_point_in_bounds(dest_x, dest_y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let source_color = source_bitmap
|
||||
.get_pixel_raw(src_x as u32, src_y as u32)
|
||||
.unwrap();
|
||||
|
||||
let mut dest_color = self.get_pixel_raw(dest_x as u32, dest_y as u32).unwrap();
|
||||
|
||||
if let Some((alpha_bitmap, (alpha_min_x, alpha_min_y), merge_alpha)) = alpha_source
|
||||
{
|
||||
let alpha_x = src_x - src_min_x + alpha_min_x;
|
||||
let alpha_y = src_y - src_min_y + alpha_min_y;
|
||||
|
||||
if alpha_bitmap.transparency
|
||||
&& !alpha_bitmap.is_point_in_bounds(alpha_x, alpha_y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let final_alpha = if alpha_bitmap.transparency {
|
||||
let a = alpha_bitmap
|
||||
.get_pixel_raw(alpha_x as u32, alpha_y as u32)
|
||||
.unwrap()
|
||||
.alpha();
|
||||
|
||||
if source_bitmap.transparency {
|
||||
((a as u16 * source_color.alpha() as u16) >> 8) as u8
|
||||
} else {
|
||||
a
|
||||
}
|
||||
} else if source_bitmap.transparency {
|
||||
source_color.alpha()
|
||||
} else {
|
||||
255
|
||||
};
|
||||
|
||||
// there could be a faster or more accurate way to do this,
|
||||
// (without converting to floats and back, twice),
|
||||
// but for now this should suffice
|
||||
let intermediate_color = source_color
|
||||
.to_un_multiplied_alpha()
|
||||
.with_alpha(final_alpha)
|
||||
.to_premultiplied_alpha(true);
|
||||
|
||||
// there are some interesting conditions in the following
|
||||
// lines, these are a result of comparing the output in
|
||||
// many parameter combinations with that of Adobe's player,
|
||||
// and finding patterns in the differences.
|
||||
dest_color = if merge_alpha || !self.transparency {
|
||||
dest_color.blend_over(&intermediate_color)
|
||||
} else {
|
||||
intermediate_color
|
||||
};
|
||||
} else {
|
||||
dest_color = if source_bitmap.transparency && !self.transparency {
|
||||
dest_color.blend_over(&source_color)
|
||||
} else {
|
||||
source_color
|
||||
};
|
||||
}
|
||||
|
||||
self.set_pixel32_raw(dest_x as u32, dest_y as u32, dest_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge(
|
||||
&mut self,
|
||||
source_bitmap: &Self,
|
||||
src_rect: (i32, i32, i32, i32),
|
||||
dest_point: (i32, i32),
|
||||
rgba_mult: (i32, i32, i32, i32),
|
||||
) {
|
||||
let (src_min_x, src_min_y, src_width, src_height) = src_rect;
|
||||
let (dest_min_x, dest_min_y) = dest_point;
|
||||
|
||||
for src_y in src_min_y..(src_min_y + src_height) {
|
||||
for src_x in src_min_x..(src_min_x + src_width) {
|
||||
let dest_x = src_x - src_min_x + dest_min_x;
|
||||
let dest_y = src_y - src_min_y + dest_min_y;
|
||||
|
||||
if !self.is_point_in_bounds(dest_x, dest_y)
|
||||
|| !source_bitmap.is_point_in_bounds(src_x, src_y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let source_color = source_bitmap
|
||||
.get_pixel_raw(src_x as u32, src_y as u32)
|
||||
.unwrap()
|
||||
.to_un_multiplied_alpha();
|
||||
|
||||
let dest_color = self
|
||||
.get_pixel_raw(dest_x as u32, dest_y as u32)
|
||||
.unwrap()
|
||||
.to_un_multiplied_alpha();
|
||||
|
||||
let red_mult = rgba_mult.0.clamp(0, 256) as u16;
|
||||
let green_mult = rgba_mult.1.clamp(0, 256) as u16;
|
||||
let blue_mult = rgba_mult.2.clamp(0, 256) as u16;
|
||||
let alpha_mult = rgba_mult.3.clamp(0, 256) as u16;
|
||||
|
||||
let red = (source_color.red() as u16 * red_mult
|
||||
+ dest_color.red() as u16 * (256 - red_mult))
|
||||
/ 256;
|
||||
let green = (source_color.green() as u16 * green_mult
|
||||
+ dest_color.green() as u16 * (256 - green_mult))
|
||||
/ 256;
|
||||
let blue = (source_color.blue() as u16 * blue_mult
|
||||
+ dest_color.blue() as u16 * (256 - blue_mult))
|
||||
/ 256;
|
||||
let alpha = (source_color.alpha() as u16 * alpha_mult
|
||||
+ dest_color.alpha() as u16 * (256 - alpha_mult))
|
||||
/ 256;
|
||||
|
||||
let mix_color = Color::argb(alpha as u8, red as u8, green as u8, blue as u8);
|
||||
|
||||
self.set_pixel32_raw(
|
||||
dest_x as u32,
|
||||
dest_y as u32,
|
||||
mix_color.to_premultiplied_alpha(self.transparency),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unlike `copy_channel` and `copy_pixels`, this function seems to
|
||||
// operate "in-place" if the source bitmap is the same object as `self`.
|
||||
// This means that we can't resolve this aliasing issue in Rust by a
|
||||
// simple clone in the caller. Instead, if the `source_bitmap` parameter
|
||||
// is `None`, it means that `self` should be used as source as well.
|
||||
pub fn palette_map(
|
||||
&mut self,
|
||||
source_bitmap: Option<&Self>,
|
||||
src_rect: (i32, i32, i32, i32),
|
||||
dest_point: (i32, i32),
|
||||
channel_arrays: ([u32; 256], [u32; 256], [u32; 256], [u32; 256]),
|
||||
) {
|
||||
let (src_min_x, src_min_y, src_width, src_height) = src_rect;
|
||||
let (dest_min_x, dest_min_y) = dest_point;
|
||||
|
||||
for src_y in src_min_y..(src_min_y + src_height) {
|
||||
for src_x in src_min_x..(src_min_x + src_width) {
|
||||
let dest_x = src_x - src_min_x + dest_min_x;
|
||||
let dest_y = src_y - src_min_y + dest_min_y;
|
||||
|
||||
if !self.is_point_in_bounds(dest_x, dest_y)
|
||||
|| !source_bitmap
|
||||
.unwrap_or(self)
|
||||
.is_point_in_bounds(src_x, src_y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let source_color = source_bitmap
|
||||
.unwrap_or(self)
|
||||
.get_pixel_raw(src_x as u32, src_y as u32)
|
||||
.unwrap()
|
||||
.to_un_multiplied_alpha();
|
||||
|
||||
let r = channel_arrays.0[source_color.red() as usize];
|
||||
let g = channel_arrays.1[source_color.green() as usize];
|
||||
let b = channel_arrays.2[source_color.blue() as usize];
|
||||
let a = channel_arrays.3[source_color.alpha() as usize];
|
||||
|
||||
let sum = u32::wrapping_add(u32::wrapping_add(r, g), u32::wrapping_add(b, a));
|
||||
let mix_color = Color(sum as i32).to_premultiplied_alpha(true);
|
||||
|
||||
self.set_pixel32_raw(dest_x as u32, dest_y as u32, mix_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn perlin_noise(
|
||||
&mut self,
|
||||
base: (f64, f64),
|
||||
num_octaves: usize,
|
||||
random_seed: i64,
|
||||
stitch: bool,
|
||||
fractal_noise: bool,
|
||||
channel_options: ChannelOptions,
|
||||
grayscale: bool,
|
||||
offsets: Vec<(f64, f64)>, // must contain `num_octaves` values
|
||||
) {
|
||||
let turb = Turbulence::from_seed(random_seed);
|
||||
|
||||
for y in 0..self.height() {
|
||||
for x in 0..self.width() {
|
||||
let px = x as f64;
|
||||
let py = y as f64;
|
||||
|
||||
let mut noise = [0.0; 4];
|
||||
|
||||
// grayscale mode is different enough to warrant its own branch
|
||||
if grayscale {
|
||||
noise[0] = turb.turbulence(
|
||||
0,
|
||||
(px, py),
|
||||
(1.0 / base.0, 1.0 / base.1),
|
||||
num_octaves,
|
||||
fractal_noise,
|
||||
stitch,
|
||||
(0.0, 0.0),
|
||||
(self.width as f64, self.height as f64),
|
||||
&offsets,
|
||||
);
|
||||
|
||||
noise[1] = noise[0];
|
||||
noise[2] = noise[0];
|
||||
|
||||
noise[3] = if channel_options.contains(ChannelOptions::ALPHA) {
|
||||
turb.turbulence(
|
||||
1,
|
||||
(px, py),
|
||||
(1.0 / base.0, 1.0 / base.1),
|
||||
num_octaves,
|
||||
fractal_noise,
|
||||
stitch,
|
||||
(0.0, 0.0),
|
||||
(self.width as f64, self.height as f64),
|
||||
&offsets,
|
||||
)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
} else {
|
||||
// Flash seems to pass the `color_channel` parameter to `turbulence`
|
||||
// somewhat strangely. It's not always r=0, g=1, b=2, a=3; instead,
|
||||
// it skips incrementing the parameter after channels that are
|
||||
// not included in `channel_options`.
|
||||
let mut channel = 0;
|
||||
|
||||
for (c, noise_c) in noise.iter_mut().enumerate() {
|
||||
// this will work both in fractal_sum and turbulence "modes",
|
||||
// because of the saturating conversion to u8
|
||||
*noise_c = if c == 3 { 1.0 } else { -1.0 };
|
||||
|
||||
// SAFETY: `c` is always in 0..4, so `1 << c` is a valid `ChannelOptions`.
|
||||
let c = unsafe { ChannelOptions::from_bits_unchecked(1 << c) };
|
||||
if channel_options.contains(c) {
|
||||
*noise_c = turb.turbulence(
|
||||
channel,
|
||||
(px, py),
|
||||
(1.0 / base.0, 1.0 / base.1),
|
||||
num_octaves,
|
||||
fractal_noise,
|
||||
stitch,
|
||||
(0.0, 0.0),
|
||||
(self.width as f64, self.height as f64),
|
||||
&offsets,
|
||||
);
|
||||
channel += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut color = [0_u8; 4];
|
||||
for chan in 0..4 {
|
||||
// This is precisely how Adobe Flash converts the -1..1 or 0..1 floats to u8.
|
||||
// Please don't touch, it was difficult to figure out the exact method. :)
|
||||
color[chan] = (if fractal_noise {
|
||||
// Yes, the + 0.5 for correct (nearest) rounding is done before the division by 2.0,
|
||||
// making it technically less correct (I think), but this is how it is!
|
||||
((noise[chan] * 255.0 + 255.0) + 0.5) / 2.0
|
||||
} else {
|
||||
(noise[chan] * 255.0) + 0.5
|
||||
}) as u8;
|
||||
}
|
||||
|
||||
if !self.transparency {
|
||||
color[3] = 255;
|
||||
}
|
||||
|
||||
self.set_pixel32_raw(x, y, Color::argb(color[3], color[0], color[1], color[2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll(&mut self, x: i32, y: i32) {
|
||||
let width = self.width() as i32;
|
||||
let height = self.height() as i32;
|
||||
|
||||
if (x == 0 && y == 0) || x.abs() >= width || y.abs() >= height {
|
||||
return; // no-op
|
||||
}
|
||||
|
||||
// since this is an "in-place copy", we have to iterate from bottom to top
|
||||
// when scrolling downwards - so if y is positive
|
||||
let reverse_y = y > 0;
|
||||
// and if only scrolling horizontally, we have to iterate from right to left
|
||||
// when scrolling right - so if x is positive
|
||||
let reverse_x = y == 0 && x > 0;
|
||||
|
||||
// iteration ranges to use as source for the copy, from is inclusive, to is exclusive
|
||||
let y_from = if reverse_y { height - y - 1 } else { -y };
|
||||
let y_to = if reverse_y { -1 } else { height };
|
||||
let dy = if reverse_y { -1 } else { 1 };
|
||||
|
||||
let x_from = if reverse_x {
|
||||
// we know x > 0
|
||||
width - x - 1
|
||||
} else {
|
||||
// x can be any sign
|
||||
(-x).max(0)
|
||||
};
|
||||
let x_to = if reverse_x { -1 } else { width.min(width - x) };
|
||||
let dx = if reverse_x { -1 } else { 1 };
|
||||
|
||||
let mut src_y = y_from;
|
||||
while src_y != y_to {
|
||||
let mut src_x = x_from;
|
||||
while src_x != x_to {
|
||||
let color = self.get_pixel_raw(src_x as u32, src_y as u32).unwrap();
|
||||
self.set_pixel32_raw((src_x + x) as u32, (src_y + y) as u32, color);
|
||||
src_x += dx;
|
||||
}
|
||||
src_y += dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A BitmapData
|
||||
#[derive(Clone, Copy, Collect)]
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod bitmap_data;
|
||||
pub mod turbulence;
|
||||
|
|
|
@ -0,0 +1,845 @@
|
|||
use gc_arena::Collect;
|
||||
|
||||
use crate::avm1::object::color_transform_object::ColorTransformObject;
|
||||
use crate::backend::render::{BitmapHandle, RenderBackend};
|
||||
use crate::bitmap::turbulence::Turbulence;
|
||||
use bitflags::bitflags;
|
||||
use downcast_rs::__std::fmt::Formatter;
|
||||
use std::ops::Range;
|
||||
|
||||
/// An implementation of the Lehmer/Park-Miller random number generator
|
||||
/// Uses the fixed parameters m = 2,147,483,647 and a = 16,807
|
||||
pub struct LehmerRng {
|
||||
x: u32,
|
||||
}
|
||||
|
||||
impl LehmerRng {
|
||||
pub fn with_seed(seed: u32) -> Self {
|
||||
Self { x: seed }
|
||||
}
|
||||
|
||||
/// Generate the next value in the sequence via the following formula
|
||||
/// X_(k+1) = a * X_k mod m
|
||||
pub fn gen(&mut self) -> u32 {
|
||||
self.x = ((self.x as u64).overflowing_mul(16_807).0 % 2_147_483_647) as u32;
|
||||
self.x
|
||||
}
|
||||
|
||||
pub fn gen_range(&mut self, rng: Range<u8>) -> u8 {
|
||||
rng.start + (self.gen() % ((rng.end - rng.start) as u32 + 1)) as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct Color(i32);
|
||||
|
||||
impl Color {
|
||||
pub fn blue(&self) -> u8 {
|
||||
(self.0 & 0xFF) as u8
|
||||
}
|
||||
|
||||
pub fn green(&self) -> u8 {
|
||||
((self.0 >> 8) & 0xFF) as u8
|
||||
}
|
||||
|
||||
pub fn red(&self) -> u8 {
|
||||
((self.0 >> 16) & 0xFF) as u8
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> u8 {
|
||||
((self.0 >> 24) & 0xFF) as u8
|
||||
}
|
||||
|
||||
pub fn to_premultiplied_alpha(self, transparency: bool) -> Self {
|
||||
// This has some accuracy issues with some alpha values
|
||||
|
||||
let old_alpha = if transparency { self.alpha() } else { 255 };
|
||||
|
||||
let a = old_alpha as f64 / 255.0;
|
||||
|
||||
let r = (self.red() as f64 * a).round() as u8;
|
||||
let g = (self.green() as f64 * a).round() as u8;
|
||||
let b = (self.blue() as f64 * a).round() as u8;
|
||||
|
||||
Self::argb(old_alpha, r, g, b)
|
||||
}
|
||||
|
||||
pub fn to_un_multiplied_alpha(self) -> Self {
|
||||
let a = self.alpha() as f64 / 255.0;
|
||||
|
||||
let r = (self.red() as f64 / a).round() as u8;
|
||||
let g = (self.green() as f64 / a).round() as u8;
|
||||
let b = (self.blue() as f64 / a).round() as u8;
|
||||
|
||||
Self::argb(self.alpha(), r, g, b)
|
||||
}
|
||||
|
||||
pub fn argb(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
|
||||
Self(i32::from_le_bytes([blue, green, red, alpha]))
|
||||
}
|
||||
|
||||
pub fn with_alpha(&self, alpha: u8) -> Self {
|
||||
Self::argb(alpha, self.red(), self.green(), self.blue())
|
||||
}
|
||||
|
||||
pub fn blend_over(&self, source: &Self) -> Self {
|
||||
let sa = source.alpha();
|
||||
|
||||
let r = source.red() + ((self.red() as u16 * (255 - sa as u16)) >> 8) as u8;
|
||||
let g = source.green() + ((self.green() as u16 * (255 - sa as u16)) >> 8) as u8;
|
||||
let b = source.blue() + ((self.blue() as u16 * (255 - sa as u16)) >> 8) as u8;
|
||||
let a = source.alpha() + ((self.alpha() as u16 * (255 - sa as u16)) >> 8) as u8;
|
||||
Self::argb(a, r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format!("{:#x}", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for i32 {
|
||||
fn from(c: Color) -> Self {
|
||||
c.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color> for u32 {
|
||||
fn from(c: Color) -> Self {
|
||||
c.0 as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Color {
|
||||
fn from(i: i32) -> Self {
|
||||
Color(i)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ChannelOptions: u8 {
|
||||
const RED = 1 << 0;
|
||||
const GREEN = 1 << 1;
|
||||
const BLUE = 1 << 2;
|
||||
const ALPHA = 1 << 3;
|
||||
const RGB = Self::RED.bits | Self::GREEN.bits | Self::BLUE.bits;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Collect, Default, Debug)]
|
||||
#[collect(no_drop)]
|
||||
pub struct BitmapData {
|
||||
/// The pixels in the bitmap, stored as a array of pre-multiplied ARGB colour values
|
||||
pub pixels: Vec<Color>,
|
||||
dirty: bool,
|
||||
width: u32,
|
||||
height: u32,
|
||||
transparency: bool,
|
||||
|
||||
bitmap_handle: Option<BitmapHandle>,
|
||||
}
|
||||
|
||||
impl BitmapData {
|
||||
pub fn init_pixels(&mut self, width: u32, height: u32, transparency: bool, fill_color: i32) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self.transparency = transparency;
|
||||
self.pixels = vec![
|
||||
Color(fill_color).to_premultiplied_alpha(self.transparency());
|
||||
width as usize * height as usize
|
||||
];
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn dispose(&mut self) {
|
||||
self.width = 0;
|
||||
self.height = 0;
|
||||
self.pixels.clear();
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn bitmap_handle(&mut self, renderer: &mut dyn RenderBackend) -> Option<BitmapHandle> {
|
||||
if self.bitmap_handle.is_none() {
|
||||
let bitmap_handle =
|
||||
renderer.register_bitmap_raw(self.width(), self.height(), self.pixels_rgba());
|
||||
if let Err(e) = &bitmap_handle {
|
||||
log::warn!("Failed to register raw bitmap for BitmapData: {:?}", e);
|
||||
}
|
||||
self.bitmap_handle = bitmap_handle.ok();
|
||||
}
|
||||
|
||||
self.bitmap_handle
|
||||
}
|
||||
|
||||
pub fn transparency(&self) -> bool {
|
||||
self.transparency
|
||||
}
|
||||
|
||||
pub fn set_transparency(&mut self, transparency: bool) {
|
||||
self.transparency = transparency;
|
||||
}
|
||||
|
||||
pub fn dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
pub fn set_dirty(&mut self, dirty: bool) {
|
||||
self.dirty = dirty;
|
||||
}
|
||||
|
||||
pub fn pixels(&self) -> &[Color] {
|
||||
&self.pixels
|
||||
}
|
||||
|
||||
pub fn set_pixels(&mut self, width: u32, height: u32, transparency: bool, pixels: Vec<Color>) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self.transparency = transparency;
|
||||
self.pixels = pixels;
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn pixels_rgba(&self) -> Vec<u8> {
|
||||
// TODO: This could have been implemented as follows:
|
||||
//
|
||||
// self.pixels
|
||||
// .iter()
|
||||
// .flat_map(|p| [p.red(), p.green(), p.blue(), p.alpha()])
|
||||
// .collect()
|
||||
//
|
||||
// But currently Rust emits suboptimal code in that case. For now we use
|
||||
// `Vec::with_capacity` manually to avoid unnecessary re-allocations.
|
||||
|
||||
let mut output = Vec::with_capacity(self.pixels.len() * 4);
|
||||
for p in &self.pixels {
|
||||
output.extend_from_slice(&[p.red(), p.green(), p.blue(), p.alpha()])
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {
|
||||
x >= 0 && x < self.width() as i32 && y >= 0 && y < self.height() as i32
|
||||
}
|
||||
|
||||
pub fn get_pixel_raw(&self, x: u32, y: u32) -> Option<Color> {
|
||||
if x >= self.width() || y >= self.height() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.pixels.get((x + y * self.width()) as usize).copied()
|
||||
}
|
||||
|
||||
pub fn get_pixel32(&self, x: i32, y: i32) -> Color {
|
||||
self.get_pixel_raw(x as u32, y as u32)
|
||||
.map(|f| f.to_un_multiplied_alpha())
|
||||
.unwrap_or_else(|| 0.into())
|
||||
}
|
||||
|
||||
pub fn get_pixel(&self, x: i32, y: i32) -> i32 {
|
||||
if self.is_point_in_bounds(x, y) {
|
||||
self.get_pixel32(x, y).with_alpha(0x0).into()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_pixel(&mut self, x: u32, y: u32, color: Color) {
|
||||
let current_alpha = self.get_pixel_raw(x, y).map(|p| p.alpha()).unwrap_or(0);
|
||||
self.set_pixel32(x as i32, y as i32, color.with_alpha(current_alpha));
|
||||
}
|
||||
|
||||
pub fn set_pixel32_raw(&mut self, x: u32, y: u32, color: Color) {
|
||||
let width = self.width();
|
||||
self.pixels[(x + y * width) as usize] = color;
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn set_pixel32(&mut self, x: i32, y: i32, color: Color) {
|
||||
if self.is_point_in_bounds(x, y) {
|
||||
self.set_pixel32_raw(
|
||||
x as u32,
|
||||
y as u32,
|
||||
color.to_premultiplied_alpha(self.transparency()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_rect(&mut self, x: u32, y: u32, width: u32, height: u32, color: Color) {
|
||||
for x_offset in 0..width {
|
||||
for y_offset in 0..height {
|
||||
self.set_pixel32((x + x_offset) as i32, (y + y_offset) as i32, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flood_fill(&mut self, x: u32, y: u32, replace_color: Color) {
|
||||
let expected_color = self.get_pixel_raw(x, y).unwrap_or_else(|| 0.into());
|
||||
|
||||
let mut pending = vec![(x, y)];
|
||||
|
||||
while !pending.is_empty() {
|
||||
if let Some((x, y)) = pending.pop() {
|
||||
if let Some(old_color) = self.get_pixel_raw(x, y) {
|
||||
if old_color == expected_color {
|
||||
if x > 0 {
|
||||
pending.push((x - 1, y));
|
||||
}
|
||||
if y > 0 {
|
||||
pending.push((x, y - 1));
|
||||
}
|
||||
if x < self.width() - 1 {
|
||||
pending.push((x + 1, y))
|
||||
}
|
||||
if y < self.height() - 1 {
|
||||
pending.push((x, y + 1));
|
||||
}
|
||||
self.set_pixel32_raw(x, y, replace_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn noise(
|
||||
&mut self,
|
||||
seed: i32,
|
||||
low: u8,
|
||||
high: u8,
|
||||
channel_options: ChannelOptions,
|
||||
gray_scale: bool,
|
||||
) {
|
||||
let true_seed = if seed <= 0 {
|
||||
(-seed + 1) as u32
|
||||
} else {
|
||||
seed as u32
|
||||
};
|
||||
|
||||
let mut rng = LehmerRng::with_seed(true_seed);
|
||||
|
||||
for y in 0..self.height() {
|
||||
for x in 0..self.width() {
|
||||
let pixel_color = if gray_scale {
|
||||
let gray = rng.gen_range(low..high);
|
||||
let alpha = if channel_options.contains(ChannelOptions::ALPHA) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
255
|
||||
};
|
||||
|
||||
Color::argb(alpha, gray, gray, gray)
|
||||
} else {
|
||||
let r = if channel_options.contains(ChannelOptions::RED) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let g = if channel_options.contains(ChannelOptions::GREEN) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let b = if channel_options.contains(ChannelOptions::BLUE) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let a = if channel_options.contains(ChannelOptions::ALPHA) {
|
||||
rng.gen_range(low..high)
|
||||
} else {
|
||||
255
|
||||
};
|
||||
|
||||
Color::argb(a, r, g, b)
|
||||
};
|
||||
|
||||
self.set_pixel32_raw(x, y, pixel_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_channel(
|
||||
&mut self,
|
||||
dest_point: (u32, u32),
|
||||
src_rect: (u32, u32, u32, u32),
|
||||
source_bitmap: &Self,
|
||||
source_channel: i32,
|
||||
dest_channel: i32,
|
||||
) {
|
||||
let (min_x, min_y) = dest_point;
|
||||
let (src_min_x, src_min_y, src_max_x, src_max_y) = src_rect;
|
||||
|
||||
for x in src_min_x.max(0)..src_max_x.min(source_bitmap.width()) {
|
||||
for y in src_min_y.max(0)..src_max_y.min(source_bitmap.height()) {
|
||||
if self.is_point_in_bounds((x + min_x) as i32, (y + min_y) as i32) {
|
||||
let original_color: u32 = self
|
||||
.get_pixel_raw((x + min_x) as u32, (y + min_y) as u32)
|
||||
.unwrap_or_else(|| 0.into())
|
||||
.into();
|
||||
let source_color: u32 = source_bitmap
|
||||
.get_pixel_raw(x, y)
|
||||
.unwrap_or_else(|| 0.into())
|
||||
.into();
|
||||
|
||||
let channel_shift: u32 = match source_channel {
|
||||
// red
|
||||
1 => 16,
|
||||
// green
|
||||
2 => 8,
|
||||
// blue
|
||||
4 => 0,
|
||||
// alpha
|
||||
8 => 24,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
let source_part = (source_color >> channel_shift) & 0xFF;
|
||||
|
||||
let result_color: u32 = match dest_channel {
|
||||
// red
|
||||
1 => (original_color & 0xFF00FFFF) | source_part << 16,
|
||||
// green
|
||||
2 => (original_color & 0xFFFF00FF) | source_part << 8,
|
||||
// blue
|
||||
4 => (original_color & 0xFFFFFF00) | source_part,
|
||||
// alpha
|
||||
8 => (original_color & 0x00FFFFFF) | source_part << 24,
|
||||
_ => original_color,
|
||||
};
|
||||
|
||||
self.set_pixel32_raw(
|
||||
(x + min_x) as u32,
|
||||
(y + min_y) as u32,
|
||||
(result_color as i32).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color_transform(
|
||||
&mut self,
|
||||
min_x: u32,
|
||||
min_y: u32,
|
||||
end_x: u32,
|
||||
end_y: u32,
|
||||
color_transform: ColorTransformObject,
|
||||
) {
|
||||
for x in min_x..end_x.min(self.width()) {
|
||||
for y in min_y..end_y.min(self.height()) {
|
||||
let color = self
|
||||
.get_pixel_raw(x, y)
|
||||
.unwrap_or_else(|| 0.into())
|
||||
.to_un_multiplied_alpha();
|
||||
|
||||
let alpha = ((color.alpha() as f32 * color_transform.get_alpha_multiplier() as f32)
|
||||
+ color_transform.get_alpha_offset() as f32) as u8;
|
||||
let red = ((color.red() as f32 * color_transform.get_red_multiplier() as f32)
|
||||
+ color_transform.get_red_offset() as f32) as u8;
|
||||
let green = ((color.green() as f32 * color_transform.get_green_multiplier() as f32)
|
||||
+ color_transform.get_green_offset() as f32) as u8;
|
||||
let blue = ((color.blue() as f32 * color_transform.get_blue_multiplier() as f32)
|
||||
+ color_transform.get_blue_offset() as f32) as u8;
|
||||
|
||||
self.set_pixel32_raw(
|
||||
x,
|
||||
y,
|
||||
Color::argb(alpha, red, green, blue)
|
||||
.to_premultiplied_alpha(self.transparency()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color_bounds_rect(
|
||||
&self,
|
||||
find_color: bool,
|
||||
mask: i32,
|
||||
color: i32,
|
||||
) -> (u32, u32, u32, u32) {
|
||||
let mut min_x = self.width();
|
||||
let mut max_x = 0;
|
||||
let mut min_y = self.height();
|
||||
let mut max_y = 0;
|
||||
|
||||
for x in 0..self.width() {
|
||||
for y in 0..self.height() {
|
||||
let pixel_raw: i32 = self.get_pixel_raw(x, y).unwrap().into();
|
||||
let color_matches = if find_color {
|
||||
(pixel_raw & mask) == color
|
||||
} else {
|
||||
(pixel_raw & mask) != color
|
||||
};
|
||||
|
||||
if color_matches {
|
||||
min_x = min_x.min(x);
|
||||
max_x = max_x.max(x);
|
||||
min_y = min_y.min(y);
|
||||
max_y = max_y.max(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flash treats a match of (0, 0) alone as none.
|
||||
if max_x > 0 || max_y > 0 {
|
||||
let x = min_x;
|
||||
let y = min_y;
|
||||
let w = max_x - min_x + 1;
|
||||
let h = max_y - min_y + 1;
|
||||
(x, y, w, h)
|
||||
} else {
|
||||
(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_pixels(
|
||||
&mut self,
|
||||
source_bitmap: &Self,
|
||||
src_rect: (i32, i32, i32, i32),
|
||||
dest_point: (i32, i32),
|
||||
alpha_source: Option<(&Self, (i32, i32), bool)>,
|
||||
) {
|
||||
let (src_min_x, src_min_y, src_width, src_height) = src_rect;
|
||||
let (dest_min_x, dest_min_y) = dest_point;
|
||||
|
||||
for src_y in src_min_y..(src_min_y + src_height) {
|
||||
for src_x in src_min_x..(src_min_x + src_width) {
|
||||
let dest_x = src_x - src_min_x + dest_min_x;
|
||||
let dest_y = src_y - src_min_y + dest_min_y;
|
||||
|
||||
if !source_bitmap.is_point_in_bounds(src_x, src_y)
|
||||
|| !self.is_point_in_bounds(dest_x, dest_y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let source_color = source_bitmap
|
||||
.get_pixel_raw(src_x as u32, src_y as u32)
|
||||
.unwrap();
|
||||
|
||||
let mut dest_color = self.get_pixel_raw(dest_x as u32, dest_y as u32).unwrap();
|
||||
|
||||
if let Some((alpha_bitmap, (alpha_min_x, alpha_min_y), merge_alpha)) = alpha_source
|
||||
{
|
||||
let alpha_x = src_x - src_min_x + alpha_min_x;
|
||||
let alpha_y = src_y - src_min_y + alpha_min_y;
|
||||
|
||||
if alpha_bitmap.transparency
|
||||
&& !alpha_bitmap.is_point_in_bounds(alpha_x, alpha_y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let final_alpha = if alpha_bitmap.transparency {
|
||||
let a = alpha_bitmap
|
||||
.get_pixel_raw(alpha_x as u32, alpha_y as u32)
|
||||
.unwrap()
|
||||
.alpha();
|
||||
|
||||
if source_bitmap.transparency {
|
||||
((a as u16 * source_color.alpha() as u16) >> 8) as u8
|
||||
} else {
|
||||
a
|
||||
}
|
||||
} else if source_bitmap.transparency {
|
||||
source_color.alpha()
|
||||
} else {
|
||||
255
|
||||
};
|
||||
|
||||
// there could be a faster or more accurate way to do this,
|
||||
// (without converting to floats and back, twice),
|
||||
// but for now this should suffice
|
||||
let intermediate_color = source_color
|
||||
.to_un_multiplied_alpha()
|
||||
.with_alpha(final_alpha)
|
||||
.to_premultiplied_alpha(true);
|
||||
|
||||
// there are some interesting conditions in the following
|
||||
// lines, these are a result of comparing the output in
|
||||
// many parameter combinations with that of Adobe's player,
|
||||
// and finding patterns in the differences.
|
||||
dest_color = if merge_alpha || !self.transparency {
|
||||
dest_color.blend_over(&intermediate_color)
|
||||
} else {
|
||||
intermediate_color
|
||||
};
|
||||
} else {
|
||||
dest_color = if source_bitmap.transparency && !self.transparency {
|
||||
dest_color.blend_over(&source_color)
|
||||
} else {
|
||||
source_color
|
||||
};
|
||||
}
|
||||
|
||||
self.set_pixel32_raw(dest_x as u32, dest_y as u32, dest_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merge(
|
||||
&mut self,
|
||||
source_bitmap: &Self,
|
||||
src_rect: (i32, i32, i32, i32),
|
||||
dest_point: (i32, i32),
|
||||
rgba_mult: (i32, i32, i32, i32),
|
||||
) {
|
||||
let (src_min_x, src_min_y, src_width, src_height) = src_rect;
|
||||
let (dest_min_x, dest_min_y) = dest_point;
|
||||
|
||||
for src_y in src_min_y..(src_min_y + src_height) {
|
||||
for src_x in src_min_x..(src_min_x + src_width) {
|
||||
let dest_x = src_x - src_min_x + dest_min_x;
|
||||
let dest_y = src_y - src_min_y + dest_min_y;
|
||||
|
||||
if !self.is_point_in_bounds(dest_x, dest_y)
|
||||
|| !source_bitmap.is_point_in_bounds(src_x, src_y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let source_color = source_bitmap
|
||||
.get_pixel_raw(src_x as u32, src_y as u32)
|
||||
.unwrap()
|
||||
.to_un_multiplied_alpha();
|
||||
|
||||
let dest_color = self
|
||||
.get_pixel_raw(dest_x as u32, dest_y as u32)
|
||||
.unwrap()
|
||||
.to_un_multiplied_alpha();
|
||||
|
||||
let red_mult = rgba_mult.0.clamp(0, 256) as u16;
|
||||
let green_mult = rgba_mult.1.clamp(0, 256) as u16;
|
||||
let blue_mult = rgba_mult.2.clamp(0, 256) as u16;
|
||||
let alpha_mult = rgba_mult.3.clamp(0, 256) as u16;
|
||||
|
||||
let red = (source_color.red() as u16 * red_mult
|
||||
+ dest_color.red() as u16 * (256 - red_mult))
|
||||
/ 256;
|
||||
let green = (source_color.green() as u16 * green_mult
|
||||
+ dest_color.green() as u16 * (256 - green_mult))
|
||||
/ 256;
|
||||
let blue = (source_color.blue() as u16 * blue_mult
|
||||
+ dest_color.blue() as u16 * (256 - blue_mult))
|
||||
/ 256;
|
||||
let alpha = (source_color.alpha() as u16 * alpha_mult
|
||||
+ dest_color.alpha() as u16 * (256 - alpha_mult))
|
||||
/ 256;
|
||||
|
||||
let mix_color = Color::argb(alpha as u8, red as u8, green as u8, blue as u8);
|
||||
|
||||
self.set_pixel32_raw(
|
||||
dest_x as u32,
|
||||
dest_y as u32,
|
||||
mix_color.to_premultiplied_alpha(self.transparency),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unlike `copy_channel` and `copy_pixels`, this function seems to
|
||||
// operate "in-place" if the source bitmap is the same object as `self`.
|
||||
// This means that we can't resolve this aliasing issue in Rust by a
|
||||
// simple clone in the caller. Instead, if the `source_bitmap` parameter
|
||||
// is `None`, it means that `self` should be used as source as well.
|
||||
pub fn palette_map(
|
||||
&mut self,
|
||||
source_bitmap: Option<&Self>,
|
||||
src_rect: (i32, i32, i32, i32),
|
||||
dest_point: (i32, i32),
|
||||
channel_arrays: ([u32; 256], [u32; 256], [u32; 256], [u32; 256]),
|
||||
) {
|
||||
let (src_min_x, src_min_y, src_width, src_height) = src_rect;
|
||||
let (dest_min_x, dest_min_y) = dest_point;
|
||||
|
||||
for src_y in src_min_y..(src_min_y + src_height) {
|
||||
for src_x in src_min_x..(src_min_x + src_width) {
|
||||
let dest_x = src_x - src_min_x + dest_min_x;
|
||||
let dest_y = src_y - src_min_y + dest_min_y;
|
||||
|
||||
if !self.is_point_in_bounds(dest_x, dest_y)
|
||||
|| !source_bitmap
|
||||
.unwrap_or(self)
|
||||
.is_point_in_bounds(src_x, src_y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let source_color = source_bitmap
|
||||
.unwrap_or(self)
|
||||
.get_pixel_raw(src_x as u32, src_y as u32)
|
||||
.unwrap()
|
||||
.to_un_multiplied_alpha();
|
||||
|
||||
let r = channel_arrays.0[source_color.red() as usize];
|
||||
let g = channel_arrays.1[source_color.green() as usize];
|
||||
let b = channel_arrays.2[source_color.blue() as usize];
|
||||
let a = channel_arrays.3[source_color.alpha() as usize];
|
||||
|
||||
let sum = u32::wrapping_add(u32::wrapping_add(r, g), u32::wrapping_add(b, a));
|
||||
let mix_color = Color(sum as i32).to_premultiplied_alpha(true);
|
||||
|
||||
self.set_pixel32_raw(dest_x as u32, dest_y as u32, mix_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn perlin_noise(
|
||||
&mut self,
|
||||
base: (f64, f64),
|
||||
num_octaves: usize,
|
||||
random_seed: i64,
|
||||
stitch: bool,
|
||||
fractal_noise: bool,
|
||||
channel_options: ChannelOptions,
|
||||
grayscale: bool,
|
||||
offsets: Vec<(f64, f64)>, // must contain `num_octaves` values
|
||||
) {
|
||||
let turb = Turbulence::from_seed(random_seed);
|
||||
|
||||
for y in 0..self.height() {
|
||||
for x in 0..self.width() {
|
||||
let px = x as f64;
|
||||
let py = y as f64;
|
||||
|
||||
let mut noise = [0.0; 4];
|
||||
|
||||
// grayscale mode is different enough to warrant its own branch
|
||||
if grayscale {
|
||||
noise[0] = turb.turbulence(
|
||||
0,
|
||||
(px, py),
|
||||
(1.0 / base.0, 1.0 / base.1),
|
||||
num_octaves,
|
||||
fractal_noise,
|
||||
stitch,
|
||||
(0.0, 0.0),
|
||||
(self.width as f64, self.height as f64),
|
||||
&offsets,
|
||||
);
|
||||
|
||||
noise[1] = noise[0];
|
||||
noise[2] = noise[0];
|
||||
|
||||
noise[3] = if channel_options.contains(ChannelOptions::ALPHA) {
|
||||
turb.turbulence(
|
||||
1,
|
||||
(px, py),
|
||||
(1.0 / base.0, 1.0 / base.1),
|
||||
num_octaves,
|
||||
fractal_noise,
|
||||
stitch,
|
||||
(0.0, 0.0),
|
||||
(self.width as f64, self.height as f64),
|
||||
&offsets,
|
||||
)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
} else {
|
||||
// Flash seems to pass the `color_channel` parameter to `turbulence`
|
||||
// somewhat strangely. It's not always r=0, g=1, b=2, a=3; instead,
|
||||
// it skips incrementing the parameter after channels that are
|
||||
// not included in `channel_options`.
|
||||
let mut channel = 0;
|
||||
|
||||
for (c, noise_c) in noise.iter_mut().enumerate() {
|
||||
// this will work both in fractal_sum and turbulence "modes",
|
||||
// because of the saturating conversion to u8
|
||||
*noise_c = if c == 3 { 1.0 } else { -1.0 };
|
||||
|
||||
// SAFETY: `c` is always in 0..4, so `1 << c` is a valid `ChannelOptions`.
|
||||
let c = unsafe { ChannelOptions::from_bits_unchecked(1 << c) };
|
||||
if channel_options.contains(c) {
|
||||
*noise_c = turb.turbulence(
|
||||
channel,
|
||||
(px, py),
|
||||
(1.0 / base.0, 1.0 / base.1),
|
||||
num_octaves,
|
||||
fractal_noise,
|
||||
stitch,
|
||||
(0.0, 0.0),
|
||||
(self.width as f64, self.height as f64),
|
||||
&offsets,
|
||||
);
|
||||
channel += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut color = [0_u8; 4];
|
||||
for chan in 0..4 {
|
||||
// This is precisely how Adobe Flash converts the -1..1 or 0..1 floats to u8.
|
||||
// Please don't touch, it was difficult to figure out the exact method. :)
|
||||
color[chan] = (if fractal_noise {
|
||||
// Yes, the + 0.5 for correct (nearest) rounding is done before the division by 2.0,
|
||||
// making it technically less correct (I think), but this is how it is!
|
||||
((noise[chan] * 255.0 + 255.0) + 0.5) / 2.0
|
||||
} else {
|
||||
(noise[chan] * 255.0) + 0.5
|
||||
}) as u8;
|
||||
}
|
||||
|
||||
if !self.transparency {
|
||||
color[3] = 255;
|
||||
}
|
||||
|
||||
self.set_pixel32_raw(x, y, Color::argb(color[3], color[0], color[1], color[2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll(&mut self, x: i32, y: i32) {
|
||||
let width = self.width() as i32;
|
||||
let height = self.height() as i32;
|
||||
|
||||
if (x == 0 && y == 0) || x.abs() >= width || y.abs() >= height {
|
||||
return; // no-op
|
||||
}
|
||||
|
||||
// since this is an "in-place copy", we have to iterate from bottom to top
|
||||
// when scrolling downwards - so if y is positive
|
||||
let reverse_y = y > 0;
|
||||
// and if only scrolling horizontally, we have to iterate from right to left
|
||||
// when scrolling right - so if x is positive
|
||||
let reverse_x = y == 0 && x > 0;
|
||||
|
||||
// iteration ranges to use as source for the copy, from is inclusive, to is exclusive
|
||||
let y_from = if reverse_y { height - y - 1 } else { -y };
|
||||
let y_to = if reverse_y { -1 } else { height };
|
||||
let dy = if reverse_y { -1 } else { 1 };
|
||||
|
||||
let x_from = if reverse_x {
|
||||
// we know x > 0
|
||||
width - x - 1
|
||||
} else {
|
||||
// x can be any sign
|
||||
(-x).max(0)
|
||||
};
|
||||
let x_to = if reverse_x { -1 } else { width.min(width - x) };
|
||||
let dx = if reverse_x { -1 } else { 1 };
|
||||
|
||||
let mut src_y = y_from;
|
||||
while src_y != y_to {
|
||||
let mut src_x = x_from;
|
||||
while src_x != x_to {
|
||||
let color = self.get_pixel_raw(src_x as u32, src_y as u32).unwrap();
|
||||
self.set_pixel32_raw((src_x + x) as u32, (src_y + y) as u32, color);
|
||||
src_x += dx;
|
||||
}
|
||||
src_y += dy;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ pub struct Bitmap<'gc>(GcCell<'gc, BitmapData<'gc>>);
|
|||
pub struct BitmapData<'gc> {
|
||||
base: DisplayObjectBase<'gc>,
|
||||
static_data: Gc<'gc, BitmapStatic>,
|
||||
bitmap_data: Option<GcCell<'gc, crate::avm1::object::bitmap_data::BitmapData>>,
|
||||
bitmap_data: Option<GcCell<'gc, crate::bitmap::bitmap_data::BitmapData>>,
|
||||
smoothing: bool,
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ impl<'gc> Bitmap<'gc> {
|
|||
bitmap_handle: BitmapHandle,
|
||||
width: u16,
|
||||
height: u16,
|
||||
bitmap_data: Option<GcCell<'gc, crate::avm1::object::bitmap_data::BitmapData>>,
|
||||
bitmap_data: Option<GcCell<'gc, crate::bitmap::bitmap_data::BitmapData>>,
|
||||
smoothing: bool,
|
||||
) -> Self {
|
||||
Bitmap(GcCell::allocate(
|
||||
|
|
Loading…
Reference in New Issue