1use tiny_skia::{
4 Color as SkiaColor, FillRule, Paint, Path, PathBuilder, Pixmap, Stroke, Transform,
5};
6
7#[derive(Debug, Clone, Copy)]
9pub struct Color {
10 pub r: u8,
12 pub g: u8,
14 pub b: u8,
16 pub a: u8,
18}
19
20impl Color {
21 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
23 Self { r, g, b, a }
24 }
25
26 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
28 Self { r, g, b, a: 255 }
29 }
30
31 pub fn to_skia(self) -> SkiaColor {
33 SkiaColor::from_rgba8(self.r, self.g, self.b, self.a)
34 }
35
36 pub fn to_paint(self) -> Paint<'static> {
38 let mut paint = Paint::default();
39 paint.set_color(self.to_skia());
40 paint.anti_alias = true;
41 paint
42 }
43}
44
45pub fn rounded_rect(x: f32, y: f32, width: f32, height: f32, radius: f32) -> Option<Path> {
47 let mut pb = PathBuilder::new();
48
49 let r = radius.min(width / 2.0).min(height / 2.0);
51
52 pb.move_to(x + r, y);
54
55 pb.line_to(x + width - r, y);
57 pb.quad_to(x + width, y, x + width, y + r);
58
59 pb.line_to(x + width, y + height - r);
61 pb.quad_to(x + width, y + height, x + width - r, y + height);
62
63 pb.line_to(x + r, y + height);
65 pb.quad_to(x, y + height, x, y + height - r);
66
67 pb.line_to(x, y + r);
69 pb.quad_to(x, y, x + r, y);
70
71 pb.close();
72 pb.finish()
73}
74
75pub fn fill_rounded_rect(
77 pixmap: &mut Pixmap,
78 x: f32,
79 y: f32,
80 width: f32,
81 height: f32,
82 radius: f32,
83 color: Color,
84) {
85 if let Some(path) = rounded_rect(x, y, width, height, radius) {
86 let paint = color.to_paint();
87 pixmap.fill_path(
88 &path,
89 &paint,
90 FillRule::Winding,
91 Transform::identity(),
92 None,
93 );
94 }
95}
96
97#[allow(clippy::too_many_arguments)]
99pub fn stroke_rounded_rect(
100 pixmap: &mut Pixmap,
101 x: f32,
102 y: f32,
103 width: f32,
104 height: f32,
105 radius: f32,
106 color: Color,
107 stroke_width: f32,
108) {
109 if let Some(path) = rounded_rect(x, y, width, height, radius) {
110 let paint = color.to_paint();
111 let stroke = Stroke {
112 width: stroke_width,
113 ..Default::default()
114 };
115 pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
116 }
117}
118
119pub fn fill_background(pixmap: &mut Pixmap, color: Color) {
121 pixmap.fill(color.to_skia());
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_color_creation() {
130 let c = Color::rgba(255, 128, 64, 200);
131 assert_eq!(c.r, 255);
132 assert_eq!(c.g, 128);
133 assert_eq!(c.b, 64);
134 assert_eq!(c.a, 200);
135 }
136
137 #[test]
138 fn test_rounded_rect_creation() {
139 let path = rounded_rect(10.0, 10.0, 100.0, 50.0, 8.0);
140 assert!(path.is_some());
141 }
142
143 #[test]
144 fn test_rounded_rect_clamped_radius() {
145 let path = rounded_rect(0.0, 0.0, 100.0, 20.0, 50.0);
147 assert!(path.is_some());
148 }
149}