Skip to content

Commit

Permalink
avm2: Implement Graphics::drawRoundRectComplex
Browse files Browse the repository at this point in the history
  • Loading branch information
adrian17 committed Aug 18, 2024
1 parent 84e4270 commit db61726
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 80 deletions.
225 changes: 145 additions & 80 deletions core/src/avm2/globals/flash/display/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,88 +495,95 @@ const UNIT_CIRCLE_POINTS: [(f64, f64); 5] = [
]; */

/// Draw a roundrect.
#[allow(clippy::too_many_arguments)]
fn draw_round_rect_internal(
draw: &mut Drawing,
x: f64,
y: f64,
width: f64,
height: f64,
mut ellipse_width: f64,
mut ellipse_height: f64,
top_left_width: f64,
top_left_height: f64,
top_right_width: f64,
top_right_height: f64,
bottom_left_width: f64,
bottom_left_height: f64,
bottom_right_width: f64,
bottom_right_height: f64,
) {
if ellipse_height.is_nan() {
ellipse_height = ellipse_width;
}
let top_left_width = top_left_width.min(width / 2.0);
let top_left_height = top_left_height.min(height / 2.0);
let top_right_width = top_right_width.min(width / 2.0);
let top_right_height = top_right_height.min(height / 2.0);
let bottom_left_width = bottom_left_width.min(width / 2.0);
let bottom_left_height = bottom_left_height.min(height / 2.0);
let bottom_right_width = bottom_right_width.min(width / 2.0);
let bottom_right_height = bottom_right_height.min(height / 2.0);

//Clamp the ellipse sizes to the size of the rectangle.
if ellipse_width > width {
ellipse_width = width;
}
let ucp = UNIT_CIRCLE_POINTS;

if ellipse_height > height {
ellipse_height = height;
}
let br_ellipse_center_x = x + width - bottom_right_width;
let br_ellipse_center_y = y + height - bottom_right_height;

// We'll start from the bottom-right corner of the rectangle,
// because that's what Flash Player does.
let ucp = UNIT_CIRCLE_POINTS;
let bl_ellipse_center_x = x + bottom_left_width;
let bl_ellipse_center_y = y + height - bottom_left_height;

let line_width = width - ellipse_width;
let line_height = height - ellipse_height;
let tl_ellipse_center_x = x + top_left_width;
let tl_ellipse_center_y = y + top_left_height;

let br_ellipse_center_x = x + ellipse_width / 2.0 + line_width;
let br_ellipse_center_y = y + ellipse_height / 2.0 + line_height;
let tr_ellipse_center_x = x + width - top_right_width;
let tr_ellipse_center_y = y + top_right_height;

let br_point_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[2].0;
let br_point_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[2].1;
// We'll start from the bottom-right corner of the rectangle,
// because that's what Flash Player does.

// Middle of bottom-right ellipse
let br_point_x = br_ellipse_center_x + bottom_right_width * ucp[2].0;
let br_point_y = br_ellipse_center_y + bottom_right_height * ucp[2].1;
let br_point = Point::from_pixels(br_point_x, br_point_y);

draw.draw_command(DrawCommand::MoveTo(br_point));

let br_b_curve_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[3].0;
let br_b_curve_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[3].1;
let br_b_curve_x = br_ellipse_center_x + bottom_right_width * ucp[3].0;
let br_b_curve_y = br_ellipse_center_y + bottom_right_height * ucp[3].1;
let br_b_curve = Point::from_pixels(br_b_curve_x, br_b_curve_y);

let right_b_point_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[4].0;
let right_b_point_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[4].1;
let right_b_point_x = br_ellipse_center_x + bottom_right_width * ucp[4].0;
let right_b_point_y = br_ellipse_center_y + bottom_right_height * ucp[4].1;
let right_b_point = Point::from_pixels(right_b_point_x, right_b_point_y);

draw.draw_command(DrawCommand::QuadraticCurveTo {
control: br_b_curve,
anchor: right_b_point,
});

// Oh, since we're drawing roundrects, we also need to draw lines
// in between each ellipse. This is the bottom line.
let tl_ellipse_center_x = x + ellipse_width / 2.0;
let tl_ellipse_center_y = y + ellipse_height / 2.0;

let left_b_point_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[4].0;
let left_b_point_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[4].1;
// Bottom line
let left_b_point_x = bl_ellipse_center_x + bottom_left_width * ucp[4].0;
let left_b_point_y = bl_ellipse_center_y + bottom_left_height * ucp[4].1;
let left_b_point = Point::from_pixels(left_b_point_x, left_b_point_y);

draw.draw_command(DrawCommand::LineTo(left_b_point));

// Bottom-left ellipse
let b_bl_curve_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[3].0;
let b_bl_curve_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[3].1;
let b_bl_curve_x = bl_ellipse_center_x - bottom_left_width * ucp[3].0;
let b_bl_curve_y = bl_ellipse_center_y + bottom_left_height * ucp[3].1;
let b_bl_curve = Point::from_pixels(b_bl_curve_x, b_bl_curve_y);

let bl_point_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[2].0;
let bl_point_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[2].1;
let bl_point_x = bl_ellipse_center_x - bottom_left_width * ucp[2].0;
let bl_point_y = bl_ellipse_center_y + bottom_left_height * ucp[2].1;
let bl_point = Point::from_pixels(bl_point_x, bl_point_y);

draw.draw_command(DrawCommand::QuadraticCurveTo {
control: b_bl_curve,
anchor: bl_point,
});

let bl_l_curve_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[1].0;
let bl_l_curve_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[1].1;
let bl_l_curve_x = bl_ellipse_center_x - bottom_left_width * ucp[1].0;
let bl_l_curve_y = bl_ellipse_center_y + bottom_left_height * ucp[1].1;
let bl_l_curve = Point::from_pixels(bl_l_curve_x, bl_l_curve_y);

let bottom_l_point_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[0].0;
let bottom_l_point_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[0].1;
let bottom_l_point_x = bl_ellipse_center_x - bottom_left_width * ucp[0].0;
let bottom_l_point_y = bl_ellipse_center_y + bottom_left_height * ucp[0].1;
let bottom_l_point = Point::from_pixels(bottom_l_point_x, bottom_l_point_y);

draw.draw_command(DrawCommand::QuadraticCurveTo {
Expand All @@ -585,32 +592,32 @@ fn draw_round_rect_internal(
});

// Left side
let top_l_point_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[0].0;
let top_l_point_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[0].1;
let top_l_point_x = tl_ellipse_center_x - top_left_width * ucp[0].0;
let top_l_point_y = tl_ellipse_center_y - top_left_height * ucp[0].1;
let top_l_point = Point::from_pixels(top_l_point_x, top_l_point_y);

draw.draw_command(DrawCommand::LineTo(top_l_point));

// Top-left ellipse
let l_tl_curve_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[1].0;
let l_tl_curve_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[1].1;
let l_tl_curve_x = tl_ellipse_center_x - top_left_width * ucp[1].0;
let l_tl_curve_y = tl_ellipse_center_y - top_left_height * ucp[1].1;
let l_tl_curve = Point::from_pixels(l_tl_curve_x, l_tl_curve_y);

let tl_point_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[2].0;
let tl_point_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[2].1;
let tl_point_x = tl_ellipse_center_x - top_left_width * ucp[2].0;
let tl_point_y = tl_ellipse_center_y - top_left_height * ucp[2].1;
let tl_point = Point::from_pixels(tl_point_x, tl_point_y);

draw.draw_command(DrawCommand::QuadraticCurveTo {
control: l_tl_curve,
anchor: tl_point,
});

let tl_t_curve_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[3].0;
let tl_t_curve_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[3].1;
let tl_t_curve_x = tl_ellipse_center_x - top_left_width * ucp[3].0;
let tl_t_curve_y = tl_ellipse_center_y - top_left_height * ucp[3].1;
let tl_t_curve = Point::from_pixels(tl_t_curve_x, tl_t_curve_y);

let left_t_point_x = tl_ellipse_center_x + ellipse_width / -2.0 * ucp[4].0;
let left_t_point_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[4].1;
let left_t_point_x = tl_ellipse_center_x - top_left_width * ucp[4].0;
let left_t_point_y = tl_ellipse_center_y - top_left_height * ucp[4].1;
let left_t_point = Point::from_pixels(left_t_point_x, left_t_point_y);

draw.draw_command(DrawCommand::QuadraticCurveTo {
Expand All @@ -619,32 +626,32 @@ fn draw_round_rect_internal(
});

// Top side
let right_t_point_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[4].0;
let right_t_point_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[4].1;
let right_t_point_x = tr_ellipse_center_x + top_right_width * ucp[4].0;
let right_t_point_y = tr_ellipse_center_y - top_right_height * ucp[4].1;
let right_t_point = Point::from_pixels(right_t_point_x, right_t_point_y);

draw.draw_command(DrawCommand::LineTo(right_t_point));

// Top-right ellipse
let t_tr_curve_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[3].0;
let t_tr_curve_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[3].1;
let t_tr_curve_x = tr_ellipse_center_x + top_right_width * ucp[3].0;
let t_tr_curve_y = tr_ellipse_center_y - top_right_height * ucp[3].1;
let t_tr_curve = Point::from_pixels(t_tr_curve_x, t_tr_curve_y);

let tr_point_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[2].0;
let tr_point_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[2].1;
let tr_point_x = tr_ellipse_center_x + top_right_width * ucp[2].0;
let tr_point_y = tr_ellipse_center_y - top_right_height * ucp[2].1;
let tr_point = Point::from_pixels(tr_point_x, tr_point_y);

draw.draw_command(DrawCommand::QuadraticCurveTo {
control: t_tr_curve,
anchor: tr_point,
});

let tr_r_curve_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[1].0;
let tr_r_curve_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[1].1;
let tr_r_curve_x = tr_ellipse_center_x + top_right_width * ucp[1].0;
let tr_r_curve_y = tr_ellipse_center_y - top_right_height * ucp[1].1;
let tr_r_curve = Point::from_pixels(tr_r_curve_x, tr_r_curve_y);

let top_r_point_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[0].0;
let top_r_point_y = tl_ellipse_center_y + ellipse_height / -2.0 * ucp[0].1;
let top_r_point_x = tr_ellipse_center_x + top_right_width * ucp[0].0;
let top_r_point_y = tr_ellipse_center_y - top_right_height * ucp[0].1;
let top_r_point = Point::from_pixels(top_r_point_x, top_r_point_y);

draw.draw_command(DrawCommand::QuadraticCurveTo {
Expand All @@ -653,14 +660,14 @@ fn draw_round_rect_internal(
});

// Right side & other half of bottom-right ellipse
let bottom_r_point_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[0].0;
let bottom_r_point_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[0].1;
let bottom_r_point_x = br_ellipse_center_x + bottom_right_width * ucp[0].0;
let bottom_r_point_y = br_ellipse_center_y + bottom_right_height * ucp[0].1;
let bottom_r_point = Point::from_pixels(bottom_r_point_x, bottom_r_point_y);

draw.draw_command(DrawCommand::LineTo(bottom_r_point));

let r_br_curve_x = br_ellipse_center_x + ellipse_width / 2.0 * ucp[1].0;
let r_br_curve_y = br_ellipse_center_y + ellipse_height / 2.0 * ucp[1].1;
let r_br_curve_x = br_ellipse_center_x + bottom_right_width * ucp[1].0;
let r_br_curve_y = br_ellipse_center_y + bottom_right_height * ucp[1].1;
let r_br_curve = Point::from_pixels(r_br_curve_x, r_br_curve_y);

draw.draw_command(DrawCommand::QuadraticCurveTo {
Expand All @@ -681,7 +688,49 @@ pub fn draw_round_rect<'gc>(
let width = args.get_f64(activation, 2)?;
let height = args.get_f64(activation, 3)?;
let ellipse_width = args.get_f64(activation, 4)?;
let ellipse_height = args.get_f64(activation, 5)?;
let mut ellipse_height = args.get_f64(activation, 5)?;

if ellipse_height.is_nan() {
ellipse_height = ellipse_width;
}

if let Some(mut draw) = this.as_drawing(activation.context.gc_context) {
draw_round_rect_internal(
&mut draw,
x,
y,
width,
height,
ellipse_width / 2.0,
ellipse_height / 2.0,
ellipse_width / 2.0,
ellipse_height / 2.0,
ellipse_width / 2.0,
ellipse_height / 2.0,
ellipse_width / 2.0,
ellipse_height / 2.0,
);
}
}

Ok(Value::Undefined)
}

/// Implements `Graphics.drawRoundRectComplex`
pub fn draw_round_rect_complex<'gc>(
activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(this) = this.as_display_object() {
let x = args.get_f64(activation, 0)?;
let y = args.get_f64(activation, 1)?;
let width = args.get_f64(activation, 2)?;
let height = args.get_f64(activation, 3)?;
let top_left = args.get_f64(activation, 4)?;
let top_right = args.get_f64(activation, 5)?;
let bottom_left = args.get_f64(activation, 6)?;
let bottom_right = args.get_f64(activation, 7)?;

if let Some(mut draw) = this.as_drawing(activation.context.gc_context) {
draw_round_rect_internal(
Expand All @@ -690,8 +739,14 @@ pub fn draw_round_rect<'gc>(
y,
width,
height,
ellipse_width,
ellipse_height,
top_left,
top_left,
top_right,
top_right,
bottom_left,
bottom_left,
bottom_right,
bottom_right,
);
}
}
Expand All @@ -717,8 +772,14 @@ pub fn draw_circle<'gc>(
y - radius,
radius * 2.0,
radius * 2.0,
radius * 2.0,
radius * 2.0,
radius,
radius,
radius,
radius,
radius,
radius,
radius,
radius,
);
}
}
Expand All @@ -739,7 +800,21 @@ pub fn draw_ellipse<'gc>(
let height = args.get_f64(activation, 3)?;

if let Some(mut draw) = this.as_drawing(activation.context.gc_context) {
draw_round_rect_internal(&mut draw, x, y, width, height, width, height)
draw_round_rect_internal(
&mut draw,
x,
y,
width,
height,
width / 2.0,
height / 2.0,
width / 2.0,
height / 2.0,
width / 2.0,
height / 2.0,
width / 2.0,
height / 2.0,
)
}
}

