Coverage Report

Created: 2026-06-07 07:04

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