2026-03-02 14:48:48 +08:00
|
|
|
|
use iced::{Color, Point};
|
2026-02-28 14:32:39 +08:00
|
|
|
|
use rand::prelude::*;
|
2026-02-26 19:38:34 +08:00
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
|
pub struct MSColor {
|
|
|
|
|
|
r: u8,
|
|
|
|
|
|
g: u8,
|
|
|
|
|
|
b: u8,
|
|
|
|
|
|
a: u8,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
|
impl MSColor {
|
|
|
|
|
|
fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
|
|
|
|
|
Self { r, g, b, a }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub const ZERO: MSColor = MSColor {
|
|
|
|
|
|
r: 0,
|
|
|
|
|
|
g: 0,
|
|
|
|
|
|
b: 0,
|
|
|
|
|
|
a: 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
pub const BLACK: MSColor = MSColor {
|
|
|
|
|
|
r: 0,
|
|
|
|
|
|
g: 0,
|
|
|
|
|
|
b: 0,
|
|
|
|
|
|
a: 255,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
pub const WHITE: MSColor = MSColor {
|
|
|
|
|
|
r: 255,
|
|
|
|
|
|
g: 255,
|
|
|
|
|
|
b: 255,
|
|
|
|
|
|
a: 255,
|
|
|
|
|
|
};
|
2026-03-02 14:48:48 +08:00
|
|
|
|
|
|
|
|
|
|
pub const RED: MSColor = MSColor {
|
|
|
|
|
|
r: 255,
|
|
|
|
|
|
g: 0,
|
|
|
|
|
|
b: 0,
|
|
|
|
|
|
a: 255,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
pub const GREEN: MSColor = MSColor {
|
|
|
|
|
|
r: 0,
|
|
|
|
|
|
g: 255,
|
|
|
|
|
|
b: 0,
|
|
|
|
|
|
a: 255,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
pub const BLUE: MSColor = MSColor {
|
|
|
|
|
|
r: 0,
|
|
|
|
|
|
g: 0,
|
|
|
|
|
|
b: 255,
|
|
|
|
|
|
a: 255,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
pub fn into_color(self) -> Color {
|
2026-03-02 16:53:07 +08:00
|
|
|
|
Color::from_rgba8(self.r, self.g, self.b, (self.a / 255) as f32)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// rgb 十六进制
|
|
|
|
|
|
pub fn from_hex(n: i32) -> Self {
|
|
|
|
|
|
let r = (n >> 16) as u8;
|
|
|
|
|
|
let g = (n >> 8) as u8;
|
|
|
|
|
|
let b = n as u8;
|
|
|
|
|
|
Self { r, g, b, a: 0xff }
|
2026-03-02 14:48:48 +08:00
|
|
|
|
}
|
2026-03-06 15:39:57 +08:00
|
|
|
|
|
|
|
|
|
|
pub fn invert(&self) -> Self {
|
|
|
|
|
|
Self::new(255 - self.r, 255 - self.g, 255 - self.b, self.a)
|
|
|
|
|
|
}
|
2026-03-02 14:48:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
|
|
|
|
struct Edge {
|
2026-03-02 16:53:07 +08:00
|
|
|
|
y_max: i32, // 边的最大 y(不包含)
|
|
|
|
|
|
x: f32, // 当前扫描线 y 处的 x 值
|
|
|
|
|
|
dx_dy: f32, // 1 / slope = Δx / Δy
|
2026-03-02 14:48:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Edge {
|
|
|
|
|
|
fn new(p1: Point, p2: Point) -> Option<Self> {
|
|
|
|
|
|
// 确保 p1.y <= p2.y
|
|
|
|
|
|
let (top, bottom) = if p1.y < p2.y { (p1, p2) } else { (p2, p1) };
|
|
|
|
|
|
|
|
|
|
|
|
// 忽略水平边(不会贡献交点)
|
|
|
|
|
|
if (top.y - bottom.y).abs() < f32::EPSILON {
|
|
|
|
|
|
return None;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 21:06:41 +08:00
|
|
|
|
let dx = bottom.x - top.x;
|
|
|
|
|
|
let dy = bottom.y - top.y;
|
2026-03-02 14:48:48 +08:00
|
|
|
|
let dx_dy = dx / dy;
|
|
|
|
|
|
|
|
|
|
|
|
Some(Edge {
|
|
|
|
|
|
y_max: bottom.y as i32,
|
|
|
|
|
|
x: top.x,
|
|
|
|
|
|
dx_dy,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 18:29:32 +08:00
|
|
|
|
pub struct Path2D {
|
|
|
|
|
|
points: Vec<Point>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Path2D {
|
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
|
Self { points: vec![] }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn push(&mut self, point: Point) {
|
|
|
|
|
|
self.points.push(point);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 17:36:27 +08:00
|
|
|
|
pub struct RectSelection {
|
|
|
|
|
|
/// 左上角坐标
|
|
|
|
|
|
x: i32,
|
|
|
|
|
|
y: i32,
|
|
|
|
|
|
w: i32,
|
|
|
|
|
|
h: i32,
|
|
|
|
|
|
pixels: Vec<u8>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl RectSelection {
|
|
|
|
|
|
pub fn new(x: i32, y: i32, w: i32, h: i32, pixels: Vec<u8>) -> RectSelection {
|
|
|
|
|
|
Self { x, y, w, h, pixels }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn contains(&self, p: Point) -> bool {
|
|
|
|
|
|
let x = p.x as i32;
|
|
|
|
|
|
let y = p.y as i32;
|
|
|
|
|
|
!(x < self.x || y < self.y || x >= self.x + self.w || y >= self.y + self.h)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn move_offset(&mut self, offset_x: i32, offset_y: i32) {
|
|
|
|
|
|
self.x += offset_x;
|
|
|
|
|
|
self.y += offset_y;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn size(&self) -> (i32, i32) {
|
|
|
|
|
|
(self.w, self.h)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn position(&self) -> (i32, i32) {
|
|
|
|
|
|
(self.x, self.y)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 19:38:34 +08:00
|
|
|
|
pub struct MSCanvas {
|
|
|
|
|
|
width: i32,
|
|
|
|
|
|
height: i32,
|
|
|
|
|
|
|
2026-02-27 22:29:17 +08:00
|
|
|
|
/// 原始像素数据:RGBA 格式
|
|
|
|
|
|
/// 长度 = width * height * 4
|
2026-02-26 19:38:34 +08:00
|
|
|
|
pixels: Vec<u8>,
|
|
|
|
|
|
pixels_bak: Vec<u8>,
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
fill_color: MSColor,
|
|
|
|
|
|
stroke_color: MSColor,
|
2026-02-26 19:38:34 +08:00
|
|
|
|
|
2026-02-27 22:29:17 +08:00
|
|
|
|
line_width: i32,
|
2026-02-28 18:29:32 +08:00
|
|
|
|
|
|
|
|
|
|
path2d: Path2D,
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
|
|
impl MSCanvas {
|
|
|
|
|
|
pub fn new(width: i32, height: i32) -> Self {
|
|
|
|
|
|
Self {
|
|
|
|
|
|
width,
|
|
|
|
|
|
height,
|
|
|
|
|
|
pixels: vec![255; (width * height * 4) as usize],
|
|
|
|
|
|
pixels_bak: Vec::new(),
|
2026-03-03 15:40:43 +08:00
|
|
|
|
fill_color: MSColor::BLACK,
|
|
|
|
|
|
stroke_color: MSColor::WHITE,
|
2026-02-27 22:29:17 +08:00
|
|
|
|
line_width: 1,
|
2026-02-28 18:29:32 +08:00
|
|
|
|
path2d: Path2D::new(),
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 00:58:35 +08:00
|
|
|
|
pub fn line_width(&mut self, line_width: i32) -> &mut Self {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.line_width = line_width;
|
2026-03-04 00:58:35 +08:00
|
|
|
|
self
|
2026-03-02 14:48:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 00:58:35 +08:00
|
|
|
|
pub fn fill_color(&mut self, color: MSColor) -> &mut Self {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_color = color;
|
2026-03-04 00:58:35 +08:00
|
|
|
|
self
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 00:58:35 +08:00
|
|
|
|
pub fn stroke_color(&mut self, color: MSColor) -> &mut Self {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_color = color;
|
2026-03-04 00:58:35 +08:00
|
|
|
|
self
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
2026-02-27 22:29:17 +08:00
|
|
|
|
pub fn size(&self) -> (i32, i32) {
|
|
|
|
|
|
(self.width, self.height)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn get_pixels_ref(&self) -> &[u8] {
|
|
|
|
|
|
&self.pixels
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 19:38:34 +08:00
|
|
|
|
pub fn get_pixels(&self) -> Vec<u8> {
|
|
|
|
|
|
self.pixels.clone()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 01:09:51 +08:00
|
|
|
|
pub fn get_pixels_scale(&self, scale: i32) -> Vec<u8> {
|
|
|
|
|
|
if scale <= 1 {
|
|
|
|
|
|
return self.pixels.clone();
|
|
|
|
|
|
}
|
|
|
|
|
|
let dst_width = self.width * scale;
|
2026-02-28 02:08:36 +08:00
|
|
|
|
let dst_height = self.height * scale;
|
|
|
|
|
|
let mut dst = vec![0; (dst_width * dst_height * 4) as usize]; // RGBA
|
2026-02-28 01:09:51 +08:00
|
|
|
|
|
|
|
|
|
|
for y in 0..self.height {
|
|
|
|
|
|
for x in 0..self.width {
|
|
|
|
|
|
// 源像素索引
|
|
|
|
|
|
let src_idx = ((y * self.width + x) * 4) as usize;
|
|
|
|
|
|
|
|
|
|
|
|
// 源像素颜色
|
|
|
|
|
|
let r = self.pixels[src_idx];
|
|
|
|
|
|
let g = self.pixels[src_idx + 1];
|
|
|
|
|
|
let b = self.pixels[src_idx + 2];
|
|
|
|
|
|
let a = self.pixels[src_idx + 3];
|
|
|
|
|
|
|
|
|
|
|
|
// 在目标图像中填充 scale×scale 区域
|
|
|
|
|
|
for dy in 0..scale {
|
|
|
|
|
|
for dx in 0..scale {
|
|
|
|
|
|
let dst_x = x * scale + dx;
|
|
|
|
|
|
let dst_y = y * scale + dy;
|
|
|
|
|
|
let dst_idx = ((dst_y * dst_width + dst_x) * 4) as usize;
|
|
|
|
|
|
|
|
|
|
|
|
dst[dst_idx] = r;
|
|
|
|
|
|
dst[dst_idx + 1] = g;
|
|
|
|
|
|
dst[dst_idx + 2] = b;
|
|
|
|
|
|
dst[dst_idx + 3] = a;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dst
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 17:36:27 +08:00
|
|
|
|
pub fn pixel_at(&self, x: i32, y: i32) -> Option<MSColor> {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
// 边界检查
|
2026-03-03 21:06:41 +08:00
|
|
|
|
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
2026-03-04 17:36:27 +08:00
|
|
|
|
return None;
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let index = ((y * self.width + x) * 4) as usize;
|
|
|
|
|
|
|
2026-03-04 17:36:27 +08:00
|
|
|
|
Some(MSColor::new(
|
2026-02-26 19:38:34 +08:00
|
|
|
|
self.pixels[index],
|
|
|
|
|
|
self.pixels[index + 1],
|
|
|
|
|
|
self.pixels[index + 2],
|
|
|
|
|
|
self.pixels[index + 3],
|
2026-03-04 17:36:27 +08:00
|
|
|
|
))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn pixel_is(&self, x: i32, y: i32, color: MSColor) -> bool {
|
|
|
|
|
|
match self.pixel_at(x, y) {
|
|
|
|
|
|
Some(c) => c == color,
|
|
|
|
|
|
None => false,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn select_rect(&self, x: i32, y: i32, w: i32, h: i32) -> RectSelection {
|
|
|
|
|
|
let mut result = vec![255; (w * h * 4) as usize];
|
|
|
|
|
|
|
|
|
|
|
|
let mut index = 0;
|
|
|
|
|
|
for yi in y..(y + h) {
|
|
|
|
|
|
for xi in x..(x + w) {
|
|
|
|
|
|
let color = self.pixel_at(xi, yi).unwrap_or(MSColor::WHITE);
|
|
|
|
|
|
result[index] = color.r;
|
|
|
|
|
|
result[index + 1] = color.g;
|
|
|
|
|
|
result[index + 2] = color.b;
|
|
|
|
|
|
result[index + 3] = color.a;
|
|
|
|
|
|
index += 4;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RectSelection::new(x, y, w, h, result)
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
2026-03-04 00:58:35 +08:00
|
|
|
|
|
|
|
|
|
|
pub fn overlay_pixels(&mut self, canvas: &MSCanvas) -> Vec<u8> {
|
|
|
|
|
|
let mut pixels = canvas.get_pixels();
|
2026-03-06 16:37:20 +08:00
|
|
|
|
let width = canvas.width.min(self.width) as usize;
|
|
|
|
|
|
let height = canvas.height.min(self.height) as usize;
|
2026-03-04 00:58:35 +08:00
|
|
|
|
|
2026-03-06 16:37:20 +08:00
|
|
|
|
let src_stride = self.width as usize * 4;
|
2026-03-07 15:51:13 +08:00
|
|
|
|
let dst_stride = canvas.width as usize * 4; // ⚠️ Bug fix: use canvas.width, not `width`
|
2026-03-04 00:58:35 +08:00
|
|
|
|
|
|
|
|
|
|
for y in 0..height {
|
2026-03-07 15:51:13 +08:00
|
|
|
|
let src_row = &self.pixels[y * src_stride..y * src_stride + width * 4];
|
|
|
|
|
|
let dst_row = &mut pixels[y * dst_stride..y * dst_stride + width * 4];
|
|
|
|
|
|
|
|
|
|
|
|
// Process 4 pixels at a time (16 bytes) for better auto-vectorization
|
|
|
|
|
|
let chunks = width / 4;
|
|
|
|
|
|
let remainder = width % 4;
|
|
|
|
|
|
|
|
|
|
|
|
for chunk in 0..chunks {
|
|
|
|
|
|
let base = chunk * 16;
|
|
|
|
|
|
// Check alpha bytes for 4 pixels at once
|
|
|
|
|
|
let a0 = src_row[base + 3];
|
|
|
|
|
|
let a1 = src_row[base + 7];
|
|
|
|
|
|
let a2 = src_row[base + 11];
|
|
|
|
|
|
let a3 = src_row[base + 15];
|
|
|
|
|
|
|
|
|
|
|
|
if a0 | a1 | a2 | a3 != 0 {
|
|
|
|
|
|
// At least one pixel is non-transparent
|
|
|
|
|
|
if a0 != 0 && a1 != 0 && a2 != 0 && a3 != 0 {
|
|
|
|
|
|
// All 4 pixels are non-transparent — bulk copy 16 bytes
|
|
|
|
|
|
dst_row[base..base + 16].copy_from_slice(&src_row[base..base + 16]);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Mixed — copy individually
|
|
|
|
|
|
for i in 0..4 {
|
|
|
|
|
|
let off = base + i * 4;
|
|
|
|
|
|
if src_row[off + 3] != 0 {
|
|
|
|
|
|
dst_row[off..off + 4].copy_from_slice(&src_row[off..off + 4]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// else: all 4 transparent — skip entirely
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Handle remaining pixels
|
|
|
|
|
|
for x in (chunks * 4)..width {
|
|
|
|
|
|
let off = x * 4;
|
|
|
|
|
|
if src_row[off + 3] != 0 {
|
|
|
|
|
|
dst_row[off..off + 4].copy_from_slice(&src_row[off..off + 4]);
|
2026-03-04 00:58:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pixels
|
|
|
|
|
|
}
|
2026-03-03 15:40:43 +08:00
|
|
|
|
}
|
2026-02-26 19:38:34 +08:00
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
#[allow(unused)]
|
|
|
|
|
|
impl MSCanvas {
|
|
|
|
|
|
pub fn fill_pixel_at(&mut self, point: Point) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
let Point { x, y } = point;
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at1(x as i32, y as i32);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn stroke_pixel_at(&mut self, point: Point) {
|
2026-02-28 01:09:51 +08:00
|
|
|
|
let Point { x, y } = point;
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_pixel_at1(x as i32, y as i32);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn fill_pixel_at1(&mut self, x: i32, y: i32) {
|
2026-02-28 01:09:51 +08:00
|
|
|
|
// 边界检查
|
|
|
|
|
|
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 计算索引:(y * width + x) * 4
|
|
|
|
|
|
let index = ((y * self.width + x) * 4) as usize;
|
|
|
|
|
|
|
|
|
|
|
|
// 写入 RGBA 数据
|
|
|
|
|
|
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.pixels[index] = self.fill_color.r; // R
|
|
|
|
|
|
self.pixels[index + 1] = self.fill_color.g; // G
|
|
|
|
|
|
self.pixels[index + 2] = self.fill_color.b; // B
|
|
|
|
|
|
self.pixels[index + 3] = self.fill_color.a; // A
|
2026-02-28 01:09:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
fn stroke_pixel_at1(&mut self, x: i32, y: i32) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
// 边界检查
|
|
|
|
|
|
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 计算索引:(y * width + x) * 4
|
|
|
|
|
|
let index = ((y * self.width + x) * 4) as usize;
|
|
|
|
|
|
|
|
|
|
|
|
// 写入 RGBA 数据
|
|
|
|
|
|
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.pixels[index] = self.stroke_color.r; // R
|
|
|
|
|
|
self.pixels[index + 1] = self.stroke_color.g; // G
|
|
|
|
|
|
self.pixels[index + 2] = self.stroke_color.b; // B
|
|
|
|
|
|
self.pixels[index + 3] = self.stroke_color.a; // A
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 00:58:35 +08:00
|
|
|
|
fn draw_column_with_color(&mut self, ys: i32, ye: i32, x: i32, color: MSColor) {
|
|
|
|
|
|
if x < 0 || x >= self.width {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let ys = ys.clamp(0, self.height - 1);
|
|
|
|
|
|
let ye = ye.clamp(0, self.height);
|
|
|
|
|
|
for y in ys..ye {
|
|
|
|
|
|
let index = ((y * self.width + x) * 4) as usize;
|
|
|
|
|
|
|
|
|
|
|
|
// 写入 RGBA 数据
|
|
|
|
|
|
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
|
|
|
|
|
self.pixels[index] = color.r; // R
|
|
|
|
|
|
self.pixels[index + 1] = color.g; // G
|
|
|
|
|
|
self.pixels[index + 2] = color.b; // B
|
|
|
|
|
|
self.pixels[index + 3] = color.a; // A
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn draw_row_with_color(&mut self, xs: i32, xe: i32, y: i32, color: MSColor) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
if y < 0 || y >= self.height {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let xs = xs.clamp(0, self.width - 1);
|
2026-03-03 21:06:41 +08:00
|
|
|
|
let xe = xe.clamp(0, self.width);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
for x in xs..xe {
|
|
|
|
|
|
let index = ((y * self.width + x) * 4) as usize;
|
|
|
|
|
|
|
|
|
|
|
|
// 写入 RGBA 数据
|
|
|
|
|
|
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
2026-03-04 00:58:35 +08:00
|
|
|
|
self.pixels[index] = color.r; // R
|
|
|
|
|
|
self.pixels[index + 1] = color.g; // G
|
|
|
|
|
|
self.pixels[index + 2] = color.b; // B
|
|
|
|
|
|
self.pixels[index + 3] = color.a; // A
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 00:58:35 +08:00
|
|
|
|
fn fill_column(&mut self, ys: i32, ye: i32, x: i32) {
|
|
|
|
|
|
self.draw_column_with_color(ys, ye, x, self.fill_color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn fill_row(&mut self, xs: i32, xe: i32, y: i32) {
|
|
|
|
|
|
self.draw_row_with_color(xs, xe, y, self.fill_color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn stroke_column(&mut self, ys: i32, ye: i32, x: i32) {
|
|
|
|
|
|
self.draw_column_with_color(ys, ye, x, self.stroke_color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn stroke_row(&mut self, xs: i32, xe: i32, y: i32) {
|
|
|
|
|
|
self.draw_row_with_color(xs, xe, y, self.stroke_color);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn fill_pixels(&mut self, points: Vec<Point>) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
for point in points {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(point);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 16:37:20 +08:00
|
|
|
|
pub fn fill_pixels_i32(&mut self, points: Vec<Point<i32>>) {
|
|
|
|
|
|
for point in points {
|
|
|
|
|
|
self.fill_pixel_at1(point.x, point.y);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 00:58:35 +08:00
|
|
|
|
pub fn stroke_cross(&mut self, point: Point) {
|
2026-02-28 01:09:51 +08:00
|
|
|
|
let Point { x, y } = point;
|
2026-03-01 17:38:11 +08:00
|
|
|
|
let r = 10;
|
2026-02-28 01:09:51 +08:00
|
|
|
|
for dy in -r..=r {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_pixel_at(Point::new(point.x, point.y + dy as f32));
|
2026-02-28 01:09:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
for dx in -r..=r {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_pixel_at(Point::new(point.x + dx as f32, point.y));
|
2026-02-28 01:09:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 22:29:17 +08:00
|
|
|
|
pub fn brush_circle(&mut self, center: Point) {
|
|
|
|
|
|
if self.line_width <= 1 {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(center);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-02-27 22:29:17 +08:00
|
|
|
|
let square = (self.line_width as f32) * (self.line_width as f32) / 4.0;
|
|
|
|
|
|
let l = self.line_width / 2;
|
|
|
|
|
|
let r = self.line_width - l;
|
|
|
|
|
|
for dy in -l..r {
|
|
|
|
|
|
for dx in -l..r {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
if (dx * dx + dy * dy) as f32 <= square {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(Point::new(center.x + dx as f32, center.y + dy as f32));
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 22:29:17 +08:00
|
|
|
|
pub fn brush_square(&mut self, center: Point) {
|
|
|
|
|
|
if self.line_width <= 1 {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(center);
|
2026-02-27 22:29:17 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
let l = self.line_width / 2;
|
|
|
|
|
|
let r = self.line_width - l;
|
|
|
|
|
|
for dy in -l..r {
|
|
|
|
|
|
for dx in -l..r {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(Point::new(center.x + dx as f32, center.y + dy as f32));
|
2026-02-27 22:29:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 01:09:51 +08:00
|
|
|
|
pub fn brush_slash(&mut self, center: Point) {
|
|
|
|
|
|
let r = self.line_width / 2;
|
|
|
|
|
|
let l = self.line_width - r;
|
2026-02-28 02:08:36 +08:00
|
|
|
|
// 左右扩展,填补线条的空白
|
|
|
|
|
|
for dx in 0..2 {
|
|
|
|
|
|
for d in -l..r {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(Point::new(
|
2026-02-28 02:08:36 +08:00
|
|
|
|
center.x - (d as f32) + (dx as f32),
|
|
|
|
|
|
center.y - d as f32,
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
2026-02-28 01:09:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn brush_backslash(&mut self, center: Point) {
|
|
|
|
|
|
let r = self.line_width / 2;
|
|
|
|
|
|
let l = self.line_width - r;
|
2026-02-28 02:08:36 +08:00
|
|
|
|
// 左右扩展,填补线条的空白
|
|
|
|
|
|
for dx in 0..2 {
|
|
|
|
|
|
for d in -l..r {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(Point::new(
|
2026-02-28 02:08:36 +08:00
|
|
|
|
center.x + d as f32 + (dx as f32),
|
|
|
|
|
|
center.y - d as f32,
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
2026-02-28 01:09:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 19:38:34 +08:00
|
|
|
|
fn bresenham_line(&mut self, begin: Point, end: Point) -> Vec<Point> {
|
|
|
|
|
|
let x1 = begin.x;
|
|
|
|
|
|
let y1 = begin.y;
|
|
|
|
|
|
let x2 = end.x;
|
|
|
|
|
|
let y2 = end.y;
|
|
|
|
|
|
|
|
|
|
|
|
let dx = (x2 - x1);
|
|
|
|
|
|
let dy = (y2 - y1);
|
|
|
|
|
|
let dx1 = dx.abs();
|
|
|
|
|
|
let dy1 = dy.abs();
|
|
|
|
|
|
let mut px = 2.0 * dy1 - dx1;
|
|
|
|
|
|
let mut py = 2.0 * dx1 - dy1;
|
|
|
|
|
|
|
|
|
|
|
|
let mut x;
|
|
|
|
|
|
let mut y;
|
|
|
|
|
|
let xe;
|
|
|
|
|
|
let ye;
|
|
|
|
|
|
|
|
|
|
|
|
let mut points = Vec::new();
|
|
|
|
|
|
if dy1 <= dx1 {
|
|
|
|
|
|
if dx >= 0.0 {
|
|
|
|
|
|
x = x1;
|
|
|
|
|
|
y = y1;
|
|
|
|
|
|
xe = x2;
|
|
|
|
|
|
ye = y2;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
x = x2;
|
|
|
|
|
|
y = y2;
|
|
|
|
|
|
xe = x1;
|
|
|
|
|
|
ye = y1;
|
|
|
|
|
|
}
|
|
|
|
|
|
points.push(Point::new(x, y));
|
|
|
|
|
|
while x < xe {
|
|
|
|
|
|
x += 1.0;
|
|
|
|
|
|
if px < 0.0 {
|
|
|
|
|
|
px = px + 2.0 * dy1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (dx < 0.0 && dy < 0.0) || (dx > 0.0 && dy > 0.0) {
|
|
|
|
|
|
y = y + 1.0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
y = y - 1.0;
|
|
|
|
|
|
}
|
|
|
|
|
|
px = px + 2.0 * (dy1 - dx1);
|
|
|
|
|
|
}
|
|
|
|
|
|
points.push(Point::new(x, y));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if dy >= 0.0 {
|
|
|
|
|
|
x = x1;
|
|
|
|
|
|
y = y1;
|
|
|
|
|
|
ye = y2;
|
|
|
|
|
|
xe = x2;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
x = x2;
|
|
|
|
|
|
y = y2;
|
|
|
|
|
|
ye = y1;
|
|
|
|
|
|
xe = x1;
|
|
|
|
|
|
}
|
|
|
|
|
|
points.push(Point::new(x, y));
|
|
|
|
|
|
while y < ye {
|
|
|
|
|
|
y = y + 1.0;
|
|
|
|
|
|
if py <= 0.0 {
|
|
|
|
|
|
py = py + 2.0 * dx1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (dx < 0.0 && dy < 0.0) || (dx > 0.0 && dy > 0.0) {
|
|
|
|
|
|
x = x + 1.0;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
x = x - 1.0;
|
|
|
|
|
|
}
|
|
|
|
|
|
py = py + 2.0 * (dx1 - dy1);
|
|
|
|
|
|
}
|
|
|
|
|
|
points.push(Point::new(x, y));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
points.push(Point::new(xe, ye));
|
|
|
|
|
|
|
|
|
|
|
|
points
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn draw_line(&mut self, begin: Point, end: Point) {
|
|
|
|
|
|
let points = self.bresenham_line(begin, end);
|
|
|
|
|
|
for point in points {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(point);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 22:29:17 +08:00
|
|
|
|
pub fn draw_line_with_circle_brush(&mut self, begin: Point, end: Point) {
|
|
|
|
|
|
self.draw_line_with(begin, end, MSCanvas::brush_circle);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn draw_line_with_square_brush(&mut self, begin: Point, end: Point) {
|
|
|
|
|
|
self.draw_line_with(begin, end, MSCanvas::brush_square);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn draw_line_with<F>(&mut self, begin: Point, end: Point, brush_fn: F)
|
|
|
|
|
|
where
|
|
|
|
|
|
F: Fn(&mut MSCanvas, Point) -> (),
|
|
|
|
|
|
{
|
2026-02-26 19:38:34 +08:00
|
|
|
|
let points = self.bresenham_line(begin, end);
|
|
|
|
|
|
for point in points {
|
2026-02-27 22:29:17 +08:00
|
|
|
|
brush_fn(self, point);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn save_pixels(&mut self) {
|
|
|
|
|
|
self.pixels_bak = self.pixels.clone();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn restore_pixels(&mut self) {
|
|
|
|
|
|
self.pixels = self.pixels_bak.clone();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn fill_slow(&mut self, begin: Point) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
let start_x = begin.x as i32;
|
|
|
|
|
|
let start_y = begin.y as i32;
|
2026-03-04 17:36:27 +08:00
|
|
|
|
let target_color = self.pixel_at(start_x, start_y).unwrap_or(MSColor::ZERO);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
if target_color == self.fill_color {
|
|
|
|
|
|
return;
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut scan_points = vec![(start_x, start_y)];
|
|
|
|
|
|
let width = self.width;
|
|
|
|
|
|
let height = self.height;
|
|
|
|
|
|
while let Some((x, y)) = scan_points.pop() {
|
|
|
|
|
|
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
2026-03-04 17:36:27 +08:00
|
|
|
|
if self.pixel_is(x, y, target_color) {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at1(x, y);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
|
|
|
|
|
|
let p1 = (x - 1, y);
|
|
|
|
|
|
let p2 = (x + 1, y);
|
|
|
|
|
|
let p3 = (x, y - 1);
|
|
|
|
|
|
let p4 = (x, y + 1);
|
|
|
|
|
|
scan_points.push(p1);
|
|
|
|
|
|
scan_points.push(p2);
|
|
|
|
|
|
scan_points.push(p3);
|
|
|
|
|
|
scan_points.push(p4);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn fill_scanline(&mut self, begin: Point) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
let start_x = begin.x as i32;
|
|
|
|
|
|
let start_y = begin.y as i32;
|
|
|
|
|
|
let width = self.width;
|
|
|
|
|
|
let height = self.height;
|
|
|
|
|
|
|
|
|
|
|
|
// 边界检查
|
|
|
|
|
|
if start_x < 0 || start_x >= width || start_y < 0 || start_y >= height {
|
2026-03-03 01:28:48 +08:00
|
|
|
|
return;
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 17:36:27 +08:00
|
|
|
|
let target_color = self.pixel_at(start_x, start_y).unwrap_or(MSColor::ZERO);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
if target_color == self.fill_color {
|
2026-03-03 01:28:48 +08:00
|
|
|
|
return;
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 栈中存储 (y, x1, x2):表示第 y 行从 x1 到 x2(含)需要向上/下扫描
|
|
|
|
|
|
let mut stack = vec![(start_y, start_x, start_x)];
|
|
|
|
|
|
|
|
|
|
|
|
while let Some((y, mut lx, mut rx)) = stack.pop() {
|
|
|
|
|
|
// 向左扩展 lx
|
2026-03-04 17:36:27 +08:00
|
|
|
|
while lx - 1 >= 0 && self.pixel_is(lx - 1, y, target_color) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
lx -= 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 向右扩展 rx
|
2026-03-04 17:36:27 +08:00
|
|
|
|
while rx + 1 < width && self.pixel_is(rx + 1, y, target_color) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
rx += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 填充当前行 [lx, rx]
|
|
|
|
|
|
for x in lx..=rx {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at1(x, y);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查上一行 (y - 1)
|
|
|
|
|
|
if y - 1 >= 0 {
|
|
|
|
|
|
let mut x = lx;
|
|
|
|
|
|
while x <= rx {
|
2026-03-04 17:36:27 +08:00
|
|
|
|
if self.pixel_is(x, y - 1, target_color) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
let span_start = x;
|
|
|
|
|
|
// 跳过连续的目标色块
|
2026-03-04 17:36:27 +08:00
|
|
|
|
while x <= rx && self.pixel_is(x, y - 1, target_color) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
x += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 将这个 span 入栈(用于后续处理上一行的上一行)
|
|
|
|
|
|
stack.push((y - 1, span_start, x - 1));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
x += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查下一行 (y + 1)
|
|
|
|
|
|
if y + 1 < height {
|
|
|
|
|
|
let mut x = lx;
|
|
|
|
|
|
while x <= rx {
|
2026-03-04 17:36:27 +08:00
|
|
|
|
if self.pixel_is(x, y + 1, target_color) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
let span_start = x;
|
2026-03-04 17:36:27 +08:00
|
|
|
|
while x <= rx && self.pixel_is(x, y + 1, target_color) {
|
2026-02-26 19:38:34 +08:00
|
|
|
|
x += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
stack.push((y + 1, span_start, x - 1));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
x += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-03 01:28:48 +08:00
|
|
|
|
}
|
2026-02-26 19:38:34 +08:00
|
|
|
|
|
2026-03-01 17:38:11 +08:00
|
|
|
|
/// 圆弧从 x 轴方向开始计算, start_angle end_angle 为弧度,counterclockwise 是否逆时针方向
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn stroke_arc(
|
2026-03-01 17:38:11 +08:00
|
|
|
|
&mut self,
|
|
|
|
|
|
center: Point,
|
|
|
|
|
|
radius: f32,
|
|
|
|
|
|
start_angle: f32,
|
|
|
|
|
|
end_angle: f32,
|
|
|
|
|
|
counterclockwise: bool,
|
|
|
|
|
|
) {
|
|
|
|
|
|
if radius <= 1.0 {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(center);
|
2026-03-01 17:38:11 +08:00
|
|
|
|
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 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)
|
|
|
|
|
|
{
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_pixel_at1(x, y);
|
2026-03-01 17:38:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 22:29:17 +08:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
fn fill_rect_stroke_color(&mut self, x: i32, y: i32, width: i32, height: i32) {
|
|
|
|
|
|
let old_fill_color = self.fill_color;
|
|
|
|
|
|
self.fill_color(self.stroke_color);
|
2026-02-27 22:29:17 +08:00
|
|
|
|
for yi in y..(y + height) {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_row(x, x + width, yi);
|
2026-02-27 22:29:17 +08:00
|
|
|
|
}
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_color(old_fill_color);
|
2026-03-02 14:48:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn fill_rect(&mut self, x: i32, y: i32, width: i32, height: i32) {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
for yi in y..(y + height) {
|
|
|
|
|
|
self.fill_row(x, x + width, yi);
|
|
|
|
|
|
}
|
2026-03-02 14:48:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 20:55:02 +08:00
|
|
|
|
pub fn fill_rect1(&mut self, p1: Point, p2: Point) {
|
|
|
|
|
|
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.fill_rect(x as i32, y as i32, width as i32, height as i32);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 00:00:42 +08:00
|
|
|
|
/// 填充一个圆角矩形
|
|
|
|
|
|
/// - x, y: 左上角坐标
|
|
|
|
|
|
/// - width, height: 尺寸(必须 > 0)
|
|
|
|
|
|
/// - radius: 圆角半径(会被 clamp 到合理范围)
|
2026-03-03 00:52:19 +08:00
|
|
|
|
pub fn fill_round_rect(&mut self, x: f32, y: f32, width: f32, height: f32, radius: f32) {
|
2026-03-03 00:00:42 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let min_x = x;
|
|
|
|
|
|
let max_x = x + width - 1.0;
|
|
|
|
|
|
let min_y = y;
|
|
|
|
|
|
let max_y = y + height - 1.0;
|
|
|
|
|
|
|
|
|
|
|
|
// 预计算四个圆角的圆心
|
|
|
|
|
|
let corners = [
|
2026-03-03 00:52:19 +08:00
|
|
|
|
((x + radius) as i32, (y + radius) as i32), // 左上
|
|
|
|
|
|
((x + width - 1.0 - radius) as i32, (y + radius) as i32), // 右上
|
|
|
|
|
|
(
|
|
|
|
|
|
(x + width - 1.0 - radius) as i32,
|
|
|
|
|
|
(y + height - 1.0 - radius) as i32,
|
|
|
|
|
|
), // 右下
|
|
|
|
|
|
((x + radius) as i32, (y + height - 1.0 - radius) as i32), // 左下
|
2026-03-03 00:00:42 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
let min_x = min_x as i32;
|
|
|
|
|
|
let max_x = max_x as i32;
|
|
|
|
|
|
let min_y = min_y as i32;
|
|
|
|
|
|
let max_y = max_y as i32;
|
|
|
|
|
|
let radius_sq = (radius + 0.05) * (radius + 0.05);
|
|
|
|
|
|
let radius_int = radius as i32;
|
|
|
|
|
|
for py in min_y..=max_y {
|
|
|
|
|
|
for px in min_x..=max_x {
|
|
|
|
|
|
// 判断是否在“直边区域”(无需检查圆角)
|
|
|
|
|
|
if px >= min_x + radius_int && px <= max_x - radius_int {
|
|
|
|
|
|
// 在左右圆角之间的竖直带 → 一定在内部
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at1(px, py);
|
2026-03-03 00:00:42 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
if py >= min_y + radius_int && py <= max_y - radius_int {
|
|
|
|
|
|
// 在上下圆角之间的水平带 → 一定在内部
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at1(px, py);
|
2026-03-03 00:00:42 +08:00
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 否则,点位于四个角落之一,需检查是否在四分之一圆内
|
|
|
|
|
|
let in_corner =
|
|
|
|
|
|
// 左上角区域
|
|
|
|
|
|
(distance_sq1((px, py), corners[0]) <= radius_sq) ||
|
|
|
|
|
|
// 右上角区域
|
|
|
|
|
|
(distance_sq1((px, py), corners[1]) <= radius_sq) ||
|
|
|
|
|
|
// 右下角区域
|
|
|
|
|
|
(distance_sq1((px, py), corners[2]) <= radius_sq) ||
|
|
|
|
|
|
// 左下角区域
|
|
|
|
|
|
(distance_sq1((px, py), corners[3]) <= radius_sq);
|
|
|
|
|
|
|
|
|
|
|
|
if in_corner {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at1(px, py);
|
2026-03-03 00:00:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn fill_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.fill_round_rect(x, y, width, height, radius);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn stroke_round_rect(&mut self, x: f32, y: f32, width: f32, height: f32, radius: f32) {
|
2026-03-01 17:38:11 +08:00
|
|
|
|
if (width as i32) < 2 * self.line_width
|
|
|
|
|
|
|| (height as i32) < 2 * self.line_width
|
|
|
|
|
|
|| width < 2.0 * radius
|
|
|
|
|
|
|| height < 2.0 * radius
|
|
|
|
|
|
{
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_rect_stroke_color(x as i32, y as i32, width as i32, height as i32);
|
2026-03-01 17:38:11 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_rect_stroke_color(
|
2026-03-01 17:38:11 +08:00
|
|
|
|
(x + radius) as i32,
|
|
|
|
|
|
y as i32,
|
|
|
|
|
|
(width - 2.0 * radius) as i32,
|
|
|
|
|
|
self.line_width,
|
|
|
|
|
|
);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_rect_stroke_color(
|
2026-03-01 17:38:11 +08:00
|
|
|
|
(x + radius) as i32,
|
|
|
|
|
|
(y + height) as i32 - self.line_width,
|
|
|
|
|
|
(width - 2.0 * radius) as i32,
|
|
|
|
|
|
self.line_width,
|
|
|
|
|
|
);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_rect_stroke_color(
|
2026-03-01 17:38:11 +08:00
|
|
|
|
x as i32,
|
|
|
|
|
|
(y + radius) as i32,
|
|
|
|
|
|
self.line_width,
|
|
|
|
|
|
(height - 2.0 * radius) as i32,
|
|
|
|
|
|
);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_rect_stroke_color(
|
2026-03-01 17:38:11 +08:00
|
|
|
|
(x + width) as i32 - self.line_width,
|
|
|
|
|
|
(y + radius) as i32,
|
|
|
|
|
|
self.line_width,
|
|
|
|
|
|
(height - 2.0 * radius) as i32,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_arc(
|
2026-03-01 17:38:11 +08:00
|
|
|
|
Point::new(x + radius, y + radius),
|
|
|
|
|
|
radius,
|
|
|
|
|
|
std::f32::consts::PI,
|
|
|
|
|
|
std::f32::consts::PI + std::f32::consts::FRAC_PI_2,
|
|
|
|
|
|
false,
|
|
|
|
|
|
);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_arc(
|
2026-03-01 17:38:11 +08:00
|
|
|
|
Point::new(x + width - 1.0 - radius, y + radius),
|
|
|
|
|
|
radius,
|
|
|
|
|
|
0.0,
|
|
|
|
|
|
std::f32::consts::PI + std::f32::consts::FRAC_PI_2,
|
|
|
|
|
|
true,
|
|
|
|
|
|
);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_arc(
|
2026-03-01 17:38:11 +08:00
|
|
|
|
Point::new(x + radius, y + height - 1.0 - radius),
|
|
|
|
|
|
radius,
|
|
|
|
|
|
std::f32::consts::FRAC_PI_2,
|
|
|
|
|
|
std::f32::consts::PI,
|
|
|
|
|
|
false,
|
|
|
|
|
|
);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_arc(
|
2026-03-01 17:38:11 +08:00
|
|
|
|
Point::new(x + width - 1.0 - radius, y + height - 1.0 - radius),
|
|
|
|
|
|
radius,
|
|
|
|
|
|
0.0,
|
|
|
|
|
|
std::f32::consts::FRAC_PI_2,
|
|
|
|
|
|
false,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn stroke_round_rect1(&mut self, p1: Point, p2: Point, radius: f32) {
|
2026-03-01 17:38:11 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_round_rect(x, y, width, height, radius);
|
2026-03-01 17:38:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn stroke_rect(&mut self, x: i32, y: i32, width: i32, height: i32) {
|
2026-02-28 01:09:51 +08:00
|
|
|
|
if width < 2 * self.line_width || height < 2 * self.line_width {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_rect_stroke_color(x, y, width, height);
|
2026-02-28 01:09:51 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_rect_stroke_color(x, y, width, self.line_width);
|
|
|
|
|
|
self.fill_rect_stroke_color(x, y + height - self.line_width, width, self.line_width);
|
|
|
|
|
|
self.fill_rect_stroke_color(x, y, self.line_width, height);
|
|
|
|
|
|
self.fill_rect_stroke_color(x + width - self.line_width, y, self.line_width, height);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn stroke_rect1(&mut self, p1: Point, p2: Point) {
|
2026-02-27 22:29:17 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_rect(x as i32, y as i32, width as i32, height as i32);
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-04 00:58:35 +08:00
|
|
|
|
pub fn stroke_dashed_rect(
|
|
|
|
|
|
&mut self,
|
|
|
|
|
|
x: i32,
|
|
|
|
|
|
y: i32,
|
|
|
|
|
|
width: i32,
|
|
|
|
|
|
height: i32,
|
|
|
|
|
|
dash_length: i32,
|
|
|
|
|
|
gap_length: i32,
|
|
|
|
|
|
) {
|
|
|
|
|
|
let mut is_dash = true;
|
|
|
|
|
|
|
|
|
|
|
|
// 上下两边边框
|
|
|
|
|
|
for dx in (x..x + width).step_by((dash_length + gap_length) as usize) {
|
|
|
|
|
|
if is_dash {
|
|
|
|
|
|
self.stroke_row(dx, dx + dash_length, y);
|
|
|
|
|
|
self.stroke_row(dx, dx + dash_length, y + height - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
is_dash = !is_dash;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 左右两边边框
|
|
|
|
|
|
for dy in (y..y + height).step_by((dash_length + gap_length) as usize) {
|
|
|
|
|
|
if is_dash {
|
|
|
|
|
|
self.stroke_column(dy, dy + dash_length, x);
|
|
|
|
|
|
self.stroke_column(dy, dy + dash_length, x + width - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
is_dash = !is_dash;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn stroke_dashed_rect1(&mut self, p1: Point, p2: Point, dash_length: i32, gap_length: i32) {
|
|
|
|
|
|
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.stroke_dashed_rect(
|
|
|
|
|
|
x as i32,
|
|
|
|
|
|
y as i32,
|
|
|
|
|
|
width as i32,
|
|
|
|
|
|
height as i32,
|
|
|
|
|
|
dash_length,
|
|
|
|
|
|
gap_length,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 19:38:34 +08:00
|
|
|
|
pub fn clear(&mut self) {
|
|
|
|
|
|
self.pixels.fill(255);
|
|
|
|
|
|
}
|
2026-02-27 22:29:17 +08:00
|
|
|
|
|
2026-03-04 00:58:35 +08:00
|
|
|
|
pub fn clear_zero(&mut self) {
|
|
|
|
|
|
self.pixels.fill(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 22:29:17 +08:00
|
|
|
|
pub fn clear_row(&mut self, xs: i32, xe: i32, y: i32) {
|
|
|
|
|
|
if y < 0 || y >= self.height {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let xs = xs.clamp(0, self.width - 1);
|
2026-03-03 21:06:41 +08:00
|
|
|
|
let xe = xe.clamp(0, self.width);
|
2026-02-27 22:29:17 +08:00
|
|
|
|
for x in xs..xe {
|
|
|
|
|
|
let index = ((y * self.width + x) * 4) as usize;
|
|
|
|
|
|
|
|
|
|
|
|
// 写入 RGBA 数据
|
|
|
|
|
|
// 注意:Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
|
|
|
|
|
|
self.pixels[index] = 255; // R
|
|
|
|
|
|
self.pixels[index + 1] = 255; // G
|
|
|
|
|
|
self.pixels[index + 2] = 255; // B
|
|
|
|
|
|
self.pixels[index + 3] = 255; // A
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-28 14:32:39 +08:00
|
|
|
|
|
|
|
|
|
|
/// 实现喷枪效果的函数
|
|
|
|
|
|
pub fn spray_paint(&mut self, center: Point, radius: i32, density: u32) {
|
|
|
|
|
|
let mut rng = rand::rng();
|
|
|
|
|
|
|
|
|
|
|
|
for _ in 0..density {
|
|
|
|
|
|
// 在给定半径内随机产生偏移量
|
|
|
|
|
|
let offset_x = rng.random_range(-radius..=radius);
|
|
|
|
|
|
let offset_y = rng.random_range(-radius..=radius);
|
|
|
|
|
|
|
|
|
|
|
|
// 确保我们只在圆形区域内绘制,而非整个正方形区域
|
|
|
|
|
|
if (offset_x * offset_x + offset_y * offset_y) <= (radius * radius) {
|
|
|
|
|
|
let point = Point::new(center.x + offset_x as f32, center.y + offset_y as f32);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at(point);
|
2026-02-28 14:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-28 16:17:17 +08:00
|
|
|
|
|
|
|
|
|
|
fn recursive_bezier(&mut self, control_points: &[Point], t: f32) -> Point {
|
|
|
|
|
|
// Implement de Casteljau's algorithm
|
|
|
|
|
|
let n = control_points.len();
|
|
|
|
|
|
if n == 1 {
|
|
|
|
|
|
return control_points[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
let mut new_points: Vec<Point> = Vec::with_capacity(n - 1);
|
|
|
|
|
|
for i in 0..(n - 1) {
|
|
|
|
|
|
let p0 = control_points[i];
|
|
|
|
|
|
let p1 = control_points[i + 1];
|
|
|
|
|
|
let point = point_add(point_muln(p0, 1.0 - t), point_muln(p1, t));
|
|
|
|
|
|
new_points.push(point);
|
|
|
|
|
|
}
|
|
|
|
|
|
self.recursive_bezier(&new_points[..], t)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn bezier(&mut self, control_points: Vec<Point>) {
|
|
|
|
|
|
// Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's recursive Bezier algorithm.
|
|
|
|
|
|
let mut t = 0.0;
|
|
|
|
|
|
while t <= 1.0 {
|
|
|
|
|
|
let point = self.recursive_bezier(&control_points, t);
|
|
|
|
|
|
self.brush_circle(point);
|
2026-03-03 00:52:19 +08:00
|
|
|
|
t += 0.0005;
|
2026-02-28 16:17:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn quadratic_bezier(&mut self, begin: Point, end: Point, p1: Point) {
|
|
|
|
|
|
let points = vec![begin, p1, end];
|
|
|
|
|
|
self.bezier(points);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn cubic_bezier(&mut self, begin: Point, end: Point, p1: Point, p2: Point) {
|
|
|
|
|
|
let points = vec![begin, p1, p2, end];
|
|
|
|
|
|
self.bezier(points);
|
|
|
|
|
|
}
|
2026-02-28 18:29:32 +08:00
|
|
|
|
|
2026-03-03 21:06:41 +08:00
|
|
|
|
/// 绘制带线宽的椭圆(描边)
|
|
|
|
|
|
/// - center: 椭圆中心
|
|
|
|
|
|
/// - rx, ry: 椭圆的水平/垂直半径(指中心线位置)
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn stroke_ellipse(&mut self, center: Point, rx: f32, ry: f32) {
|
2026-03-03 21:06:41 +08:00
|
|
|
|
if self.line_width <= 0 || rx <= 0.0 || ry <= 0.0 {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-02-28 18:44:50 +08:00
|
|
|
|
|
2026-03-03 21:06:41 +08:00
|
|
|
|
// 外椭圆半径(膨胀)
|
|
|
|
|
|
let outer_rx = rx;
|
|
|
|
|
|
let outer_ry = ry;
|
|
|
|
|
|
|
|
|
|
|
|
// 内椭圆半径(收缩)——不能为负
|
|
|
|
|
|
let inner_rx = (rx - self.line_width as f32).max(0.0);
|
|
|
|
|
|
let inner_ry = (ry - self.line_width as f32).max(0.0);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算包围矩形(基于外椭圆)
|
|
|
|
|
|
let min_x = (center.x - outer_rx).ceil() as i32;
|
|
|
|
|
|
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 {
|
|
|
|
|
|
(0.0, 0.0, -1.0) // inner_threshold < 0 表示无内区域(实心圆)
|
|
|
|
|
|
};
|
2026-02-28 18:44:50 +08:00
|
|
|
|
|
2026-03-03 21:06:41 +08:00
|
|
|
|
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;
|
2026-02-28 18:44:50 +08:00
|
|
|
|
|
2026-03-03 21:06:41 +08:00
|
|
|
|
// 判断是否在外椭圆内
|
|
|
|
|
|
let outer_val = dx * dx * outer_ry2 + dy * dy * outer_rx2;
|
|
|
|
|
|
if outer_val > outer_threshold {
|
|
|
|
|
|
continue; // 在外椭圆外
|
|
|
|
|
|
}
|
2026-02-28 18:44:50 +08:00
|
|
|
|
|
2026-03-03 21:06:41 +08:00
|
|
|
|
// 判断是否在内椭圆内(如果是,则跳过)
|
|
|
|
|
|
if inner_threshold >= 0.0 {
|
|
|
|
|
|
let inner_val = dx * dx * inner_ry2 + dy * dy * inner_rx2;
|
|
|
|
|
|
if inner_val <= inner_threshold {
|
|
|
|
|
|
continue; // 在内椭圆内 → 不绘制(空心)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-28 18:44:50 +08:00
|
|
|
|
|
2026-03-03 21:06:41 +08:00
|
|
|
|
// 否则:在外椭圆内,且不在内椭圆内 → 属于描边区域
|
|
|
|
|
|
self.stroke_pixel_at1(x, y);
|
2026-02-28 18:44:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 15:40:43 +08:00
|
|
|
|
pub fn stroke_ellipse1(&mut self, p1: Point, p2: Point) {
|
2026-02-28 18:44:50 +08:00
|
|
|
|
let mut rx = (p2.x - p1.x) / 2.0;
|
|
|
|
|
|
let mut ry = (p2.y - p1.y) / 2.0;
|
|
|
|
|
|
if rx > 0.0 && ry > 0.0 {
|
|
|
|
|
|
let center = Point::new(p1.x + rx, p1.y + ry);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_ellipse(center, rx, ry);
|
2026-02-28 18:44:50 +08:00
|
|
|
|
} else if rx < 0.0 && ry < 0.0 {
|
|
|
|
|
|
rx = -rx;
|
|
|
|
|
|
ry = -ry;
|
|
|
|
|
|
let center = Point::new(p1.x - rx, p1.y - ry);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_ellipse(center, rx, ry);
|
2026-02-28 18:44:50 +08:00
|
|
|
|
} else if ry < 0.0 {
|
|
|
|
|
|
ry = -ry;
|
|
|
|
|
|
let center = Point::new(p1.x + rx, p1.y - ry);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_ellipse(center, rx, ry);
|
2026-02-28 18:44:50 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
rx = -rx;
|
|
|
|
|
|
let center = Point::new(p1.x - rx, p1.y + ry);
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.stroke_ellipse(center, rx, ry);
|
2026-02-28 18:44:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 01:28:48 +08:00
|
|
|
|
/// 填充一个椭圆
|
|
|
|
|
|
/// - center: 椭圆中心
|
|
|
|
|
|
/// - rx: 水平半径(必须 >= 0)
|
|
|
|
|
|
/// - ry: 垂直半径(必须 >= 0)
|
|
|
|
|
|
pub fn fill_ellipse(&mut self, center: Point, rx: f32, ry: f32) {
|
|
|
|
|
|
if rx <= 0.0 || ry <= 0.0 {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-03 21:06:41 +08:00
|
|
|
|
let rx = rx;
|
|
|
|
|
|
let ry = ry;
|
2026-03-03 01:28:48 +08:00
|
|
|
|
|
|
|
|
|
|
// 计算包围矩形(整数边界)
|
2026-03-03 21:06:41 +08:00
|
|
|
|
let min_x = (center.x - rx).ceil() as i32;
|
|
|
|
|
|
let max_x = (center.x + rx).floor() as i32;
|
|
|
|
|
|
let min_y = (center.y - ry).ceil() as i32;
|
|
|
|
|
|
let max_y = (center.y + ry).floor() as i32;
|
2026-03-03 01:28:48 +08:00
|
|
|
|
|
|
|
|
|
|
// 预计算常量:rx², ry², rx² * ry²
|
|
|
|
|
|
let rx2 = rx * rx;
|
|
|
|
|
|
let ry2 = ry * ry;
|
|
|
|
|
|
let rxy2 = rx2 * ry2; // 右边阈值
|
|
|
|
|
|
|
|
|
|
|
|
for y in min_y..=max_y {
|
|
|
|
|
|
for x in min_x..=max_x {
|
2026-03-03 21:06:41 +08:00
|
|
|
|
let dx = x as f32 - center.x;
|
|
|
|
|
|
let dy = y as f32 - center.y;
|
2026-03-03 01:28:48 +08:00
|
|
|
|
|
|
|
|
|
|
// 判断是否在椭圆内:dx²/ rx² + dy²/ ry² <= 1
|
|
|
|
|
|
// 等价于:dx² * ry² + dy² * rx² <= rx² * ry²
|
|
|
|
|
|
if dx * dx * ry2 + dy * dy * rx2 <= rxy2 {
|
2026-03-03 15:40:43 +08:00
|
|
|
|
self.fill_pixel_at1(x, y);
|
2026-03-03 01:28:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn fill_ellipse1(&mut self, p1: Point, p2: Point) {
|
|
|
|
|
|
let mut rx = (p2.x - p1.x) / 2.0;
|
|
|
|
|
|
let mut ry = (p2.y - p1.y) / 2.0;
|
|
|
|
|
|
if rx > 0.0 && ry > 0.0 {
|
|
|
|
|
|
let center = Point::new(p1.x + rx, p1.y + ry);
|
|
|
|
|
|
self.fill_ellipse(center, rx, ry);
|
|
|
|
|
|
} else if rx < 0.0 && ry < 0.0 {
|
|
|
|
|
|
rx = -rx;
|
|
|
|
|
|
ry = -ry;
|
|
|
|
|
|
let center = Point::new(p1.x - rx, p1.y - ry);
|
|
|
|
|
|
self.fill_ellipse(center, rx, ry);
|
|
|
|
|
|
} else if ry < 0.0 {
|
|
|
|
|
|
ry = -ry;
|
|
|
|
|
|
let center = Point::new(p1.x + rx, p1.y - ry);
|
|
|
|
|
|
self.fill_ellipse(center, rx, ry);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
rx = -rx;
|
|
|
|
|
|
let center = Point::new(p1.x - rx, p1.y + ry);
|
|
|
|
|
|
self.fill_ellipse(center, rx, ry);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-06 16:37:20 +08:00
|
|
|
|
fn rasterize_polygon(&mut self, points: &[Point]) -> Vec<Point<i32>> {
|
|
|
|
|
|
let mut result = Vec::new();
|
2026-03-02 14:48:48 +08:00
|
|
|
|
if points.len() < 3 {
|
2026-03-06 16:37:20 +08:00
|
|
|
|
return result;
|
2026-03-02 14:48:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算 y 范围
|
|
|
|
|
|
let min_y = points.iter().map(|p| p.y as i32).min().unwrap_or(0);
|
|
|
|
|
|
let max_y = points.iter().map(|p| p.y as i32).max().unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
|
|
// 构建边表(Edge Table):按 ymin 分组
|
|
|
|
|
|
let mut edge_table: Vec<Vec<Edge>> = vec![Vec::new(); (max_y - min_y + 1) as usize];
|
|
|
|
|
|
|
|
|
|
|
|
for i in 0..points.len() {
|
|
|
|
|
|
let p1 = points[i];
|
|
|
|
|
|
let p2 = points[(i + 1) % points.len()];
|
|
|
|
|
|
if let Some(edge) = Edge::new(p1, p2) {
|
|
|
|
|
|
let idx = (edge.x as i32).min(max_y).max(min_y); // 实际应使用 top.y
|
|
|
|
|
|
// 正确做法:使用 top.y 作为插入位置
|
|
|
|
|
|
let top_y = p1.y.min(p2.y) as i32;
|
|
|
|
|
|
if top_y >= min_y && top_y <= max_y {
|
|
|
|
|
|
edge_table[(top_y - min_y) as usize].push(edge);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 活动边表(Active Edge List),按 x 排序
|
|
|
|
|
|
let mut active_edges: Vec<Edge> = Vec::new();
|
|
|
|
|
|
|
|
|
|
|
|
// 从 min_y 到 max_y 扫描每一行
|
|
|
|
|
|
for y in min_y..=max_y {
|
|
|
|
|
|
let y_idx = (y - min_y) as usize;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 添加新边(y == ymin 的边)
|
|
|
|
|
|
if y_idx < edge_table.len() {
|
|
|
|
|
|
active_edges.append(&mut edge_table[y_idx]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 移除 y >= y_max 的边
|
|
|
|
|
|
active_edges.retain(|e| y < e.y_max);
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 按 x 排序
|
|
|
|
|
|
active_edges.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 成对填充像素
|
|
|
|
|
|
for i in (0..active_edges.len()).step_by(2) {
|
|
|
|
|
|
if i + 1 < active_edges.len() {
|
|
|
|
|
|
let x_start = active_edges[i].x.ceil() as i32;
|
|
|
|
|
|
let x_end = active_edges[i + 1].x.floor() as i32;
|
|
|
|
|
|
for x in x_start..=x_end {
|
2026-03-06 16:37:20 +08:00
|
|
|
|
result.push(Point::new(x, y));
|
2026-03-02 14:48:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 更新活动边的 x(为下一行准备)
|
|
|
|
|
|
for edge in &mut active_edges {
|
|
|
|
|
|
edge.x += edge.dx_dy;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-06 16:37:20 +08:00
|
|
|
|
|
|
|
|
|
|
result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 填充一个简单多边形(支持凹多边形)
|
|
|
|
|
|
pub fn fill_polygon(&mut self, points: &[Point]) {
|
|
|
|
|
|
let filled_points = self.rasterize_polygon(points);
|
|
|
|
|
|
self.fill_pixels_i32(filled_points);
|
2026-03-02 14:48:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 18:29:32 +08:00
|
|
|
|
pub fn begin_path(&mut self) {
|
|
|
|
|
|
self.path2d = Path2D::new();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn close_path(&mut self) {
|
|
|
|
|
|
let points = &self.path2d.points;
|
|
|
|
|
|
if points.len() < 3 {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
self.path2d.push(points[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn move_to(&mut self, point: Point) {
|
|
|
|
|
|
self.path2d.push(point);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn line_to(&mut self, point: Point) {
|
|
|
|
|
|
self.path2d.push(point);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn stroke(&mut self) {
|
|
|
|
|
|
let mut points = self.path2d.points.clone();
|
|
|
|
|
|
if points.len() < 2 {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 连线
|
|
|
|
|
|
for i in 0..(points.len() - 1) {
|
|
|
|
|
|
self.draw_line_with_circle_brush(points[i], points[i + 1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-02 14:48:48 +08:00
|
|
|
|
|
|
|
|
|
|
pub fn fill(&mut self) {
|
|
|
|
|
|
let points = self.path2d.points.clone();
|
|
|
|
|
|
self.fill_polygon(&points[..]);
|
|
|
|
|
|
}
|
2026-03-04 17:36:27 +08:00
|
|
|
|
|
|
|
|
|
|
pub fn draw_rect_selection(&mut self, x: i32, y: i32, rect: &RectSelection) {
|
2026-03-06 15:39:57 +08:00
|
|
|
|
// 边界检查
|
|
|
|
|
|
if x >= self.width || y >= self.height {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 考虑矩形四边出界的情况
|
2026-03-06 16:37:20 +08:00
|
|
|
|
let w = (rect.w).min(self.width - x).min(rect.w + x);
|
|
|
|
|
|
let h = (rect.h).min(self.height - y).min(rect.h + y);
|
2026-03-06 15:39:57 +08:00
|
|
|
|
|
|
|
|
|
|
if w == 0 || h == 0 {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut src_offset_x = 0usize;
|
|
|
|
|
|
let mut src_offset_y = 0usize;
|
|
|
|
|
|
if x < 0 {
|
|
|
|
|
|
src_offset_x = (-x) as usize;
|
|
|
|
|
|
}
|
|
|
|
|
|
if y < 0 {
|
|
|
|
|
|
src_offset_y = (-y) as usize;
|
|
|
|
|
|
}
|
|
|
|
|
|
let x = x.max(0) as usize;
|
|
|
|
|
|
let y = y.max(0) as usize;
|
|
|
|
|
|
let w = w as usize;
|
|
|
|
|
|
let h = h as usize;
|
|
|
|
|
|
let width = self.width as usize;
|
2026-03-04 17:36:27 +08:00
|
|
|
|
|
|
|
|
|
|
for dy in 0..h {
|
2026-03-06 16:37:20 +08:00
|
|
|
|
for dx in 0..w {
|
|
|
|
|
|
let src_index = ((dy + src_offset_y) * rect.w as usize + dx + src_offset_x) * 4;
|
|
|
|
|
|
if rect.pixels[src_index + 3] == 0 {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
let dst_index = ((y + dy) * width + x + dx) * 4;
|
|
|
|
|
|
|
|
|
|
|
|
self.pixels[dst_index] = rect.pixels[src_index];
|
|
|
|
|
|
self.pixels[dst_index + 1] = rect.pixels[src_index + 1];
|
|
|
|
|
|
self.pixels[dst_index + 2] = rect.pixels[src_index + 2];
|
|
|
|
|
|
self.pixels[dst_index + 3] = rect.pixels[src_index + 3];
|
2026-03-04 17:36:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-28 16:17:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn point_muln(point: Point, t: f32) -> Point {
|
|
|
|
|
|
Point::new(point.x * t, point.y * t)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn point_add(p1: Point, p2: Point) -> Point {
|
|
|
|
|
|
Point::new(p1.x + p2.x, p1.y + p2.y)
|
2026-02-26 19:38:34 +08:00
|
|
|
|
}
|
2026-03-01 17:38:11 +08:00
|
|
|
|
|
|
|
|
|
|
/// 将弧度规范到 [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
|
|
|
|
|
|
}
|
2026-03-03 00:00:42 +08:00
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
/// 辅助函数:计算两点间距离的平方(避免开方)
|
|
|
|
|
|
fn distance_sq1(a: (i32, i32), b: (i32, i32)) -> f32 {
|
|
|
|
|
|
let dx = a.0 - b.0;
|
|
|
|
|
|
let dy = a.1 - b.1;
|
|
|
|
|
|
(dx * dx + dy * dy) as f32
|
2026-03-03 00:52:19 +08:00
|
|
|
|
}
|