1#![doc = include_str!("../README.md")]
2
3use ::image::{DynamicImage, RgbaImage};
4use dessin::{
5 export::{Export, Exporter},
6 prelude::*,
7};
8use nalgebra::{Point2, Transform2, Translation2, Vector2};
9use raqote::{
10 DrawOptions, DrawTarget, LineCap, LineJoin, PathBuilder, Point, SolidSource, Source,
11 StrokeStyle,
12};
13use std::fmt;
14
15#[derive(Debug)]
16pub enum ImageError {
17 WriteError(fmt::Error),
18 CurveHasNoStartingPoint(CurvePosition),
19 FontLoadingError(font_kit::error::FontLoadingError),
20 ImageError,
21}
22impl fmt::Display for ImageError {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(f, "{self:?}")
25 }
26}
27impl From<fmt::Error> for ImageError {
28 fn from(value: fmt::Error) -> Self {
29 ImageError::WriteError(value)
30 }
31}
32impl std::error::Error for ImageError {}
33
34#[derive(Default)]
35pub struct ImageOptions {
36 pub canvas: Option<(f32, f32)>,
37}
38
39pub struct ImageExporter {
40 buffer: DrawTarget,
41 style: Vec<StylePosition>,
42}
43
44impl ImageExporter {
45 fn new(width: u32, height: u32) -> Self {
46 ImageExporter {
47 buffer: DrawTarget::new(width as i32, height as i32),
48 style: vec![],
49 }
50 }
51
52 fn finalize(self) -> DrawTarget {
53 self.buffer
54 }
55
56 fn style(&self) -> StylePosition {
57 let mut acc = StylePosition {
58 stroke: None,
59 fill: None,
60 };
61
62 for style in self.style.iter().rev() {
63 match (acc.fill, style.fill) {
64 (None, Some(s)) => acc.fill = Some(s),
65 _ => {}
66 }
67
68 match (acc.stroke, style.stroke) {
69 (None, Some(s)) => acc.stroke = Some(s),
70 _ => {}
71 }
72
73 if acc.fill.is_some() && acc.fill.is_some() {
74 break;
75 }
76 }
77
78 acc
79 }
80}
81
82impl Exporter for ImageExporter {
83 type Error = ImageError;
84
85 const CAN_EXPORT_ELLIPSE: bool = false;
86
87 fn start_style(&mut self, style: StylePosition) -> Result<(), Self::Error> {
88 self.style.push(style);
89 Ok(())
90 }
91
92 fn end_style(&mut self) -> Result<(), Self::Error> {
93 self.style.pop();
94 Ok(())
95 }
96
97 fn export_image(
98 &mut self,
99 ImagePosition {
100 top_left: _,
101 top_right: _,
102 bottom_right: _,
103 bottom_left: _,
104 center: _,
105 width: _,
106 height: _,
107 rotation: _,
108 image: _,
109 }: ImagePosition,
110 ) -> Result<(), Self::Error> {
111 Ok(())
134 }
135
136 fn export_curve(&mut self, curve: CurvePosition, _: StylePosition) -> Result<(), Self::Error> {
137 let mut path = PathBuilder::new();
138
139 for (idx, k) in curve.keypoints.iter().enumerate() {
140 let is_first = idx == 0;
141
142 match k {
143 KeypointPosition::Point(p) if is_first => path.move_to(p.x, p.y),
144 KeypointPosition::Point(p) => path.line_to(p.x, p.y),
145 KeypointPosition::Bezier(b) => {
146 match (is_first, b.start) {
147 (true, None) => return Err(ImageError::CurveHasNoStartingPoint(curve)),
148 (true, Some(s)) => path.move_to(s.x, s.y),
149 (false, None) => {}
150 (false, Some(s)) => path.line_to(s.x, s.y),
151 }
152
153 path.cubic_to(
154 b.start_control.x,
155 b.start_control.y,
156 b.end_control.x,
157 b.end_control.y,
158 b.end.x,
159 b.end.y,
160 );
161 }
162 }
163 }
164
165 if curve.closed {
166 path.close()
167 }
168
169 let path = path.finish();
170
171 let style = self.style();
172
173 if let Some(Fill::Solid { color }) = style.fill {
174 let (r, g, b, a) = (
175 color.into_format::<u8, f32>().red,
176 color.into_format::<u8, f32>().green,
177 color.into_format::<u8, f32>().blue,
178 color.into_format::<u8, u8>().alpha,
179 );
180 self.buffer.fill(
181 &path,
182 &Source::Solid(SolidSource { r: b, g, b: r, a }),
183 &DrawOptions::new(),
184 )
185 }
186
187 match style.stroke {
188 Some(Stroke::Solid { color, width }) => {
189 let (r, g, b, a) = (
190 color.into_format::<u8, f32>().red,
191 color.into_format::<u8, f32>().green,
192 color.into_format::<u8, f32>().blue,
193 color.into_format::<u8, u8>().alpha,
194 );
195 self.buffer.stroke(
196 &path,
197 &Source::Solid(SolidSource { r: b, g, b: r, a }),
198 &StrokeStyle {
199 cap: LineCap::Butt,
200 join: LineJoin::Miter,
201 width,
202 miter_limit: 2.,
203 dash_array: vec![],
204 dash_offset: 0.,
205 },
206 &DrawOptions::new(),
207 );
208 }
209 Some(Stroke::Dashed {
210 color,
211 width,
212 on,
213 off,
214 }) => {
215 let (r, g, b, a) = (
216 color.into_format::<u8, f32>().red,
217 color.into_format::<u8, f32>().green,
218 color.into_format::<u8, f32>().blue,
219 color.into_format::<u8, u8>().alpha,
220 );
221 self.buffer.stroke(
222 &path,
223 &Source::Solid(SolidSource { r: b, g, b: r, a }),
224 &StrokeStyle {
225 cap: LineCap::Butt,
226 join: LineJoin::Miter,
227 width,
228 miter_limit: 2.,
229 dash_array: vec![on, off],
230 dash_offset: 0.,
231 },
232 &DrawOptions::new(),
233 );
234 }
235 None => {}
236 }
237
238 Ok(())
239 }
240
241 fn export_text(
242 &mut self,
243 TextPosition {
244 text,
245 align: _,
246 font_weight,
247 on_curve: _,
248 font_size,
249 reference_start,
250 direction: _,
251 font,
252 }: TextPosition,
253 _: StylePosition,
254 ) -> Result<(), Self::Error> {
255 let fg = dessin::font::get_or_default(font.as_ref());
256 let font = fg.get(font_weight).as_bytes();
257
258 let color = match self.style().fill {
262 Some(Fill::Solid { color }) => color,
263 None => return Ok(()),
264 };
265 let (r, g, b, a) = (
266 color.into_format::<u8, f32>().red, color.into_format::<u8, f32>().green, color.into_format::<u8, f32>().blue, color.into_format::<u8, u8>().alpha, );
271
272 let font = font_kit::loader::Loader::from_bytes(std::sync::Arc::new(font.to_vec()), 0)
273 .map_err(|e| ImageError::FontLoadingError(e))?;
274 self.buffer.draw_text(
275 &font,
276 font_size,
277 text,
278 Point::new(reference_start.x, reference_start.y),
279 &Source::Solid(SolidSource { r: b, g, b: r, a }),
280 &DrawOptions::new(),
281 );
282
283 Ok(())
284 }
285}
286
287pub trait ToImage {
288 fn rasterize(&self) -> Result<DynamicImage, ImageError>;
289}
290
291impl ToImage for Shape {
292 fn rasterize(&self) -> Result<DynamicImage, ImageError> {
293 let bb = self.local_bounding_box().straigthen();
294
295 let center: Vector2<f32> = bb.center() - Point2::origin();
296 let translation =
297 Translation2::from(Vector2::new(bb.width() / 2., bb.height() / 2.) - center);
298 let scale = nalgebra::Scale2::new(1., -1.);
299 let transform = nalgebra::convert::<_, Transform2<f32>>(translation)
300 * nalgebra::convert::<_, Transform2<f32>>(scale);
301
302 let width = bb.width().ceil() as u32;
303 let height = bb.height().ceil() as u32;
304 let mut exporter = ImageExporter::new(width, height);
305
306 if let Shape::Style { fill, stroke, .. } = self {
316 self.write_into_exporter(
317 &mut exporter,
318 &transform,
319 StylePosition {
320 fill: *fill,
321 stroke: *stroke,
322 },
323 )? } else {
325 self.write_into_exporter(
326 &mut exporter,
327 &transform,
328 StylePosition {
329 fill: None,
330 stroke: None,
331 },
332 )?
333 }
334
335 let raw: Vec<u32> = exporter.finalize().into_vec();
336 let raw: Vec<u8> = unsafe {
337 let cap = raw.capacity();
338 let len = raw.len();
339 let ptr = Box::into_raw(raw.into_boxed_slice());
340
341 Vec::from_raw_parts(ptr.cast(), len * 4, cap * 4)
342 };
343
344 let img = DynamicImage::ImageRgba8(
345 RgbaImage::from_raw(width, height, raw).ok_or(ImageError::ImageError)?,
346 );
347
348 Ok(img)
349 }
350}