Skip to content

Commit e0e8e5f

Browse files
committed
[*] feat: video editor supoort drawing rectangle with round corner
1 parent 8a36502 commit e0e8e5f

File tree

1 file changed

+236
-98
lines changed

1 file changed

+236
-98
lines changed

lib/video-editor/src/filters/video/draw_rectangle.rs

Lines changed: 236 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -117,66 +117,69 @@ impl DrawRectangleFilter {
117117
*pixel = Rgba([r, g, b, a]);
118118
}
119119

120-
fn sdf_rounded_rect(
121-
px: f32,
122-
py: f32,
123-
rect_x: f32,
124-
rect_y: f32,
125-
width: f32,
126-
height: f32,
127-
r: f32,
128-
) -> f32 {
129-
// Translate point relative to rectangle top-left
130-
let p_x = px - rect_x;
131-
let p_y = py - rect_y;
132-
133-
// Calculate distance from each edge
134-
let d_left = p_x;
135-
let d_right = width - p_x;
136-
let d_top = p_y;
137-
let d_bottom = height - p_y;
138-
139-
// Find minimum distance to any edge
140-
let d_x = d_left.min(d_right);
141-
let d_y = d_top.min(d_bottom);
142-
143-
// Check if we're in a corner region (both d_x and d_y are small)
144-
if d_x < r && d_y < r && d_x >= 0.0 && d_y >= 0.0 {
145-
// In corner region: calculate distance to the corner's arc
146-
// Determine which corner
147-
let corner_cx = if p_x < width / 2.0 { r } else { width - r };
148-
let corner_cy = if p_y < height / 2.0 { r } else { height - r };
149-
150-
let dx = p_x - corner_cx;
151-
let dy = p_y - corner_cy;
152-
let dist_to_center = (dx * dx + dy * dy).sqrt();
153-
r - dist_to_center
120+
/// 判断点是否在圆角矩形内部
121+
fn inside_rounded_rect(x: f64, y: f64, w: f64, h: f64, r: f64) -> bool {
122+
// 检查是否在圆角区域
123+
let near_left = x < r;
124+
let near_right = x > w - r;
125+
let near_top = y < r;
126+
let near_bottom = y > h - r;
127+
128+
let in_corner = (near_left || near_right) && (near_top || near_bottom);
129+
130+
if in_corner {
131+
// 圆角区域:检查是否在圆内
132+
let cx = if near_left { r } else { w - r };
133+
let cy = if near_top { r } else { h - r };
134+
let dx = x - cx;
135+
let dy = y - cy;
136+
dx * dx + dy * dy <= r * r
154137
} else {
155-
// In edge or interior region
156-
let min_dist = d_x.min(d_y);
157-
if min_dist >= 0.0 {
158-
// Inside: return distance to nearest edge
159-
min_dist.min(r)
160-
} else {
161-
// Outside: negative distance
162-
min_dist
163-
}
138+
// 直线区域
139+
x >= 0.0 && x <= w && y >= 0.0 && y <= h
164140
}
165141
}
166142

167-
#[inline]
168-
fn aa_smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
169-
let t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
170-
t * t * (3.0 - 2.0 * t)
143+
/// 计算点到圆角矩形边缘的距离(正值表示在内部)
144+
fn distance_to_edge(x: f64, y: f64, w: f64, h: f64, r: f64) -> f64 {
145+
let near_left = x < r;
146+
let near_right = x > w - r;
147+
let near_top = y < r;
148+
let near_bottom = y > h - r;
149+
150+
let in_corner = (near_left || near_right) && (near_top || near_bottom);
151+
152+
if in_corner {
153+
// 圆角区域:到圆弧的距离
154+
let cx = if near_left { r } else { w - r };
155+
let cy = if near_top { r } else { h - r };
156+
let dx = x - cx;
157+
let dy = y - cy;
158+
let dist = (dx * dx + dy * dy).sqrt();
159+
r - dist
160+
} else if near_left {
161+
x
162+
} else if near_right {
163+
w - x
164+
} else if near_top {
165+
y
166+
} else if near_bottom {
167+
h - y
168+
} else {
169+
// 内部区域:到最近边的距离
170+
x.min(w - x).min(y).min(h - y)
171+
}
171172
}
172173

173-
#[inline]
174-
fn aa_alpha(sdf: f32) -> f32 {
175-
// 1 pixel smooth transition range
176-
const AA_RANGE: f32 = 1.0;
177-
// SDF positive = inside, negative = outside
178-
// Smooth transition from 0 to 1 across AA_RANGE
179-
Self::aa_smoothstep(-AA_RANGE, AA_RANGE, sdf)
174+
/// 颜色混合
175+
fn blend_colors(c1: Rgba<u8>, c2: Rgba<u8>, t: f64) -> Rgba<u8> {
176+
let t = t.clamp(0.0, 1.0);
177+
Rgba([
178+
(c1[0] as f64 * (1.0 - t) + c2[0] as f64 * t).round() as u8,
179+
(c1[1] as f64 * (1.0 - t) + c2[1] as f64 * t).round() as u8,
180+
(c1[2] as f64 * (1.0 - t) + c2[2] as f64 * t).round() as u8,
181+
(c1[3] as f64 * (1.0 - t) + c2[3] as f64 * t).round() as u8,
182+
])
180183
}
181184

182185
fn draw_filled_rounded_rect(
@@ -186,32 +189,148 @@ impl DrawRectangleFilter {
186189
width: u32,
187190
height: u32,
188191
corner_radius: u32,
189-
color: &Rgba<u8>,
192+
fill_color: &Rgba<u8>,
190193
) {
191-
let rx = rect_x as f32;
192-
let ry = rect_y as f32;
193-
let w = width as f32;
194-
let h = height as f32;
195-
let r = corner_radius as f32;
196-
197-
for dy in 0..height as i32 {
198-
for dx in 0..width as i32 {
199-
let px = rect_x + dx;
200-
let py = rect_y + dy;
201-
202-
if px < 0 || py < 0 || px >= buffer.width() as i32 || py >= buffer.height() as i32 {
194+
let x = rect_x as u32;
195+
let y = rect_y as u32;
196+
let (img_width, img_height) = buffer.dimensions();
197+
198+
// 限制圆角半径
199+
let max_radius = (width / 2).min(height / 2);
200+
let radius = corner_radius.min(max_radius);
201+
202+
// 遍历绘制区域
203+
for dy in 0..height {
204+
for dx in 0..width {
205+
let px = x + dx;
206+
let py = y + dy;
207+
208+
if px >= img_width || py >= img_height {
203209
continue;
204210
}
205211

206-
// Calculate signed distance field value
207-
let sdf = Self::sdf_rounded_rect(px as f32, py as f32, rx, ry, w, h, r);
212+
// 使用浮点数计算像素中心
213+
let fx = dx as f64 + 0.5;
214+
let fy = dy as f64 + 0.5;
215+
let fw = width as f64;
216+
let fh = height as f64;
217+
let fr = radius as f64;
218+
219+
// 判断是否在圆角矩形内
220+
let in_rect = Self::inside_rounded_rect(fx, fy, fw, fh, fr);
221+
if !in_rect {
222+
continue;
223+
}
224+
225+
// 计算抗锯齿
226+
let dist = Self::distance_to_edge(fx, fy, fw, fh, fr);
227+
let aa_range = 1.5;
228+
229+
let final_color = if dist < aa_range && dist >= 0.0 {
230+
let alpha = dist / aa_range;
231+
Self::blend_colors(Rgba([0, 0, 0, 0]), *fill_color, alpha)
232+
} else {
233+
*fill_color
234+
};
235+
236+
Self::draw_pixel_aa(buffer, px, py, &final_color, 1.0);
237+
}
238+
}
239+
}
208240

209-
// Apply anti-aliasing based on SDF
210-
let alpha = Self::aa_alpha(sdf);
241+
/// 绘制带边框的圆角矩形
242+
fn draw_rounded_rectangle_with_border(
243+
buffer: &mut RgbaImage,
244+
x: u32,
245+
y: u32,
246+
width: u32,
247+
height: u32,
248+
corner_radius: u32,
249+
fill_color: &Rgba<u8>,
250+
border_width: u32,
251+
border_color: &Rgba<u8>,
252+
) {
253+
let (img_width, img_height) = buffer.dimensions();
211254

212-
if alpha > 0.01 {
213-
Self::draw_pixel_aa(buffer, px as u32, py as u32, color, alpha);
255+
// 限制圆角半径
256+
let max_radius = (width / 2).min(height / 2);
257+
let radius = corner_radius.min(max_radius);
258+
259+
// 遍历绘制区域
260+
for dy in 0..height {
261+
for dx in 0..width {
262+
let px = x + dx;
263+
let py = y + dy;
264+
265+
if px >= img_width || py >= img_height {
266+
continue;
267+
}
268+
269+
// 使用浮点数计算像素中心
270+
let fx = dx as f64 + 0.5;
271+
let fy = dy as f64 + 0.5;
272+
let fw = width as f64;
273+
let fh = height as f64;
274+
let fr = radius as f64;
275+
276+
// 判断是否在外部圆角矩形内
277+
let in_outer = Self::inside_rounded_rect(fx, fy, fw, fh, fr);
278+
if !in_outer {
279+
continue;
214280
}
281+
282+
// 计算颜色和抗锯齿
283+
let final_color = {
284+
let bw = border_width as f64;
285+
let inner_w = (fw - 2.0 * bw).max(0.0);
286+
let inner_h = (fh - 2.0 * bw).max(0.0);
287+
let inner_r = (fr - bw).max(0.0);
288+
289+
// 计算到外边界的距离
290+
let outer_dist = Self::distance_to_edge(fx, fy, fw, fh, fr);
291+
292+
// 计算到内边界的距离(边框与填充的交界)
293+
let inner_dist = if inner_w > 0.0 && inner_h > 0.0 {
294+
Self::distance_to_edge(fx - bw, fy - bw, inner_w, inner_h, inner_r)
295+
} else {
296+
f64::INFINITY
297+
};
298+
299+
let aa_range = 1.5;
300+
301+
// 判断是在填充区还是边框区
302+
let in_fill = inner_w > 0.0 && inner_h > 0.0 &&
303+
Self::inside_rounded_rect(fx - bw, fy - bw, inner_w, inner_h, inner_r);
304+
305+
if in_fill {
306+
// 填充区:检查是否靠近内边界
307+
if inner_dist < aa_range && inner_dist >= 0.0 {
308+
let alpha = inner_dist / aa_range;
309+
Self::blend_colors(*border_color, *fill_color, alpha)
310+
} else {
311+
*fill_color
312+
}
313+
} else {
314+
// 边框区:需要检查外边界和内边界
315+
let mut color = *border_color;
316+
317+
// 外边界抗锯齿(与背景混合)
318+
if outer_dist < aa_range && outer_dist >= 0.0 {
319+
let alpha = outer_dist / aa_range;
320+
color = Self::blend_colors(Rgba([0, 0, 0, 0]), color, alpha);
321+
}
322+
323+
// 内边界抗锯齿(与填充混合)- 仅当靠近内边界时
324+
if inner_dist < aa_range && inner_dist >= 0.0 {
325+
let alpha = inner_dist / aa_range;
326+
color = Self::blend_colors(color, *fill_color, 1.0 - alpha);
327+
}
328+
329+
color
330+
}
331+
};
332+
333+
Self::draw_pixel_aa(buffer, px, py, &final_color, 1.0);
215334
}
216335
}
217336
}
@@ -234,35 +353,41 @@ impl DrawRectangleFilter {
234353
};
235354

236355
// Draw filled rectangle
356+
// Skip if we have a rounded rectangle with border (will be handled together)
237357
if let Some(fill) = self.fill_color {
238358
let fill_color = Rgba(apply_opacity(fill));
239359

240-
if corner_radius == 0 {
241-
// Simple filled rectangle
242-
for dy in 0..height {
243-
for dx in 0..width {
244-
let px = x + dx as i32;
245-
let py = y + dy as i32;
246-
if px >= 0
247-
&& py >= 0
248-
&& px < buffer.width() as i32
249-
&& py < buffer.height() as i32
250-
{
251-
Self::draw_pixel_aa(buffer, px as u32, py as u32, &fill_color, 1.0);
360+
// Only draw fill separately if: no border OR corner_radius is 0
361+
let should_draw_fill_separately = self.border_color.is_none() || corner_radius == 0;
362+
363+
if should_draw_fill_separately {
364+
if corner_radius == 0 {
365+
// Simple filled rectangle
366+
for dy in 0..height {
367+
for dx in 0..width {
368+
let px = x + dx as i32;
369+
let py = y + dy as i32;
370+
if px >= 0
371+
&& py >= 0
372+
&& px < buffer.width() as i32
373+
&& py < buffer.height() as i32
374+
{
375+
Self::draw_pixel_aa(buffer, px as u32, py as u32, &fill_color, 1.0);
376+
}
252377
}
253378
}
379+
} else {
380+
// Rounded rectangle fill (no border)
381+
Self::draw_filled_rounded_rect(
382+
buffer,
383+
x,
384+
y,
385+
width,
386+
height,
387+
corner_radius,
388+
&fill_color,
389+
);
254390
}
255-
} else {
256-
// Rounded rectangle fill
257-
Self::draw_filled_rounded_rect(
258-
buffer,
259-
x,
260-
y,
261-
width,
262-
height,
263-
corner_radius,
264-
&fill_color,
265-
);
266391
}
267392
}
268393

@@ -334,8 +459,21 @@ impl DrawRectangleFilter {
334459
}
335460
}
336461
} else {
337-
// Note: Border rendering is not supported for rounded rectangles
338-
// Only the fill will be rendered
462+
// Rounded rectangle with border support
463+
let fill_rgba = self.fill_color.map(|f| Rgba(apply_opacity(f)))
464+
.unwrap_or(Rgba([0, 0, 0, 0]));
465+
466+
Self::draw_rounded_rectangle_with_border(
467+
buffer,
468+
x.max(0) as u32,
469+
y.max(0) as u32,
470+
width,
471+
height,
472+
corner_radius,
473+
&fill_rgba,
474+
self.border_width,
475+
&border_color,
476+
);
339477
}
340478
}
341479

0 commit comments

Comments
 (0)