Expand Down Expand Up @@ -891,16 +966,6 @@ pub fn draw_path<'gc>(
Ok(Value::Undefined)
}

/// Implements `Graphics.drawRoundRectComplex`
pub fn draw_round_rect_complex<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.display.Graphics", "drawRoundRectComplex");
Ok(Value::Undefined)
}

/// Implements `Graphics.drawTriangles`
pub fn draw_triangles<'gc>(
activation: &mut Activation<'_, 'gc>,
Expand Down
29 changes: 29 additions & 0 deletions tests/tests/swfs/avm2/graphics_round_rects/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// compiled with mxmlc

package {
import flash.display.MovieClip;

public class Test extends MovieClip {
public function Test(){
graphics.beginFill(0xFFCC00);
// top left: normal
graphics.drawRoundRect(10, 10, 100, 100, 70, 30);
// top right: clamping
graphics.drawRoundRect(150, 10, 100, 100, 100, 100);

// middle left: complex, normal
graphics.drawRoundRectComplex(10, 150, 100, 100,
20, 30, 40, 50);

// middle right: complex, clamping
graphics.drawRoundRectComplex(150, 150, 100, 100,
80, 60, 50, 30);

// bottom left: circle
graphics.drawCircle(80, 300, 50);
// bottom right: ellipse
graphics.drawEllipse(150, 300, 100, 50);
graphics.endFill();
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Binary file added tests/tests/swfs/avm2/graphics_round_rects/test.swf
Binary file not shown.
Loading

0 comments on commit db61726

Please sign in to comment.