/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 | | } |