dessin_image/
lib.rs

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		// let mut raw_image = Cursor::new(vec![]);
112		// image.write_to(&mut raw_image, ImageFormat::Png).unwrap();
113
114		// let data = data_encoding::BASE64.encode(&raw_image.into_inner());
115
116		// write!(
117		//	 self.acc,
118		//	 r#"<image width="{width}" height="{height}" x="{x}" y="{y}" "#,
119		//	 x = center.x - width / 2.,
120		//	 y = center.y - height / 2.,
121		// )?;
122
123		// if rotation.abs() > 10e-6 {
124		//	 write!(
125		//		 self.acc,
126		//		 r#" transform="rotate({rot})" "#,
127		//		 rot = -rotation.to_degrees()
128		//	 )?;
129		// }
130
131		// write!(self.acc, r#"href="data:image/png;base64,{data}"/>"#,)?;
132
133		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		//dt.set_transform(&Transform::create_translation(50.0, 0.0));
259		// dt.set_transform(&Transform::rotation(euclid::Angle::degrees(15.0)));
260
261		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, //-----------------------------------------------------------------------------
267			color.into_format::<u8, f32>().green, //before : color.rgba();
268			color.into_format::<u8, f32>().blue, //rgba() modification should be better
269			color.into_format::<u8, u8>().alpha, //-----------------------------------------------------------------------------
270		);
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		// self.write_into_exporter(
307		//	 &mut exporter,
308		//	 &transform,
309		//	 StylePosition {
310		//		 stroke: None,
311		//		 fill: None,
312		//	 },
313		// )?;
314
315		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			)? //Needed to be complete
324		} 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}