feat: 实现圆角矩形功能

This commit is contained in:
2026-03-01 17:38:11 +08:00
parent d7f4571217
commit 62be61c574
2 changed files with 259 additions and 6 deletions

View File

@@ -237,7 +237,7 @@ impl MSCanvas {
pub fn draw_cross_color(&mut self, point: Point, color: MSColor) {
let Point { x, y } = point;
let r = 5;
let r = 10;
for dy in -r..=r {
self.draw_pixel_color_at(Point::new(point.x, point.y + dy as f32), color);
}
@@ -392,10 +392,6 @@ impl MSCanvas {
}
pub fn draw_line_with_circle_brush(&mut self, begin: Point, end: Point) {
// let points = self.bresenham_line(begin, end);
// for point in points {
// self.draw_brush_at(point);
// }
self.draw_line_with(begin, end, MSCanvas::brush_circle);
}
@@ -534,6 +530,118 @@ impl MSCanvas {
(iter_count, fill_count)
}
pub fn arc_1px(
&mut self,
center: Point,
radius: f32,
start_angle: f32,
end_angle: f32,
counterclockwise: bool,
) {
if radius <= 0.0 {
return;
}
// 根据半径确定步长,步长越小曲线越平滑
// 圆周长约为 2πr每像素对应的角度步长约为 1/r 弧度
let step = (1.0 / radius).max(0.001_f32);
// 将角度规范化,确保 start_angle 和 end_angle 在合理范围内
let (start, end) = if counterclockwise {
// 逆时针:角度递减,将区间转成递增处理
// 逆时针从 start_angle 到 end_angle等价于顺时针从 end_angle 到 start_angle
let mut s = end_angle;
let mut e = start_angle;
// 保证 e >= s
while e < s {
e += std::f32::consts::TAU;
}
(s, e)
} else {
// 顺时针:角度递增
let mut s = start_angle;
let mut e = end_angle;
// 保证 e >= s
while e < s {
e += std::f32::consts::TAU;
}
(s, e)
};
// 沿角度步进,绘制每个像素点
let mut angle = start;
while angle <= end + step {
let a = angle.min(end);
let x = center.x + radius * a.cos();
let y = center.y + radius * a.sin();
self.draw_pixel_at(Point::new(x, y));
angle += step;
}
}
/// 圆弧从 x 轴方向开始计算, start_angle end_angle 为弧度counterclockwise 是否逆时针方向
pub fn arc(
&mut self,
center: Point,
radius: f32,
start_angle: f32,
end_angle: f32,
counterclockwise: bool,
) {
if radius <= 1.0 {
self.draw_pixel_at(center);
return;
}
let start_angle = normalize_radian(start_angle);
let end_angle = normalize_radian(end_angle);
let full_circle = (start_angle - end_angle).abs() < f32::EPSILON;
let min_x = ((center.x - radius) as i32).max(0);
let max_x = ((center.x + radius) as i32).min(self.width - 1);
let min_y = ((center.y - radius) as i32).max(0);
let max_y = ((center.y + radius) as i32).min(self.height - 1);
let min_r = (radius - self.line_width as f32).max(0.0);
let max_r = radius + 0.05;
let min_sq = min_r * min_r;
let max_sq = max_r * max_r;
let color = MSColor::new(
(255 + self.color.r) / 2,
self.color.g,
self.color.b,
self.color.a,
);
let center_x = center.x as i32;
let center_y = center.y as i32;
for y in min_y..=max_y {
for x in min_x..=max_x {
let dx = (x - center_x) as f32;
let dy = (y - center_y) as f32;
let dist_sq = dx * dx + dy * dy;
// 判断点是不是在扇区内
if dist_sq > max_sq || dist_sq <= min_sq {
continue;
}
// 判断角度是不是在扇区内
let theta = dy.atan2(dx);
let theta = normalize_radian(theta);
if full_circle
|| (counterclockwise && (theta >= end_angle || theta <= start_angle))
|| (!counterclockwise && theta >= start_angle && theta <= end_angle)
{
self.draw_pixel_at(Point::new(x as f32, y as f32));
}
}
}
}
pub fn clear_rect(&mut self, x: i32, y: i32, width: i32, height: i32) {
for yi in y..(y + height) {
self.clear_row(x, x + width, yi);
@@ -546,6 +654,91 @@ impl MSCanvas {
}
}
pub fn round_rect(&mut self, x: f32, y: f32, width: f32, height: f32, radius: f32) {
if (width as i32) < 2 * self.line_width
|| (height as i32) < 2 * self.line_width
|| width < 2.0 * radius
|| height < 2.0 * radius
{
self.fill_rect(x as i32, y as i32, width as i32, height as i32);
return;
}
self.fill_rect(
(x + radius) as i32,
y as i32,
(width - 2.0 * radius) as i32,
self.line_width,
);
self.fill_rect(
(x + radius) as i32,
(y + height) as i32 - self.line_width,
(width - 2.0 * radius) as i32,
self.line_width,
);
self.fill_rect(
x as i32,
(y + radius) as i32,
self.line_width,
(height - 2.0 * radius) as i32,
);
self.fill_rect(
(x + width) as i32 - self.line_width,
(y + radius) as i32,
self.line_width,
(height - 2.0 * radius) as i32,
);
self.arc(
Point::new(x + radius, y + radius),
radius,
std::f32::consts::PI,
std::f32::consts::PI + std::f32::consts::FRAC_PI_2,
false,
);
self.arc(
Point::new(x + width - 1.0 - radius, y + radius),
radius,
0.0,
std::f32::consts::PI + std::f32::consts::FRAC_PI_2,
true,
);
self.arc(
Point::new(x + radius, y + height - 1.0 - radius),
radius,
std::f32::consts::FRAC_PI_2,
std::f32::consts::PI,
false,
);
self.arc(
Point::new(x + width - 1.0 - radius, y + height - 1.0 - radius),
radius,
0.0,
std::f32::consts::FRAC_PI_2,
false,
);
}
pub fn round_rect1(&mut self, p1: Point, p2: Point, radius: f32) {
let mut x = p1.x;
let mut y = p1.y;
let mut width = (p2.x - p1.x);
let mut height = (p2.y - p1.y);
if width < 0.0 && height < 0.0 {
x = p2.x;
y = p2.y;
width = (p1.x - p2.x);
height = (p1.y - p2.y);
} else if width < 0.0 {
x += width;
width = -width;
} else if height < 0.0 {
y += height;
height = -height;
}
self.round_rect(x, y, width, height, radius);
}
pub fn stroke_rect(&mut self, x: i32, y: i32, width: i32, height: i32) {
if width < 2 * self.line_width || height < 2 * self.line_width {
self.fill_rect(x, y, width, height);
@@ -759,3 +952,16 @@ fn point_muln(point: Point, t: f32) -> Point {
fn point_add(p1: Point, p2: Point) -> Point {
Point::new(p1.x + p2.x, p1.y + p2.y)
}
/// 将弧度规范到 [0, 2π)
fn normalize_radian(radian: f32) -> f32 {
let mut r = radian;
while r < 0.0 {
r += std::f32::consts::TAU;
}
while r >= std::f32::consts::TAU {
r -= std::f32::consts::TAU;
}
r
}