1use tiny_skia::{
2 BlendMode, Color, FillRule, LineCap as SkiaLineCap, LineJoin as SkiaLineJoin, Mask,
3 Paint, Pixmap, Stroke, Transform,
4};
5
6use crate::error::{RenderError, Result};
7use crate::graphics_state;
8
9pub struct PixmapDevice {
11 pub pixmap: Pixmap,
12 pub(crate) clip_mask: Option<Mask>,
13}
14
15impl PixmapDevice {
16 pub fn new(width: u32, height: u32) -> Result<Self> {
17 let pixmap = Pixmap::new(width, height).ok_or_else(|| RenderError::InvalidDimensions {
18 detail: format!("cannot create {width}x{height} pixmap"),
19 })?;
20 Ok(Self {
21 pixmap,
22 clip_mask: None,
23 })
24 }
25
26 pub fn set_clip_path(
28 &mut self,
29 path: &tiny_skia::Path,
30 fill_rule: FillRule,
31 transform: Transform,
32 ) {
33 let w = self.pixmap.width();
34 let h = self.pixmap.height();
35 if let Some(mut mask) = Mask::new(w, h) {
36 mask.fill_path(path, fill_rule, true, transform);
37 self.clip_mask = Some(mask);
38 }
39 }
40
41 pub fn intersect_clip_path(
43 &mut self,
44 path: &tiny_skia::Path,
45 fill_rule: FillRule,
46 transform: Transform,
47 ) {
48 if let Some(mask) = &mut self.clip_mask {
49 mask.intersect_path(path, fill_rule, true, transform);
50 } else {
51 self.set_clip_path(path, fill_rule, transform);
52 }
53 }
54
55 pub fn clear_clip(&mut self) {
57 self.clip_mask = None;
58 }
59
60 pub fn fill_path(
62 &mut self,
63 path: &tiny_skia::Path,
64 fill_rule: FillRule,
65 transform: Transform,
66 color: [u8; 4],
67 blend_mode: BlendMode,
68 ) {
69 let mut paint = Paint::default();
70 paint.set_color(Color::from_rgba8(color[0], color[1], color[2], color[3]));
71 paint.anti_alias = true;
72 paint.blend_mode = blend_mode;
73
74 let clip = self.clip_mask.as_ref();
75 self.pixmap
76 .fill_path(path, &paint, fill_rule, transform, clip);
77 }
78
79 pub fn stroke_path(
81 &mut self,
82 path: &tiny_skia::Path,
83 transform: Transform,
84 color: [u8; 4],
85 gs: &graphics_state::GraphicsState,
86 blend_mode: BlendMode,
87 ) {
88 let mut paint = Paint::default();
89 paint.set_color(Color::from_rgba8(color[0], color[1], color[2], color[3]));
90 paint.anti_alias = true;
91 paint.blend_mode = blend_mode;
92
93 let mut stroke = Stroke::default();
94 stroke.width = gs.line_width as f32;
95 stroke.line_cap = match gs.line_cap {
96 graphics_state::LineCap::Butt => SkiaLineCap::Butt,
97 graphics_state::LineCap::Round => SkiaLineCap::Round,
98 graphics_state::LineCap::Square => SkiaLineCap::Square,
99 };
100 stroke.line_join = match gs.line_join {
101 graphics_state::LineJoin::Miter => SkiaLineJoin::Miter,
102 graphics_state::LineJoin::Round => SkiaLineJoin::Round,
103 graphics_state::LineJoin::Bevel => SkiaLineJoin::Bevel,
104 };
105 stroke.miter_limit = gs.miter_limit as f32;
106
107 if !gs.dash_pattern.is_empty() {
108 let dashes: Vec<f32> = gs.dash_pattern.iter().map(|d| *d as f32).collect();
109 if let Some(dash) = tiny_skia::StrokeDash::new(dashes, gs.dash_phase as f32) {
110 stroke.dash = Some(dash);
111 }
112 }
113
114 let clip = self.clip_mask.as_ref();
115 self.pixmap
116 .stroke_path(path, &paint, &stroke, transform, clip);
117 }
118
119 pub fn draw_image(
121 &mut self,
122 image_pixmap: &tiny_skia::PixmapRef,
123 transform: Transform,
124 alpha: f32,
125 blend_mode: BlendMode,
126 ) {
127 let mut paint = tiny_skia::PixmapPaint::default();
128 paint.opacity = alpha;
129 paint.blend_mode = blend_mode;
130 paint.quality = tiny_skia::FilterQuality::Bilinear;
131
132 let clip = self.clip_mask.as_ref();
133 self.pixmap
134 .draw_pixmap(0, 0, *image_pixmap, &paint, transform, clip);
135 }
136
137 pub fn draw_pixmap(
139 &mut self,
140 src: &tiny_skia::PixmapRef,
141 transform: Transform,
142 alpha: f32,
143 blend_mode: BlendMode,
144 ) {
145 let mut paint = tiny_skia::PixmapPaint::default();
146 paint.opacity = alpha;
147 paint.blend_mode = blend_mode;
148 paint.quality = tiny_skia::FilterQuality::Bilinear;
149
150 let clip = self.clip_mask.as_ref();
151 self.pixmap
152 .draw_pixmap(0, 0, *src, &paint, transform, clip);
153 }
154
155 pub fn fill_path_with_pattern(
157 &mut self,
158 path: &tiny_skia::Path,
159 fill_rule: FillRule,
160 transform: Transform,
161 pattern_pixmap: &tiny_skia::PixmapRef,
162 pattern_transform: Transform,
163 blend_mode: BlendMode,
164 ) {
165 let mut paint = Paint::default();
166 paint.anti_alias = true;
167 paint.blend_mode = blend_mode;
168
169 paint.shader = tiny_skia::Pattern::new(
170 *pattern_pixmap,
171 tiny_skia::SpreadMode::Repeat,
172 tiny_skia::FilterQuality::Bilinear,
173 1.0,
174 pattern_transform,
175 );
176
177 let clip = self.clip_mask.as_ref();
178 self.pixmap
179 .fill_path(path, &paint, fill_rule, transform, clip);
180 }
181
182 pub fn stroke_path_with_pattern(
184 &mut self,
185 path: &tiny_skia::Path,
186 transform: Transform,
187 gs: &graphics_state::GraphicsState,
188 pattern_pixmap: &tiny_skia::PixmapRef,
189 pattern_transform: Transform,
190 blend_mode: BlendMode,
191 ) {
192 let mut paint = Paint::default();
193 paint.anti_alias = true;
194 paint.blend_mode = blend_mode;
195
196 paint.shader = tiny_skia::Pattern::new(
197 *pattern_pixmap,
198 tiny_skia::SpreadMode::Repeat,
199 tiny_skia::FilterQuality::Bilinear,
200 1.0,
201 pattern_transform,
202 );
203
204 let mut stroke = Stroke::default();
205 stroke.width = gs.line_width as f32;
206 stroke.line_cap = match gs.line_cap {
207 graphics_state::LineCap::Butt => SkiaLineCap::Butt,
208 graphics_state::LineCap::Round => SkiaLineCap::Round,
209 graphics_state::LineCap::Square => SkiaLineCap::Square,
210 };
211 stroke.line_join = match gs.line_join {
212 graphics_state::LineJoin::Miter => SkiaLineJoin::Miter,
213 graphics_state::LineJoin::Round => SkiaLineJoin::Round,
214 graphics_state::LineJoin::Bevel => SkiaLineJoin::Bevel,
215 };
216 stroke.miter_limit = gs.miter_limit as f32;
217
218 if !gs.dash_pattern.is_empty() {
219 let dashes: Vec<f32> = gs.dash_pattern.iter().map(|d| *d as f32).collect();
220 if let Some(dash) = tiny_skia::StrokeDash::new(dashes, gs.dash_phase as f32) {
221 stroke.dash = Some(dash);
222 }
223 }
224
225 let clip = self.clip_mask.as_ref();
226 self.pixmap
227 .stroke_path(path, &paint, &stroke, transform, clip);
228 }
229
230 pub fn clear(&mut self, color: Color) {
232 self.pixmap.fill(color);
233 }
234
235 pub fn encode_png(&self) -> Result<Vec<u8>> {
237 self.pixmap
238 .encode_png()
239 .map_err(|e| RenderError::Encode {
240 detail: e.to_string(),
241 })
242 }
243
244 pub fn raw_rgba(&self) -> &[u8] {
246 self.pixmap.data()
247 }
248
249 pub fn dimensions(&self) -> (u32, u32) {
251 (self.pixmap.width(), self.pixmap.height())
252 }
253
254 pub fn encode_jpeg(&self, quality: u8) -> Result<Vec<u8>> {
256 let width = self.pixmap.width();
257 let height = self.pixmap.height();
258 let rgba_data = self.pixmap.data();
259
260 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
262 for pixel in rgba_data.chunks(4) {
263 rgb_data.push(pixel[0]);
264 rgb_data.push(pixel[1]);
265 rgb_data.push(pixel[2]);
266 }
267
268 let mut buf = std::io::Cursor::new(Vec::new());
269 let encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, quality);
270 image::ImageEncoder::write_image(
271 encoder,
272 &rgb_data,
273 width,
274 height,
275 image::ColorType::Rgb8.into(),
276 )
277 .map_err(|e| RenderError::Encode {
278 detail: e.to_string(),
279 })?;
280
281 Ok(buf.into_inner())
282 }
283}