Skip to main content

ai_image/images/
dynimage.rs

1use alloc::{borrow::ToOwned, boxed::Box, vec::Vec};
2use no_std_io::io::{Seek, Write};
3#[cfg(feature = "std")]
4use std::fs::File;
5#[cfg(feature = "std")]
6use std::path::Path;
7
8use crate::color::{self, FromColor, IntoColor};
9use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind};
10use crate::flat::FlatSamples;
11use crate::imageops::{gaussian_blur_dyn_image, GaussianBlurParameters};
12use crate::images::buffer::{
13    ConvertBuffer, Gray16Image, GrayAlpha16Image, GrayAlphaImage, GrayImage, ImageBuffer,
14    Rgb16Image, Rgb32FImage, RgbImage, Rgba16Image, Rgba32FImage, RgbaImage,
15};
16use crate::io::encoder::ImageEncoderBoxed;
17use crate::io::free_functions::{self, encoder_for_format};
18use crate::math::resize_dimensions;
19use crate::metadata::Orientation;
20use crate::traits::Pixel;
21#[cfg(feature = "std")]
22use crate::ImageReader;
23use crate::{
24    imageops,
25    metadata::{Cicp, CicpColorPrimaries, CicpTransferCharacteristics},
26    ConvertColorOptions, ExtendedColorType, GenericImage, GenericImageView, ImageDecoder,
27    ImageEncoder, ImageFormat, Luma, LumaA,
28};
29
30/// A Dynamic Image
31///
32/// This represents a _matrix_ of _pixels_ which are _convertible_ from and to an _RGBA_
33/// representation. More variants that adhere to these principles may get added in the future, in
34/// particular to cover other combinations typically used.
35///
36/// # Usage
37///
38/// This type can act as a converter between specific `ImageBuffer` instances.
39///
40/// ```
41/// use ai_image::{DynamicImage, GrayImage, RgbImage};
42///
43/// let rgb: RgbImage = RgbImage::new(10, 10);
44/// let luma: GrayImage = DynamicImage::ImageRgb8(rgb).into_luma8();
45/// ```
46///
47/// # Design
48///
49/// There is no goal to provide an all-encompassing type with all possible memory layouts. This
50/// would hardly be feasible as a simple enum, due to the sheer number of combinations of channel
51/// kinds, channel order, and bit depth. Rather, this type provides an opinionated selection with
52/// normalized channel order which can store common pixel values without loss.
53///
54/// # Color space
55///
56/// Each image has an associated color space in the form of [CICP] data ([ITU Rec H.273]). Not all
57/// color spaces are supported in the sense that you can compute in them ([Context][w3c-png]).
58/// Conversion into different pixels types ([`ColorType`][`crate::ColorType`]) _generally_ take the
59/// color space into account, with the exception of [`DynamicImage::to`] due to historical design
60/// baggage.
61///
62/// The imageops functions operate in _encoded_ space, directly on the channel values, and do _not_
63/// linearize colors internally as you might be used to from GPU shader programming. Their return
64/// values however copy the color space annotation of the source.
65///
66/// The IO functions do _not yet_ write ICC or CICP indications into the result formats. We're
67/// aware of this problem, it is tracked in [#2493] and [#1460].
68///
69/// [CICP]: https://www.w3.org/TR/png-3/#cICP-chunk
70/// [w3c-png]: https://github.com/w3c/png/issues/312
71/// [ITU Rec H.273]: https://www.itu.int/rec/T-REC-H.273-202407-I/en
72/// [#2493]: https://github.com/image-rs/image/issues/2493
73/// [#1460]: https://github.com/image-rs/image/issues/1460
74#[derive(Debug, PartialEq)]
75#[non_exhaustive]
76pub enum DynamicImage {
77    /// Each pixel in this image is 8-bit Luma
78    ImageLuma8(GrayImage),
79
80    /// Each pixel in this image is 8-bit Luma with alpha
81    ImageLumaA8(GrayAlphaImage),
82
83    /// Each pixel in this image is 8-bit Rgb
84    ImageRgb8(RgbImage),
85
86    /// Each pixel in this image is 8-bit Rgb with alpha
87    ImageRgba8(RgbaImage),
88
89    /// Each pixel in this image is 16-bit Luma
90    ImageLuma16(Gray16Image),
91
92    /// Each pixel in this image is 16-bit Luma with alpha
93    ImageLumaA16(GrayAlpha16Image),
94
95    /// Each pixel in this image is 16-bit Rgb
96    ImageRgb16(Rgb16Image),
97
98    /// Each pixel in this image is 16-bit Rgb with alpha
99    ImageRgba16(Rgba16Image),
100
101    /// Each pixel in this image is 32-bit float Rgb
102    ImageRgb32F(Rgb32FImage),
103
104    /// Each pixel in this image is 32-bit float Rgb with alpha
105    ImageRgba32F(Rgba32FImage),
106}
107
108macro_rules! dynamic_map(
109        ($dynimage: expr, $image: pat => $action: expr) => ({
110            use DynamicImage::*;
111            match $dynimage {
112                ImageLuma8($image) => ImageLuma8($action),
113                ImageLumaA8($image) => ImageLumaA8($action),
114                ImageRgb8($image) => ImageRgb8($action),
115                ImageRgba8($image) => ImageRgba8($action),
116                ImageLuma16($image) => ImageLuma16($action),
117                ImageLumaA16($image) => ImageLumaA16($action),
118                ImageRgb16($image) => ImageRgb16($action),
119                ImageRgba16($image) => ImageRgba16($action),
120                ImageRgb32F($image) => ImageRgb32F($action),
121                ImageRgba32F($image) => ImageRgba32F($action),
122            }
123        });
124
125        ($dynimage: expr, $image:pat_param, $action: expr) => (
126            match $dynimage {
127                DynamicImage::ImageLuma8($image) => $action,
128                DynamicImage::ImageLumaA8($image) => $action,
129                DynamicImage::ImageRgb8($image) => $action,
130                DynamicImage::ImageRgba8($image) => $action,
131                DynamicImage::ImageLuma16($image) => $action,
132                DynamicImage::ImageLumaA16($image) => $action,
133                DynamicImage::ImageRgb16($image) => $action,
134                DynamicImage::ImageRgba16($image) => $action,
135                DynamicImage::ImageRgb32F($image) => $action,
136                DynamicImage::ImageRgba32F($image) => $action,
137            }
138        );
139);
140
141impl Clone for DynamicImage {
142    fn clone(&self) -> Self {
143        dynamic_map!(*self, ref p, DynamicImage::from(p.clone()))
144    }
145
146    fn clone_from(&mut self, source: &Self) {
147        match (self, source) {
148            (Self::ImageLuma8(p1), Self::ImageLuma8(p2)) => p1.clone_from(p2),
149            (Self::ImageLumaA8(p1), Self::ImageLumaA8(p2)) => p1.clone_from(p2),
150            (Self::ImageRgb8(p1), Self::ImageRgb8(p2)) => p1.clone_from(p2),
151            (Self::ImageRgba8(p1), Self::ImageRgba8(p2)) => p1.clone_from(p2),
152            (Self::ImageLuma16(p1), Self::ImageLuma16(p2)) => p1.clone_from(p2),
153            (Self::ImageLumaA16(p1), Self::ImageLumaA16(p2)) => p1.clone_from(p2),
154            (Self::ImageRgb16(p1), Self::ImageRgb16(p2)) => p1.clone_from(p2),
155            (Self::ImageRgba16(p1), Self::ImageRgba16(p2)) => p1.clone_from(p2),
156            (Self::ImageRgb32F(p1), Self::ImageRgb32F(p2)) => p1.clone_from(p2),
157            (Self::ImageRgba32F(p1), Self::ImageRgba32F(p2)) => p1.clone_from(p2),
158            (this, source) => *this = source.clone(),
159        }
160    }
161}
162
163impl DynamicImage {
164    /// Creates a dynamic image backed by a buffer depending on
165    /// the color type given.
166    ///
167    /// The color space is initially set to [`sRGB`][`Cicp::SRGB`].
168    #[must_use]
169    pub fn new(w: u32, h: u32, color: color::ColorType) -> DynamicImage {
170        use color::ColorType::*;
171        match color {
172            L8 => Self::new_luma8(w, h),
173            La8 => Self::new_luma_a8(w, h),
174            Rgb8 => Self::new_rgb8(w, h),
175            Rgba8 => Self::new_rgba8(w, h),
176            L16 => Self::new_luma16(w, h),
177            La16 => Self::new_luma_a16(w, h),
178            Rgb16 => Self::new_rgb16(w, h),
179            Rgba16 => Self::new_rgba16(w, h),
180            Rgb32F => Self::new_rgb32f(w, h),
181            Rgba32F => Self::new_rgba32f(w, h),
182        }
183    }
184
185    /// Creates a dynamic image backed by a buffer of gray pixels.
186    #[must_use]
187    pub fn new_luma8(w: u32, h: u32) -> DynamicImage {
188        DynamicImage::ImageLuma8(ImageBuffer::new(w, h))
189    }
190
191    /// Creates a dynamic image backed by a buffer of gray
192    /// pixels with transparency.
193    #[must_use]
194    pub fn new_luma_a8(w: u32, h: u32) -> DynamicImage {
195        DynamicImage::ImageLumaA8(ImageBuffer::new(w, h))
196    }
197
198    /// Creates a dynamic image backed by a buffer of RGB pixels.
199    #[must_use]
200    pub fn new_rgb8(w: u32, h: u32) -> DynamicImage {
201        DynamicImage::ImageRgb8(ImageBuffer::new(w, h))
202    }
203
204    /// Creates a dynamic image backed by a buffer of RGBA pixels.
205    #[must_use]
206    pub fn new_rgba8(w: u32, h: u32) -> DynamicImage {
207        DynamicImage::ImageRgba8(ImageBuffer::new(w, h))
208    }
209
210    /// Creates a dynamic image backed by a buffer of gray pixels.
211    #[must_use]
212    pub fn new_luma16(w: u32, h: u32) -> DynamicImage {
213        DynamicImage::ImageLuma16(ImageBuffer::new(w, h))
214    }
215
216    /// Creates a dynamic image backed by a buffer of gray
217    /// pixels with transparency.
218    #[must_use]
219    pub fn new_luma_a16(w: u32, h: u32) -> DynamicImage {
220        DynamicImage::ImageLumaA16(ImageBuffer::new(w, h))
221    }
222
223    /// Creates a dynamic image backed by a buffer of RGB pixels.
224    #[must_use]
225    pub fn new_rgb16(w: u32, h: u32) -> DynamicImage {
226        DynamicImage::ImageRgb16(ImageBuffer::new(w, h))
227    }
228
229    /// Creates a dynamic image backed by a buffer of RGBA pixels.
230    #[must_use]
231    pub fn new_rgba16(w: u32, h: u32) -> DynamicImage {
232        DynamicImage::ImageRgba16(ImageBuffer::new(w, h))
233    }
234
235    /// Creates a dynamic image backed by a buffer of RGB pixels.
236    #[must_use]
237    pub fn new_rgb32f(w: u32, h: u32) -> DynamicImage {
238        DynamicImage::ImageRgb32F(ImageBuffer::new(w, h))
239    }
240
241    /// Creates a dynamic image backed by a buffer of RGBA pixels.
242    #[must_use]
243    pub fn new_rgba32f(w: u32, h: u32) -> DynamicImage {
244        DynamicImage::ImageRgba32F(ImageBuffer::new(w, h))
245    }
246
247    /// Decodes an encoded image into a dynamic image.
248    pub fn from_decoder(decoder: impl ImageDecoder) -> ImageResult<Self> {
249        decoder_to_image(decoder)
250    }
251
252    /// Encodes a dynamic image into a buffer.
253    ///
254    /// **WARNING**: Conversion between RGB and Luma is not aware of the color space and always
255    /// uses sRGB coefficients to determine a non-constant luminance from an RGB color (and
256    /// conversely).
257    ///
258    /// This unfortunately owes to the public bounds of `T` which does not allow for passing a
259    /// color space as a parameter. This function will likely be deprecated and replaced.
260    #[inline]
261    #[must_use]
262    pub fn to<
263        T: Pixel
264            + FromColor<color::Rgb<u8>>
265            + FromColor<color::Rgb<f32>>
266            + FromColor<color::Rgba<u8>>
267            + FromColor<color::Rgba<u16>>
268            + FromColor<color::Rgba<f32>>
269            + FromColor<color::Rgb<u16>>
270            + FromColor<Luma<u8>>
271            + FromColor<Luma<u16>>
272            + FromColor<LumaA<u16>>
273            + FromColor<LumaA<u8>>,
274    >(
275        &self,
276    ) -> ImageBuffer<T, Vec<T::Subpixel>> {
277        dynamic_map!(*self, ref p, p.convert())
278    }
279
280    /// Returns a copy of this image as an RGB image.
281    #[must_use]
282    pub fn to_rgb8(&self) -> RgbImage {
283        match self {
284            DynamicImage::ImageRgb8(x) => x.clone(),
285            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
286        }
287    }
288
289    /// Returns a copy of this image as an RGB image.
290    #[must_use]
291    pub fn to_rgb16(&self) -> Rgb16Image {
292        match self {
293            DynamicImage::ImageRgb16(x) => x.clone(),
294            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
295        }
296    }
297
298    /// Returns a copy of this image as an RGB image.
299    #[must_use]
300    pub fn to_rgb32f(&self) -> Rgb32FImage {
301        match self {
302            DynamicImage::ImageRgb32F(x) => x.clone(),
303            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
304        }
305    }
306
307    /// Returns a copy of this image as an RGBA image.
308    #[must_use]
309    pub fn to_rgba8(&self) -> RgbaImage {
310        match self {
311            DynamicImage::ImageRgba8(x) => x.clone(),
312            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
313        }
314    }
315
316    /// Returns a copy of this image as an RGBA image.
317    #[must_use]
318    pub fn to_rgba16(&self) -> Rgba16Image {
319        match self {
320            DynamicImage::ImageRgba16(x) => x.clone(),
321            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
322        }
323    }
324
325    /// Returns a copy of this image as an RGBA image.
326    #[must_use]
327    pub fn to_rgba32f(&self) -> Rgba32FImage {
328        match self {
329            DynamicImage::ImageRgba32F(x) => x.clone(),
330            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
331        }
332    }
333
334    /// Returns a copy of this image as a Luma image.
335    #[must_use]
336    pub fn to_luma8(&self) -> GrayImage {
337        match self {
338            DynamicImage::ImageLuma8(x) => x.clone(),
339            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
340        }
341    }
342
343    /// Returns a copy of this image as a Luma image.
344    #[must_use]
345    pub fn to_luma16(&self) -> Gray16Image {
346        match self {
347            DynamicImage::ImageLuma16(x) => x.clone(),
348            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
349        }
350    }
351
352    /// Returns a copy of this image as a Luma image.
353    #[must_use]
354    pub fn to_luma32f(&self) -> ImageBuffer<Luma<f32>, Vec<f32>> {
355        dynamic_map!(self, ref p, p.cast_in_color_space())
356    }
357
358    /// Returns a copy of this image as a `LumaA` image.
359    #[must_use]
360    pub fn to_luma_alpha8(&self) -> GrayAlphaImage {
361        match self {
362            DynamicImage::ImageLumaA8(x) => x.clone(),
363            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
364        }
365    }
366
367    /// Returns a copy of this image as a `LumaA` image.
368    #[must_use]
369    pub fn to_luma_alpha16(&self) -> GrayAlpha16Image {
370        match self {
371            DynamicImage::ImageLumaA16(x) => x.clone(),
372            x => dynamic_map!(x, ref p, p.cast_in_color_space()),
373        }
374    }
375
376    /// Returns a copy of this image as a `LumaA` image.
377    #[must_use]
378    pub fn to_luma_alpha32f(&self) -> ImageBuffer<LumaA<f32>, Vec<f32>> {
379        dynamic_map!(self, ref p, p.cast_in_color_space())
380    }
381
382    /// Consume the image and returns a RGB image.
383    ///
384    /// If the image was already the correct format, it is returned as is.
385    /// Otherwise, a copy is created.
386    #[must_use]
387    pub fn into_rgb8(self) -> RgbImage {
388        match self {
389            DynamicImage::ImageRgb8(x) => x,
390            x => x.to_rgb8(),
391        }
392    }
393
394    /// Consume the image and returns a RGB image.
395    ///
396    /// If the image was already the correct format, it is returned as is.
397    /// Otherwise, a copy is created.
398    #[must_use]
399    pub fn into_rgb16(self) -> Rgb16Image {
400        match self {
401            DynamicImage::ImageRgb16(x) => x,
402            x => x.to_rgb16(),
403        }
404    }
405
406    /// Consume the image and returns a RGB image.
407    ///
408    /// If the image was already the correct format, it is returned as is.
409    /// Otherwise, a copy is created.
410    #[must_use]
411    pub fn into_rgb32f(self) -> Rgb32FImage {
412        match self {
413            DynamicImage::ImageRgb32F(x) => x,
414            x => x.to_rgb32f(),
415        }
416    }
417
418    /// Consume the image and returns a RGBA image.
419    ///
420    /// If the image was already the correct format, it is returned as is.
421    /// Otherwise, a copy is created.
422    #[must_use]
423    pub fn into_rgba8(self) -> RgbaImage {
424        match self {
425            DynamicImage::ImageRgba8(x) => x,
426            x => x.to_rgba8(),
427        }
428    }
429
430    /// Consume the image and returns a RGBA image.
431    ///
432    /// If the image was already the correct format, it is returned as is.
433    /// Otherwise, a copy is created.
434    #[must_use]
435    pub fn into_rgba16(self) -> Rgba16Image {
436        match self {
437            DynamicImage::ImageRgba16(x) => x,
438            x => x.to_rgba16(),
439        }
440    }
441
442    /// Consume the image and returns a RGBA image.
443    ///
444    /// If the image was already the correct format, it is returned as is.
445    /// Otherwise, a copy is created.
446    #[must_use]
447    pub fn into_rgba32f(self) -> Rgba32FImage {
448        match self {
449            DynamicImage::ImageRgba32F(x) => x,
450            x => x.to_rgba32f(),
451        }
452    }
453
454    /// Consume the image and returns a Luma image.
455    ///
456    /// If the image was already the correct format, it is returned as is.
457    /// Otherwise, a copy is created.
458    #[must_use]
459    pub fn into_luma8(self) -> GrayImage {
460        match self {
461            DynamicImage::ImageLuma8(x) => x,
462            x => x.to_luma8(),
463        }
464    }
465
466    /// Consume the image and returns a Luma image.
467    ///
468    /// If the image was already the correct format, it is returned as is.
469    /// Otherwise, a copy is created.
470    #[must_use]
471    pub fn into_luma16(self) -> Gray16Image {
472        match self {
473            DynamicImage::ImageLuma16(x) => x,
474            x => x.to_luma16(),
475        }
476    }
477
478    /// Consume the image and returns a `LumaA` image.
479    ///
480    /// If the image was already the correct format, it is returned as is.
481    /// Otherwise, a copy is created.
482    #[must_use]
483    pub fn into_luma_alpha8(self) -> GrayAlphaImage {
484        match self {
485            DynamicImage::ImageLumaA8(x) => x,
486            x => x.to_luma_alpha8(),
487        }
488    }
489
490    /// Consume the image and returns a `LumaA` image.
491    ///
492    /// If the image was already the correct format, it is returned as is.
493    /// Otherwise, a copy is created.
494    #[must_use]
495    pub fn into_luma_alpha16(self) -> GrayAlpha16Image {
496        match self {
497            DynamicImage::ImageLumaA16(x) => x,
498            x => x.to_luma_alpha16(),
499        }
500    }
501
502    /// Return a cut-out of this image delimited by the bounding rectangle.
503    ///
504    /// Note: this method does *not* modify the object,
505    /// and its signature will be replaced with `crop_imm()`'s in the 0.24 release
506    #[must_use]
507    pub fn crop(&mut self, x: u32, y: u32, width: u32, height: u32) -> DynamicImage {
508        dynamic_map!(*self, ref mut p => imageops::crop(p, x, y, width, height).to_image())
509    }
510
511    /// Return a cut-out of this image delimited by the bounding rectangle.
512    #[must_use]
513    pub fn crop_imm(&self, x: u32, y: u32, width: u32, height: u32) -> DynamicImage {
514        dynamic_map!(*self, ref p => imageops::crop_imm(p, x, y, width, height).to_image())
515    }
516
517    /// Return a reference to an 8bit RGB image
518    #[must_use]
519    pub fn as_rgb8(&self) -> Option<&RgbImage> {
520        match *self {
521            DynamicImage::ImageRgb8(ref p) => Some(p),
522            _ => None,
523        }
524    }
525
526    /// Return a mutable reference to an 8bit RGB image
527    pub fn as_mut_rgb8(&mut self) -> Option<&mut RgbImage> {
528        match *self {
529            DynamicImage::ImageRgb8(ref mut p) => Some(p),
530            _ => None,
531        }
532    }
533
534    /// Return a reference to an 8bit RGBA image
535    #[must_use]
536    pub fn as_rgba8(&self) -> Option<&RgbaImage> {
537        match *self {
538            DynamicImage::ImageRgba8(ref p) => Some(p),
539            _ => None,
540        }
541    }
542
543    /// Return a mutable reference to an 8bit RGBA image
544    pub fn as_mut_rgba8(&mut self) -> Option<&mut RgbaImage> {
545        match *self {
546            DynamicImage::ImageRgba8(ref mut p) => Some(p),
547            _ => None,
548        }
549    }
550
551    /// Return a reference to an 8bit Grayscale image
552    #[must_use]
553    pub fn as_luma8(&self) -> Option<&GrayImage> {
554        match *self {
555            DynamicImage::ImageLuma8(ref p) => Some(p),
556            _ => None,
557        }
558    }
559
560    /// Return a mutable reference to an 8bit Grayscale image
561    pub fn as_mut_luma8(&mut self) -> Option<&mut GrayImage> {
562        match *self {
563            DynamicImage::ImageLuma8(ref mut p) => Some(p),
564            _ => None,
565        }
566    }
567
568    /// Return a reference to an 8bit Grayscale image with an alpha channel
569    #[must_use]
570    pub fn as_luma_alpha8(&self) -> Option<&GrayAlphaImage> {
571        match *self {
572            DynamicImage::ImageLumaA8(ref p) => Some(p),
573            _ => None,
574        }
575    }
576
577    /// Return a mutable reference to an 8bit Grayscale image with an alpha channel
578    pub fn as_mut_luma_alpha8(&mut self) -> Option<&mut GrayAlphaImage> {
579        match *self {
580            DynamicImage::ImageLumaA8(ref mut p) => Some(p),
581            _ => None,
582        }
583    }
584
585    /// Return a reference to an 16bit RGB image
586    #[must_use]
587    pub fn as_rgb16(&self) -> Option<&Rgb16Image> {
588        match *self {
589            DynamicImage::ImageRgb16(ref p) => Some(p),
590            _ => None,
591        }
592    }
593
594    /// Return a mutable reference to an 16bit RGB image
595    pub fn as_mut_rgb16(&mut self) -> Option<&mut Rgb16Image> {
596        match *self {
597            DynamicImage::ImageRgb16(ref mut p) => Some(p),
598            _ => None,
599        }
600    }
601
602    /// Return a reference to an 16bit RGBA image
603    #[must_use]
604    pub fn as_rgba16(&self) -> Option<&Rgba16Image> {
605        match *self {
606            DynamicImage::ImageRgba16(ref p) => Some(p),
607            _ => None,
608        }
609    }
610
611    /// Return a mutable reference to an 16bit RGBA image
612    pub fn as_mut_rgba16(&mut self) -> Option<&mut Rgba16Image> {
613        match *self {
614            DynamicImage::ImageRgba16(ref mut p) => Some(p),
615            _ => None,
616        }
617    }
618
619    /// Return a reference to an 32bit RGB image
620    #[must_use]
621    pub fn as_rgb32f(&self) -> Option<&Rgb32FImage> {
622        match *self {
623            DynamicImage::ImageRgb32F(ref p) => Some(p),
624            _ => None,
625        }
626    }
627
628    /// Return a mutable reference to an 32bit RGB image
629    pub fn as_mut_rgb32f(&mut self) -> Option<&mut Rgb32FImage> {
630        match *self {
631            DynamicImage::ImageRgb32F(ref mut p) => Some(p),
632            _ => None,
633        }
634    }
635
636    /// Return a reference to an 32bit RGBA image
637    #[must_use]
638    pub fn as_rgba32f(&self) -> Option<&Rgba32FImage> {
639        match *self {
640            DynamicImage::ImageRgba32F(ref p) => Some(p),
641            _ => None,
642        }
643    }
644
645    /// Return a mutable reference to an 32bit RGBA image
646    pub fn as_mut_rgba32f(&mut self) -> Option<&mut Rgba32FImage> {
647        match *self {
648            DynamicImage::ImageRgba32F(ref mut p) => Some(p),
649            _ => None,
650        }
651    }
652
653    /// Return a reference to an 16bit Grayscale image
654    #[must_use]
655    pub fn as_luma16(&self) -> Option<&Gray16Image> {
656        match *self {
657            DynamicImage::ImageLuma16(ref p) => Some(p),
658            _ => None,
659        }
660    }
661
662    /// Return a mutable reference to an 16bit Grayscale image
663    pub fn as_mut_luma16(&mut self) -> Option<&mut Gray16Image> {
664        match *self {
665            DynamicImage::ImageLuma16(ref mut p) => Some(p),
666            _ => None,
667        }
668    }
669
670    /// Return a reference to an 16bit Grayscale image with an alpha channel
671    #[must_use]
672    pub fn as_luma_alpha16(&self) -> Option<&GrayAlpha16Image> {
673        match *self {
674            DynamicImage::ImageLumaA16(ref p) => Some(p),
675            _ => None,
676        }
677    }
678
679    /// Return a mutable reference to an 16bit Grayscale image with an alpha channel
680    pub fn as_mut_luma_alpha16(&mut self) -> Option<&mut GrayAlpha16Image> {
681        match *self {
682            DynamicImage::ImageLumaA16(ref mut p) => Some(p),
683            _ => None,
684        }
685    }
686
687    /// Return a view on the raw sample buffer for 8 bit per channel images.
688    #[must_use]
689    pub fn as_flat_samples_u8(&self) -> Option<FlatSamples<&[u8]>> {
690        match *self {
691            DynamicImage::ImageLuma8(ref p) => Some(p.as_flat_samples()),
692            DynamicImage::ImageLumaA8(ref p) => Some(p.as_flat_samples()),
693            DynamicImage::ImageRgb8(ref p) => Some(p.as_flat_samples()),
694            DynamicImage::ImageRgba8(ref p) => Some(p.as_flat_samples()),
695            _ => None,
696        }
697    }
698
699    /// Return a view on the raw sample buffer for 16 bit per channel images.
700    #[must_use]
701    pub fn as_flat_samples_u16(&self) -> Option<FlatSamples<&[u16]>> {
702        match *self {
703            DynamicImage::ImageLuma16(ref p) => Some(p.as_flat_samples()),
704            DynamicImage::ImageLumaA16(ref p) => Some(p.as_flat_samples()),
705            DynamicImage::ImageRgb16(ref p) => Some(p.as_flat_samples()),
706            DynamicImage::ImageRgba16(ref p) => Some(p.as_flat_samples()),
707            _ => None,
708        }
709    }
710
711    /// Return a view on the raw sample buffer for 32bit per channel images.
712    #[must_use]
713    pub fn as_flat_samples_f32(&self) -> Option<FlatSamples<&[f32]>> {
714        match *self {
715            DynamicImage::ImageRgb32F(ref p) => Some(p.as_flat_samples()),
716            DynamicImage::ImageRgba32F(ref p) => Some(p.as_flat_samples()),
717            _ => None,
718        }
719    }
720
721    /// Return this image's pixels as a native endian byte slice.
722    #[must_use]
723    pub fn as_bytes(&self) -> &[u8] {
724        // we can do this because every variant contains an `ImageBuffer<_, Vec<_>>`
725        dynamic_map!(
726            *self,
727            ref image_buffer,
728            bytemuck::cast_slice(image_buffer.as_raw())
729        )
730    }
731
732    /// Return this image's pixels as a byte vector. If the `ImageBuffer`
733    /// container is `Vec<u8>`, this operation is free. Otherwise, a copy
734    /// is returned.
735    #[must_use]
736    pub fn into_bytes(self) -> Vec<u8> {
737        // we can do this because every variant contains an `ImageBuffer<_, Vec<_>>`
738        dynamic_map!(self, image_buffer, {
739            match bytemuck::allocation::try_cast_vec(image_buffer.into_raw()) {
740                Ok(vec) => vec,
741                Err((_, vec)) => {
742                    // Fallback: vector requires an exact alignment and size match
743                    // Reuse of the allocation as done in the Ok branch only works if the
744                    // underlying container is exactly Vec<u8> (or compatible but that's the only
745                    // alternative at the time of writing).
746                    // In all other cases we must allocate a new vector with the 'same' contents.
747                    bytemuck::cast_slice(&vec).to_owned()
748                }
749            }
750        })
751    }
752
753    /// Return this image's color type.
754    #[must_use]
755    pub fn color(&self) -> color::ColorType {
756        match *self {
757            DynamicImage::ImageLuma8(_) => color::ColorType::L8,
758            DynamicImage::ImageLumaA8(_) => color::ColorType::La8,
759            DynamicImage::ImageRgb8(_) => color::ColorType::Rgb8,
760            DynamicImage::ImageRgba8(_) => color::ColorType::Rgba8,
761            DynamicImage::ImageLuma16(_) => color::ColorType::L16,
762            DynamicImage::ImageLumaA16(_) => color::ColorType::La16,
763            DynamicImage::ImageRgb16(_) => color::ColorType::Rgb16,
764            DynamicImage::ImageRgba16(_) => color::ColorType::Rgba16,
765            DynamicImage::ImageRgb32F(_) => color::ColorType::Rgb32F,
766            DynamicImage::ImageRgba32F(_) => color::ColorType::Rgba32F,
767        }
768    }
769
770    /// Returns the width of the underlying image
771    #[must_use]
772    pub fn width(&self) -> u32 {
773        dynamic_map!(*self, ref p, { p.width() })
774    }
775
776    /// Returns the height of the underlying image
777    #[must_use]
778    pub fn height(&self) -> u32 {
779        dynamic_map!(*self, ref p, { p.height() })
780    }
781
782    /// Define the color space for the image.
783    ///
784    /// The color data is unchanged. Reinterprets the existing red, blue, green channels as points
785    /// in the new set of primary colors, changing the apparent shade of pixels.
786    ///
787    /// Note that the primaries also define a reference whitepoint When this buffer contains Luma
788    /// data, the luminance channel is interpreted as the `Y` channel of a related `YCbCr` color
789    /// space as if by a non-constant chromaticity derived matrix. That is, coefficients are *not*
790    /// applied in the linear RGB space but use encoded channel values. (In a color space with the
791    /// linear transfer function there is no difference).
792    pub fn set_rgb_primaries(&mut self, color: CicpColorPrimaries) {
793        dynamic_map!(self, ref mut p, p.set_rgb_primaries(color));
794    }
795
796    /// Define the transfer function for the image.
797    ///
798    /// The color data is unchanged. Reinterprets all (non-alpha) components in the image,
799    /// potentially changing the apparent shade of pixels. Individual components are always
800    /// interpreted as encoded numbers. To denote numbers in a linear RGB space, use
801    /// [`CicpTransferCharacteristics::Linear`].
802    pub fn set_transfer_function(&mut self, tf: CicpTransferCharacteristics) {
803        dynamic_map!(self, ref mut p, p.set_transfer_function(tf));
804    }
805
806    /// Get the Cicp encoding of this buffer's color data.
807    pub fn color_space(&self) -> Cicp {
808        dynamic_map!(self, ref p, p.color_space())
809    }
810
811    /// Set primaries and transfer characteristics from a Cicp color space.
812    ///
813    /// Returns an error if `cicp` uses features that are not support with an RGB color space, e.g.
814    /// a matrix or narrow range (studio encoding) channels.
815    pub fn set_color_space(&mut self, cicp: Cicp) -> ImageResult<()> {
816        dynamic_map!(self, ref mut p, p.set_color_space(cicp))
817    }
818
819    /// Whether the image contains an alpha channel
820    ///
821    /// This is a convenience wrapper around `self.color().has_alpha()`.
822    /// For inspecting other properties of the color type you should call
823    /// [DynamicImage::color] and use the methods on the returned [ColorType](color::ColorType).
824    ///
825    /// This only checks that the image's pixel type can express transparency,
826    /// not whether the image actually has any transparent areas.
827    #[must_use]
828    pub fn has_alpha(&self) -> bool {
829        self.color().has_alpha()
830    }
831
832    /// Return a grayscale version of this image.
833    /// Returns `Luma` images in most cases. However, for `f32` images,
834    /// this will return a grayscale `Rgb/Rgba` image instead.
835    #[must_use]
836    pub fn grayscale(&self) -> DynamicImage {
837        match *self {
838            DynamicImage::ImageLuma8(ref p) => DynamicImage::ImageLuma8(p.clone()),
839            DynamicImage::ImageLumaA8(ref p) => {
840                DynamicImage::ImageLumaA8(imageops::grayscale_alpha(p))
841            }
842            DynamicImage::ImageRgb8(ref p) => DynamicImage::ImageLuma8(imageops::grayscale(p)),
843            DynamicImage::ImageRgba8(ref p) => {
844                DynamicImage::ImageLumaA8(imageops::grayscale_alpha(p))
845            }
846            DynamicImage::ImageLuma16(ref p) => DynamicImage::ImageLuma16(p.clone()),
847            DynamicImage::ImageLumaA16(ref p) => {
848                DynamicImage::ImageLumaA16(imageops::grayscale_alpha(p))
849            }
850            DynamicImage::ImageRgb16(ref p) => DynamicImage::ImageLuma16(imageops::grayscale(p)),
851            DynamicImage::ImageRgba16(ref p) => {
852                DynamicImage::ImageLumaA16(imageops::grayscale_alpha(p))
853            }
854            DynamicImage::ImageRgb32F(ref p) => {
855                DynamicImage::ImageRgb32F(imageops::grayscale_with_type(p))
856            }
857            DynamicImage::ImageRgba32F(ref p) => {
858                DynamicImage::ImageRgba32F(imageops::grayscale_with_type_alpha(p))
859            }
860        }
861    }
862
863    /// Invert the colors of this image.
864    /// This method operates inplace.
865    ///
866    /// This method operates on pixel channel values directly without taking into account color
867    /// space data.
868    pub fn invert(&mut self) {
869        dynamic_map!(*self, ref mut p, imageops::invert(p));
870    }
871
872    /// Resize this image using the specified filter algorithm.
873    /// Returns a new image. The image's aspect ratio is preserved.
874    /// The image is scaled to the maximum possible size that fits
875    /// within the bounds specified by `nwidth` and `nheight`.
876    ///
877    /// This method operates on pixel channel values directly without taking into account color
878    /// space data.
879    #[must_use]
880    pub fn resize(&self, nwidth: u32, nheight: u32, filter: imageops::FilterType) -> DynamicImage {
881        if (nwidth, nheight) == self.dimensions() {
882            return self.clone();
883        }
884        let (width2, height2) =
885            resize_dimensions(self.width(), self.height(), nwidth, nheight, false);
886
887        self.resize_exact(width2, height2, filter)
888    }
889
890    /// Resize this image using the specified filter algorithm.
891    /// Returns a new image. Does not preserve aspect ratio.
892    /// `nwidth` and `nheight` are the new image's dimensions
893    ///
894    /// This method operates on pixel channel values directly without taking into account color
895    /// space data.
896    #[must_use]
897    pub fn resize_exact(
898        &self,
899        nwidth: u32,
900        nheight: u32,
901        filter: imageops::FilterType,
902    ) -> DynamicImage {
903        dynamic_map!(*self, ref p => imageops::resize(p, nwidth, nheight, filter))
904    }
905
906    /// Scale this image down to fit within a specific size.
907    /// Returns a new image. The image's aspect ratio is preserved.
908    /// The image is scaled to the maximum possible size that fits
909    /// within the bounds specified by `nwidth` and `nheight`.
910    ///
911    /// This method uses a fast integer algorithm where each source
912    /// pixel contributes to exactly one target pixel.
913    /// May give aliasing artifacts if new size is close to old size.
914    ///
915    /// This method operates on pixel channel values directly without taking into account color
916    /// space data.
917    #[must_use]
918    pub fn thumbnail(&self, nwidth: u32, nheight: u32) -> DynamicImage {
919        let (width2, height2) =
920            resize_dimensions(self.width(), self.height(), nwidth, nheight, false);
921        self.thumbnail_exact(width2, height2)
922    }
923
924    /// Scale this image down to a specific size.
925    /// Returns a new image. Does not preserve aspect ratio.
926    /// `nwidth` and `nheight` are the new image's dimensions.
927    /// This method uses a fast integer algorithm where each source
928    /// pixel contributes to exactly one target pixel.
929    /// May give aliasing artifacts if new size is close to old size.
930    ///
931    /// This method operates on pixel channel values directly without taking into account color
932    /// space data.
933    #[must_use]
934    pub fn thumbnail_exact(&self, nwidth: u32, nheight: u32) -> DynamicImage {
935        dynamic_map!(*self, ref p => imageops::thumbnail(p, nwidth, nheight))
936    }
937
938    /// Resize this image using the specified filter algorithm.
939    /// Returns a new image. The image's aspect ratio is preserved.
940    /// The image is scaled to the maximum possible size that fits
941    /// within the larger (relative to aspect ratio) of the bounds
942    /// specified by `nwidth` and `nheight`, then cropped to
943    /// fit within the other bound.
944    ///
945    /// This method operates on pixel channel values directly without taking into account color
946    /// space data.
947    #[must_use]
948    pub fn resize_to_fill(
949        &self,
950        nwidth: u32,
951        nheight: u32,
952        filter: imageops::FilterType,
953    ) -> DynamicImage {
954        let (width2, height2) =
955            resize_dimensions(self.width(), self.height(), nwidth, nheight, true);
956
957        let mut intermediate = self.resize_exact(width2, height2, filter);
958        let (iwidth, iheight) = intermediate.dimensions();
959        let ratio = u64::from(iwidth) * u64::from(nheight);
960        let nratio = u64::from(nwidth) * u64::from(iheight);
961
962        if nratio > ratio {
963            intermediate.crop(0, (iheight - nheight) / 2, nwidth, nheight)
964        } else {
965            intermediate.crop((iwidth - nwidth) / 2, 0, nwidth, nheight)
966        }
967    }
968
969    /// Performs a Gaussian blur on this image.
970    ///
971    /// # Arguments
972    ///
973    /// * `sigma` - gaussian bell flattening level.
974    ///
975    /// Use [DynamicImage::fast_blur()] for a faster but less
976    /// accurate version.
977    ///
978    /// This method assumes alpha pre-multiplication for images that contain non-constant alpha.
979    /// This method typically assumes that the input is scene-linear light.
980    /// If it is not, color distortion may occur.
981    ///
982    /// This method operates on pixel channel values directly without taking into account color
983    /// space data.
984    #[must_use]
985    pub fn blur(&self, sigma: f32) -> DynamicImage {
986        gaussian_blur_dyn_image(
987            self,
988            GaussianBlurParameters::new_from_sigma(if sigma == 0.0 { 0.8 } else { sigma }),
989        )
990    }
991
992    /// Performs a Gaussian blur on this image.
993    ///
994    /// # Arguments
995    ///
996    /// * `parameters` - see [GaussianBlurParameters] for more info
997    ///
998    /// This method assumes alpha pre-multiplication for images that contain non-constant alpha.
999    /// This method typically assumes that the input is scene-linear light.
1000    /// If it is not, color distortion may occur.
1001    ///
1002    /// This method operates on pixel channel values directly without taking into account color
1003    /// space data.
1004    #[must_use]
1005    pub fn blur_advanced(&self, parameters: GaussianBlurParameters) -> DynamicImage {
1006        gaussian_blur_dyn_image(self, parameters)
1007    }
1008
1009    /// Performs a fast blur on this image.
1010    ///
1011    /// # Arguments
1012    ///
1013    /// * `sigma` - value controls image flattening level.
1014    ///
1015    /// This method typically assumes that the input is scene-linear light.
1016    /// If it is not, color distortion may occur.
1017    ///
1018    /// This method operates on pixel channel values directly without taking into account color
1019    /// space data.
1020    #[must_use]
1021    pub fn fast_blur(&self, sigma: f32) -> DynamicImage {
1022        dynamic_map!(*self, ref p => imageops::fast_blur(p, sigma))
1023    }
1024
1025    /// Performs an unsharpen mask on this image.
1026    ///
1027    /// # Arguments
1028    ///
1029    /// * `sigma` - value controls image flattening level.
1030    /// * `threshold` - is a control of how much to sharpen.
1031    ///
1032    /// This method typically assumes that the input is scene-linear light. If it is not, color
1033    /// distortion may occur. It operates on pixel channel values directly without taking into
1034    /// account color space data.
1035    ///
1036    /// See [Digital unsharp masking](https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking)
1037    /// for more information
1038    #[must_use]
1039    pub fn unsharpen(&self, sigma: f32, threshold: i32) -> DynamicImage {
1040        dynamic_map!(*self, ref p => imageops::unsharpen(p, sigma, threshold))
1041    }
1042
1043    /// Filters this image with the specified 3x3 kernel.
1044    ///
1045    /// # Arguments
1046    ///
1047    /// * `kernel` - slice contains filter. Only slice len is 9 length is accepted.
1048    ///
1049    /// This method typically assumes that the input is scene-linear light. It operates on pixel
1050    /// channel values directly without taking into account color space data. If it is not, color
1051    /// distortion may occur.
1052    #[must_use]
1053    pub fn filter3x3(&self, kernel: &[f32]) -> DynamicImage {
1054        assert_eq!(9, kernel.len(), "filter must be 3 x 3");
1055
1056        dynamic_map!(*self, ref p => imageops::filter3x3(p, kernel))
1057    }
1058
1059    /// Adjust the contrast of this image.
1060    /// `contrast` is the amount to adjust the contrast by.
1061    /// Negative values decrease the contrast and positive values increase the contrast.
1062    ///
1063    /// This method operates on pixel channel values directly without taking into account color
1064    /// space data.
1065    #[must_use]
1066    pub fn adjust_contrast(&self, c: f32) -> DynamicImage {
1067        dynamic_map!(*self, ref p => imageops::contrast(p, c))
1068    }
1069
1070    /// Brighten the pixels of this image.
1071    /// `value` is the amount to brighten each pixel by.
1072    /// Negative values decrease the brightness and positive values increase it.
1073    ///
1074    /// This method operates on pixel channel values directly without taking into account color
1075    /// space data.
1076    #[must_use]
1077    pub fn brighten(&self, value: i32) -> DynamicImage {
1078        dynamic_map!(*self, ref p => imageops::brighten(p, value))
1079    }
1080
1081    /// Hue rotate the supplied image.
1082    /// `value` is the degrees to rotate each pixel by.
1083    /// 0 and 360 do nothing, the rest rotates by the given degree value.
1084    /// just like the css webkit filter hue-rotate(180)
1085    ///
1086    /// This method operates on pixel channel values directly without taking into account color
1087    /// space data. The HSV color space is dependent on the current color space primaries.
1088    #[must_use]
1089    pub fn huerotate(&self, value: i32) -> DynamicImage {
1090        dynamic_map!(*self, ref p => imageops::huerotate(p, value))
1091    }
1092
1093    /// Flip this image vertically
1094    ///
1095    /// Use [`apply_orientation`](Self::apply_orientation) if you want to flip the image in-place instead.
1096    #[must_use]
1097    pub fn flipv(&self) -> DynamicImage {
1098        dynamic_map!(*self, ref p => imageops::flip_vertical(p))
1099    }
1100
1101    /// Flip this image vertically in place
1102    fn flipv_in_place(&mut self) {
1103        dynamic_map!(*self, ref mut p, imageops::flip_vertical_in_place(p))
1104    }
1105
1106    /// Flip this image horizontally
1107    ///
1108    /// Use [`apply_orientation`](Self::apply_orientation) if you want to flip the image in-place.
1109    #[must_use]
1110    pub fn fliph(&self) -> DynamicImage {
1111        dynamic_map!(*self, ref p => imageops::flip_horizontal(p))
1112    }
1113
1114    /// Flip this image horizontally in place
1115    fn fliph_in_place(&mut self) {
1116        dynamic_map!(*self, ref mut p, imageops::flip_horizontal_in_place(p))
1117    }
1118
1119    /// Rotate this image 90 degrees clockwise.
1120    #[must_use]
1121    pub fn rotate90(&self) -> DynamicImage {
1122        dynamic_map!(*self, ref p => imageops::rotate90(p))
1123    }
1124
1125    /// Rotate this image 180 degrees.
1126    ///
1127    /// Use [`apply_orientation`](Self::apply_orientation) if you want to rotate the image in-place.
1128    #[must_use]
1129    pub fn rotate180(&self) -> DynamicImage {
1130        dynamic_map!(*self, ref p => imageops::rotate180(p))
1131    }
1132
1133    /// Rotate this image 180 degrees in place.
1134    fn rotate180_in_place(&mut self) {
1135        dynamic_map!(*self, ref mut p, imageops::rotate180_in_place(p))
1136    }
1137
1138    /// Rotate this image 270 degrees clockwise.
1139    #[must_use]
1140    pub fn rotate270(&self) -> DynamicImage {
1141        dynamic_map!(*self, ref p => imageops::rotate270(p))
1142    }
1143
1144    /// Rotates and/or flips the image as indicated by [Orientation].
1145    ///
1146    /// This can be used to apply Exif orientation to an image,
1147    /// e.g. to correctly display a photo taken by a smartphone camera:
1148    ///
1149    /// ```
1150    /// # fn only_check_if_this_compiles() -> Result<(), Box<dyn core::error::Error>> {
1151    /// use ai_image::{DynamicImage, ImageReader, ImageDecoder};
1152    ///
1153    /// let mut decoder = ImageReader::open("file.jpg")?.into_decoder()?;
1154    /// let orientation = decoder.orientation()?;
1155    /// let mut image = DynamicImage::from_decoder(decoder)?;
1156    /// image.apply_orientation(orientation);
1157    /// # Ok(())
1158    /// # }
1159    /// ```
1160    ///
1161    /// Note that for some orientations cannot be efficiently applied in-place.
1162    /// In that case this function will make a copy of the image internally.
1163    ///
1164    /// If this matters to you, please see the documentation on the variants of [Orientation]
1165    /// to learn which orientations can and cannot be applied without copying.
1166    pub fn apply_orientation(&mut self, orientation: Orientation) {
1167        let image = self;
1168        match orientation {
1169            Orientation::NoTransforms => (),
1170            Orientation::Rotate90 => *image = image.rotate90(),
1171            Orientation::Rotate180 => image.rotate180_in_place(),
1172            Orientation::Rotate270 => *image = image.rotate270(),
1173            Orientation::FlipHorizontal => image.fliph_in_place(),
1174            Orientation::FlipVertical => image.flipv_in_place(),
1175            Orientation::Rotate90FlipH => {
1176                let mut new_image = image.rotate90();
1177                new_image.fliph_in_place();
1178                *image = new_image;
1179            }
1180            Orientation::Rotate270FlipH => {
1181                let mut new_image = image.rotate270();
1182                new_image.fliph_in_place();
1183                *image = new_image;
1184            }
1185        }
1186    }
1187
1188    /// Copy pixel data from one buffer to another.
1189    ///
1190    /// On success, this dynamic image contains color data equivalent to the sources color data.
1191    /// Neither the color space nor the sample type of `self` is changed, the data representation
1192    /// is transformed and copied into the current buffer.
1193    ///
1194    /// Returns `Ok` if:
1195    /// - Both images to have the same dimensions, otherwise returns a [`ImageError::Parameter`].
1196    /// - The primaries and transfer functions of both image's color spaces must be supported,
1197    ///   otherwise returns a [`ImageError::Unsupported`].
1198    ///
1199    /// See also [`Self::apply_color_space`] and [`Self::convert_color_space`] to modify an image
1200    /// directly.
1201    ///
1202    /// ## Accuracy
1203    ///
1204    /// All color values are subject to change to their _intended_ values. Please do not rely on
1205    /// them further than your own colorimetric understanding shows them correct. For instance,
1206    /// conversion of RGB to their corresponding Luma values needs to be modified in future
1207    /// versions of this library. Expect colors to be too bright or too dark until further notice.
1208    pub fn copy_from_color_space(
1209        &mut self,
1210        other: &DynamicImage,
1211        mut options: ConvertColorOptions,
1212    ) -> ImageResult<()> {
1213        // Try to no-op this transformation, we may be lucky..
1214        if self.color_space() == other.color_space() {
1215            // Nothing to transform, just rescale samples and type cast.
1216            dynamic_map!(
1217                self,
1218                ref mut p,
1219                *p = dynamic_map!(other, ref o, o.cast_in_color_space())
1220            );
1221
1222            return Ok(());
1223        }
1224
1225        // Do a transformation from existing buffer to existing buffer, only for color types that
1226        // are currently supported. Other color types must use the fallback below. If we expand the
1227        // range of supported color types we must consider how to write this more neatly.
1228        match (&mut *self, other) {
1229            // u8 sample types
1230            (DynamicImage::ImageRgb8(img), DynamicImage::ImageRgb8(other)) => {
1231                return img.copy_from_color_space(other, options);
1232            }
1233            (DynamicImage::ImageRgb8(img), DynamicImage::ImageRgba8(other)) => {
1234                return img.copy_from_color_space(other, options);
1235            }
1236            (DynamicImage::ImageRgba8(img), DynamicImage::ImageRgb8(other)) => {
1237                return img.copy_from_color_space(other, options);
1238            }
1239            (DynamicImage::ImageRgba8(img), DynamicImage::ImageRgba8(other)) => {
1240                return img.copy_from_color_space(other, options);
1241            }
1242            // u16 sample types
1243            (DynamicImage::ImageRgb16(img), DynamicImage::ImageRgb16(other)) => {
1244                return img.copy_from_color_space(other, options);
1245            }
1246            (DynamicImage::ImageRgb16(img), DynamicImage::ImageRgba16(other)) => {
1247                return img.copy_from_color_space(other, options);
1248            }
1249            (DynamicImage::ImageRgba16(img), DynamicImage::ImageRgb16(other)) => {
1250                return img.copy_from_color_space(other, options);
1251            }
1252            (DynamicImage::ImageRgba16(img), DynamicImage::ImageRgba16(other)) => {
1253                return img.copy_from_color_space(other, options);
1254            }
1255            // 32F sample types.
1256            (DynamicImage::ImageRgb32F(img), DynamicImage::ImageRgb32F(other)) => {
1257                return img.copy_from_color_space(other, options);
1258            }
1259            (DynamicImage::ImageRgb32F(img), DynamicImage::ImageRgba32F(other)) => {
1260                return img.copy_from_color_space(other, options);
1261            }
1262            (DynamicImage::ImageRgba32F(img), DynamicImage::ImageRgb32F(other)) => {
1263                return img.copy_from_color_space(other, options);
1264            }
1265            (DynamicImage::ImageRgba32F(img), DynamicImage::ImageRgba32F(other)) => {
1266                return img.copy_from_color_space(other, options);
1267            }
1268            _ => {}
1269        };
1270
1271        // If we reach here we have a mismatch of sample types. Our conversion supports only input
1272        // and output of the same sample type. As a simplification we will do the conversion only
1273        // in `f32` samples. Thus we first convert the source to `f32` samples taking care it does
1274        // not involve (lossy) color conversion (into with luma -> rgb for instance). Note: we do
1275        // not have Luma<f32> as a type.
1276        let cicp = options.as_transform(other.color_space(), self.color_space())?;
1277        cicp.transform_dynamic(self, other);
1278
1279        Ok(())
1280    }
1281
1282    /// Change the color space, modifying pixel values to refer to the same colors.
1283    ///
1284    /// On success, this dynamic image contains color data equivalent to its previous color data.
1285    /// The sample type of `self` is not changed, the data representation is transformed within the
1286    /// current buffer.
1287    ///
1288    /// Returns `Ok` if:
1289    /// - The primaries and transfer functions of both image's color spaces must be supported,
1290    ///   otherwise returns a [`ImageError::Unsupported`].
1291    /// - The target `Cicp` must have full range and an `Identity` matrix. (This library's
1292    ///   [`Luma`][`crate::Luma`] refers implicity to a chromaticity derived non-constant luminance
1293    ///   color).
1294    ///
1295    /// See also [`Self::copy_from_color_space`].
1296    pub fn apply_color_space(
1297        &mut self,
1298        cicp: Cicp,
1299        options: ConvertColorOptions,
1300    ) -> ImageResult<()> {
1301        // If the color space is already set, we can just return.
1302        if self.color_space() == cicp {
1303            return Ok(());
1304        }
1305
1306        // We could conceivably do this in-place faster but to handle the Luma conversion as we
1307        // want this requires the full machinery as `CicpTransform::transform_dynamic` which is
1308        // quite the replication. Let's just see if it is fast enough. Feel free to PR something if
1309        // it is easy enough to review.
1310        let mut target = self.clone();
1311        target.set_color_space(cicp)?;
1312        target.copy_from_color_space(self, options)?;
1313
1314        *self = target;
1315        Ok(())
1316    }
1317
1318    /// Change the color space and pixel type of this image.
1319    ///
1320    /// On success, this dynamic image contains color data equivalent to its previous color data
1321    /// with another type of pixels.
1322    ///
1323    /// Returns `Ok` if:
1324    /// - The primaries and transfer functions of both image's color spaces must be supported,
1325    ///   otherwise returns a [`ImageError::Unsupported`].
1326    /// - The target `Cicp` must have full range and an `Identity` matrix. (This library's
1327    ///   [`Luma`][`crate::Luma`] refers implicity to a chromaticity derived non-constant luminance
1328    ///   color).
1329    ///
1330    /// See also [`Self::copy_from_color_space`].
1331    pub fn convert_color_space(
1332        &mut self,
1333        cicp: Cicp,
1334        options: ConvertColorOptions,
1335        color: color::ColorType,
1336    ) -> ImageResult<()> {
1337        if self.color() == color {
1338            return self.apply_color_space(cicp, options);
1339        }
1340
1341        // Forward compatibility: make sure we do not drop any details here.
1342        let rgb = cicp.try_into_rgb()?;
1343        let mut target = DynamicImage::new(self.width(), self.height(), color);
1344        dynamic_map!(target, ref mut p, p.set_rgb_color_space(rgb));
1345        target.copy_from_color_space(self, options)?;
1346
1347        *self = target;
1348        Ok(())
1349    }
1350
1351    fn write_with_encoder_impl<'a>(
1352        &self,
1353        encoder: Box<dyn ImageEncoderBoxed + 'a>,
1354    ) -> ImageResult<()> {
1355        let converted = encoder.make_compatible_img(crate::io::encoder::MethodSealedToImage, self);
1356        let img = converted.as_ref().unwrap_or(self);
1357
1358        encoder.write_image(
1359            img.as_bytes(),
1360            img.width(),
1361            img.height(),
1362            img.color().into(),
1363        )
1364    }
1365
1366    /// Encode this image and write it to `w`.
1367    ///
1368    /// Assumes the writer is buffered. In most cases, you should wrap your writer in a `BufWriter`
1369    /// for best performance.
1370    ///
1371    /// ## Color Conversion
1372    ///
1373    /// Unlike other encoding methods in this crate, methods on `DynamicImage` try to automatically
1374    /// convert the image to some color type supported by the encoder. This may result in a loss of
1375    /// precision or the removal of the alpha channel.
1376    pub fn write_to<W: Write + Seek>(&self, mut w: W, format: ImageFormat) -> ImageResult<()> {
1377        let encoder = encoder_for_format(format, &mut w)?;
1378        self.write_with_encoder_impl(encoder)
1379    }
1380
1381    /// Encode this image with the provided encoder.
1382    ///
1383    /// ## Color Conversion
1384    ///
1385    /// Unlike other encoding methods in this crate, methods on `DynamicImage` try to automatically
1386    /// convert the image to some color type supported by the encoder. This may result in a loss of
1387    /// precision or the removal of the alpha channel.
1388    pub fn write_with_encoder(&self, encoder: impl ImageEncoder) -> ImageResult<()> {
1389        self.write_with_encoder_impl(Box::new(encoder))
1390    }
1391
1392    /// Saves the buffer to a file with the format derived from the file extension.
1393    ///
1394    /// ## Color Conversion
1395    ///
1396    /// Unlike other encoding methods in this crate, methods on `DynamicImage` try to automatically
1397    /// convert the image to some color type supported by the encoder. This may result in a loss of
1398    /// precision or the removal of the alpha channel.
1399    #[cfg(feature = "std")]
1400    pub fn save<Q>(&self, path: Q) -> ImageResult<()>
1401    where
1402        Q: AsRef<Path>,
1403    {
1404        let format = ImageFormat::from_path(path.as_ref())?;
1405        self.save_with_format(path, format)
1406    }
1407
1408    /// Saves the buffer to a file with the specified format.
1409    ///
1410    /// ## Color Conversion
1411    ///
1412    /// Unlike other encoding methods in this crate, methods on `DynamicImage` try to automatically
1413    /// convert the image to some color type supported by the encoder. This may result in a loss of
1414    /// precision or the removal of the alpha channel.
1415    #[cfg(feature = "std")]
1416    pub fn save_with_format<Q>(&self, path: Q, format: ImageFormat) -> ImageResult<()>
1417    where
1418        Q: AsRef<Path>,
1419    {
1420        let file = &mut std::io::BufWriter::new(File::create(path)?);
1421        let encoder = encoder_for_format(format, file)?;
1422        self.write_with_encoder_impl(encoder)
1423    }
1424}
1425
1426impl From<GrayImage> for DynamicImage {
1427    fn from(image: GrayImage) -> Self {
1428        DynamicImage::ImageLuma8(image)
1429    }
1430}
1431
1432impl From<GrayAlphaImage> for DynamicImage {
1433    fn from(image: GrayAlphaImage) -> Self {
1434        DynamicImage::ImageLumaA8(image)
1435    }
1436}
1437
1438impl From<RgbImage> for DynamicImage {
1439    fn from(image: RgbImage) -> Self {
1440        DynamicImage::ImageRgb8(image)
1441    }
1442}
1443
1444impl From<RgbaImage> for DynamicImage {
1445    fn from(image: RgbaImage) -> Self {
1446        DynamicImage::ImageRgba8(image)
1447    }
1448}
1449
1450impl From<Gray16Image> for DynamicImage {
1451    fn from(image: Gray16Image) -> Self {
1452        DynamicImage::ImageLuma16(image)
1453    }
1454}
1455
1456impl From<GrayAlpha16Image> for DynamicImage {
1457    fn from(image: GrayAlpha16Image) -> Self {
1458        DynamicImage::ImageLumaA16(image)
1459    }
1460}
1461
1462impl From<Rgb16Image> for DynamicImage {
1463    fn from(image: Rgb16Image) -> Self {
1464        DynamicImage::ImageRgb16(image)
1465    }
1466}
1467
1468impl From<Rgba16Image> for DynamicImage {
1469    fn from(image: Rgba16Image) -> Self {
1470        DynamicImage::ImageRgba16(image)
1471    }
1472}
1473
1474impl From<Rgb32FImage> for DynamicImage {
1475    fn from(image: Rgb32FImage) -> Self {
1476        DynamicImage::ImageRgb32F(image)
1477    }
1478}
1479
1480impl From<Rgba32FImage> for DynamicImage {
1481    fn from(image: Rgba32FImage) -> Self {
1482        DynamicImage::ImageRgba32F(image)
1483    }
1484}
1485
1486impl From<ImageBuffer<Luma<f32>, Vec<f32>>> for DynamicImage {
1487    fn from(image: ImageBuffer<Luma<f32>, Vec<f32>>) -> Self {
1488        DynamicImage::ImageRgb32F(image.convert())
1489    }
1490}
1491
1492impl From<ImageBuffer<LumaA<f32>, Vec<f32>>> for DynamicImage {
1493    fn from(image: ImageBuffer<LumaA<f32>, Vec<f32>>) -> Self {
1494        DynamicImage::ImageRgba32F(image.convert())
1495    }
1496}
1497
1498#[allow(deprecated)]
1499impl GenericImageView for DynamicImage {
1500    type Pixel = color::Rgba<u8>; // TODO use f32 as default for best precision and unbounded color?
1501
1502    fn dimensions(&self) -> (u32, u32) {
1503        dynamic_map!(*self, ref p, p.dimensions())
1504    }
1505
1506    fn get_pixel(&self, x: u32, y: u32) -> color::Rgba<u8> {
1507        dynamic_map!(*self, ref p, p.get_pixel(x, y).to_rgba().into_color())
1508    }
1509}
1510
1511#[allow(deprecated)]
1512impl GenericImage for DynamicImage {
1513    fn put_pixel(&mut self, x: u32, y: u32, pixel: color::Rgba<u8>) {
1514        match *self {
1515            DynamicImage::ImageLuma8(ref mut p) => p.put_pixel(x, y, pixel.to_luma()),
1516            DynamicImage::ImageLumaA8(ref mut p) => p.put_pixel(x, y, pixel.to_luma_alpha()),
1517            DynamicImage::ImageRgb8(ref mut p) => p.put_pixel(x, y, pixel.to_rgb()),
1518            DynamicImage::ImageRgba8(ref mut p) => p.put_pixel(x, y, pixel),
1519            DynamicImage::ImageLuma16(ref mut p) => p.put_pixel(x, y, pixel.to_luma().into_color()),
1520            DynamicImage::ImageLumaA16(ref mut p) => {
1521                p.put_pixel(x, y, pixel.to_luma_alpha().into_color());
1522            }
1523            DynamicImage::ImageRgb16(ref mut p) => p.put_pixel(x, y, pixel.to_rgb().into_color()),
1524            DynamicImage::ImageRgba16(ref mut p) => p.put_pixel(x, y, pixel.into_color()),
1525            DynamicImage::ImageRgb32F(ref mut p) => p.put_pixel(x, y, pixel.to_rgb().into_color()),
1526            DynamicImage::ImageRgba32F(ref mut p) => p.put_pixel(x, y, pixel.into_color()),
1527        }
1528    }
1529
1530    fn blend_pixel(&mut self, x: u32, y: u32, pixel: color::Rgba<u8>) {
1531        match *self {
1532            DynamicImage::ImageLuma8(ref mut p) => p.blend_pixel(x, y, pixel.to_luma()),
1533            DynamicImage::ImageLumaA8(ref mut p) => p.blend_pixel(x, y, pixel.to_luma_alpha()),
1534            DynamicImage::ImageRgb8(ref mut p) => p.blend_pixel(x, y, pixel.to_rgb()),
1535            DynamicImage::ImageRgba8(ref mut p) => p.blend_pixel(x, y, pixel),
1536            DynamicImage::ImageLuma16(ref mut p) => {
1537                p.blend_pixel(x, y, pixel.to_luma().into_color());
1538            }
1539            DynamicImage::ImageLumaA16(ref mut p) => {
1540                p.blend_pixel(x, y, pixel.to_luma_alpha().into_color());
1541            }
1542            DynamicImage::ImageRgb16(ref mut p) => p.blend_pixel(x, y, pixel.to_rgb().into_color()),
1543            DynamicImage::ImageRgba16(ref mut p) => p.blend_pixel(x, y, pixel.into_color()),
1544            DynamicImage::ImageRgb32F(ref mut p) => {
1545                p.blend_pixel(x, y, pixel.to_rgb().into_color());
1546            }
1547            DynamicImage::ImageRgba32F(ref mut p) => p.blend_pixel(x, y, pixel.into_color()),
1548        }
1549    }
1550
1551    /// Do not use is function: It is unimplemented!
1552    fn get_pixel_mut(&mut self, _: u32, _: u32) -> &mut color::Rgba<u8> {
1553        unimplemented!()
1554    }
1555}
1556
1557impl Default for DynamicImage {
1558    fn default() -> Self {
1559        Self::ImageRgba8(Default::default())
1560    }
1561}
1562
1563/// Decodes an image and stores it into a dynamic image
1564fn decoder_to_image<I: ImageDecoder>(decoder: I) -> ImageResult<DynamicImage> {
1565    let (w, h) = decoder.dimensions();
1566    let color_type = decoder.color_type();
1567
1568    let mut image = match color_type {
1569        color::ColorType::Rgb8 => {
1570            let buf = free_functions::decoder_to_vec(decoder)?;
1571            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb8)
1572        }
1573
1574        color::ColorType::Rgba8 => {
1575            let buf = free_functions::decoder_to_vec(decoder)?;
1576            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba8)
1577        }
1578
1579        color::ColorType::L8 => {
1580            let buf = free_functions::decoder_to_vec(decoder)?;
1581            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma8)
1582        }
1583
1584        color::ColorType::La8 => {
1585            let buf = free_functions::decoder_to_vec(decoder)?;
1586            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA8)
1587        }
1588
1589        color::ColorType::Rgb16 => {
1590            let buf = free_functions::decoder_to_vec(decoder)?;
1591            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb16)
1592        }
1593
1594        color::ColorType::Rgba16 => {
1595            let buf = free_functions::decoder_to_vec(decoder)?;
1596            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba16)
1597        }
1598
1599        color::ColorType::Rgb32F => {
1600            let buf = free_functions::decoder_to_vec(decoder)?;
1601            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb32F)
1602        }
1603
1604        color::ColorType::Rgba32F => {
1605            let buf = free_functions::decoder_to_vec(decoder)?;
1606            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba32F)
1607        }
1608
1609        color::ColorType::L16 => {
1610            let buf = free_functions::decoder_to_vec(decoder)?;
1611            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma16)
1612        }
1613
1614        color::ColorType::La16 => {
1615            let buf = free_functions::decoder_to_vec(decoder)?;
1616            ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA16)
1617        }
1618    }
1619    .ok_or_else(|| {
1620        ImageError::Parameter(ParameterError::from_kind(
1621            ParameterErrorKind::DimensionMismatch,
1622        ))
1623    })?;
1624
1625    // Presume SRGB for now. This is the one we convert into in some decoders and the one that is
1626    // most widely used in the wild. FIXME: add an API to decoder to indicate the color space as a
1627    // CICP directly or through interpreting the ICC information.
1628    image.set_rgb_primaries(Cicp::SRGB.primaries);
1629    image.set_transfer_function(Cicp::SRGB.transfer);
1630
1631    Ok(image)
1632}
1633
1634/// Open the image located at the path specified.
1635/// The image's format is determined from the path's file extension.
1636///
1637/// Try [`ImageReader`] for more advanced uses, including guessing the format based on the file's
1638/// content before its path.
1639#[cfg(feature = "std")]
1640pub fn open<P>(path: P) -> ImageResult<DynamicImage>
1641where
1642    P: AsRef<Path>,
1643{
1644    ImageReader::open(path)?.decode()
1645}
1646
1647/// Read a tuple containing the (width, height) of the image located at the specified path.
1648/// This is faster than fully loading the image and then getting its dimensions.
1649///
1650/// Try [`ImageReader`] for more advanced uses, including guessing the format based on the file's
1651/// content before its path or manually supplying the format.
1652#[cfg(feature = "std")]
1653pub fn image_dimensions<P>(path: P) -> ImageResult<(u32, u32)>
1654where
1655    P: AsRef<Path>,
1656{
1657    ImageReader::open(path)?.into_dimensions()
1658}
1659
1660/// Writes the supplied buffer to a writer in the specified format.
1661///
1662/// The buffer is assumed to have the correct format according to the specified color type. This
1663/// will lead to corrupted writers if the buffer contains malformed data.
1664///
1665/// Assumes the writer is buffered. In most cases, you should wrap your writer in a `BufWriter` for
1666/// best performance.
1667pub fn write_buffer_with_format<W: Write + Seek>(
1668    buffered_writer: &mut W,
1669    buf: &[u8],
1670    width: u32,
1671    height: u32,
1672    color: impl Into<ExtendedColorType>,
1673    format: ImageFormat,
1674) -> ImageResult<()> {
1675    let encoder = encoder_for_format(format, buffered_writer)?;
1676    encoder.write_image(buf, width, height, color.into())
1677}
1678
1679/// Create a new image from a byte slice
1680///
1681/// Makes an educated guess about the image format.
1682/// TGA is not supported by this function.
1683///
1684/// Try [`ImageReader`] for more advanced uses.
1685#[cfg(feature = "std")]
1686pub fn load_from_memory(buffer: &[u8]) -> ImageResult<DynamicImage> {
1687    ImageReader::new(std::io::Cursor::new(buffer))
1688        .with_guessed_format()?
1689        .decode()
1690}
1691
1692/// Create a new image from a byte slice
1693///
1694/// This is just a simple wrapper that constructs an `no_std_io::io::Cursor` around the buffer and then
1695/// calls `load` with that reader.
1696///
1697/// Try [`ImageReader`] for more advanced uses.
1698///
1699/// [`load`]: fn.load.html
1700#[cfg(feature = "std")]
1701#[inline(always)]
1702pub fn load_from_memory_with_format(buf: &[u8], format: ImageFormat) -> ImageResult<DynamicImage> {
1703    // Note: this function (and `load_from_memory`) were supposed to be generic over `AsRef<[u8]>`
1704    // so that we do not monomorphize copies of all our decoders unless some downsteam crate
1705    // actually calls one of these functions. See https://github.com/image-rs/image/pull/2470.
1706    //
1707    // However the type inference break of this is apparently quite large in the ecosystem so for
1708    // now they are unfortunately not. See https://github.com/image-rs/image/issues/2585.
1709    let b = no_std_io::io::Cursor::new(buf);
1710    free_functions::load(b, format)
1711}
1712
1713#[cfg(test)]
1714mod bench {
1715    #[bench]
1716    #[cfg(feature = "benchmarks")]
1717    fn bench_conversion(b: &mut test::Bencher) {
1718        let a = super::DynamicImage::ImageRgb8(crate::ImageBuffer::new(1000, 1000));
1719        b.iter(|| a.to_luma8());
1720        b.bytes = 1000 * 1000 * 3;
1721    }
1722}
1723
1724#[cfg(test)]
1725mod test {
1726    use crate::metadata::{CicpColorPrimaries, CicpTransform};
1727    use crate::ConvertColorOptions;
1728    use crate::{color::ColorType, images::dynimage::Gray16Image};
1729    use crate::{metadata::Cicp, ImageBuffer, Luma, Rgb, Rgba};
1730
1731    #[test]
1732    fn test_empty_file() {
1733        assert!(super::load_from_memory(b"").is_err());
1734    }
1735
1736    #[cfg(feature = "jpeg")]
1737    #[test]
1738    fn image_dimensions() {
1739        let im_path = "./tests/images/jpg/progressive/cat.jpg";
1740        let dims = super::image_dimensions(im_path).unwrap();
1741        assert_eq!(dims, (320, 240));
1742    }
1743
1744    #[cfg(feature = "png")]
1745    #[test]
1746    fn open_16bpc_png() {
1747        let im_path = "./tests/images/png/16bpc/basn6a16.png";
1748        let image = super::open(im_path).unwrap();
1749        assert_eq!(image.color(), ColorType::Rgba16);
1750    }
1751
1752    fn test_grayscale(mut img: super::DynamicImage, alpha_discarded: bool) {
1753        use crate::{GenericImage as _, GenericImageView as _};
1754        img.put_pixel(0, 0, Rgba([255, 0, 0, 100]));
1755        let expected_alpha = if alpha_discarded { 255 } else { 100 };
1756        assert_eq!(
1757            img.grayscale().get_pixel(0, 0),
1758            Rgba([54, 54, 54, expected_alpha])
1759        );
1760    }
1761
1762    fn test_grayscale_alpha_discarded(img: super::DynamicImage) {
1763        test_grayscale(img, true);
1764    }
1765
1766    fn test_grayscale_alpha_preserved(img: super::DynamicImage) {
1767        test_grayscale(img, false);
1768    }
1769
1770    #[test]
1771    fn test_grayscale_luma8() {
1772        test_grayscale_alpha_discarded(super::DynamicImage::new_luma8(1, 1));
1773        test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::L8));
1774    }
1775
1776    #[test]
1777    fn test_grayscale_luma_a8() {
1778        test_grayscale_alpha_preserved(super::DynamicImage::new_luma_a8(1, 1));
1779        test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::La8));
1780    }
1781
1782    #[test]
1783    fn test_grayscale_rgb8() {
1784        test_grayscale_alpha_discarded(super::DynamicImage::new_rgb8(1, 1));
1785        test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::Rgb8));
1786    }
1787
1788    #[test]
1789    fn test_grayscale_rgba8() {
1790        test_grayscale_alpha_preserved(super::DynamicImage::new_rgba8(1, 1));
1791        test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::Rgba8));
1792    }
1793
1794    #[test]
1795    fn test_grayscale_luma16() {
1796        test_grayscale_alpha_discarded(super::DynamicImage::new_luma16(1, 1));
1797        test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::L16));
1798    }
1799
1800    #[test]
1801    fn test_grayscale_luma_a16() {
1802        test_grayscale_alpha_preserved(super::DynamicImage::new_luma_a16(1, 1));
1803        test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::La16));
1804    }
1805
1806    #[test]
1807    fn test_grayscale_rgb16() {
1808        test_grayscale_alpha_discarded(super::DynamicImage::new_rgb16(1, 1));
1809        test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::Rgb16));
1810    }
1811
1812    #[test]
1813    fn test_grayscale_rgba16() {
1814        test_grayscale_alpha_preserved(super::DynamicImage::new_rgba16(1, 1));
1815        test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::Rgba16));
1816    }
1817
1818    #[test]
1819    fn test_grayscale_rgb32f() {
1820        test_grayscale_alpha_discarded(super::DynamicImage::new_rgb32f(1, 1));
1821        test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::Rgb32F));
1822    }
1823
1824    #[test]
1825    fn test_grayscale_rgba32f() {
1826        test_grayscale_alpha_preserved(super::DynamicImage::new_rgba32f(1, 1));
1827        test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::Rgba32F));
1828    }
1829
1830    #[test]
1831    fn test_dynamic_image_default_implementation() {
1832        // Test that structs wrapping a DynamicImage are able to auto-derive the Default trait
1833        // ensures that DynamicImage implements Default (if it didn't, this would cause a compile error).
1834        #[derive(Default)]
1835        #[allow(dead_code)]
1836        struct Foo {
1837            _image: super::DynamicImage,
1838        }
1839    }
1840
1841    #[test]
1842    fn test_to_vecu8() {
1843        let _ = super::DynamicImage::new_luma8(1, 1).into_bytes();
1844        let _ = super::DynamicImage::new_luma16(1, 1).into_bytes();
1845    }
1846
1847    #[test]
1848    fn issue_1705_can_turn_16bit_image_into_bytes() {
1849        let pixels = vec![65535u16; 64 * 64];
1850        let img = ImageBuffer::from_vec(64, 64, pixels).unwrap();
1851
1852        let img = super::DynamicImage::ImageLuma16(img);
1853        assert!(img.as_luma16().is_some());
1854
1855        let bytes: Vec<u8> = img.into_bytes();
1856        assert_eq!(bytes, vec![0xFF; 64 * 64 * 2]);
1857    }
1858
1859    #[test]
1860    fn test_convert_to() {
1861        use crate::Luma;
1862        let image_luma8 = super::DynamicImage::new_luma8(1, 1);
1863        let image_luma16 = super::DynamicImage::new_luma16(1, 1);
1864        assert_eq!(image_luma8.to_luma16(), image_luma16.to_luma16());
1865
1866        // test conversion using typed result
1867        let conv: Gray16Image = image_luma8.to();
1868        assert_eq!(image_luma8.to_luma16(), conv);
1869
1870        // test conversion using turbofish syntax
1871        let converted = image_luma8.to::<Luma<u16>>();
1872        assert_eq!(image_luma8.to_luma16(), converted);
1873    }
1874
1875    #[test]
1876    fn color_conversion_srgb_p3() {
1877        let mut source = super::DynamicImage::ImageRgb8({
1878            ImageBuffer::from_fn(128, 128, |_, _| Rgb([255, 0, 0]))
1879        });
1880
1881        let mut target = super::DynamicImage::ImageRgba8({
1882            ImageBuffer::from_fn(128, 128, |_, _| Rgba(Default::default()))
1883        });
1884
1885        source.set_rgb_primaries(Cicp::SRGB.primaries);
1886        source.set_transfer_function(Cicp::SRGB.transfer);
1887        target.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
1888        target.set_transfer_function(Cicp::DISPLAY_P3.transfer);
1889
1890        let result = target.copy_from_color_space(&source, Default::default());
1891
1892        assert!(result.is_ok(), "{result:?}");
1893        let target = target.as_rgba8().expect("Sample type unchanged");
1894        assert_eq!(target[(0, 0)], Rgba([234u8, 51, 35, 255]));
1895    }
1896
1897    #[test]
1898    fn color_conversion_preserves_sample() {
1899        let mut source = super::DynamicImage::ImageRgb16({
1900            ImageBuffer::from_fn(128, 128, |_, _| Rgb([u16::MAX, 0, 0]))
1901        });
1902
1903        let mut target = super::DynamicImage::ImageRgba8({
1904            ImageBuffer::from_fn(128, 128, |_, _| Rgba(Default::default()))
1905        });
1906
1907        source.set_rgb_primaries(Cicp::SRGB.primaries);
1908        source.set_transfer_function(Cicp::SRGB.transfer);
1909        target.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
1910        target.set_transfer_function(Cicp::DISPLAY_P3.transfer);
1911
1912        let result = target.copy_from_color_space(&source, Default::default());
1913
1914        assert!(result.is_ok(), "{result:?}");
1915        let target = target.as_rgba8().expect("Sample type unchanged");
1916        assert_eq!(target[(0, 0)], Rgba([234u8, 51, 35, 255]));
1917    }
1918
1919    #[test]
1920    fn color_conversion_preserves_sample_in_fastpath() {
1921        let source = super::DynamicImage::ImageRgb16({
1922            ImageBuffer::from_fn(128, 128, |_, _| Rgb([u16::MAX, 0, 0]))
1923        });
1924
1925        let mut target = super::DynamicImage::ImageRgba8({
1926            ImageBuffer::from_fn(128, 128, |_, _| Rgba(Default::default()))
1927        });
1928
1929        // No color space change takes place, but still sample should be converted.
1930        let result = target.copy_from_color_space(&source, Default::default());
1931
1932        assert!(result.is_ok(), "{result:?}");
1933        let target = target.as_rgba8().expect("Sample type unchanged");
1934        assert_eq!(target[(0, 0)], Rgba([255u8, 0, 0, 255]));
1935    }
1936
1937    #[test]
1938    fn color_conversion_rgb_to_luma() {
1939        let source = super::DynamicImage::ImageRgb16({
1940            ImageBuffer::from_fn(128, 128, |_, _| Rgb([0, u16::MAX, 0]))
1941        });
1942
1943        let mut target = super::DynamicImage::ImageLuma8({
1944            ImageBuffer::from_fn(128, 128, |_, _| Luma(Default::default()))
1945        });
1946
1947        // No color space change takes place, but still sample should be converted.
1948        let result = target.copy_from_color_space(&source, Default::default());
1949
1950        assert!(result.is_ok(), "{result:?}");
1951        // FIXME: but the result value is .. not ideal.
1952        target.as_luma8().expect("Sample type unchanged");
1953    }
1954
1955    #[test]
1956    fn copy_color_space_coverage() {
1957        const TYPES: [ColorType; 10] = [
1958            ColorType::L8,
1959            ColorType::La8,
1960            ColorType::Rgb8,
1961            ColorType::Rgba8,
1962            ColorType::L16,
1963            ColorType::La16,
1964            ColorType::Rgb16,
1965            ColorType::Rgba16,
1966            ColorType::Rgb32F,
1967            ColorType::Rgba32F,
1968        ];
1969
1970        let transform =
1971            CicpTransform::new(Cicp::SRGB, Cicp::DISPLAY_P3).expect("Failed to create transform");
1972
1973        for from in TYPES {
1974            for to in TYPES {
1975                let mut source = super::DynamicImage::new(16, 16, from);
1976                let mut target = super::DynamicImage::new(16, 16, to);
1977
1978                source.set_rgb_primaries(Cicp::SRGB.primaries);
1979                source.set_transfer_function(Cicp::SRGB.transfer);
1980
1981                target.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
1982                target.set_transfer_function(Cicp::DISPLAY_P3.transfer);
1983
1984                target
1985                    .copy_from_color_space(
1986                        &source,
1987                        ConvertColorOptions {
1988                            transform: Some(transform.clone()),
1989                            ..Default::default()
1990                        },
1991                    )
1992                    .expect("Failed to convert color space");
1993            }
1994        }
1995    }
1996
1997    #[test]
1998    fn apply_color_space() {
1999        let mut buffer = super::DynamicImage::ImageRgb8({
2000            ImageBuffer::from_fn(128, 128, |_, _| Rgb([u8::MAX, 0, 0]))
2001        });
2002
2003        buffer.set_rgb_primaries(Cicp::SRGB.primaries);
2004        buffer.set_transfer_function(Cicp::SRGB.transfer);
2005
2006        buffer
2007            .apply_color_space(Cicp::DISPLAY_P3, Default::default())
2008            .unwrap();
2009
2010        let target = buffer.as_rgb8().expect("Sample type unchanged");
2011        assert_eq!(target[(0, 0)], Rgb([234u8, 51, 35]));
2012    }
2013
2014    #[test]
2015    fn apply_color_space_coverage() {
2016        const TYPES: [ColorType; 10] = [
2017            ColorType::L8,
2018            ColorType::La8,
2019            ColorType::Rgb8,
2020            ColorType::Rgba8,
2021            ColorType::L16,
2022            ColorType::La16,
2023            ColorType::Rgb16,
2024            ColorType::Rgba16,
2025            ColorType::Rgb32F,
2026            ColorType::Rgba32F,
2027        ];
2028
2029        let transform =
2030            CicpTransform::new(Cicp::SRGB, Cicp::DISPLAY_P3).expect("Failed to create transform");
2031
2032        for buffer in TYPES {
2033            let mut buffer = super::DynamicImage::new(16, 16, buffer);
2034
2035            buffer.set_rgb_primaries(Cicp::SRGB.primaries);
2036            buffer.set_transfer_function(Cicp::SRGB.transfer);
2037
2038            buffer
2039                .apply_color_space(
2040                    Cicp::DISPLAY_P3,
2041                    ConvertColorOptions {
2042                        transform: Some(transform.clone()),
2043                        ..Default::default()
2044                    },
2045                )
2046                .expect("Failed to convert color space");
2047        }
2048    }
2049
2050    #[test]
2051    fn convert_color_space() {
2052        let mut buffer = super::DynamicImage::ImageRgb16({
2053            ImageBuffer::from_fn(128, 128, |_, _| Rgb([u16::MAX, 0, 0]))
2054        });
2055
2056        buffer.set_rgb_primaries(Cicp::SRGB.primaries);
2057        buffer.set_transfer_function(Cicp::SRGB.transfer);
2058
2059        buffer
2060            .convert_color_space(Cicp::DISPLAY_P3, Default::default(), ColorType::Rgb8)
2061            .unwrap();
2062
2063        let target = buffer.as_rgb8().expect("Sample type now rgb8");
2064        assert_eq!(target[(0, 0)], Rgb([234u8, 51, 35]));
2065    }
2066
2067    #[test]
2068    fn into_luma_is_color_space_aware() {
2069        let mut buffer = super::DynamicImage::ImageRgb16({
2070            ImageBuffer::from_fn(128, 128, |_, _| Rgb([u16::MAX, 0, 0]))
2071        });
2072
2073        buffer.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
2074        buffer.set_transfer_function(Cicp::DISPLAY_P3.transfer);
2075
2076        let luma8 = buffer.clone().into_luma8();
2077        assert_eq!(luma8[(0, 0)], Luma([58u8]));
2078        assert_eq!(luma8.color_space(), Cicp::DISPLAY_P3);
2079
2080        buffer.set_rgb_primaries(Cicp::SRGB.primaries);
2081
2082        let luma8 = buffer.clone().into_luma8();
2083        assert_eq!(luma8[(0, 0)], Luma([54u8]));
2084        assert_ne!(luma8.color_space(), Cicp::DISPLAY_P3);
2085    }
2086
2087    #[test]
2088    fn from_luma_is_color_space_aware() {
2089        let mut buffer = super::DynamicImage::ImageLuma16({
2090            ImageBuffer::from_fn(128, 128, |_, _| Luma([u16::MAX]))
2091        });
2092
2093        buffer.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
2094        buffer.set_transfer_function(Cicp::DISPLAY_P3.transfer);
2095
2096        let rgb8 = buffer.clone().into_rgb8();
2097        assert_eq!(rgb8[(0, 0)], Rgb([u8::MAX; 3]));
2098        assert_eq!(rgb8.color_space(), Cicp::DISPLAY_P3);
2099
2100        buffer.set_rgb_primaries(Cicp::SRGB.primaries);
2101
2102        let rgb8 = buffer.clone().into_rgb8();
2103        assert_eq!(rgb8[(0, 0)], Rgb([u8::MAX; 3]));
2104        assert_ne!(rgb8.color_space(), Cicp::DISPLAY_P3);
2105    }
2106
2107    #[test]
2108    fn from_luma_for_all_chromaticities() {
2109        const CHROMA: &[CicpColorPrimaries] = &[
2110            (CicpColorPrimaries::SRgb),
2111            (CicpColorPrimaries::RgbM),
2112            (CicpColorPrimaries::RgbB),
2113            (CicpColorPrimaries::Bt601),
2114            (CicpColorPrimaries::Rgb240m),
2115            (CicpColorPrimaries::GenericFilm),
2116            (CicpColorPrimaries::Rgb2020),
2117            // Note: here red=X and blue=Z and both are free of luminance
2118            (CicpColorPrimaries::Xyz),
2119            (CicpColorPrimaries::SmpteRp431),
2120            (CicpColorPrimaries::SmpteRp432),
2121            (CicpColorPrimaries::Industry22),
2122            // Falls back to sRGB
2123            (CicpColorPrimaries::Unspecified),
2124        ];
2125
2126        let mut buffer = super::DynamicImage::ImageLuma16({
2127            ImageBuffer::from_fn(128, 128, |_, _| Luma([u16::MAX]))
2128        });
2129
2130        for &chroma in CHROMA {
2131            buffer.set_rgb_primaries(chroma);
2132            let rgb = buffer.to_rgb8();
2133            assert_eq!(
2134                rgb[(0, 0)],
2135                Rgb([u8::MAX; 3]),
2136                "Failed for chroma: {chroma:?}"
2137            );
2138        }
2139    }
2140
2141    #[test]
2142    fn from_rgb_for_all_chromaticities() {
2143        // The colors following the coefficients must result in a luma that is the square-root
2144        // length of the coefficient vector, which is unique enough for a test.
2145        const CHROMA: &[(CicpColorPrimaries, [u8; 3], u8)] = &[
2146            (CicpColorPrimaries::SRgb, [54, 182, 18], 143),
2147            (CicpColorPrimaries::RgbM, [76, 150, 29], 114),
2148            (CicpColorPrimaries::RgbB, [57, 180, 18], 141),
2149            (CicpColorPrimaries::Bt601, [54, 179, 22], 139),
2150            (CicpColorPrimaries::Rgb240m, [54, 179, 22], 139),
2151            (CicpColorPrimaries::GenericFilm, [65, 173, 17], 135),
2152            (CicpColorPrimaries::Rgb2020, [67, 173, 15], 136),
2153            // Note: here red=X and blue=Z and both are free of luminance
2154            (CicpColorPrimaries::Xyz, [0, 255, 0], 255),
2155            (CicpColorPrimaries::SmpteRp431, [53, 184, 18], 145),
2156            (CicpColorPrimaries::SmpteRp432, [58, 176, 20], 137),
2157            (CicpColorPrimaries::Industry22, [59, 171, 24], 131),
2158            // Falls back to sRGB
2159            (CicpColorPrimaries::Unspecified, [54, 182, 18], 143),
2160        ];
2161
2162        let mut buffer = super::DynamicImage::ImageRgb8({
2163            ImageBuffer::from_fn(128, 128, |_, _| Rgb([u8::MAX; 3]))
2164        });
2165
2166        for &(chroma, rgb, luma) in CHROMA {
2167            buffer.set_rgb_primaries(chroma);
2168
2169            for px in buffer.as_mut_rgb8().unwrap().pixels_mut() {
2170                px.0 = rgb;
2171            }
2172
2173            let buf = buffer.to_luma8();
2174            assert_eq!(buf[(0, 0)], Luma([luma]), "Failed for chroma: {chroma:?}");
2175        }
2176    }
2177
2178    #[test]
2179    fn convert_color_space_coverage() {
2180        const TYPES: [ColorType; 10] = [
2181            ColorType::L8,
2182            ColorType::La8,
2183            ColorType::Rgb8,
2184            ColorType::Rgba8,
2185            ColorType::L16,
2186            ColorType::La16,
2187            ColorType::Rgb16,
2188            ColorType::Rgba16,
2189            ColorType::Rgb32F,
2190            ColorType::Rgba32F,
2191        ];
2192
2193        let transform =
2194            CicpTransform::new(Cicp::SRGB, Cicp::DISPLAY_P3).expect("Failed to create transform");
2195
2196        for from in TYPES {
2197            for to in TYPES {
2198                let mut buffer = super::DynamicImage::new(16, 16, from);
2199
2200                buffer.set_rgb_primaries(Cicp::SRGB.primaries);
2201                buffer.set_transfer_function(Cicp::SRGB.transfer);
2202
2203                let options = ConvertColorOptions {
2204                    transform: Some(transform.clone()),
2205                    ..Default::default()
2206                };
2207
2208                buffer
2209                    .convert_color_space(Cicp::DISPLAY_P3, options, to)
2210                    .expect("Failed to convert color space");
2211            }
2212        }
2213    }
2214
2215    /// Check that operations that are not cicp-aware behave as such. We introduce new methods (not
2216    /// based directly on the public imageops interface) at a later point.
2217    #[cfg(feature = "png")]
2218    #[test]
2219    fn color_space_independent_imageops() {
2220        let im_path = "./tests/images/png/16bpc/basn6a16.png";
2221
2222        let mut image = super::open(im_path).unwrap();
2223        let mut clone = image.clone();
2224
2225        image.set_color_space(Cicp::SRGB).unwrap();
2226        clone.set_color_space(Cicp::DISPLAY_P3).unwrap();
2227
2228        const IMAGEOPS: &[&dyn Fn(&super::DynamicImage) -> super::DynamicImage] = &[
2229            &|img| img.resize(32, 32, crate::imageops::FilterType::Lanczos3),
2230            &|img| img.resize_exact(32, 32, crate::imageops::FilterType::Lanczos3),
2231            &|img| img.thumbnail(8, 8),
2232            &|img| img.thumbnail_exact(8, 8),
2233            &|img| img.resize_to_fill(32, 32, crate::imageops::FilterType::Lanczos3),
2234            &|img| img.blur(1.0),
2235            &|img| {
2236                img.blur_advanced(
2237                    crate::imageops::GaussianBlurParameters::new_anisotropic_kernel_size(1.0, 2.0),
2238                )
2239            },
2240            &|img| img.fast_blur(1.0),
2241            &|img| img.unsharpen(1.0, 3),
2242            &|img| img.filter3x3(&[0.0, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0]),
2243            &|img| img.adjust_contrast(0.5),
2244            &|img| img.brighten(10),
2245            &|img| img.huerotate(180),
2246        ];
2247
2248        for (idx, &op) in IMAGEOPS.iter().enumerate() {
2249            let result_a = op(&image);
2250            let result_b = op(&clone);
2251            assert_eq!(result_a.color_space(), image.color_space(), "{idx}");
2252            assert_eq!(result_b.color_space(), clone.color_space(), "{idx}");
2253
2254            assert_ne!(result_a, result_b, "{idx}");
2255            assert_eq!(result_a.as_bytes(), result_b.as_bytes(), "{idx}");
2256        }
2257    }
2258}