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