Coverage Report

Created: 2026-04-12 07:31

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