dir_structure/
image.rs

1//! Implementations of [`ReadFrom`] and [`WriteTo`] for image files.
2
3#[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
101/// An image format. This is used to implement [`ReadFrom`] and [`WriteTo`] for specific image formats.
102pub trait ImgFormat: Clone {
103    /// The image format.
104    const FORMAT: image::ImageFormat;
105    /// The writer type for this image format.
106    type WriterType<'a, Vfs>: From<&'a image::DynamicImage>
107    where
108        Vfs: 'a;
109
110    /// Create an instance of this type from a [`image::DynamicImage`].
111    fn from_image(img: image::DynamicImage) -> Self;
112    /// Get a reference to the inner [`image::DynamicImage`].
113    fn as_image(&self) -> &image::DynamicImage;
114    /// Consumes this instance and returns the inner [`image::DynamicImage`].
115    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
228// async implementations are available only for specific VFS implementations,
229// as they require specific async traits that are not part of the standard library (e.g. a la tokio::task::spawn_blocking,
230// or similar, for other runtimes, given that image encoding / decoding with the image crate is CPU-bound and blocking).
231//
232// as such, they are implemented in the respective VFS modules.
233// for new async VFS implementations, the following impls are required for Image formats to work with it:
234// - `impl<T: ImgFormat> ReadFromAsync<'vfs, NewVfsType> for T`                                 to satisfy the bound `T: ReadFromAsync<'vfs, NewVfsType>`
235// - `impl<'a> WriteToAsync<'a, NewVfsType> for (image::DynamicImage, image::ImageFormat)`      to satisfy the bound `T: WriteToAsync<'a, NewVfsType>`
236// - `impl<'a> WriteToAsync<'a, NewVfsType> for (&'a image::DynamicImage, image::ImageFormat)`  to satisfy the bound `T: WriteToAsyncRef<'a, NewVfsType>`
237//
238// These impls are automatically generated for Vfs types that implement the following traits:
239// - `ReadImageFromAsync<T>`
240// - `WriteImageToAsync<'a>`
241// - `WriteImageToAsyncRef<'a>`
242
243macro_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    /// An image in PNG format.
310    cfg
311    #[cfg(feature = "image-format-png")]
312    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-png")))]
313    Png,
314    image::ImageFormat::Png,
315    /// A writer for PNG images.
316    PngWriter
317);
318
319img_format!(
320    /// An image in JPEG format.
321    cfg
322    #[cfg(feature = "image-format-jpeg")]
323    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-jpeg")))]
324    Jpeg,
325    image::ImageFormat::Jpeg,
326    /// A writer for JPEG images.
327    JpegWriter
328);
329
330img_format!(
331    /// An image in GIF format.
332    cfg
333    #[cfg(feature = "image-format-gif")]
334    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-gif")))]
335    Gif,
336    image::ImageFormat::Gif,
337    /// A writer for GIF images.
338    GifWriter
339);
340
341img_format!(
342    /// An image in WebP format.
343    cfg
344    #[cfg(feature = "image-format-webp")]
345    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-webp")))]
346    WebP,
347    image::ImageFormat::WebP,
348    /// A writer for WebP images.
349    WebPWriter
350);
351
352img_format!(
353    /// An image in PNM format.
354    cfg
355    #[cfg(feature = "image-format-pnm")]
356    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-pnm")))]
357    Pnm,
358    image::ImageFormat::Pnm,
359    /// A writer for PNM images.
360    PnmWriter
361);
362
363img_format!(
364    /// An image in TIFF format.
365    cfg
366    #[cfg(feature = "image-format-tiff")]
367    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-tiff")))]
368    Tiff,
369    image::ImageFormat::Tiff,
370    /// A writer for TIFF images.
371    TiffWriter
372);
373
374img_format!(
375    /// An image in TGA format.
376    cfg
377    #[cfg(feature = "image-format-tga")]
378    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-tga")))]
379    Tga,
380    image::ImageFormat::Tga,
381    /// A writer for TGA images.
382    TgaWriter
383);
384
385// DDS support is not there as `image` doesn't support reading / writing DDS files.
386// img_format!(
387//     /// An image in DDS format.
388//     cfg
389//     #[cfg(feature = "image-format-dds")]
390//     #[cfg_attr(docsrs, doc(cfg(feature = "image-format-dds")))]
391//     Dds,
392//     image::ImageFormat::Dds
393// );
394
395img_format!(
396    /// An image in BMP format.
397    cfg
398    #[cfg(feature = "image-format-bmp")]
399    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-bmp")))]
400    Bmp,
401    image::ImageFormat::Bmp,
402    /// A writer for BMP images.
403    BmpWriter
404);
405
406img_format!(
407    /// An image in ICO format.
408    cfg
409    #[cfg(feature = "image-format-ico")]
410    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-ico")))]
411    Ico,
412    image::ImageFormat::Ico,
413    /// A writer for ICO images.
414    IcoWriter
415);
416
417img_format!(
418    /// An image in HDR format.
419    cfg
420    #[cfg(feature = "image-format-hdr")]
421    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-hdr")))]
422    Hdr,
423    image::ImageFormat::Hdr,
424    /// A writer for HDR images.
425    HdrWriter
426);
427
428img_format!(
429    /// An image in OpenEXR format.
430    cfg
431    #[cfg(feature = "image-format-exr")]
432    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-exr")))]
433    OpenExr,
434    image::ImageFormat::OpenExr,
435    /// A writer for OpenEXR images.
436    OpenExrWriter
437);
438
439img_format!(
440    /// An image in Farbfeld format.
441    cfg
442    #[cfg(feature = "image-format-ff")]
443    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-ff")))]
444    Farbfeld,
445    image::ImageFormat::Farbfeld,
446    /// A writer for Farbfeld images.
447    FarbfeldWriter
448);
449
450img_format!(
451    /// An image in AVIF format.
452    cfg
453    #[cfg(feature = "image-format-avif")]
454    #[cfg_attr(docsrs, doc(cfg(feature = "image-format-avif")))]
455    Avif,
456    image::ImageFormat::Avif,
457    /// A writer for AVIF images.
458    AvifWriter
459);
460
461img_format!(
462    /// An image in QOI format.
463    cfg
464    #[cfg(feature = "image-format-qoi")]
465    Qoi,
466    image::ImageFormat::Qoi,
467    /// A writer for QOI images.
468    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}