feat: 椭圆支持线宽和一些重构
This commit is contained in:
116
src/mscanvas.rs
116
src/mscanvas.rs
@@ -87,8 +87,8 @@ impl Edge {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let dx = (bottom.x - top.x);
|
let dx = bottom.x - top.x;
|
||||||
let dy = (bottom.y - top.y);
|
let dy = bottom.y - top.y;
|
||||||
let dx_dy = dx / dy;
|
let dx_dy = dx / dy;
|
||||||
|
|
||||||
Some(Edge {
|
Some(Edge {
|
||||||
@@ -208,7 +208,7 @@ impl MSCanvas {
|
|||||||
|
|
||||||
pub fn pixel_at(&self, x: i32, y: i32) -> MSColor {
|
pub fn pixel_at(&self, x: i32, y: i32) -> MSColor {
|
||||||
// 边界检查
|
// 边界检查
|
||||||
if x < 0 || x >= self.width || y < 0 || y >= self.height as i32 {
|
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
||||||
return MSColor::ZERO;
|
return MSColor::ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ impl MSCanvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let xs = xs.clamp(0, self.width - 1);
|
let xs = xs.clamp(0, self.width - 1);
|
||||||
let xe = xe.clamp(0, self.width as i32);
|
let xe = xe.clamp(0, self.width);
|
||||||
for x in xs..xe {
|
for x in xs..xe {
|
||||||
let index = ((y * self.width + x) * 4) as usize;
|
let index = ((y * self.width + x) * 4) as usize;
|
||||||
|
|
||||||
@@ -885,7 +885,7 @@ impl MSCanvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let xs = xs.clamp(0, self.width - 1);
|
let xs = xs.clamp(0, self.width - 1);
|
||||||
let xe = xe.clamp(0, self.width as i32);
|
let xe = xe.clamp(0, self.width);
|
||||||
for x in xs..xe {
|
for x in xs..xe {
|
||||||
let index = ((y * self.width + x) * 4) as usize;
|
let index = ((y * self.width + x) * 4) as usize;
|
||||||
|
|
||||||
@@ -951,47 +951,63 @@ impl MSCanvas {
|
|||||||
self.bezier(points);
|
self.bezier(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 绘制带线宽的椭圆(描边)
|
||||||
|
/// - center: 椭圆中心
|
||||||
|
/// - rx, ry: 椭圆的水平/垂直半径(指中心线位置)
|
||||||
pub fn stroke_ellipse(&mut self, center: Point, rx: f32, ry: f32) {
|
pub fn stroke_ellipse(&mut self, center: Point, rx: f32, ry: f32) {
|
||||||
let mut x = 0.0;
|
if self.line_width <= 0 || rx <= 0.0 || ry <= 0.0 {
|
||||||
let mut y = ry; // 初始时将y设置为半高
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 计算初始决策参数
|
// 外椭圆半径(膨胀)
|
||||||
let mut decision = ry * ry - rx * rx * ry + rx * rx / 4.0;
|
let outer_rx = rx;
|
||||||
|
let outer_ry = ry;
|
||||||
|
|
||||||
while ry * ry * x < rx * rx * y {
|
// 内椭圆半径(收缩)——不能为负
|
||||||
// 在每个阶段,根据对称性绘制四个方向上的点
|
let inner_rx = (rx - self.line_width as f32).max(0.0);
|
||||||
self.stroke_pixel_at(Point::new(center.x + x, center.y + y));
|
let inner_ry = (ry - self.line_width as f32).max(0.0);
|
||||||
self.stroke_pixel_at(Point::new(center.x - x, center.y + y));
|
|
||||||
self.stroke_pixel_at(Point::new(center.x + x, center.y - y));
|
|
||||||
self.stroke_pixel_at(Point::new(center.x - x, center.y - y));
|
|
||||||
|
|
||||||
if decision < 0.0 {
|
// 计算包围矩形(基于外椭圆)
|
||||||
x += 1.0;
|
let min_x = (center.x - outer_rx).ceil() as i32;
|
||||||
decision += 2.0 * ry * ry * x + ry * ry;
|
let max_x = (center.x + outer_rx).floor() as i32;
|
||||||
|
let min_y = (center.y - outer_ry).ceil() as i32;
|
||||||
|
let max_y = (center.y + outer_ry).floor() as i32;
|
||||||
|
|
||||||
|
// 预计算外椭圆参数
|
||||||
|
let outer_rx2 = outer_rx * outer_rx;
|
||||||
|
let outer_ry2 = outer_ry * outer_ry;
|
||||||
|
let outer_threshold = outer_rx2 * outer_ry2;
|
||||||
|
|
||||||
|
// 预计算内椭圆参数(仅当存在内区域时)
|
||||||
|
let (inner_rx2, inner_ry2, inner_threshold) = if inner_rx > 0.0 && inner_ry > 0.0 {
|
||||||
|
let rx2 = inner_rx * inner_rx;
|
||||||
|
let ry2 = inner_ry * inner_ry;
|
||||||
|
(rx2, ry2, rx2 * ry2)
|
||||||
} else {
|
} else {
|
||||||
x += 1.0;
|
(0.0, 0.0, -1.0) // inner_threshold < 0 表示无内区域(实心圆)
|
||||||
y -= 1.0;
|
};
|
||||||
decision += 2.0 * ry * ry * x - 2.0 * rx * rx * y + ry * ry;
|
|
||||||
|
for y in min_y..=max_y {
|
||||||
|
for x in min_x..=max_x {
|
||||||
|
let dx = x as f32 - center.x;
|
||||||
|
let dy = y as f32 - center.y;
|
||||||
|
|
||||||
|
// 判断是否在外椭圆内
|
||||||
|
let outer_val = dx * dx * outer_ry2 + dy * dy * outer_rx2;
|
||||||
|
if outer_val > outer_threshold {
|
||||||
|
continue; // 在外椭圆外
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否在内椭圆内(如果是,则跳过)
|
||||||
|
if inner_threshold >= 0.0 {
|
||||||
|
let inner_val = dx * dx * inner_ry2 + dy * dy * inner_rx2;
|
||||||
|
if inner_val <= inner_threshold {
|
||||||
|
continue; // 在内椭圆内 → 不绘制(空心)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decision =
|
// 否则:在外椭圆内,且不在内椭圆内 → 属于描边区域
|
||||||
ry * ry * (x + 0.5) * (x + 0.5) + rx * rx * (y - 1.0) * (y - 1.0) - rx * rx * ry * ry;
|
self.stroke_pixel_at1(x, y);
|
||||||
|
|
||||||
while y > 0.0 {
|
|
||||||
// 同样地,根据对称性绘制四个方向上的点
|
|
||||||
self.stroke_pixel_at(Point::new(center.x + x, center.y + y));
|
|
||||||
self.stroke_pixel_at(Point::new(center.x - x, center.y + y));
|
|
||||||
self.stroke_pixel_at(Point::new(center.x + x, center.y - y));
|
|
||||||
self.stroke_pixel_at(Point::new(center.x - x, center.y - y));
|
|
||||||
|
|
||||||
if decision > 0.0 {
|
|
||||||
y -= 1.0;
|
|
||||||
decision += rx * rx - 2.0 * rx * rx * y;
|
|
||||||
} else {
|
|
||||||
x += 1.0;
|
|
||||||
y -= 1.0;
|
|
||||||
decision += 2.0 * ry * ry * x - 2.0 * rx * rx * y + rx * rx;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1027,14 +1043,14 @@ impl MSCanvas {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rx = rx as f32;
|
let rx = rx;
|
||||||
let ry = ry as f32;
|
let ry = ry;
|
||||||
|
|
||||||
// 计算包围矩形(整数边界)
|
// 计算包围矩形(整数边界)
|
||||||
let min_x = (center.x as f32 - rx).ceil() as i32;
|
let min_x = (center.x - rx).ceil() as i32;
|
||||||
let max_x = (center.x as f32 + rx).floor() as i32;
|
let max_x = (center.x + rx).floor() as i32;
|
||||||
let min_y = (center.y as f32 - ry).ceil() as i32;
|
let min_y = (center.y - ry).ceil() as i32;
|
||||||
let max_y = (center.y as f32 + ry).floor() as i32;
|
let max_y = (center.y + ry).floor() as i32;
|
||||||
|
|
||||||
// 预计算常量:rx², ry², rx² * ry²
|
// 预计算常量:rx², ry², rx² * ry²
|
||||||
let rx2 = rx * rx;
|
let rx2 = rx * rx;
|
||||||
@@ -1043,8 +1059,8 @@ impl MSCanvas {
|
|||||||
|
|
||||||
for y in min_y..=max_y {
|
for y in min_y..=max_y {
|
||||||
for x in min_x..=max_x {
|
for x in min_x..=max_x {
|
||||||
let dx = x as f32 - center.x as f32;
|
let dx = x as f32 - center.x;
|
||||||
let dy = y as f32 - center.y as f32;
|
let dy = y as f32 - center.y;
|
||||||
|
|
||||||
// 判断是否在椭圆内:dx²/ rx² + dy²/ ry² <= 1
|
// 判断是否在椭圆内:dx²/ rx² + dy²/ ry² <= 1
|
||||||
// 等价于:dx² * ry² + dy² * rx² <= rx² * ry²
|
// 等价于:dx² * ry² + dy² * rx² <= rx² * ry²
|
||||||
@@ -1197,14 +1213,6 @@ fn normalize_radian(radian: f32) -> f32 {
|
|||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// 辅助函数:计算两点间距离的平方(避免开方)
|
|
||||||
fn distance_sq(a: Point, b: Point) -> f32 {
|
|
||||||
let dx = a.x - b.x;
|
|
||||||
let dy = a.y - b.y;
|
|
||||||
dx * dx + dy * dy
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// 辅助函数:计算两点间距离的平方(避免开方)
|
/// 辅助函数:计算两点间距离的平方(避免开方)
|
||||||
fn distance_sq1(a: (i32, i32), b: (i32, i32)) -> f32 {
|
fn distance_sq1(a: (i32, i32), b: (i32, i32)) -> f32 {
|
||||||
|
|||||||
57
src/paint.rs
57
src/paint.rs
@@ -766,15 +766,12 @@ impl PaintApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_with_brush(&mut self, message: Message) {
|
pub fn update_with_brush(&mut self, message: Message) {
|
||||||
let mut brush_fn: fn(&mut MSCanvas, Point) = MSCanvas::brush_circle;
|
let brush_fn = match self.get_brush_kind() {
|
||||||
if let Some(kind) = self.config.brush_kind {
|
|
||||||
brush_fn = match kind {
|
|
||||||
BrushKind::Circle => MSCanvas::brush_circle,
|
BrushKind::Circle => MSCanvas::brush_circle,
|
||||||
BrushKind::Square => MSCanvas::brush_square,
|
BrushKind::Square => MSCanvas::brush_square,
|
||||||
BrushKind::Slash => MSCanvas::brush_slash,
|
BrushKind::Slash => MSCanvas::brush_slash,
|
||||||
BrushKind::Backslash => MSCanvas::brush_backslash,
|
BrushKind::Backslash => MSCanvas::brush_backslash,
|
||||||
}
|
};
|
||||||
}
|
|
||||||
match message {
|
match message {
|
||||||
Message::MousePressed(pos) => {
|
Message::MousePressed(pos) => {
|
||||||
self.is_drawing = true;
|
self.is_drawing = true;
|
||||||
@@ -969,6 +966,24 @@ impl PaintApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_polygon(&mut self) {
|
||||||
|
self.canvas.close_path();
|
||||||
|
match self.get_shape_style() {
|
||||||
|
ShapeStyle::Stroke => {
|
||||||
|
self.canvas.stroke();
|
||||||
|
}
|
||||||
|
ShapeStyle::Fill => {
|
||||||
|
self.canvas.fill();
|
||||||
|
}
|
||||||
|
ShapeStyle::FillAndStroke => {
|
||||||
|
self.canvas.fill_color(self.background_color);
|
||||||
|
self.canvas.fill();
|
||||||
|
self.canvas.fill_color(self.foreground_color);
|
||||||
|
self.canvas.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_with_polygon(&mut self, message: Message) {
|
pub fn update_with_polygon(&mut self, message: Message) {
|
||||||
match message {
|
match message {
|
||||||
Message::MousePressed(pos) => match self.control_state {
|
Message::MousePressed(pos) => match self.control_state {
|
||||||
@@ -1005,23 +1020,9 @@ impl PaintApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::MouseDoubleClick(_pos) => {
|
Message::MouseDoubleClick(_pos) => {
|
||||||
self.canvas.close_path();
|
|
||||||
match self.get_shape_style() {
|
|
||||||
ShapeStyle::Stroke => {
|
|
||||||
self.canvas.stroke();
|
|
||||||
}
|
|
||||||
ShapeStyle::Fill => {
|
|
||||||
self.canvas.fill();
|
|
||||||
}
|
|
||||||
ShapeStyle::FillAndStroke => {
|
|
||||||
self.canvas.fill_color(self.background_color);
|
|
||||||
self.canvas.fill();
|
|
||||||
self.canvas.fill_color(self.foreground_color);
|
|
||||||
self.canvas.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.is_drawing = false;
|
self.is_drawing = false;
|
||||||
self.control_state = ControlState::Zero;
|
self.control_state = ControlState::Zero;
|
||||||
|
self.close_polygon();
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -1131,21 +1132,7 @@ impl PaintApp {
|
|||||||
}
|
}
|
||||||
if self.tool_selected == Tool::Polygon && self.control_state != ControlState::Zero {
|
if self.tool_selected == Tool::Polygon && self.control_state != ControlState::Zero {
|
||||||
// 切换到其他工具,闭合路径
|
// 切换到其他工具,闭合路径
|
||||||
self.canvas.close_path();
|
self.close_polygon();
|
||||||
match self.get_shape_style() {
|
|
||||||
ShapeStyle::Stroke => {
|
|
||||||
self.canvas.stroke();
|
|
||||||
}
|
|
||||||
ShapeStyle::Fill => {
|
|
||||||
self.canvas.fill();
|
|
||||||
}
|
|
||||||
ShapeStyle::FillAndStroke => {
|
|
||||||
self.canvas.fill_color(self.background_color);
|
|
||||||
self.canvas.fill();
|
|
||||||
self.canvas.fill_color(self.foreground_color);
|
|
||||||
self.canvas.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user