1pub mod brush;
19pub mod builtin;
20pub mod dabs;
21pub mod render;
22pub mod strokes;
23
24pub use brush::BrushDefaults;
25pub use dabs::{paint_polygons_dabs, DabFillStyle};
26pub use hokusai::color::RgbaF32;
27pub use hokusai::Brush;
28#[cfg(feature = "parallel")]
29pub use strokes::paint_lines_parallel;
30pub use strokes::{paint_lines, LineStrokeStyle};
31
32use ezu_features::Polygon;
33use tiny_skia::{
34 Color, FillRule, Paint, PathBuilder, Pixmap, PixmapPaint, PremultipliedColorU8, Stroke,
35 Transform,
36};
37
38pub mod host;
44pub mod nodes;
45
46pub struct Canvas {
47 pixmap: Pixmap,
48 tile_w: u32,
49 tile_h: u32,
50 pad: u32,
51}
52
53impl Canvas {
54 pub fn new(tile_w: u32, tile_h: u32) -> Option<Self> {
58 Self::new_padded(tile_w, tile_h, 0)
59 }
60
61 pub fn new_padded(tile_w: u32, tile_h: u32, pad: u32) -> Option<Self> {
66 let pw = tile_w.checked_add(2u32.checked_mul(pad)?)?;
67 let ph = tile_h.checked_add(2u32.checked_mul(pad)?)?;
68 let pixmap = Pixmap::new(pw, ph)?;
69 Some(Self {
70 pixmap,
71 tile_w,
72 tile_h,
73 pad,
74 })
75 }
76
77 pub fn fill(&mut self, color: Color) {
79 self.pixmap.fill(color);
80 }
81
82 pub fn pixmap(&self) -> &Pixmap {
83 &self.pixmap
84 }
85
86 pub fn pixmap_mut(&mut self) -> &mut Pixmap {
87 &mut self.pixmap
88 }
89
90 pub fn width(&self) -> u32 {
93 self.tile_w + 2 * self.pad
94 }
95
96 pub fn height(&self) -> u32 {
98 self.tile_h + 2 * self.pad
99 }
100
101 pub fn tile_width(&self) -> u32 {
102 self.tile_w
103 }
104
105 pub fn tile_height(&self) -> u32 {
106 self.tile_h
107 }
108
109 pub fn pad(&self) -> u32 {
110 self.pad
111 }
112
113 pub fn into_pixmap(self) -> Pixmap {
118 self.pixmap
119 }
120}
121
122#[derive(Debug, Clone)]
124pub struct WatercolorStyle {
125 pub fill: Color,
126 pub edge: Option<Color>,
128 pub edge_width: f32,
129 pub blur_sigma: f32,
131}
132
133impl Default for WatercolorStyle {
134 fn default() -> Self {
135 Self {
136 fill: Color::from_rgba8(150, 180, 210, 180),
137 edge: Some(Color::from_rgba8(80, 110, 150, 220)),
138 edge_width: 1.5,
139 blur_sigma: 1.2,
140 }
141 }
142}
143
144pub fn paint_polygons(
150 canvas: &mut Canvas,
151 polygons: &[Polygon],
152 extent: u32,
153 style: &WatercolorStyle,
154) {
155 let w = canvas.width();
156 let h = canvas.height();
157 let mut layer = Pixmap::new(w, h).expect("non-zero layer");
158
159 let sx = canvas.tile_w as f32 / extent as f32;
160 let sy = canvas.tile_h as f32 / extent as f32;
161 let ox = canvas.pad as f32;
162 let oy = canvas.pad as f32;
163
164 let mut fill_paint = Paint::default();
165 fill_paint.set_color(style.fill);
166 fill_paint.anti_alias = true;
167
168 let mut edge_paint = Paint::default();
169 if let Some(edge) = style.edge {
170 edge_paint.set_color(edge);
171 edge_paint.anti_alias = true;
172 }
173
174 for poly in polygons {
175 let Some(path) = build_polygon_path(poly, sx, sy, ox, oy) else {
176 continue;
177 };
178 layer.fill_path(
179 &path,
180 &fill_paint,
181 FillRule::EvenOdd,
182 Transform::identity(),
183 None,
184 );
185 if style.edge.is_some() {
186 let stroke = Stroke {
187 width: style.edge_width,
188 ..Stroke::default()
189 };
190 layer.stroke_path(&path, &edge_paint, &stroke, Transform::identity(), None);
191 }
192 }
193
194 if style.blur_sigma > 0.0 {
195 blur_pixmap(&mut layer, style.blur_sigma);
196 }
197
198 canvas.pixmap.draw_pixmap(
199 0,
200 0,
201 layer.as_ref(),
202 &PixmapPaint::default(),
203 Transform::identity(),
204 None,
205 );
206}
207
208pub(crate) fn build_polygon_path(
209 poly: &Polygon,
210 sx: f32,
211 sy: f32,
212 ox: f32,
213 oy: f32,
214) -> Option<tiny_skia::Path> {
215 let mut pb = PathBuilder::new();
216 push_ring(&mut pb, &poly.exterior, sx, sy, ox, oy)?;
217 for hole in &poly.holes {
218 push_ring(&mut pb, hole, sx, sy, ox, oy)?;
219 }
220 pb.finish()
221}
222
223fn push_ring(
224 pb: &mut PathBuilder,
225 ring: &[(i32, i32)],
226 sx: f32,
227 sy: f32,
228 ox: f32,
229 oy: f32,
230) -> Option<()> {
231 if ring.len() < 3 {
232 return None;
233 }
234 let (x0, y0) = ring[0];
235 pb.move_to(x0 as f32 * sx + ox, y0 as f32 * sy + oy);
236 for &(x, y) in &ring[1..] {
237 pb.line_to(x as f32 * sx + ox, y as f32 * sy + oy);
238 }
239 pb.close();
240 Some(())
241}
242
243fn blur_pixmap(pixmap: &mut Pixmap, sigma: f32) {
245 let w = pixmap.width() as usize;
246 let h = pixmap.height() as usize;
247 let mut rgba: Vec<u8> = Vec::with_capacity(w * h * 4);
248 for px in pixmap.pixels() {
249 let p = px.demultiply();
250 rgba.extend_from_slice(&[p.red(), p.green(), p.blue(), p.alpha()]);
251 }
252
253 let src_buf = rgba.clone();
254 let src = libblur::BlurImage::borrow(
255 &src_buf,
256 w as u32,
257 h as u32,
258 libblur::FastBlurChannels::Channels4,
259 );
260 let mut dst = libblur::BlurImageMut::borrow(
261 &mut rgba,
262 w as u32,
263 h as u32,
264 libblur::FastBlurChannels::Channels4,
265 );
266 if libblur::gaussian_blur(
267 &src,
268 &mut dst,
269 libblur::GaussianBlurParams::new_from_sigma(sigma as f64),
270 libblur::EdgeMode2D::new(libblur::EdgeMode::Clamp),
271 libblur::ThreadingPolicy::Single,
272 libblur::ConvolutionMode::Exact,
273 )
274 .is_err()
275 {
276 return;
277 }
278
279 let out = pixmap.pixels_mut();
280 for (i, dst) in out.iter_mut().enumerate() {
281 let r = rgba[i * 4];
282 let g = rgba[i * 4 + 1];
283 let b = rgba[i * 4 + 2];
284 let a = rgba[i * 4 + 3];
285 *dst = PremultipliedColorU8::from_rgba(mul(r, a), mul(g, a), mul(b, a), a).unwrap_or_else(
286 || {
287 PremultipliedColorU8::from_rgba(0, 0, 0, 0)
291 .expect("transparent black is a valid premul color")
292 },
293 );
294 }
295}
296
297#[inline]
298fn mul(c: u8, a: u8) -> u8 {
299 ((c as u16 * a as u16 + 127) / 255) as u8
300}
301
302#[derive(Debug, thiserror::Error)]
303pub enum PaintError {
304 #[error("png encode failed")]
305 PngEncode,
306 #[error("webp encode failed: {0}")]
307 WebpEncode(String),
308}