1#[cfg(all(
4 feature = "assert_eq",
5 any(
6 feature = "image-format-png",
7 feature = "image-format-jpeg",
8 feature = "image-format-gif",
9 feature = "image-format-webp",
10 feature = "image-format-pnm",
11 feature = "image-format-tiff",
12 feature = "image-format-tga",
13 feature = "image-format-bmp",
14 feature = "image-format-ico",
15 feature = "image-format-hdr",
16 feature = "image-format-exr",
17 feature = "image-format-ff",
18 feature = "image-format-avif",
19 feature = "image-format-qoi",
20 )
21))]
22use std::fmt;
23use std::io;
24use std::io::Seek;
25#[cfg(any(
26 feature = "image-format-png",
27 feature = "image-format-jpeg",
28 feature = "image-format-gif",
29 feature = "image-format-webp",
30 feature = "image-format-pnm",
31 feature = "image-format-tiff",
32 feature = "image-format-tga",
33 feature = "image-format-bmp",
34 feature = "image-format-ico",
35 feature = "image-format-hdr",
36 feature = "image-format-exr",
37 feature = "image-format-ff",
38 feature = "image-format-avif",
39 feature = "image-format-qoi",
40))]
41use std::marker;
42use std::pin::Pin;
43
44#[cfg(feature = "async")]
45use futures::AsyncSeek;
46
47use crate::error::Error;
48use crate::error::VfsResult;
49use crate::error::WrapIoError;
50use crate::prelude::*;
51#[cfg(feature = "async")]
52use crate::traits::async_vfs::VfsAsyncWithSeekWrite;
53use crate::traits::sync::FromRefForWriter;
54use crate::traits::sync::NewtypeToInner;
55use crate::traits::vfs;
56use crate::traits::vfs::PathType;
57
58impl<'vfs, Vfs: vfs::VfsWithSeekRead<'vfs>> ReadFrom<'vfs, Vfs> for image::DynamicImage
59where
60 Vfs::RFile: Seek,
61{
62 fn read_from(path: &Vfs::Path, vfs: Pin<&'vfs Vfs>) -> VfsResult<Self, Vfs> {
63 image::ImageReader::new(&mut io::BufReader::new(vfs.open_read(path)?))
64 .with_guessed_format()
65 .wrap_io_error_with(path)?
66 .decode()
67 .map_err(|e| Error::Parse(path.owned(), Box::new(e)))
68 }
69}
70
71impl<'vfs, Vfs: vfs::VfsWithSeekWrite<'vfs>> WriteTo<'vfs, Vfs>
72 for (image::DynamicImage, image::ImageFormat)
73where
74 Vfs::WFile: Seek,
75{
76 fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'vfs Vfs>) -> VfsResult<(), Vfs> {
77 vfs.create_parent_dir(path)?;
78 let mut f = vfs.open_write(path)?;
79
80 let (img, format) = self;
81 img.write_to(&mut f, *format)
82 .map_err(|e| Error::Write(path.owned(), Box::new(e)))
83 }
84}
85
86impl<'vfs, Vfs: vfs::VfsWithSeekWrite<'vfs>> WriteTo<'vfs, Vfs>
87 for (&image::DynamicImage, image::ImageFormat)
88where
89 Vfs::WFile: Seek,
90{
91 fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'vfs Vfs>) -> VfsResult<(), Vfs> {
92 vfs.create_parent_dir(path)?;
93 let mut f = vfs.open_write(path)?;
94
95 let (img, format) = self;
96 img.write_to(&mut f, *format)
97 .map_err(|e| Error::Write(path.owned(), Box::new(e)))
98 }
99}
100
101pub trait ImgFormat: Clone {
103 const FORMAT: image::ImageFormat;
105 type WriterType<'a, Vfs>: From<&'a image::DynamicImage>
107 where
108 Vfs: 'a;
109
110 fn from_image(img: image::DynamicImage) -> Self;
112 fn as_image(&self) -> &image::DynamicImage;
114 fn into_image(self) -> image::DynamicImage;
116}
117
118impl<T: ImgFormat> NewtypeToInner for T {
119 type Inner = image::DynamicImage;
120
121 fn into_inner(self) -> Self::Inner {
122 self.into_image()
123 }
124}
125
126impl<'a, 'vfs, T: ImgFormat + 'a, Vfs: vfs::VfsWithSeekWrite<'vfs>> FromRefForWriter<'a, 'vfs, Vfs>
127 for T
128where
129 Vfs: 'a,
130 Vfs::WFile: Seek,
131 T::WriterType<'a, Vfs>: WriteTo<'vfs, Vfs> + 'a,
132 'vfs: 'a,
133{
134 type Inner = image::DynamicImage;
135 type Wr = T::WriterType<'a, Vfs>;
136
137 fn from_ref_for_writer(inner: &'a Self::Inner) -> Self::Wr {
138 T::WriterType::<'a, Vfs>::from(inner)
139 }
140}
141
142impl<'vfs, Vfs: vfs::VfsWithSeekRead<'vfs>, T> ReadFrom<'vfs, Vfs> for T
143where
144 T: ImgFormat + 'vfs,
145 Vfs::RFile: Seek,
146{
147 fn read_from(path: &Vfs::Path, vfs: Pin<&'vfs Vfs>) -> VfsResult<Self, Vfs> {
148 debug_assert!(
149 T::FORMAT.reading_enabled(),
150 "Image format {:?} does not support reading; enable the corresponding feature",
151 T::FORMAT
152 );
153 let mut img_reader = image::ImageReader::new(io::BufReader::new(vfs.open_read(path)?));
154 img_reader.set_format(T::FORMAT);
155 let img = img_reader
156 .decode()
157 .map_err(|e| Error::Parse(path.owned(), Box::new(e)))?;
158 Ok(T::from_image(img))
159 }
160}
161
162impl<'vfs, Vfs: vfs::VfsWithSeekWrite<'vfs>, T> WriteTo<'vfs, Vfs> for T
163where
164 T: ImgFormat,
165 Vfs::WFile: Seek,
166{
167 fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'vfs Vfs>) -> VfsResult<(), Vfs> {
168 debug_assert!(
169 T::FORMAT.writing_enabled(),
170 "Image format {:?} does not support writing; enable the corresponding feature",
171 T::FORMAT
172 );
173 (self.as_image(), T::FORMAT).write_to(path, vfs)
174 }
175}
176
177#[cfg(feature = "async")]
178#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
179impl<'vfs, Vfs: VfsAsyncWithSeekWrite<Path = P>, P: PathType + ?Sized + 'vfs, T: ImgFormat>
180 WriteToAsync<'vfs, Vfs> for T
181where
182 Vfs: 'vfs,
183 Vfs::WFile: AsyncSeek,
184 (image::DynamicImage, image::ImageFormat): WriteToAsync<'vfs, Vfs>,
185{
186 type Future = <(image::DynamicImage, image::ImageFormat) as WriteToAsync<'vfs, Vfs>>::Future;
187
188 fn write_to_async(self, path: P::OwnedPath, vfs: Pin<&'vfs Vfs>) -> Self::Future {
189 debug_assert!(
190 T::FORMAT.writing_enabled(),
191 "Image format {:?} does not support writing; enable the corresponding feature",
192 T::FORMAT
193 );
194 (self.into_image(), T::FORMAT).write_to_async(path, vfs)
195 }
196}
197
198#[cfg(feature = "async")]
199#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
200impl<'vfs, Vfs: VfsAsyncWithSeekWrite<Path = P>, P: PathType + ?Sized + 'vfs, T: ImgFormat>
201 WriteToAsyncRef<'vfs, Vfs> for T
202where
203 Vfs: 'vfs,
204 Vfs::WFile: AsyncSeek,
205 for<'a> (&'a image::DynamicImage, image::ImageFormat): WriteToAsync<'a, Vfs>,
206{
207 type Future<'a>
208 = <(&'a image::DynamicImage, image::ImageFormat) as WriteToAsync<'a, Vfs>>::Future
209 where
210 Self: 'a,
211 'vfs: 'a,
212 Vfs: 'a;
213
214 fn write_to_async_ref<'a>(&'a self, path: P::OwnedPath, vfs: Pin<&'a Vfs>) -> Self::Future<'a>
215 where
216 'vfs: 'a,
217 {
218 debug_assert!(
219 T::FORMAT.writing_enabled(),
220 "Image format {:?} does not support writing; enable the corresponding feature",
221 T::FORMAT
222 );
223 let img = self.as_image();
224 (img, T::FORMAT).write_to_async(path, vfs)
225 }
226}
227
228macro_rules! img_format {
244 ($(#[$meta:meta])* cfg $(#[$cfg_meta:meta])* $struct_name:ident, $format:expr, $(#[$writer_meta:meta])* $writer_type:ident) => {
245 $(#[$meta])*
246 $(#[$cfg_meta])*
247 #[derive(Debug, Clone, PartialEq)]
248 pub struct $struct_name(image::DynamicImage);
249
250 $(#[$cfg_meta])*
251 #[cfg(feature = "assert_eq")]
252 impl assert_eq::AssertEq for $struct_name {
253 fn assert_eq(&self, other: &Self, path: &mut assert_eq::AssertPath, init_left: &impl fmt::Display, init_right: &impl fmt::Display) {
254 if self.0 != other.0 {
255 panic!("Images differ (at {:?})\nassert_eq! initially called with:\n left: {}\n right: {}", &*path.__guard("image"), init_left, init_right);
256 }
257 }
258 }
259
260 $(#[$cfg_meta])*
261 impl ImgFormat for $struct_name {
262 const FORMAT: image::ImageFormat = $format;
263
264 type WriterType<'a, Vfs> = $writer_type<'a, Vfs> where Vfs: 'a;
265
266 fn from_image(img: image::DynamicImage) -> Self {
267 $struct_name(img)
268 }
269
270 fn as_image(&self) -> &image::DynamicImage {
271 &self.0
272 }
273
274 fn into_image(self) -> image::DynamicImage {
275 self.0
276 }
277 }
278
279 $(#[$writer_meta])*
280 $(#[$cfg_meta])*
281 pub struct $writer_type<'a, Vfs: 'a>(&'a image::DynamicImage, marker::PhantomData<Vfs>);
282
283 $(#[$cfg_meta])*
284 impl<'a, Vfs> From<&'a image::DynamicImage> for $writer_type<'a, Vfs> {
285 fn from(img: &'a image::DynamicImage) -> Self {
286 $writer_type(img, marker::PhantomData)
287 }
288 }
289
290 $(#[$cfg_meta])*
291 impl<'a, 'vfs, Vfs: vfs::VfsWithSeekWrite<'vfs>> WriteTo<'vfs, Vfs> for $writer_type<'a, Vfs>
292 where
293 Vfs::WFile: Seek,
294 'vfs: 'a,
295 {
296 fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'vfs Vfs>) -> VfsResult<(), Vfs> {
297 debug_assert!(
298 $struct_name::FORMAT.writing_enabled(),
299 "Image format {:?} does not support writing; enable the corresponding feature",
300 $struct_name::FORMAT
301 );
302 (self.0, $struct_name::FORMAT).write_to(path, vfs)
303 }
304 }
305 };
306}
307
308img_format!(
309 cfg
311 #[cfg(feature = "image-format-png")]
312 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-png")))]
313 Png,
314 image::ImageFormat::Png,
315 PngWriter
317);
318
319img_format!(
320 cfg
322 #[cfg(feature = "image-format-jpeg")]
323 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-jpeg")))]
324 Jpeg,
325 image::ImageFormat::Jpeg,
326 JpegWriter
328);
329
330img_format!(
331 cfg
333 #[cfg(feature = "image-format-gif")]
334 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-gif")))]
335 Gif,
336 image::ImageFormat::Gif,
337 GifWriter
339);
340
341img_format!(
342 cfg
344 #[cfg(feature = "image-format-webp")]
345 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-webp")))]
346 WebP,
347 image::ImageFormat::WebP,
348 WebPWriter
350);
351
352img_format!(
353 cfg
355 #[cfg(feature = "image-format-pnm")]
356 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-pnm")))]
357 Pnm,
358 image::ImageFormat::Pnm,
359 PnmWriter
361);
362
363img_format!(
364 cfg
366 #[cfg(feature = "image-format-tiff")]
367 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-tiff")))]
368 Tiff,
369 image::ImageFormat::Tiff,
370 TiffWriter
372);
373
374img_format!(
375 cfg
377 #[cfg(feature = "image-format-tga")]
378 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-tga")))]
379 Tga,
380 image::ImageFormat::Tga,
381 TgaWriter
383);
384
385img_format!(
396 cfg
398 #[cfg(feature = "image-format-bmp")]
399 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-bmp")))]
400 Bmp,
401 image::ImageFormat::Bmp,
402 BmpWriter
404);
405
406img_format!(
407 cfg
409 #[cfg(feature = "image-format-ico")]
410 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-ico")))]
411 Ico,
412 image::ImageFormat::Ico,
413 IcoWriter
415);
416
417img_format!(
418 cfg
420 #[cfg(feature = "image-format-hdr")]
421 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-hdr")))]
422 Hdr,
423 image::ImageFormat::Hdr,
424 HdrWriter
426);
427
428img_format!(
429 cfg
431 #[cfg(feature = "image-format-exr")]
432 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-exr")))]
433 OpenExr,
434 image::ImageFormat::OpenExr,
435 OpenExrWriter
437);
438
439img_format!(
440 cfg
442 #[cfg(feature = "image-format-ff")]
443 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-ff")))]
444 Farbfeld,
445 image::ImageFormat::Farbfeld,
446 FarbfeldWriter
448);
449
450img_format!(
451 cfg
453 #[cfg(feature = "image-format-avif")]
454 #[cfg_attr(docsrs, doc(cfg(feature = "image-format-avif")))]
455 Avif,
456 image::ImageFormat::Avif,
457 AvifWriter
459);
460
461img_format!(
462 cfg
464 #[cfg(feature = "image-format-qoi")]
465 Qoi,
466 image::ImageFormat::Qoi,
467 QoiWriter
469);
470
471#[cfg(test)]
472#[allow(dead_code)]
473mod tests {
474 use std::io::Seek;
475
476 #[cfg(feature = "async")]
477 use futures::AsyncSeek;
478
479 use crate::prelude::*;
480 #[cfg(feature = "async")]
481 use crate::traits::async_vfs::VfsAsyncWithSeekRead;
482 #[cfg(feature = "async")]
483 use crate::traits::async_vfs::VfsAsyncWithSeekWrite;
484 use crate::traits::vfs;
485
486 fn assert_is_read_sync<'vfs, Vfs: vfs::VfsWithSeekRead<'vfs> + 'vfs, T: ReadFrom<'vfs, Vfs>>()
487 where
488 Vfs::RFile: Seek,
489 {
490 }
491
492 fn assert_is_write_sync<'vfs, Vfs: vfs::VfsWithSeekWrite<'vfs> + 'vfs, T: WriteTo<'vfs, Vfs>>()
493 where
494 Vfs::WFile: Seek,
495 {
496 }
497
498 #[cfg(feature = "async")]
499 fn assert_is_read_async<'vfs, Vfs: VfsAsyncWithSeekRead + 'vfs, T: ReadFromAsync<'vfs, Vfs>>()
500 where
501 Vfs::RFile: AsyncSeek,
502 {
503 }
504 #[cfg(feature = "async")]
505 fn assert_is_write_async<'vfs, Vfs: VfsAsyncWithSeekWrite + 'vfs, T: WriteToAsync<'vfs, Vfs>>()
506 where
507 Vfs::WFile: AsyncSeek,
508 {
509 }
510 #[cfg(feature = "async")]
511 fn assert_is_write_async_ref<
512 'vfs,
513 Vfs: VfsAsyncWithSeekWrite + 'vfs,
514 T: WriteToAsyncRef<'vfs, Vfs>,
515 >()
516 where
517 Vfs::WFile: AsyncSeek,
518 {
519 }
520
521 macro_rules! test_sync_traits {
522 ($(#[$attr:meta])* fn $test_name:ident (), $vfs:ty, $image:ty) => {
523 #[test]
524 $(#[$attr])*
525 fn $test_name() {
526 assert_is_read_sync::<$vfs, $image>();
527 assert_is_write_sync::<$vfs, $image>();
528 }
529 };
530 }
531
532 macro_rules! test_async_traits {
533 ($(#[$attr:meta])* fn $test_name:ident (), $vfs:ty, $image:ty) => {
534 #[test]
535 $(#[$attr])*
536 fn $test_name() {
537 assert_is_read_async::<$vfs, $image>();
538 assert_is_write_async::<$vfs, $image>();
539 assert_is_write_async_ref::<$vfs, $image>();
540 }
541 };
542 }
543
544 test_sync_traits!(
545 #[cfg(feature = "image-format-png")]
546 fn test_png_sync_traits(),
547 crate::vfs::fs_vfs::FsVfs,
548 crate::image::Png
549 );
550
551 test_async_traits!(
552 #[cfg(all(feature = "image-format-png", feature = "tokio"))]
553 fn test_png_async_traits(),
554 crate::vfs::tokio_fs_vfs::TokioFsVfs,
555 crate::image::Png
556 );
557
558 test_sync_traits!(
559 #[cfg(feature = "image-format-jpeg")]
560 fn test_jpeg_sync_traits(),
561 crate::vfs::fs_vfs::FsVfs,
562 crate::image::Jpeg
563 );
564
565 test_async_traits!(
566 #[cfg(all(feature = "image-format-jpeg", feature = "tokio"))]
567 fn test_jpeg_async_traits(),
568 crate::vfs::tokio_fs_vfs::TokioFsVfs,
569 crate::image::Jpeg
570 );
571
572 test_sync_traits!(
573 #[cfg(feature = "image-format-gif")]
574 fn test_gif_sync_traits(),
575 crate::vfs::fs_vfs::FsVfs,
576 crate::image::Gif
577 );
578
579 test_async_traits!(
580 #[cfg(all(feature = "image-format-gif", feature = "tokio"))]
581 fn test_gif_async_traits(),
582 crate::vfs::tokio_fs_vfs::TokioFsVfs,
583 crate::image::Gif
584 );
585
586 test_sync_traits!(
587 #[cfg(feature = "image-format-webp")]
588 fn test_webp_sync_traits(),
589 crate::vfs::fs_vfs::FsVfs,
590 crate::image::WebP
591 );
592
593 test_async_traits!(
594 #[cfg(all(feature = "image-format-webp", feature = "tokio"))]
595 fn test_webp_async_traits(),
596 crate::vfs::tokio_fs_vfs::TokioFsVfs,
597 crate::image::WebP
598 );
599
600 test_sync_traits!(
601 #[cfg(feature = "image-format-pnm")]
602 fn test_pnm_sync_traits(),
603 crate::vfs::fs_vfs::FsVfs,
604 crate::image::Pnm
605 );
606
607 test_async_traits!(
608 #[cfg(all(feature = "image-format-pnm", feature = "tokio"))]
609 fn test_pnm_async_traits(),
610 crate::vfs::tokio_fs_vfs::TokioFsVfs,
611 crate::image::Pnm
612 );
613
614 test_sync_traits!(
615 #[cfg(feature = "image-format-tiff")]
616 fn test_tiff_sync_traits(),
617 crate::vfs::fs_vfs::FsVfs,
618 crate::image::Tiff
619 );
620
621 test_async_traits!(
622 #[cfg(all(feature = "image-format-tiff", feature = "tokio"))]
623 fn test_tiff_async_traits(),
624 crate::vfs::tokio_fs_vfs::TokioFsVfs,
625 crate::image::Tiff
626 );
627
628 test_sync_traits!(
629 #[cfg(feature = "image-format-tga")]
630 fn test_tga_sync_traits(),
631 crate::vfs::fs_vfs::FsVfs,
632 crate::image::Tga
633 );
634
635 test_async_traits!(
636 #[cfg(all(feature = "image-format-tga", feature = "tokio"))]
637 fn test_tga_async_traits(),
638 crate::vfs::tokio_fs_vfs::TokioFsVfs,
639 crate::image::Tga
640 );
641
642 test_sync_traits!(
643 #[cfg(feature = "image-format-bmp")]
644 fn test_bmp_sync_traits(),
645 crate::vfs::fs_vfs::FsVfs,
646 crate::image::Bmp
647 );
648
649 test_async_traits!(
650 #[cfg(all(feature = "image-format-bmp", feature = "tokio"))]
651 fn test_bmp_async_traits(),
652 crate::vfs::tokio_fs_vfs::TokioFsVfs,
653 crate::image::Bmp
654 );
655
656 test_sync_traits!(
657 #[cfg(feature = "image-format-ico")]
658 fn test_ico_sync_traits(),
659 crate::vfs::fs_vfs::FsVfs,
660 crate::image::Ico
661 );
662
663 test_async_traits!(
664 #[cfg(all(feature = "image-format-ico", feature = "tokio"))]
665 fn test_ico_async_traits(),
666 crate::vfs::tokio_fs_vfs::TokioFsVfs,
667 crate::image::Ico
668 );
669
670 test_sync_traits!(
671 #[cfg(feature = "image-format-hdr")]
672 fn test_hdr_sync_traits(),
673 crate::vfs::fs_vfs::FsVfs,
674 crate::image::Hdr
675 );
676
677 test_async_traits!(
678 #[cfg(all(feature = "image-format-hdr", feature = "tokio"))]
679 fn test_hdr_async_traits(),
680 crate::vfs::tokio_fs_vfs::TokioFsVfs,
681 crate::image::Hdr
682 );
683
684 test_sync_traits!(
685 #[cfg(feature = "image-format-exr")]
686 fn test_openexr_sync_traits(),
687 crate::vfs::fs_vfs::FsVfs,
688 crate::image::OpenExr
689 );
690
691 test_async_traits!(
692 #[cfg(all(feature = "image-format-exr", feature = "tokio"))]
693 fn test_openexr_async_traits(),
694 crate::vfs::tokio_fs_vfs::TokioFsVfs,
695 crate::image::OpenExr
696 );
697
698 test_sync_traits!(
699 #[cfg(feature = "image-format-ff")]
700 fn test_farbfeld_sync_traits(),
701 crate::vfs::fs_vfs::FsVfs,
702 crate::image::Farbfeld
703 );
704
705 test_async_traits!(
706 #[cfg(all(feature = "image-format-ff", feature = "tokio"))]
707 fn test_farbfeld_async_traits(),
708 crate::vfs::tokio_fs_vfs::TokioFsVfs,
709 crate::image::Farbfeld
710 );
711
712 test_sync_traits!(
713 #[cfg(feature = "image-format-avif")]
714 fn test_avif_sync_traits(),
715 crate::vfs::fs_vfs::FsVfs,
716 crate::image::Avif
717 );
718
719 test_async_traits!(
720 #[cfg(all(feature = "image-format-avif", feature = "tokio"))]
721 fn test_avif_async_traits(),
722 crate::vfs::tokio_fs_vfs::TokioFsVfs,
723 crate::image::Avif
724 );
725
726 test_sync_traits!(
727 #[cfg(feature = "image-format-qoi")]
728 fn test_qoi_sync_traits(),
729 crate::vfs::fs_vfs::FsVfs,
730 crate::image::Qoi
731 );
732
733 test_async_traits!(
734 #[cfg(all(feature = "image-format-qoi", feature = "tokio"))]
735 fn test_qoi_async_traits(),
736 crate::vfs::tokio_fs_vfs::TokioFsVfs,
737 crate::image::Qoi
738 );
739}