1use crate::color::{Color, PremultipliedColor, PremultipliedColorU8};
5use crate::geometry::{Point, Transform};
6use crate::paint::{blend, BlendMode, Paint, Shader};
7use crate::path::stroke::{build_stroke, Stroke};
8use crate::path::{FillRule, Path};
9use crate::raster::mask::Mask;
10use crate::raster::Rasterizer;
11use crate::text::Font;
12
13mod decode;
14mod encode;
15
16pub(crate) const FLATTEN_TOLERANCE: f32 = 0.1;
19
20#[derive(Clone)]
22pub struct Pixmap {
23 width: u32,
24 height: u32,
25 data: Vec<u8>,
26}
27
28impl Pixmap {
29 pub fn new(width: u32, height: u32) -> Option<Pixmap> {
31 if width == 0 || height == 0 {
32 return None;
33 }
34 let len = (width as usize).checked_mul(height as usize)?.checked_mul(4)?;
35 Some(Pixmap { width, height, data: vec![0; len] })
36 }
37
38 #[inline]
40 pub fn width(&self) -> u32 {
41 self.width
42 }
43
44 #[inline]
46 pub fn height(&self) -> u32 {
47 self.height
48 }
49
50 #[inline]
52 pub fn data(&self) -> &[u8] {
53 &self.data
54 }
55
56 #[inline]
58 pub fn data_mut(&mut self) -> &mut [u8] {
59 &mut self.data
60 }
61
62 pub fn take(self) -> Vec<u8> {
64 self.data
65 }
66
67 pub fn pixel(&self, x: u32, y: u32) -> Option<PremultipliedColorU8> {
69 if x >= self.width || y >= self.height {
70 return None;
71 }
72 let i = (y as usize * self.width as usize + x as usize) * 4;
73 Some(PremultipliedColorU8::from_rgba_unchecked(
74 self.data[i],
75 self.data[i + 1],
76 self.data[i + 2],
77 self.data[i + 3],
78 ))
79 }
80
81 pub fn fill(&mut self, color: Color) {
83 let p = color.premultiply().to_color_u8();
84 for px in self.data.chunks_exact_mut(4) {
85 px[0] = p.red();
86 px[1] = p.green();
87 px[2] = p.blue();
88 px[3] = p.alpha();
89 }
90 }
91
92 pub fn fill_path(
99 &mut self,
100 path: &Path,
101 paint: &Paint,
102 fill_rule: FillRule,
103 transform: Transform,
104 clip: Option<&Mask>,
105 ) {
106 let tol = FLATTEN_TOLERANCE / transform.max_scale().max(1e-3);
107 let contours = path.to_contours(transform, tol);
108 let polys: Vec<&[Point]> = contours.iter().map(|c| c.points.as_slice()).collect();
110 self.fill_polys(&polys, paint, fill_rule, clip);
111 }
112
113 pub fn stroke_path(
128 &mut self,
129 path: &Path,
130 paint: &Paint,
131 stroke: &Stroke,
132 transform: Transform,
133 clip: Option<&Mask>,
134 ) {
135 let scale = transform.max_scale().max(1e-3);
136 let tol = FLATTEN_TOLERANCE / scale;
137 let contours = path.to_contours(transform, tol);
138
139 let polys = build_stroke(&contours, &scaled_stroke(stroke, scale), FLATTEN_TOLERANCE);
141 let refs: Vec<&[Point]> = polys.iter().map(|p| p.as_slice()).collect();
142
143 self.fill_polys(&refs, paint, FillRule::NonZero, clip);
145 }
146
147 #[allow(clippy::too_many_arguments)]
162 pub fn fill_text(
163 &mut self,
164 font: &Font,
165 text: &str,
166 size: f32,
167 x: f32,
168 y: f32,
169 paint: &Paint,
170 transform: Transform,
171 clip: Option<&Mask>,
172 ) {
173 if let Some(path) = font.text_path(text, size, x, y) {
174 self.fill_path(&path, paint, FillRule::NonZero, transform, clip);
175 }
176 }
177
178 pub fn draw_pixmap(&mut self, src: &Pixmap, x: i32, y: i32, opacity: f32, blend_mode: BlendMode) {
187 let opacity = opacity.clamp(0.0, 1.0);
188 if opacity <= 0.0 {
189 return;
190 }
191
192 let dx0 = x.max(0);
194 let dy0 = y.max(0);
195 let dx1 = (x + src.width as i32).min(self.width as i32);
196 let dy1 = (y + src.height as i32).min(self.height as i32);
197 if dx1 <= dx0 || dy1 <= dy0 {
198 return;
199 }
200
201 let dst_w = self.width as usize;
202 let src_w = src.width as usize;
203 for dy in dy0..dy1 {
204 let sy = (dy - y) as usize;
205 let dst_row = dy as usize * dst_w * 4;
206 let src_row = sy * src_w * 4;
207 for dx in dx0..dx1 {
208 let si = src_row + (dx - x) as usize * 4;
209 let sa = src.data[si + 3];
210 if sa == 0 && blend_mode == BlendMode::SourceOver {
211 continue; }
213
214 let s = PremultipliedColor {
217 r: src.data[si] as f32 / 255.0 * opacity,
218 g: src.data[si + 1] as f32 / 255.0 * opacity,
219 b: src.data[si + 2] as f32 / 255.0 * opacity,
220 a: sa as f32 / 255.0 * opacity,
221 };
222 let di = dst_row + dx as usize * 4;
223 let d = PremultipliedColor {
224 r: self.data[di] as f32 / 255.0,
225 g: self.data[di + 1] as f32 / 255.0,
226 b: self.data[di + 2] as f32 / 255.0,
227 a: self.data[di + 3] as f32 / 255.0,
228 };
229 let out = blend(blend_mode, s, d).to_color_u8();
230 self.data[di] = out.red();
231 self.data[di + 1] = out.green();
232 self.data[di + 2] = out.blue();
233 self.data[di + 3] = out.alpha();
234 }
235 }
236 }
237
238 fn fill_polys(
243 &mut self,
244 polys: &[&[Point]],
245 paint: &Paint,
246 fill_rule: FillRule,
247 clip: Option<&Mask>,
248 ) {
249 if polys.is_empty() {
250 return;
251 }
252 let clip = clip.filter(|m| m.width() == self.width && m.height() == self.height);
255
256 let (mut min_x, mut min_y) = (f32::INFINITY, f32::INFINITY);
262 let (mut max_x, mut max_y) = (f32::NEG_INFINITY, f32::NEG_INFINITY);
263 for poly in polys {
264 for p in *poly {
265 min_x = min_x.min(p.x);
266 min_y = min_y.min(p.y);
267 max_x = max_x.max(p.x);
268 max_y = max_y.max(p.y);
269 }
270 }
271 if !(min_x <= max_x && min_y <= max_y) {
272 return; }
274
275 let pm_w = self.width as i32;
276 let pm_h = self.height as i32;
277 let x0 = (min_x.floor() as i32 - 1).clamp(0, pm_w);
279 let y0 = (min_y.floor() as i32 - 1).clamp(0, pm_h);
280 let x1 = (max_x.ceil() as i32 + 1).clamp(0, pm_w);
281 let y1 = (max_y.ceil() as i32 + 1).clamp(0, pm_h);
282 let bw = (x1 - x0) as usize;
283 let bh = (y1 - y0) as usize;
284 if bw == 0 || bh == 0 {
285 return; }
287
288 let mut rast = Rasterizer::new(x0, y0, bw, bh);
289 for poly in polys {
290 let n = poly.len();
291 if n < 2 {
292 continue;
293 }
294 for i in 0..n {
295 rast.add_line(poly[i], poly[(i + 1) % n]);
296 }
297 }
298
299 let width = self.width as usize;
300 let data = &mut self.data;
301 let shader = &paint.shader;
302 let mode = paint.blend_mode;
303 let anti_alias = paint.anti_alias;
304 let opacity = paint.opacity.clamp(0.0, 1.0);
305
306 let solid = match shader {
310 Shader::SolidColor(c) => Some((c.red(), c.green(), c.blue(), c.alpha() * opacity)),
311 _ => None,
312 };
313 let mut span: Vec<Color> = Vec::new();
315
316 rast.for_each_row(fill_rule, |y, x_start, coverages| {
317 if solid.is_none() {
321 span.clear();
322 shader.shade_span(x_start, y, coverages.len(), &mut span);
323 }
324
325 for (i, &coverage) in coverages.iter().enumerate() {
326 let x = x_start + i;
327 let mut cov = if anti_alias {
328 coverage
329 } else if coverage >= 0.5 {
330 1.0
331 } else {
332 0.0
333 };
334 if let Some(m) = clip {
336 cov *= m.coverage_at(x, y);
337 }
338 if cov <= 0.0 {
339 continue;
340 }
341
342 let src = if let Some((r, g, b, a_opacity)) = solid {
343 let alpha = a_opacity * cov;
344 PremultipliedColor { r: r * alpha, g: g * alpha, b: b * alpha, a: alpha }
345 } else {
346 let color = span[i];
347 let alpha = color.alpha() * cov * opacity;
348 PremultipliedColor {
349 r: color.red() * alpha,
350 g: color.green() * alpha,
351 b: color.blue() * alpha,
352 a: alpha,
353 }
354 };
355
356 let idx = (y * width + x) * 4;
357 let dst = PremultipliedColor {
358 r: data[idx] as f32 / 255.0,
359 g: data[idx + 1] as f32 / 255.0,
360 b: data[idx + 2] as f32 / 255.0,
361 a: data[idx + 3] as f32 / 255.0,
362 };
363
364 let out = blend(mode, src, dst).to_color_u8();
365 data[idx] = out.red();
366 data[idx + 1] = out.green();
367 data[idx + 2] = out.blue();
368 data[idx + 3] = out.alpha();
369 }
370 });
371 }
372}
373
374fn scaled_stroke(stroke: &Stroke, scale: f32) -> Stroke {
384 let width = if stroke.is_hairline() { 1.0 } else { stroke.width * scale };
385 Stroke {
386 width,
387 line_cap: stroke.line_cap,
388 line_join: stroke.line_join,
389 miter_limit: stroke.miter_limit,
390 dash: stroke.dash.iter().map(|&d| d * scale).collect(),
391 dash_offset: stroke.dash_offset * scale,
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::path::PathBuilder;
399 use crate::geometry::Rect;
400
401 #[test]
402 fn fill_then_read_pixel() {
403 let mut pm = Pixmap::new(8, 8).unwrap();
404 pm.fill(Color::from_rgba8(255, 0, 0, 255));
405 let p = pm.pixel(3, 3).unwrap();
406 assert_eq!((p.red(), p.green(), p.blue(), p.alpha()), (255, 0, 0, 255));
407 }
408
409 #[test]
410 fn fill_rect_path_covers_interior() {
411 let mut pm = Pixmap::new(20, 20).unwrap();
412 let path = PathBuilder::from_rect(Rect::from_xywh(4.0, 4.0, 12.0, 12.0).unwrap());
413 let paint = Paint::from_color(Color::from_rgba8(0, 128, 255, 255));
414 pm.fill_path(&path, &paint, FillRule::NonZero, Transform::identity(), None);
415 let inside = pm.pixel(10, 10).unwrap();
416 assert_eq!(inside.alpha(), 255);
417 let outside = pm.pixel(1, 1).unwrap();
418 assert_eq!(outside.alpha(), 0);
419 }
420
421 #[test]
424 fn fill_offset_rect_maps_to_absolute_coords() {
425 let mut pm = Pixmap::new(200, 200).unwrap();
426 let path = PathBuilder::from_rect(Rect::from_xywh(150.0, 150.0, 20.0, 20.0).unwrap());
427 let paint = Paint::from_color(Color::from_rgba8(0, 128, 255, 255));
428 pm.fill_path(&path, &paint, FillRule::NonZero, Transform::identity(), None);
429 assert_eq!(pm.pixel(160, 160).unwrap().alpha(), 255);
431 assert_eq!(pm.pixel(10, 10).unwrap().alpha(), 0);
433 assert_eq!(pm.pixel(175, 160).unwrap().alpha(), 0);
435 }
436
437 #[test]
438 fn stroke_line_paints_pixels() {
439 let mut pm = Pixmap::new(20, 20).unwrap();
440 let mut b = PathBuilder::new();
441 b.move_to(2.0, 10.0).line_to(18.0, 10.0);
442 let path = b.finish().unwrap();
443 let paint = Paint::from_color(Color::BLACK);
444 let stroke = Stroke { width: 4.0, ..Stroke::default() };
445 pm.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
446 assert_eq!(pm.pixel(10, 10).unwrap().alpha(), 255);
447 assert_eq!(pm.pixel(10, 0).unwrap().alpha(), 0);
448 }
449
450 #[test]
451 fn hairline_stroke_paints_thin_line() {
452 let mut pm = Pixmap::new(20, 20).unwrap();
455 let mut b = PathBuilder::new();
456 b.move_to(2.0, 10.0).line_to(18.0, 10.0);
457 let path = b.finish().unwrap();
458 let paint = Paint::from_color(Color::BLACK);
459 let stroke = Stroke { width: 0.0, ..Stroke::default() };
460 pm.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
461 let mut painted = false;
463 for y in 9..=10 {
464 if pm.pixel(10, y).unwrap().alpha() > 0 {
465 painted = true;
466 }
467 }
468 assert!(painted, "hairline was not drawn");
469 }
470
471 #[test]
472 fn draw_pixmap_offset_places_source() {
473 let mut dst = Pixmap::new(20, 20).unwrap();
474 let mut src = Pixmap::new(5, 5).unwrap();
475 src.fill(Color::from_rgba8(0, 200, 0, 255));
476 dst.draw_pixmap(&src, 10, 10, 1.0, BlendMode::SourceOver);
477 assert_eq!(dst.pixel(12, 12).unwrap().alpha(), 255);
479 assert_eq!(dst.pixel(2, 2).unwrap().alpha(), 0);
480 assert_eq!(dst.pixel(16, 16).unwrap().alpha(), 0);
481 }
482
483 #[test]
484 fn draw_pixmap_negative_offset_clips() {
485 let mut dst = Pixmap::new(10, 10).unwrap();
486 let mut src = Pixmap::new(8, 8).unwrap();
487 src.fill(Color::from_rgba8(255, 0, 0, 255));
488 dst.draw_pixmap(&src, -4, -4, 1.0, BlendMode::SourceOver);
490 assert_eq!(dst.pixel(0, 0).unwrap().alpha(), 255); assert_eq!(dst.pixel(5, 5).unwrap().alpha(), 0); }
493
494 #[test]
495 fn draw_pixmap_opacity_halves_alpha() {
496 let mut dst = Pixmap::new(4, 4).unwrap();
497 let mut src = Pixmap::new(4, 4).unwrap();
498 src.fill(Color::from_rgba8(255, 255, 255, 255));
499 dst.draw_pixmap(&src, 0, 0, 0.5, BlendMode::SourceOver);
500 let a = dst.pixel(2, 2).unwrap().alpha();
501 assert!((a as i32 - 128).abs() <= 2, "alpha={a}");
502 }
503
504 #[test]
505 fn fill_clipped_by_rounded_parent() {
506 let clip_path = PathBuilder::from_circle(10.0, 10.0, 8.0).unwrap();
508 let mask =
509 Mask::from_path(20, 20, &clip_path, FillRule::NonZero, true, Transform::identity())
510 .unwrap();
511 let mut pm = Pixmap::new(20, 20).unwrap();
512 let rect = PathBuilder::from_rect(Rect::from_xywh(0.0, 0.0, 20.0, 20.0).unwrap());
513 let paint = Paint::from_color(Color::from_rgba8(255, 0, 0, 255));
514 pm.fill_path(&rect, &paint, FillRule::NonZero, Transform::identity(), Some(&mask));
515 assert_eq!(pm.pixel(10, 10).unwrap().alpha(), 255);
517 assert_eq!(pm.pixel(1, 1).unwrap().alpha(), 0);
518 }
519}