diff --git a/examples/concat/main.rs b/examples/concat/main.rs index b36e88f681..9d96166100 100644 --- a/examples/concat/main.rs +++ b/examples/concat/main.rs @@ -8,9 +8,15 @@ use image::{GenericImage, GenericImageView, ImageBuffer, Pixel, Primitive}; /// cargo run --release --example concat fn main() { h_concat(&[ - image::open("examples/concat/200x300.png").unwrap(), - image::open("examples/concat/300x300.png").unwrap(), - image::open("examples/concat/400x300.png").unwrap(), + image::open("examples/concat/200x300.png") + .unwrap() + .to_rgba8(), + image::open("examples/concat/300x300.png") + .unwrap() + .to_rgba8(), + image::open("examples/concat/400x300.png") + .unwrap() + .to_rgba8(), ]) .save("examples/concat/concatenated_900x300.png") .unwrap(); diff --git a/examples/opening.rs b/examples/opening.rs index 8e7c071eb9..8255f2743a 100644 --- a/examples/opening.rs +++ b/examples/opening.rs @@ -5,7 +5,7 @@ use std::env; use std::fs::File; use std::path::Path; -use image::{GenericImageView, ImageFormat}; +use image::ImageFormat; fn main() { let file = if env::args().count() == 2 { diff --git a/examples/tile/main.rs b/examples/tile/main.rs index 6d7357a76e..0d6fdee063 100644 --- a/examples/tile/main.rs +++ b/examples/tile/main.rs @@ -2,7 +2,9 @@ use image::RgbaImage; fn main() { let mut img = RgbaImage::new(1920, 1080); - let tile = image::open("examples/scaleup/tinycross.png").unwrap(); + let tile = image::open("examples/scaleup/tinycross.png") + .unwrap() + .to_rgba8(); image::imageops::tile(&mut img, &tile); img.save("tiled_wallpaper.png").unwrap(); diff --git a/src/buffer.rs b/src/buffer.rs index 6a87ae9313..bb41cd9748 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1193,10 +1193,6 @@ where P: Pixel, Container: Deref + DerefMut, { - fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut P { - self.get_pixel_mut(x, y) - } - fn put_pixel(&mut self, x: u32, y: u32, pixel: P) { *self.get_pixel_mut(x, y) = pixel; } diff --git a/src/dynimage.rs b/src/dynimage.rs index e04944ad5e..c99e73932d 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -1,3 +1,4 @@ +use core::marker::PhantomData; use std::io::{self, Seek, Write}; use std::path::Path; @@ -10,17 +11,16 @@ use crate::buffer_::{ ConvertBuffer, Gray16Image, GrayAlpha16Image, GrayAlphaImage, GrayImage, ImageBuffer, Rgb16Image, RgbImage, Rgba16Image, RgbaImage, }; -use crate::color::{self, IntoColor}; use crate::error::{ImageError, ImageResult, ParameterError, ParameterErrorKind}; use crate::flat::FlatSamples; -use crate::image::{GenericImage, GenericImageView, ImageDecoder, ImageEncoder, ImageFormat}; +use crate::image::{ImageDecoder, ImageEncoder, ImageFormat}; use crate::image_reader::free_functions; use crate::math::resize_dimensions; use crate::metadata::Orientation; -use crate::traits::Pixel; -use crate::ImageReader; -use crate::{image, Luma, LumaA}; +use crate::{color, GenericImage}; +use crate::{image, GenericImageView, Luma, LumaA}; use crate::{imageops, ExtendedColorType}; +use crate::{ImageReader, Pixel}; use crate::{Rgb32FImage, Rgba32FImage}; /// A Dynamic Image @@ -135,6 +135,89 @@ impl Clone for DynamicImage { } } +/// Refers to a [`DynamicImage`], proxying a concrete pixel type. +/// +/// Construct via [`DynamicImage::as_pixel_view`] or [`DynamicImage::as_pixel_view_mut`] for a read +/// or a read+write access to the underlying image. The variant is never modified by this view. It +/// will instead convert between colors with every access to pixels. +pub struct DynamicView { + inner: Inner, + pixel: PhantomData, +} + +/// A marker for Pixel types that appear in `DynamicImage`. +pub trait DynamicPixel +where + Self: Pixel, + Self: sealed::Sealed, +{ +} + +mod sealed { + use crate::color::{self, FromColor, IntoColor as _}; + use std::any::Any; + + pub(crate) fn convert_between(ref v: T) -> U { + if let Some(&v) = ::downcast_ref::(v) { + v + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else if let Some(&v) = ::downcast_ref::>(v) { + v.into_color() + } else { + unreachable!("Exhaustively matched all implementations of `Sealed"); + } + } + + // A sealed trait to mark colors, convertible from all the different variants of + // `DynamicImage`. The seal ensures that the trait's requirements and implementing traits can + // be non-exhaustive. + pub trait Sealed + where + Self: Sized + 'static, + Self: FromColor>, + Self: FromColor>, + Self: FromColor>, + Self: FromColor>, + Self: FromColor>, + Self: FromColor>, + Self: FromColor>, + Self: FromColor>, + Self: FromColor>, + Self: FromColor>, + { + } + + impl Sealed for color::Luma {} + impl Sealed for color::LumaA {} + impl Sealed for color::Rgb {} + impl Sealed for color::Rgba {} + impl Sealed for color::Luma {} + impl Sealed for color::LumaA {} + impl Sealed for color::Rgb {} + impl Sealed for color::Rgba {} + impl Sealed for color::Rgb {} + impl Sealed for color::Rgba {} +} + impl DynamicImage { /// Creates a dynamic image backed by a buffer depending on /// the color type given. @@ -704,6 +787,11 @@ impl DynamicImage { dynamic_map!(*self, ref p, { p.height() }) } + /// Returns the dimensions of the underlying image + pub fn dimensions(&self) -> (u32, u32) { + (self.width(), self.height()) + } + /// 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. @@ -1035,6 +1123,26 @@ impl DynamicImage { { dynamic_map!(*self, ref p, p.save_with_format(path, format)) } + + /// View the pixels in this buffer with a particular type. + /// + /// The view will convert each individual pixel on access. + pub fn as_pixel_view(&self) -> DynamicView { + DynamicView { + inner: self, + pixel: PhantomData, + } + } + + /// View the pixels in this buffer with a particular type. + /// + /// The view will convert each individual pixel on access. + pub fn as_pixel_view_mut(&mut self) -> DynamicView { + DynamicView { + inner: self, + pixel: PhantomData, + } + } } impl From for DynamicImage { @@ -1109,68 +1217,60 @@ impl From, Vec>> for DynamicImage { } } -#[allow(deprecated)] -impl GenericImageView for DynamicImage { - type Pixel = color::Rgba; // TODO use f32 as default for best precision and unbounded color? +impl Default for DynamicImage { + fn default() -> Self { + Self::ImageRgba8(Default::default()) + } +} + +impl GenericImageView for DynamicView +where + P: DynamicPixel, +{ + type Pixel = P; fn dimensions(&self) -> (u32, u32) { - dynamic_map!(*self, ref p, p.dimensions()) + self.inner.dimensions() } - fn get_pixel(&self, x: u32, y: u32) -> color::Rgba { - dynamic_map!(*self, ref p, p.get_pixel(x, y).to_rgba().into_color()) + fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel { + use color::IntoColor as _; + dynamic_map!(&self.inner, ref img, img.get_pixel(x, y).into_color()) } } -#[allow(deprecated)] -impl GenericImage for DynamicImage { - fn put_pixel(&mut self, x: u32, y: u32, pixel: color::Rgba) { - match *self { - DynamicImage::ImageLuma8(ref mut p) => p.put_pixel(x, y, pixel.to_luma()), - DynamicImage::ImageLumaA8(ref mut p) => p.put_pixel(x, y, pixel.to_luma_alpha()), - DynamicImage::ImageRgb8(ref mut p) => p.put_pixel(x, y, pixel.to_rgb()), - DynamicImage::ImageRgba8(ref mut p) => p.put_pixel(x, y, pixel), - DynamicImage::ImageLuma16(ref mut p) => p.put_pixel(x, y, pixel.to_luma().into_color()), - DynamicImage::ImageLumaA16(ref mut p) => { - p.put_pixel(x, y, pixel.to_luma_alpha().into_color()); - } - 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::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()), - } - } +impl GenericImageView for DynamicView +where + P: DynamicPixel, +{ + type Pixel = P; - fn blend_pixel(&mut self, x: u32, y: u32, pixel: color::Rgba) { - match *self { - DynamicImage::ImageLuma8(ref mut p) => p.blend_pixel(x, y, pixel.to_luma()), - DynamicImage::ImageLumaA8(ref mut p) => p.blend_pixel(x, y, pixel.to_luma_alpha()), - DynamicImage::ImageRgb8(ref mut p) => p.blend_pixel(x, y, pixel.to_rgb()), - DynamicImage::ImageRgba8(ref mut p) => p.blend_pixel(x, y, pixel), - DynamicImage::ImageLuma16(ref mut p) => { - p.blend_pixel(x, y, pixel.to_luma().into_color()); - } - DynamicImage::ImageLumaA16(ref mut p) => { - p.blend_pixel(x, y, pixel.to_luma_alpha().into_color()); - } - DynamicImage::ImageRgb16(ref mut p) => p.blend_pixel(x, y, pixel.to_rgb().into_color()), - DynamicImage::ImageRgba16(ref mut p) => p.blend_pixel(x, y, pixel.into_color()), - DynamicImage::ImageRgb32F(ref mut p) => { - p.blend_pixel(x, y, pixel.to_rgb().into_color()); - } - DynamicImage::ImageRgba32F(ref mut p) => p.blend_pixel(x, y, pixel.into_color()), - } + fn dimensions(&self) -> (u32, u32) { + self.inner.dimensions() } - /// Do not use is function: It is unimplemented! - fn get_pixel_mut(&mut self, _: u32, _: u32) -> &mut color::Rgba { - unimplemented!() + fn get_pixel(&self, x: u32, y: u32) -> Self::Pixel { + use color::IntoColor as _; + dynamic_map!(&self.inner, ref img, img.get_pixel(x, y).into_color()) } } -impl Default for DynamicImage { - fn default() -> Self { - Self::ImageRgba8(Default::default()) +impl GenericImage for DynamicView +where + P: DynamicPixel, +{ + fn put_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { + dynamic_map!( + &mut self.inner, + ref mut img, + img.put_pixel(x, y, sealed::convert_between(pixel)) + ); + } + + fn blend_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { + let mut pix = self.get_pixel(x, y); + pix.blend(&pixel); + self.put_pixel(x, y, pix); } } @@ -1388,84 +1488,6 @@ mod test { assert_eq!(image.color(), ColorType::Rgba16); } - fn test_grayscale(mut img: super::DynamicImage, alpha_discarded: bool) { - use crate::image::{GenericImage, GenericImageView}; - img.put_pixel(0, 0, crate::color::Rgba([255, 0, 0, 100])); - let expected_alpha = if alpha_discarded { 255 } else { 100 }; - assert_eq!( - img.grayscale().get_pixel(0, 0), - crate::color::Rgba([54, 54, 54, expected_alpha]) - ); - } - - fn test_grayscale_alpha_discarded(img: super::DynamicImage) { - test_grayscale(img, true); - } - - fn test_grayscale_alpha_preserved(img: super::DynamicImage) { - test_grayscale(img, false); - } - - #[test] - fn test_grayscale_luma8() { - test_grayscale_alpha_discarded(super::DynamicImage::new_luma8(1, 1)); - test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::L8)); - } - - #[test] - fn test_grayscale_luma_a8() { - test_grayscale_alpha_preserved(super::DynamicImage::new_luma_a8(1, 1)); - test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::La8)); - } - - #[test] - fn test_grayscale_rgb8() { - test_grayscale_alpha_discarded(super::DynamicImage::new_rgb8(1, 1)); - test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::Rgb8)); - } - - #[test] - fn test_grayscale_rgba8() { - test_grayscale_alpha_preserved(super::DynamicImage::new_rgba8(1, 1)); - test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::Rgba8)); - } - - #[test] - fn test_grayscale_luma16() { - test_grayscale_alpha_discarded(super::DynamicImage::new_luma16(1, 1)); - test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::L16)); - } - - #[test] - fn test_grayscale_luma_a16() { - test_grayscale_alpha_preserved(super::DynamicImage::new_luma_a16(1, 1)); - test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::La16)); - } - - #[test] - fn test_grayscale_rgb16() { - test_grayscale_alpha_discarded(super::DynamicImage::new_rgb16(1, 1)); - test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::Rgb16)); - } - - #[test] - fn test_grayscale_rgba16() { - test_grayscale_alpha_preserved(super::DynamicImage::new_rgba16(1, 1)); - test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::Rgba16)); - } - - #[test] - fn test_grayscale_rgb32f() { - test_grayscale_alpha_discarded(super::DynamicImage::new_rgb32f(1, 1)); - test_grayscale_alpha_discarded(super::DynamicImage::new(1, 1, ColorType::Rgb32F)); - } - - #[test] - fn test_grayscale_rgba32f() { - test_grayscale_alpha_preserved(super::DynamicImage::new_rgba32f(1, 1)); - test_grayscale_alpha_preserved(super::DynamicImage::new(1, 1, ColorType::Rgba32F)); - } - #[test] fn test_dynamic_image_default_implementation() { // Test that structs wrapping a DynamicImage are able to auto-derive the Default trait diff --git a/src/flat.rs b/src/flat.rs index 687d480368..d6c075de0e 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -1327,6 +1327,20 @@ where let channels = self.inner.layout.channels; self.inner.shrink_to(channels, width, height); } + + fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut P + where + Buffer: AsMut<[P::Subpixel]> + AsRef<[P::Subpixel]>, + { + if !self.inner.in_bounds(0, x, y) { + panic_pixel_out_of_bounds((x, y), self.dimensions()) + } + + let base_index = self.inner.in_bounds_index(0, x, y); + let channel_count =

