image/images/
dynimage.rs

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