1mod raw;
13
14#[cfg(feature = "png")]
15mod png;
16
17use crate::{Canvas, Pixel};
18
19#[derive(Debug, Clone, Copy)]
54pub struct Window {
55 pub x: u32,
57 pub y: u32,
59 pub w: u32,
61 pub h: u32,
63}
64
65#[derive(Debug, Clone, Copy)]
67#[non_exhaustive]
68pub enum ImageFormat {
69 RawGamma8Bpp,
72 RawLinear10BppLE,
74 RawLinear12BppLE,
76
77 PngGamma8Bpp,
80 PngLinear16Bpp,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
86#[non_exhaustive]
87pub enum EncoderError {
88 NotImplemented,
90 BrokenWindow,
92 InvalidSubsamplingRate,
94}
95
96pub struct WindowSpans<'a> {
118 canvas: &'a Canvas,
120
121 window: Window,
123
124 scanline: u32,
126}
127
128impl<'a> Iterator for WindowSpans<'a> {
129 type Item = &'a [Pixel];
131
132 fn next(&mut self) -> Option<Self::Item> {
135 if self.scanline >= self.window.y + self.window.h {
137 return None;
138 }
139
140 let base = (self.canvas.width * self.scanline + self.window.x) as usize;
142 let end = base + self.window.w as usize;
143
144 self.scanline += 1;
145
146 Some(&self.canvas.pixbuf[base..end])
147 }
148
149 fn size_hint(&self) -> (usize, Option<usize>) {
150 let size = (self.window.y + self.window.h - self.scanline) as usize;
151
152 (size, Some(size))
153 }
154}
155
156impl<'a> ExactSizeIterator for WindowSpans<'a> {}
157
158impl From<((u32, u32), (u32, u32))> for Window {
159 fn from(tuple: ((u32, u32), (u32, u32))) -> Self {
161 let ((x, y), (w, h)) = tuple;
162
163 Window { x, y, w, h }
164 }
165}
166
167impl std::fmt::Display for Window {
168 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
169 write!(f, "({}, {})+({}, {})", self.x, self.y, self.w, self.h)
170 }
171}
172
173impl std::fmt::Display for EncoderError {
174 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
175 write!(f, "{self:?}")
177 }
178}
179
180impl std::error::Error for EncoderError {}
181
182impl Window {
183 #[must_use]
185 pub fn new(width: u32, height: u32) -> Self {
186 Window {
187 x: 0,
188 y: 0,
189 w: width,
190 h: height,
191 }
192 }
193
194 #[must_use]
196 pub fn at(&self, x: u32, y: u32) -> Window {
197 let w = self.w;
198 let h = self.h;
199
200 Window { x, y, w, h }
201 }
202
203 #[must_use]
205 fn is_inside(&self, width: u32, height: u32) -> bool {
206 self.x + self.w <= width && self.y + self.h <= height
207 }
208
209 #[must_use]
211 fn len(&self) -> usize {
212 (self.w * self.h) as usize
213 }
214}
215
216const MAX_SUBSAMPLING_RATE: u32 = 16;
218
219fn validate_subsampling_rate(factors: (u32, u32)) -> Result<(), EncoderError> {
226 let is_valid = |x| x > 0 && x <= MAX_SUBSAMPLING_RATE;
227
228 if is_valid(factors.0) && is_valid(factors.1) {
229 Ok(())
230 } else {
231 Err(EncoderError::InvalidSubsamplingRate)
232 }
233}
234
235impl Canvas {
236 #[must_use]
246 pub fn window_spans(&self, window: Window) -> Option<WindowSpans<'_>> {
247 if !window.is_inside(self.width, self.height) {
248 return None;
249 }
250
251 let canvas = self;
252
253 let scanline = window.y;
255
256 let iter = WindowSpans {
257 canvas,
258 window,
259 scanline,
260 };
261
262 Some(iter)
263 }
264
265 #[cfg(not(feature = "png"))]
272 pub fn export_image(&self, format: ImageFormat) -> Result<Vec<u8>, EncoderError> {
273 let window = Window::new(self.width, self.height);
275
276 match format {
277 ImageFormat::RawGamma8Bpp => self.export_raw8bpp(window),
278 ImageFormat::RawLinear10BppLE => self.export_raw1xbpp::<10>(window),
279 ImageFormat::RawLinear12BppLE => self.export_raw1xbpp::<12>(window),
280 _ => Err(EncoderError::NotImplemented),
281 }
282 }
283
284 #[cfg(not(feature = "png"))]
294 pub fn export_window_image(
295 &self,
296 window: Window,
297 format: ImageFormat,
298 ) -> Result<Vec<u8>, EncoderError> {
299 if !window.is_inside(self.width, self.height) {
300 return Err(EncoderError::BrokenWindow);
301 }
302
303 match format {
304 ImageFormat::RawGamma8Bpp => self.export_raw8bpp(window),
305 ImageFormat::RawLinear10BppLE => self.export_raw1xbpp::<10>(window),
306 ImageFormat::RawLinear12BppLE => self.export_raw1xbpp::<12>(window),
307 _ => Err(EncoderError::NotImplemented),
308 }
309 }
310
311 #[cfg(not(feature = "png"))]
321 pub fn export_subsampled_image(
322 &self,
323 factors: (u32, u32),
324 format: ImageFormat,
325 ) -> Result<Vec<u8>, EncoderError> {
326 validate_subsampling_rate(factors)?;
327
328 match format {
329 ImageFormat::RawGamma8Bpp => self.export_sub_raw8bpp(factors),
330 ImageFormat::RawLinear10BppLE => self.export_sub_raw1xbpp::<10>(factors),
331 ImageFormat::RawLinear12BppLE => self.export_sub_raw1xbpp::<12>(factors),
332 _ => Err(EncoderError::NotImplemented),
333 }
334 }
335
336 #[cfg(feature = "png")]
343 pub fn export_image(&self, format: ImageFormat) -> Result<Vec<u8>, EncoderError> {
344 let window = Window::new(self.width, self.height);
346
347 match format {
348 ImageFormat::RawGamma8Bpp => self.export_raw8bpp(window),
349 ImageFormat::RawLinear10BppLE => self.export_raw1xbpp::<10>(window),
350 ImageFormat::RawLinear12BppLE => self.export_raw1xbpp::<12>(window),
351 ImageFormat::PngGamma8Bpp => self.export_png8bpp(window),
352 ImageFormat::PngLinear16Bpp => self.export_png16bpp(window),
353 }
354 }
355
356 #[cfg(feature = "png")]
366 pub fn export_window_image(
367 &self,
368 window: Window,
369 format: ImageFormat,
370 ) -> Result<Vec<u8>, EncoderError> {
371 if !window.is_inside(self.width, self.height) {
372 return Err(EncoderError::BrokenWindow);
373 }
374
375 match format {
376 ImageFormat::RawGamma8Bpp => self.export_raw8bpp(window),
377 ImageFormat::RawLinear10BppLE => self.export_raw1xbpp::<10>(window),
378 ImageFormat::RawLinear12BppLE => self.export_raw1xbpp::<12>(window),
379 ImageFormat::PngGamma8Bpp => self.export_png8bpp(window),
380 ImageFormat::PngLinear16Bpp => self.export_png16bpp(window),
381 }
382 }
383
384 #[cfg(feature = "png")]
394 pub fn export_subsampled_image(
395 &self,
396 factors: (u32, u32),
397 format: ImageFormat,
398 ) -> Result<Vec<u8>, EncoderError> {
399 validate_subsampling_rate(factors)?;
400
401 match format {
402 ImageFormat::RawGamma8Bpp => self.export_sub_raw8bpp(factors),
403 ImageFormat::RawLinear10BppLE => self.export_sub_raw1xbpp::<10>(factors),
404 ImageFormat::RawLinear12BppLE => self.export_sub_raw1xbpp::<12>(factors),
405 ImageFormat::PngGamma8Bpp => self.export_sub_png8bpp(factors),
406 ImageFormat::PngLinear16Bpp => self.export_sub_png16bpp(factors),
407 }
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use crate::SpotShape;
415
416 #[cfg(not(feature = "png"))]
417 #[test]
418 fn image_format_error() {
419 let c = Canvas::new(0, 0);
420
421 assert_eq!(
422 c.export_image(ImageFormat::PngGamma8Bpp),
423 Err(EncoderError::NotImplemented)
424 );
425 }
426
427 #[test]
428 fn broken_window_error() {
429 let c = Canvas::new(10, 10);
430 let wnd = Window::new(8, 8).at(5, 5);
431
432 assert_eq!(
433 c.export_window_image(wnd, ImageFormat::RawGamma8Bpp),
434 Err(EncoderError::BrokenWindow)
435 );
436 }
437
438 #[test]
439 fn subsampling_rate_error() {
440 let c = Canvas::new(0, 0);
441
442 assert_eq!(
443 c.export_subsampled_image((1, 0), ImageFormat::RawGamma8Bpp),
444 Err(EncoderError::InvalidSubsamplingRate)
445 );
446 assert_eq!(
447 c.export_subsampled_image((0, 1), ImageFormat::RawLinear10BppLE),
448 Err(EncoderError::InvalidSubsamplingRate)
449 );
450 assert_eq!(
451 c.export_subsampled_image((4, 17), ImageFormat::RawLinear12BppLE),
452 Err(EncoderError::InvalidSubsamplingRate)
453 );
454 }
455
456 #[test]
457 fn window_ops() {
458 let wnd = Window::new(128, 64).at(200, 100);
459
460 assert_eq!(wnd.len(), 128 * 64);
461 assert!(wnd.is_inside(400, 500));
462 assert!(!wnd.is_inside(100, 100));
463 assert!(!wnd.at(300, 100).is_inside(400, 500));
464 }
465
466 #[test]
467 fn get_window_spans() {
468 let mut c = Canvas::new(100, 100);
469
470 c.add_spot((50.75, 50.5), SpotShape::default(), 1.0);
471 c.draw();
472
473 let wnd1 = Window::new(4, 3).at(50, 50);
474
475 let mut vec = Vec::new();
476 for span in c.window_spans(wnd1).unwrap() {
477 vec.extend_from_slice(span);
478 }
479
480 assert_eq!(
481 vec,
482 [542, 18087, 1146, 0, 542, 18087, 1146, 0, 193, 731, 0, 0]
483 );
484
485 let wnd2 = wnd1.at(48, 51);
486
487 vec.clear();
488 for span in c.window_spans(wnd2).unwrap() {
489 vec.extend_from_slice(span);
490 }
491
492 assert_eq!(vec, [0, 0, 542, 18087, 0, 0, 193, 731, 0, 0, 0, 0]);
493 }
494
495 #[test]
496 fn broken_windows() {
497 let c = Canvas::new(100, 100);
498
499 let wnd1 = Window::new(4, 100).at(50, 50);
500 assert!(c.window_spans(wnd1).is_none());
501
502 let wnd2 = Window::new(4, 5).at(100, 100);
503 assert!(c.window_spans(wnd2).is_none());
504
505 let wnd3 = Window::new(1, 1).at(100, 100);
506 assert!(c.window_spans(wnd3).is_none());
507
508 let wnd4 = Window::new(0, 0).at(100, 100);
509 let mut spans = c.window_spans(wnd4).unwrap();
510 assert_eq!(spans.len(), 0);
511 assert!(spans.next().is_none());
512 }
513}