::CHANNEL_COUNT as usize; + let pixel_range = base_index..base_index + channel_count; + P::from_slice_mut(&mut self.inner.samples.as_mut()[pixel_range]) + } } // The out-of-bounds panic for single sample access similar to `slice::index`. @@ -1462,23 +1476,10 @@ impl GenericImage for ViewMut where Buffer: AsMut<[P::Subpixel]> + AsRef<[P::Subpixel]>, { - fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut Self::Pixel { - if !self.inner.in_bounds(0, x, y) { - panic_pixel_out_of_bounds((x, y), self.dimensions()) - } - - let base_index = self.inner.in_bounds_index(0, x, y); - let channel_count =

::CHANNEL_COUNT as usize; - let pixel_range = base_index..base_index + channel_count; - P::from_slice_mut(&mut self.inner.samples.as_mut()[pixel_range]) - } - - #[allow(deprecated)] fn put_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { *self.get_pixel_mut(x, y) = pixel; } - #[allow(deprecated)] fn blend_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { self.get_pixel_mut(x, y).blend(&pixel); } diff --git a/src/image.rs b/src/image.rs index e2bddf3a29..46c1be178b 100644 --- a/src/image.rs +++ b/src/image.rs @@ -943,29 +943,6 @@ pub trait GenericImageView { /// A trait for manipulating images. pub trait GenericImage: GenericImageView { - /// Gets a reference to the mutable pixel at location `(x, y)`. Indexed from top left. - /// - /// # Panics - /// - /// Panics if `(x, y)` is out of bounds. - /// - /// Panics for dynamic images (this method is deprecated and will be removed). - /// - /// ## Known issues - /// - /// This requires the buffer to contain a unique set of continuous channels in the exact order - /// and byte representation that the pixel type requires. This is somewhat restrictive. - /// - /// TODO: Maybe use some kind of entry API? this would allow pixel type conversion on the fly - /// while still doing only one array lookup: - /// - /// ```ignore - /// let px = image.pixel_entry_at(x,y); - /// px.set_from_rgba(rgba) - /// ``` - #[deprecated(since = "0.24.0", note = "Use `get_pixel` and `put_pixel` instead.")] - fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut Self::Pixel; - /// Put a pixel at location (x, y). Indexed from top left. /// /// # Panics @@ -1290,10 +1267,6 @@ where I: DerefMut, I::Target: GenericImage + Sized, { - fn get_pixel_mut(&mut self, x: u32, y: u32) -> &mut Self::Pixel { - self.image.get_pixel_mut(x + self.xoffset, y + self.yoffset) - } - fn put_pixel(&mut self, x: u32, y: u32, pixel: Self::Pixel) { self.image .put_pixel(x + self.xoffset, y + self.yoffset, pixel); diff --git a/src/imageops/mod.rs b/src/imageops/mod.rs index 61a742e038..35436fdf88 100644 --- a/src/imageops/mod.rs +++ b/src/imageops/mod.rs @@ -249,7 +249,7 @@ where /// use image::{RgbaImage}; /// /// let mut img = RgbaImage::new(1920, 1080); -/// let tile = image::open("tile.png").unwrap(); +/// let tile = image::open("tile.png").unwrap().to_rgba8(); /// /// image::imageops::tile(&mut img, &tile); /// img.save("tiled_wallpaper.png").unwrap(); diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index 1f6ce53a77..ca2d6536c7 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -1060,7 +1060,7 @@ where #[cfg(test)] mod tests { use super::{resize, sample_bilinear, sample_nearest, FilterType}; - use crate::{GenericImageView, ImageBuffer, RgbImage}; + use crate::{ImageBuffer, RgbImage}; #[cfg(feature = "benchmarks")] use test; @@ -1070,7 +1070,7 @@ mod tests { use std::path::Path; let img = crate::open(Path::new("./examples/fractal.png")).unwrap(); b.iter(|| { - test::black_box(resize(&img, 200, 200, FilterType::Nearest)); + test::black_box(img.resize(200, 200, FilterType::Nearest)); }); b.bytes = 800 * 800 * 3 + 200 * 200 * 3; } @@ -1079,8 +1079,10 @@ mod tests { #[cfg(feature = "png")] fn test_resize_same_size() { use std::path::Path; - let img = crate::open(Path::new("./examples/fractal.png")).unwrap(); - let resize = img.resize(img.width(), img.height(), FilterType::Triangle); + let img = crate::open(Path::new("./examples/fractal.png")) + .unwrap() + .to_rgba8(); + let resize = resize(&img, img.width(), img.height(), FilterType::Triangle); assert!(img.pixels().eq(resize.pixels())) } @@ -1088,7 +1090,9 @@ mod tests { #[cfg(feature = "png")] fn test_sample_bilinear() { use std::path::Path; - let img = crate::open(Path::new("./examples/fractal.png")).unwrap(); + let img = crate::open(Path::new("./examples/fractal.png")) + .unwrap() + .to_rgb8(); assert!(sample_bilinear(&img, 0., 0.).is_some()); assert!(sample_bilinear(&img, 1., 0.).is_some()); assert!(sample_bilinear(&img, 0., 1.).is_some()); @@ -1107,7 +1111,9 @@ mod tests { #[cfg(feature = "png")] fn test_sample_nearest() { use std::path::Path; - let img = crate::open(Path::new("./examples/fractal.png")).unwrap(); + let img = crate::open(Path::new("./examples/fractal.png")) + .unwrap() + .to_rgba8(); assert!(sample_nearest(&img, 0., 0.).is_some()); assert!(sample_nearest(&img, 1., 0.).is_some()); assert!(sample_nearest(&img, 0., 1.).is_some());