diff --git a/CHANGES.md b/CHANGES.md index a9688dba2d..1f674ab645 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,9 @@ Breaking Changes: - Trait `ImageDecoderRect` has been removed (#2355, #2681) + - DynamicImage now has Luma32F and LumaA32F variants, and conversions (like + `DynamicImage::grayscale`, and `From`) may now directly + produce these instead of Rgb32F or Rgba32F ### Version 0.25.9 diff --git a/benches/convert.rs b/benches/convert.rs index 55adacf4e6..7839accf52 100644 --- a/benches/convert.rs +++ b/benches/convert.rs @@ -36,6 +36,10 @@ pub fn bench_cast_intra_colorspace(c: &mut Criterion) { b.iter(|| black_box(&luma_source).to_luma16()); }); + c.bench_function("cast_dynamic_luma8_luma32f", |b| { + b.iter(|| black_box(&luma_source).to_luma32f()); + }); + c.bench_function("cast_dynamic_luma8_luma_alpha8", |b| { b.iter(|| black_box(&luma_source).to_luma_alpha8()); }); @@ -44,6 +48,10 @@ pub fn bench_cast_intra_colorspace(c: &mut Criterion) { b.iter(|| black_box(&luma_source).to_luma_alpha16()); }); + c.bench_function("cast_dynamic_luma8_luma_alpha32f", |b| { + b.iter(|| black_box(&luma_source).to_luma_alpha32f()); + }); + let la_source = DynamicImage::ImageLumaA8(ImageBuffer::from_pixel(256, 256, image::LumaA([0u8, 255]))); @@ -51,6 +59,10 @@ pub fn bench_cast_intra_colorspace(c: &mut Criterion) { b.iter(|| black_box(&la_source).to_luma_alpha16()); }); + c.bench_function("cast_dynamic_luma_alpha8_luma_alpha32f", |b| { + b.iter(|| black_box(&la_source).to_luma_alpha32f()); + }); + c.bench_function("cast_dynamic_luma_alpha8_luma8", |b| { b.iter(|| black_box(&la_source).to_luma8()); }); @@ -59,21 +71,29 @@ pub fn bench_cast_intra_colorspace(c: &mut Criterion) { b.iter(|| black_box(&la_source).to_luma16()); }); + c.bench_function("cast_dynamic_luma_alpha8_luma32f", |b| { + b.iter(|| black_box(&la_source).to_luma32f()); + }); + let la_source = DynamicImage::ImageLumaA16(ImageBuffer::from_pixel(256, 256, image::LumaA([0u16, 255]))); - c.bench_function("cast_dynamic_luma_alpha16_luma_alpha16", |b| { - b.iter(|| black_box(&la_source).to_luma_alpha16()); + c.bench_function("cast_dynamic_luma_alpha16_luma_alpha8", |b| { + b.iter(|| black_box(&la_source).to_luma_alpha8()); }); - c.bench_function("cast_dynamic_luma_alpha16_luma8", |b| { - b.iter(|| black_box(&la_source).to_luma8()); + c.bench_function("cast_dynamic_luma_alpha16_luma_alpha32f", |b| { + b.iter(|| black_box(&la_source).to_luma_alpha32f()); }); c.bench_function("cast_dynamic_luma_alpha16_luma16", |b| { b.iter(|| black_box(&la_source).to_luma16()); }); + c.bench_function("cast_dynamic_luma_alpha16_luma32f", |b| { + b.iter(|| black_box(&la_source).to_luma32f()); + }); + let rgba32_source = DynamicImage::ImageRgba32F(ImageBuffer::from_pixel(256, 256, Rgba(Default::default()))); diff --git a/src/codecs/jpeg/encoder.rs b/src/codecs/jpeg/encoder.rs index 11cbc0a359..81dc719269 100644 --- a/src/codecs/jpeg/encoder.rs +++ b/src/codecs/jpeg/encoder.rs @@ -299,7 +299,7 @@ impl ImageEncoder for JpegEncoder { use ColorType::*; match img.color() { L8 | Rgb8 => None, - La8 | L16 | La16 => Some(img.to_luma8().into()), + La8 | L16 | L32F | La16 | La32F => Some(img.to_luma8().into()), Rgba8 | Rgb16 | Rgb32F | Rgba16 | Rgba32F => Some(img.to_rgb8().into()), } } diff --git a/src/codecs/png.rs b/src/codecs/png.rs index 7c3f880376..280584051d 100644 --- a/src/codecs/png.rs +++ b/src/codecs/png.rs @@ -849,6 +849,8 @@ impl ImageEncoder for PngEncoder { ) -> Option { use ColorType::*; match img.color() { + L32F => Some(img.to_luma16().into()), + La32F => Some(img.to_luma_alpha16().into()), Rgb32F => Some(img.to_rgb16().into()), Rgba32F => Some(img.to_rgba16().into()), L8 | La8 | Rgb8 | Rgba8 | L16 | La16 | Rgb16 | Rgba16 => None, diff --git a/src/color.rs b/src/color.rs index 31d7c7520e..19f2c9aa70 100644 --- a/src/color.rs +++ b/src/color.rs @@ -31,6 +31,10 @@ pub enum ColorType { /// Pixel is 16-bit RGBA Rgba16, + /// Pixel is 32-bit float luminance + L32F, + /// Pixel is 32-bit float luminance with an alpha channel + La32F, /// Pixel is 32-bit float RGB Rgb32F, /// Pixel is 32-bit float RGBA @@ -45,9 +49,9 @@ impl ColorType { ColorType::L8 => 1, ColorType::L16 | ColorType::La8 => 2, ColorType::Rgb8 => 3, - ColorType::Rgba8 | ColorType::La16 => 4, + ColorType::Rgba8 | ColorType::La16 | ColorType::L32F => 4, ColorType::Rgb16 => 6, - ColorType::Rgba16 => 8, + ColorType::Rgba16 | ColorType::La32F => 8, ColorType::Rgb32F => 3 * 4, ColorType::Rgba32F => 4 * 4, } @@ -58,8 +62,8 @@ impl ColorType { pub fn has_alpha(self) -> bool { use ColorType::*; match self { - L8 | L16 | Rgb8 | Rgb16 | Rgb32F => false, - La8 | Rgba8 | La16 | Rgba16 | Rgba32F => true, + L8 | L16 | L32F | Rgb8 | Rgb16 | Rgb32F => false, + La8 | Rgba8 | La16 | Rgba16 | La32F | Rgba32F => true, } } @@ -68,7 +72,7 @@ impl ColorType { pub fn has_color(self) -> bool { use ColorType::*; match self { - L8 | L16 | La8 | La16 => false, + L8 | L16 | L32F | La8 | La16 | La32F => false, Rgb8 | Rgb16 | Rgba8 | Rgba16 | Rgb32F | Rgba32F => true, } } @@ -288,6 +292,8 @@ impl ExtendedColorType { ExtendedColorType::La16 => Some(ColorType::La16), ExtendedColorType::Rgb16 => Some(ColorType::Rgb16), ExtendedColorType::Rgba16 => Some(ColorType::Rgba16), + ExtendedColorType::L32F => Some(ColorType::L32F), + ExtendedColorType::La32F => Some(ColorType::La32F), ExtendedColorType::Rgb32F => Some(ColorType::Rgb32F), ExtendedColorType::Rgba32F => Some(ColorType::Rgba32F), ExtendedColorType::YCbCr8 => Some(ColorType::Rgb8), @@ -314,6 +320,8 @@ impl From for ExtendedColorType { ColorType::La16 => ExtendedColorType::La16, ColorType::Rgb16 => ExtendedColorType::Rgb16, ColorType::Rgba16 => ExtendedColorType::Rgba16, + ColorType::L32F => ExtendedColorType::L32F, + ColorType::La32F => ExtendedColorType::La32F, ColorType::Rgb32F => ExtendedColorType::Rgb32F, ColorType::Rgba32F => ExtendedColorType::Rgba32F, } diff --git a/src/imageops/resize.rs b/src/imageops/resize.rs index 57325a604a..c6cfa6e06a 100644 --- a/src/imageops/resize.rs +++ b/src/imageops/resize.rs @@ -68,6 +68,14 @@ pub(crate) fn resize_impl( let resized = resize_rgba16(src.as_raw(), src_size, dst_size, 16, alg)?; ImageRgba16(ImageBuffer::from_raw(dst_width, dst_height, resized).unwrap()) } + ImageLuma32F(src) => { + let resized = resize_plane_f32(src.as_raw(), src_size, dst_size, alg)?; + ImageLuma32F(ImageBuffer::from_raw(dst_width, dst_height, resized).unwrap()) + } + ImageLumaA32F(src) => { + let resized = resize_luma_alpha_f32(src.as_raw(), src_size, dst_size, alg)?; + ImageLumaA32F(ImageBuffer::from_raw(dst_width, dst_height, resized).unwrap()) + } ImageRgb32F(src) => { let resized = resize_rgb_f32(src.as_raw(), src_size, dst_size, alg)?; ImageRgb32F(ImageBuffer::from_raw(dst_width, dst_height, resized).unwrap()) @@ -113,6 +121,10 @@ fn premultiply_alpha(image: &mut DynamicImage) { DynamicImage::ImageRgba16(buf) => { premultiply_rgba16(buf.as_mut(), 16); } + DynamicImage::ImageLuma32F(_) => (), + DynamicImage::ImageLumaA32F(buf) => { + premultiply_luma_alpha_f32(buf.as_mut()); + } DynamicImage::ImageRgb32F(_) => (), DynamicImage::ImageRgba32F(buf) => { premultiply_rgba_f32(buf.as_mut()); @@ -132,6 +144,8 @@ fn unpremultiply_alpha(image: &mut DynamicImage) { DynamicImage::ImageLumaA16(buf) => unpremultiply_la16(buf.as_mut(), 16), DynamicImage::ImageRgb16(_) => (), DynamicImage::ImageRgba16(buf) => unpremultiply_rgba16(buf.as_mut(), 16), + DynamicImage::ImageLuma32F(_) => (), + DynamicImage::ImageLumaA32F(buf) => unpremultiply_luma_alpha_f32(buf), DynamicImage::ImageRgb32F(_) => (), DynamicImage::ImageRgba32F(buf) => unpremultiply_rgba_f32(buf), } @@ -148,6 +162,8 @@ fn has_constant_alpha(image: &DynamicImage) -> bool { DynamicImage::ImageLumaA16(buf) => has_constant_alpha_integer(buf), DynamicImage::ImageRgb16(_) => true, DynamicImage::ImageRgba16(buf) => has_constant_alpha_integer(buf), + DynamicImage::ImageLuma32F(_) => true, + DynamicImage::ImageLumaA32F(buf) => has_constant_alpha_f32(buf), DynamicImage::ImageRgb32F(_) => true, DynamicImage::ImageRgba32F(buf) => has_constant_alpha_f32(buf), } @@ -185,7 +201,11 @@ where } #[must_use] -fn has_constant_alpha_f32(img: &ImageBuffer, Vec>) -> bool { +fn has_constant_alpha_f32(img: &ImageBuffer) -> bool +where + P: Pixel, + Container: std::ops::Deref, +{ // Optimizing correctly in presence of NaNs and infinities is tricky, so just do the naive thing for now let first_pixel_alpha = match img.pixels().next() { Some(pixel) => pixel.alpha(), diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 614c8ff117..60f30e2484 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -16,7 +16,9 @@ use crate::imageops::filter_1d::{ filter_2d_sep_rgb_u16, filter_2d_sep_rgba, filter_2d_sep_rgba_f32, filter_2d_sep_rgba_u16, FilterImageSize, }; -use crate::images::buffer::{Gray16Image, GrayAlpha16Image, Rgb16Image, Rgba16Image}; +use crate::images::buffer::{ + Gray16Image, Gray32FImage, GrayAlpha16Image, GrayAlpha32FImage, Rgb16Image, Rgba16Image, +}; use crate::traits::{Enlargeable, Pixel, Primitive}; use crate::utils::{clamp, is_integer, vec_try_with_capacity}; use crate::{ @@ -1435,6 +1437,34 @@ pub(crate) fn gaussian_blur_dyn_image( Rgba16Image::from_raw(img.width(), img.height(), dest_image).unwrap(), ) } + DynamicImage::ImageLuma32F(img) => { + let mut dest_image = vec![0f32; img.len()]; + filter_2d_sep_plane_f32( + img.as_raw(), + &mut dest_image, + filter_image_size, + &x_axis_kernel, + &y_axis_kernel, + ) + .unwrap(); + DynamicImage::ImageLuma32F( + Gray32FImage::from_raw(img.width(), img.height(), dest_image).unwrap(), + ) + } + DynamicImage::ImageLumaA32F(img) => { + let mut dest_image = vec![0f32; img.len()]; + filter_2d_sep_la_f32( + img.as_raw(), + &mut dest_image, + filter_image_size, + &x_axis_kernel, + &y_axis_kernel, + ) + .unwrap(); + DynamicImage::ImageLumaA32F( + GrayAlpha32FImage::from_raw(img.width(), img.height(), dest_image).unwrap(), + ) + } DynamicImage::ImageRgb32F(img) => { let mut dest_image = vec![0f32; img.len()]; filter_2d_sep_rgb_f32( diff --git a/src/images/buffer.rs b/src/images/buffer.rs index 27521df0e7..a9d1601617 100644 --- a/src/images/buffer.rs +++ b/src/images/buffer.rs @@ -1916,10 +1916,15 @@ pub(crate) type Gray16Image = ImageBuffer, Vec>; /// Sendable 16-bit grayscale + alpha channel image buffer pub(crate) type GrayAlpha16Image = ImageBuffer, Vec>; +/// An image buffer for 32-bit float grayscale pixels, +/// where the backing container is a flattened vector of floats. +pub(crate) type Gray32FImage = ImageBuffer, Vec>; +/// An image buffer for 32-bit float grayscale + alpha pixels, +/// where the backing container is a flattened vector of floats. +pub(crate) type GrayAlpha32FImage = ImageBuffer, Vec>; /// An image buffer for 32-bit float RGB pixels, /// where the backing container is a flattened vector of floats. pub type Rgb32FImage = ImageBuffer, Vec>; - /// An image buffer for 32-bit float RGBA pixels, /// where the backing container is a flattened vector of floats. pub type Rgba32FImage = ImageBuffer, Vec>; @@ -1972,11 +1977,26 @@ impl From for GrayAlpha16Image { } } +impl From for Rgb32FImage { + fn from(value: DynamicImage) -> Self { + value.into_rgb32f() + } +} impl From for Rgba32FImage { fn from(value: DynamicImage) -> Self { value.into_rgba32f() } } +impl From for Gray32FImage { + fn from(value: DynamicImage) -> Self { + value.into_luma32f() + } +} +impl From for GrayAlpha32FImage { + fn from(value: DynamicImage) -> Self { + value.into_luma_alpha32f() + } +} #[cfg(test)] mod test { diff --git a/src/images/dynimage.rs b/src/images/dynimage.rs index d6a69d01c8..d6367e5538 100644 --- a/src/images/dynimage.rs +++ b/src/images/dynimage.rs @@ -7,8 +7,9 @@ use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::flat::FlatSamples; use crate::imageops::{gaussian_blur_dyn_image, GaussianBlurParameters}; use crate::images::buffer::{ - ConvertBuffer, Gray16Image, GrayAlpha16Image, GrayAlphaImage, GrayImage, ImageBuffer, - Rgb16Image, Rgb32FImage, RgbImage, Rgba16Image, Rgba32FImage, RgbaImage, + ConvertBuffer, Gray16Image, Gray32FImage, GrayAlpha16Image, GrayAlpha32FImage, GrayAlphaImage, + GrayImage, ImageBuffer, Rgb16Image, Rgb32FImage, RgbImage, Rgba16Image, Rgba32FImage, + RgbaImage, }; use crate::io::encoder::ImageEncoderBoxed; use crate::io::free_functions::{self, encoder_for_format}; @@ -93,6 +94,12 @@ pub enum DynamicImage { /// Each pixel in this image is 16-bit Rgb with alpha ImageRgba16(Rgba16Image), + /// Each pixel in this image is 32-bit float Luma + ImageLuma32F(Gray32FImage), + + /// Each pixel in this image is 32-bit float Luma with alpha + ImageLumaA32F(GrayAlpha32FImage), + /// Each pixel in this image is 32-bit float Rgb ImageRgb32F(Rgb32FImage), @@ -112,6 +119,8 @@ macro_rules! dynamic_map( ImageLumaA16($image) => ImageLumaA16($action), ImageRgb16($image) => ImageRgb16($action), ImageRgba16($image) => ImageRgba16($action), + ImageLuma32F($image) => ImageLuma32F($action), + ImageLumaA32F($image) => ImageLumaA32F($action), ImageRgb32F($image) => ImageRgb32F($action), ImageRgba32F($image) => ImageRgba32F($action), } @@ -127,6 +136,8 @@ macro_rules! dynamic_map( DynamicImage::ImageLumaA16($image) => $action, DynamicImage::ImageRgb16($image) => $action, DynamicImage::ImageRgba16($image) => $action, + DynamicImage::ImageLuma32F($image) => $action, + DynamicImage::ImageLumaA32F($image) => $action, DynamicImage::ImageRgb32F($image) => $action, DynamicImage::ImageRgba32F($image) => $action, } @@ -148,6 +159,8 @@ impl Clone for DynamicImage { (Self::ImageLumaA16(p1), Self::ImageLumaA16(p2)) => p1.clone_from(p2), (Self::ImageRgb16(p1), Self::ImageRgb16(p2)) => p1.clone_from(p2), (Self::ImageRgba16(p1), Self::ImageRgba16(p2)) => p1.clone_from(p2), + (Self::ImageLuma32F(p1), Self::ImageLuma32F(p2)) => p1.clone_from(p2), + (Self::ImageLumaA32F(p1), Self::ImageLumaA32F(p2)) => p1.clone_from(p2), (Self::ImageRgb32F(p1), Self::ImageRgb32F(p2)) => p1.clone_from(p2), (Self::ImageRgba32F(p1), Self::ImageRgba32F(p2)) => p1.clone_from(p2), (this, source) => *this = source.clone(), @@ -169,7 +182,9 @@ impl DynamicImage { Rgb8 => Self::new_rgb8(w, h), Rgba8 => Self::new_rgba8(w, h), L16 => Self::new_luma16(w, h), + L32F => Self::new_luma32f(w, h), La16 => Self::new_luma_a16(w, h), + La32F => Self::new_luma_a32f(w, h), Rgb16 => Self::new_rgb16(w, h), Rgba16 => Self::new_rgba16(w, h), Rgb32F => Self::new_rgb32f(w, h), @@ -227,6 +242,19 @@ impl DynamicImage { DynamicImage::ImageRgba16(ImageBuffer::new(w, h)) } + /// Creates a dynamic image backed by a buffer of gray pixels. + #[must_use] + pub fn new_luma32f(w: u32, h: u32) -> DynamicImage { + DynamicImage::ImageLuma32F(ImageBuffer::new(w, h)) + } + + /// Creates a dynamic image backed by a buffer of gray + /// pixels with transparency. + #[must_use] + pub fn new_luma_a32f(w: u32, h: u32) -> DynamicImage { + DynamicImage::ImageLumaA32F(ImageBuffer::new(w, h)) + } + /// Creates a dynamic image backed by a buffer of RGB pixels. #[must_use] pub fn new_rgb32f(w: u32, h: u32) -> DynamicImage { @@ -264,8 +292,10 @@ impl DynamicImage { + FromColor> + FromColor> + FromColor> + + FromColor> + FromColor> - + FromColor>, + + FromColor> + + FromColor>, >( &self, ) -> ImageBuffer> { @@ -346,8 +376,11 @@ impl DynamicImage { /// Returns a copy of this image as a Luma image. #[must_use] - pub fn to_luma32f(&self) -> ImageBuffer, Vec> { - dynamic_map!(self, ref p, p.cast_in_color_space()) + pub fn to_luma32f(&self) -> Gray32FImage { + match self { + DynamicImage::ImageLuma32F(x) => x.clone(), + x => dynamic_map!(x, ref p, p.cast_in_color_space()), + } } /// Returns a copy of this image as a `LumaA` image. @@ -370,8 +403,11 @@ impl DynamicImage { /// Returns a copy of this image as a `LumaA` image. #[must_use] - pub fn to_luma_alpha32f(&self) -> ImageBuffer, Vec> { - dynamic_map!(self, ref p, p.cast_in_color_space()) + pub fn to_luma_alpha32f(&self) -> GrayAlpha32FImage { + match self { + DynamicImage::ImageLumaA32F(x) => x.clone(), + x => dynamic_map!(x, ref p, p.cast_in_color_space()), + } } /// Consume the image and returns a RGB image. @@ -470,6 +506,18 @@ impl DynamicImage { } } + /// Consume the image and returns a Luma image. + /// + /// If the image was already the correct format, it is returned as is. + /// Otherwise, a copy is created. + #[must_use] + pub fn into_luma32f(self) -> Gray32FImage { + match self { + DynamicImage::ImageLuma32F(x) => x, + x => x.to_luma32f(), + } + } + /// Consume the image and returns a `LumaA` image. /// /// If the image was already the correct format, it is returned as is. @@ -494,6 +542,18 @@ impl DynamicImage { } } + /// Consume the image and returns a `LumaA` image. + /// + /// If the image was already the correct format, it is returned as is. + /// Otherwise, a copy is created. + #[must_use] + pub fn into_luma_alpha32f(self) -> GrayAlpha32FImage { + match self { + DynamicImage::ImageLumaA32F(x) => x, + x => x.to_luma_alpha32f(), + } + } + /// Return a cut-out of this image delimited by the bounding rectangle. #[must_use] pub fn crop(&self, selection: Rect) -> DynamicImage { @@ -654,6 +714,15 @@ impl DynamicImage { } } + /// Return a reference to an 32bit Grayscale image + #[must_use] + pub fn as_luma32f(&self) -> Option<&Gray32FImage> { + match *self { + DynamicImage::ImageLuma32F(ref p) => Some(p), + _ => None, + } + } + /// Return a mutable reference to an 16bit Grayscale image pub fn as_mut_luma16(&mut self) -> Option<&mut Gray16Image> { match *self { @@ -662,6 +731,15 @@ impl DynamicImage { } } + /// Return a mutable reference to an 32bit Grayscale image + #[must_use] + pub fn as_mut_luma32f(&mut self) -> Option<&mut Gray32FImage> { + match *self { + DynamicImage::ImageLuma32F(ref mut p) => Some(p), + _ => None, + } + } + /// Return a reference to an 16bit Grayscale image with an alpha channel #[must_use] pub fn as_luma_alpha16(&self) -> Option<&GrayAlpha16Image> { @@ -671,6 +749,14 @@ impl DynamicImage { } } + /// Return a reference to an 32bit Grayscale image with an alpha channel + pub fn as_luma_alpha32f(&self) -> Option<&GrayAlpha32FImage> { + match *self { + DynamicImage::ImageLumaA32F(ref p) => Some(p), + _ => None, + } + } + /// Return a mutable reference to an 16bit Grayscale image with an alpha channel pub fn as_mut_luma_alpha16(&mut self) -> Option<&mut GrayAlpha16Image> { match *self { @@ -679,6 +765,14 @@ impl DynamicImage { } } + /// Return a mutable reference to an 32bit Grayscale image with an alpha channel + pub fn as_mut_luma_alpha32f(&mut self) -> Option<&mut GrayAlpha32FImage> { + match *self { + DynamicImage::ImageLumaA32F(ref mut p) => Some(p), + _ => None, + } + } + /// Return a view on the raw sample buffer for 8 bit per channel images. #[must_use] pub fn as_flat_samples_u8(&self) -> Option> { @@ -707,6 +801,8 @@ impl DynamicImage { #[must_use] pub fn as_flat_samples_f32(&self) -> Option> { match *self { + DynamicImage::ImageLuma32F(ref p) => Some(p.as_flat_samples()), + DynamicImage::ImageLumaA32F(ref p) => Some(p.as_flat_samples()), DynamicImage::ImageRgb32F(ref p) => Some(p.as_flat_samples()), DynamicImage::ImageRgba32F(ref p) => Some(p.as_flat_samples()), _ => None, @@ -765,6 +861,8 @@ impl DynamicImage { DynamicImage::ImageLumaA16(_) => color::ColorType::La16, DynamicImage::ImageRgb16(_) => color::ColorType::Rgb16, DynamicImage::ImageRgba16(_) => color::ColorType::Rgba16, + DynamicImage::ImageLuma32F(_) => color::ColorType::L32F, + DynamicImage::ImageLumaA32F(_) => color::ColorType::La32F, DynamicImage::ImageRgb32F(_) => color::ColorType::Rgb32F, DynamicImage::ImageRgba32F(_) => color::ColorType::Rgba32F, } @@ -833,8 +931,7 @@ impl DynamicImage { } /// Return a grayscale version of this image. - /// Returns `Luma` images in most cases. However, for `f32` images, - /// this will return a grayscale `Rgb/Rgba` image instead. + /// Returns either a `Luma` or `LumaA` image. #[must_use] pub fn grayscale(&self) -> DynamicImage { match *self { @@ -854,11 +951,13 @@ impl DynamicImage { DynamicImage::ImageRgba16(ref p) => { DynamicImage::ImageLumaA16(imageops::grayscale_alpha(p)) } - DynamicImage::ImageRgb32F(ref p) => { - DynamicImage::ImageRgb32F(imageops::grayscale_with_type(p)) + DynamicImage::ImageLuma32F(ref p) => DynamicImage::ImageLuma32F(p.clone()), + DynamicImage::ImageLumaA32F(ref p) => { + DynamicImage::ImageLumaA32F(imageops::grayscale_alpha(p)) } + DynamicImage::ImageRgb32F(ref p) => DynamicImage::ImageLuma32F(imageops::grayscale(p)), DynamicImage::ImageRgba32F(ref p) => { - DynamicImage::ImageRgba32F(imageops::grayscale_with_type_alpha(p)) + DynamicImage::ImageLumaA32F(imageops::grayscale_alpha(p)) } } } @@ -1484,15 +1583,15 @@ impl From for DynamicImage { } } -impl From, Vec>> for DynamicImage { - fn from(image: ImageBuffer, Vec>) -> Self { - DynamicImage::ImageRgb32F(image.convert()) +impl From for DynamicImage { + fn from(image: Gray32FImage) -> Self { + DynamicImage::ImageLuma32F(image) } } -impl From, Vec>> for DynamicImage { - fn from(image: ImageBuffer, Vec>) -> Self { - DynamicImage::ImageRgba32F(image.convert()) +impl From for DynamicImage { + fn from(image: GrayAlpha32FImage) -> Self { + DynamicImage::ImageLumaA32F(image) } } @@ -1521,6 +1620,12 @@ impl GenericImage for DynamicImage { } DynamicImage::ImageRgb16(ref mut p) => p.put_pixel(x, y, pixel.to_rgb().into_color()), DynamicImage::ImageRgba16(ref mut p) => p.put_pixel(x, y, pixel.into_color()), + DynamicImage::ImageLuma32F(ref mut p) => { + p.put_pixel(x, y, pixel.to_luma().into_color()) + } + DynamicImage::ImageLumaA32F(ref mut p) => { + p.put_pixel(x, y, pixel.to_luma_alpha().into_color()); + } DynamicImage::ImageRgb32F(ref mut p) => p.put_pixel(x, y, pixel.to_rgb().into_color()), DynamicImage::ImageRgba32F(ref mut p) => p.put_pixel(x, y, pixel.into_color()), } @@ -1569,6 +1674,16 @@ fn decoder_to_image(decoder: I) -> ImageResult { ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba16) } + color::ColorType::L16 => { + let buf = free_functions::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma16) + } + + color::ColorType::La16 => { + let buf = free_functions::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA16) + } + color::ColorType::Rgb32F => { let buf = free_functions::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb32F) @@ -1579,14 +1694,14 @@ fn decoder_to_image(decoder: I) -> ImageResult { ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba32F) } - color::ColorType::L16 => { + color::ColorType::L32F => { let buf = free_functions::decoder_to_vec(decoder)?; - ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma16) + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma32F) } - color::ColorType::La16 => { + color::ColorType::La32F => { let buf = free_functions::decoder_to_vec(decoder)?; - ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA16) + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA32F) } } .ok_or_else(|| { @@ -1697,6 +1812,21 @@ mod test { use crate::{color::ColorType, images::dynimage::Gray16Image}; use crate::{metadata::Cicp, ImageBuffer, Luma, Rgb, Rgba}; + const TYPES: [ColorType; 12] = [ + ColorType::L8, + ColorType::La8, + ColorType::Rgb8, + ColorType::Rgba8, + ColorType::L16, + ColorType::La16, + ColorType::Rgb16, + ColorType::Rgba16, + ColorType::L32F, + ColorType::La32F, + ColorType::Rgb32F, + ColorType::Rgba32F, + ]; + #[test] fn test_empty_file() { assert!(super::load_from_memory(b"").is_err()); @@ -1923,19 +2053,6 @@ mod test { #[test] fn copy_color_space_coverage() { - const TYPES: [ColorType; 10] = [ - ColorType::L8, - ColorType::La8, - ColorType::Rgb8, - ColorType::Rgba8, - ColorType::L16, - ColorType::La16, - ColorType::Rgb16, - ColorType::Rgba16, - ColorType::Rgb32F, - ColorType::Rgba32F, - ]; - let transform = CicpTransform::new(Cicp::SRGB, Cicp::DISPLAY_P3).expect("Failed to create transform"); @@ -1982,19 +2099,6 @@ mod test { #[test] fn apply_color_space_coverage() { - const TYPES: [ColorType; 10] = [ - ColorType::L8, - ColorType::La8, - ColorType::Rgb8, - ColorType::Rgba8, - ColorType::L16, - ColorType::La16, - ColorType::Rgb16, - ColorType::Rgba16, - ColorType::Rgb32F, - ColorType::Rgba32F, - ]; - let transform = CicpTransform::new(Cicp::SRGB, Cicp::DISPLAY_P3).expect("Failed to create transform"); @@ -2146,19 +2250,6 @@ mod test { #[test] fn convert_color_space_coverage() { - const TYPES: [ColorType; 10] = [ - ColorType::L8, - ColorType::La8, - ColorType::Rgb8, - ColorType::Rgba8, - ColorType::L16, - ColorType::La16, - ColorType::Rgb16, - ColorType::Rgba16, - ColorType::Rgb32F, - ColorType::Rgba32F, - ]; - let transform = CicpTransform::new(Cicp::SRGB, Cicp::DISPLAY_P3).expect("Failed to create transform"); diff --git a/src/io/encoder.rs b/src/io/encoder.rs index b58ea84234..8206d09f29 100644 --- a/src/io/encoder.rs +++ b/src/io/encoder.rs @@ -144,8 +144,8 @@ pub(crate) fn dynimage_conversion_8bit(img: &DynamicImage) -> Option None, - L16 => Some(img.to_luma8().into()), - La16 => Some(img.to_luma_alpha8().into()), + L16 | L32F => Some(img.to_luma8().into()), + La16 | La32F => Some(img.to_luma_alpha8().into()), Rgb16 | Rgb32F => Some(img.to_rgb8().into()), Rgba16 | Rgba32F => Some(img.to_rgba8().into()), } diff --git a/src/metadata/cicp.rs b/src/metadata/cicp.rs index 5ede89478e..d40d59b5d6 100644 --- a/src/metadata/cicp.rs +++ b/src/metadata/cicp.rs @@ -825,6 +825,18 @@ impl CicpTransform { &mut ibuffer[..4 * count], ); } + DynamicImage::ImageLuma32F(buf) => { + CicpTransform::expand_luma_rgb( + &buf.inner_pixels()[start_idx..end_idx], + &mut ibuffer[..3 * count], + ); + } + DynamicImage::ImageLumaA32F(buf) => { + CicpTransform::expand_luma_rgba( + &buf.inner_pixels()[2 * start_idx..2 * end_idx], + &mut ibuffer[..4 * count], + ); + } DynamicImage::ImageRgb32F(buf) => { CicpTransform::expand_rgb( &buf.inner_pixels()[3 * start_idx..3 * end_idx], @@ -891,13 +903,26 @@ impl CicpTransform { &mut buf.inner_pixels_mut()[3 * start_idx..3 * end_idx], ); } - DynamicImage::ImageRgba16(buf) => { CicpTransform::clamp_rgba( &obuffer[..4 * count], &mut buf.inner_pixels_mut()[4 * start_idx..4 * end_idx], ); } + DynamicImage::ImageLuma32F(buf) => { + CicpTransform::clamp_rgb_luma( + &obuffer[..3 * count], + &mut buf.inner_pixels_mut()[start_idx..end_idx], + self.output_coefs, + ); + } + DynamicImage::ImageLumaA32F(buf) => { + CicpTransform::clamp_rgba_luma( + &obuffer[..4 * count], + &mut buf.inner_pixels_mut()[2 * start_idx..2 * end_idx], + self.output_coefs, + ); + } DynamicImage::ImageRgb32F(buf) => { CicpTransform::clamp_rgb( &obuffer[..3 * count], diff --git a/src/traits.rs b/src/traits.rs index 2e6ce7ae0f..5f507b0e22 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -254,8 +254,8 @@ pub(crate) mod private { impl From for LayoutWithColor { fn from(color: ColorType) -> LayoutWithColor { match color { - ColorType::L8 | ColorType::L16 => LayoutWithColor::Luma, - ColorType::La8 | ColorType::La16 => LayoutWithColor::LumaAlpha, + ColorType::L8 | ColorType::L16 | ColorType::L32F => LayoutWithColor::Luma, + ColorType::La8 | ColorType::La16 | ColorType::La32F => LayoutWithColor::LumaAlpha, ColorType::Rgb8 | ColorType::Rgb16 | ColorType::Rgb32F => LayoutWithColor::Rgb, ColorType::Rgba8 | ColorType::Rgba16 | ColorType::Rgba32F => LayoutWithColor::Rgba, } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index dd9d7d1722..adcccbada4 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -60,6 +60,8 @@ pub(crate) fn interleave_planes(out: &mut [u8], color: crate::ColorType, planes: crate::ColorType::La16 => trampoline::<2, 2>(out, planes), crate::ColorType::Rgb16 => trampoline::<3, 2>(out, planes), crate::ColorType::Rgba16 => trampoline::<4, 2>(out, planes), + crate::ColorType::L32F => trampoline::<1, 4>(out, planes), + crate::ColorType::La32F => trampoline::<2, 4>(out, planes), crate::ColorType::Rgb32F => trampoline::<3, 4>(out, planes), crate::ColorType::Rgba32F => trampoline::<4, 4>(out, planes), }