use std::fs::File;
use std::io::{self, BufWriter, Seek, Write};
use std::path::Path;
use crate::color::{self, FromColor, IntoColor};
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,
};
use crate::io::encoder::ImageEncoderBoxed;
use crate::io::free_functions::{self, encoder_for_format};
use crate::math::resize_dimensions;
use crate::metadata::Orientation;
use crate::traits::Pixel;
use crate::{
imageops,
metadata::{Cicp, CicpColorPrimaries, CicpTransferCharacteristics},
ConvertColorOptions, ExtendedColorType, GenericImage, GenericImageView, ImageDecoder,
ImageEncoder, ImageFormat, ImageReader, Luma, LumaA,
};
#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum DynamicImage {
ImageLuma8(GrayImage),
ImageLumaA8(GrayAlphaImage),
ImageRgb8(RgbImage),
ImageRgba8(RgbaImage),
ImageLuma16(Gray16Image),
ImageLumaA16(GrayAlpha16Image),
ImageRgb16(Rgb16Image),
ImageRgba16(Rgba16Image),
ImageRgb32F(Rgb32FImage),
ImageRgba32F(Rgba32FImage),
}
macro_rules! dynamic_map(
($dynimage: expr, $image: pat => $action: expr) => ({
use DynamicImage::*;
match $dynimage {
ImageLuma8($image) => ImageLuma8($action),
ImageLumaA8($image) => ImageLumaA8($action),
ImageRgb8($image) => ImageRgb8($action),
ImageRgba8($image) => ImageRgba8($action),
ImageLuma16($image) => ImageLuma16($action),
ImageLumaA16($image) => ImageLumaA16($action),
ImageRgb16($image) => ImageRgb16($action),
ImageRgba16($image) => ImageRgba16($action),
ImageRgb32F($image) => ImageRgb32F($action),
ImageRgba32F($image) => ImageRgba32F($action),
}
});
($dynimage: expr, $image:pat_param, $action: expr) => (
match $dynimage {
DynamicImage::ImageLuma8($image) => $action,
DynamicImage::ImageLumaA8($image) => $action,
DynamicImage::ImageRgb8($image) => $action,
DynamicImage::ImageRgba8($image) => $action,
DynamicImage::ImageLuma16($image) => $action,
DynamicImage::ImageLumaA16($image) => $action,
DynamicImage::ImageRgb16($image) => $action,
DynamicImage::ImageRgba16($image) => $action,
DynamicImage::ImageRgb32F($image) => $action,
DynamicImage::ImageRgba32F($image) => $action,
}
);
);
impl Clone for DynamicImage {
fn clone(&self) -> Self {
dynamic_map!(*self, ref p, DynamicImage::from(p.clone()))
}
fn clone_from(&mut self, source: &Self) {
match (self, source) {
(Self::ImageLuma8(p1), Self::ImageLuma8(p2)) => p1.clone_from(p2),
(Self::ImageLumaA8(p1), Self::ImageLumaA8(p2)) => p1.clone_from(p2),
(Self::ImageRgb8(p1), Self::ImageRgb8(p2)) => p1.clone_from(p2),
(Self::ImageRgba8(p1), Self::ImageRgba8(p2)) => p1.clone_from(p2),
(Self::ImageLuma16(p1), Self::ImageLuma16(p2)) => p1.clone_from(p2),
(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::ImageRgb32F(p1), Self::ImageRgb32F(p2)) => p1.clone_from(p2),
(Self::ImageRgba32F(p1), Self::ImageRgba32F(p2)) => p1.clone_from(p2),
(this, source) => *this = source.clone(),
}
}
}
impl DynamicImage {
#[must_use]
pub fn new(w: u32, h: u32, color: color::ColorType) -> DynamicImage {
use color::ColorType::*;
match color {
L8 => Self::new_luma8(w, h),
La8 => Self::new_luma_a8(w, h),
Rgb8 => Self::new_rgb8(w, h),
Rgba8 => Self::new_rgba8(w, h),
L16 => Self::new_luma16(w, h),
La16 => Self::new_luma_a16(w, h),
Rgb16 => Self::new_rgb16(w, h),
Rgba16 => Self::new_rgba16(w, h),
Rgb32F => Self::new_rgb32f(w, h),
Rgba32F => Self::new_rgba32f(w, h),
}
}
#[must_use]
pub fn new_luma8(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageLuma8(ImageBuffer::new(w, h))
}
#[must_use]
pub fn new_luma_a8(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageLumaA8(ImageBuffer::new(w, h))
}
#[must_use]
pub fn new_rgb8(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageRgb8(ImageBuffer::new(w, h))
}
#[must_use]
pub fn new_rgba8(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageRgba8(ImageBuffer::new(w, h))
}
#[must_use]
pub fn new_luma16(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageLuma16(ImageBuffer::new(w, h))
}
#[must_use]
pub fn new_luma_a16(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageLumaA16(ImageBuffer::new(w, h))
}
#[must_use]
pub fn new_rgb16(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageRgb16(ImageBuffer::new(w, h))
}
#[must_use]
pub fn new_rgba16(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageRgba16(ImageBuffer::new(w, h))
}
#[must_use]
pub fn new_rgb32f(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageRgb32F(ImageBuffer::new(w, h))
}
#[must_use]
pub fn new_rgba32f(w: u32, h: u32) -> DynamicImage {
DynamicImage::ImageRgba32F(ImageBuffer::new(w, h))
}
pub fn from_decoder(decoder: impl ImageDecoder) -> ImageResult<Self> {
decoder_to_image(decoder)
}
#[inline]
#[must_use]
pub fn to<
T: Pixel
+ FromColor<color::Rgb<u8>>
+ FromColor<color::Rgb<f32>>
+ FromColor<color::Rgba<u8>>
+ FromColor<color::Rgba<u16>>
+ FromColor<color::Rgba<f32>>
+ FromColor<color::Rgb<u16>>
+ FromColor<Luma<u8>>
+ FromColor<Luma<u16>>
+ FromColor<LumaA<u16>>
+ FromColor<LumaA<u8>>,
>(
&self,
) -> ImageBuffer<T, Vec<T::Subpixel>> {
dynamic_map!(*self, ref p, p.convert())
}
#[must_use]
pub fn to_rgb8(&self) -> RgbImage {
match self {
DynamicImage::ImageRgb8(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_rgb16(&self) -> Rgb16Image {
match self {
DynamicImage::ImageRgb16(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_rgb32f(&self) -> Rgb32FImage {
match self {
DynamicImage::ImageRgb32F(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_rgba8(&self) -> RgbaImage {
match self {
DynamicImage::ImageRgba8(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_rgba16(&self) -> Rgba16Image {
match self {
DynamicImage::ImageRgba16(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_rgba32f(&self) -> Rgba32FImage {
match self {
DynamicImage::ImageRgba32F(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_luma8(&self) -> GrayImage {
match self {
DynamicImage::ImageLuma8(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_luma16(&self) -> Gray16Image {
match self {
DynamicImage::ImageLuma16(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_luma32f(&self) -> ImageBuffer<Luma<f32>, Vec<f32>> {
dynamic_map!(self, ref p, p.cast_in_color_space())
}
#[must_use]
pub fn to_luma_alpha8(&self) -> GrayAlphaImage {
match self {
DynamicImage::ImageLumaA8(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_luma_alpha16(&self) -> GrayAlpha16Image {
match self {
DynamicImage::ImageLumaA16(x) => x.clone(),
x => dynamic_map!(x, ref p, p.cast_in_color_space()),
}
}
#[must_use]
pub fn to_luma_alpha32f(&self) -> ImageBuffer<LumaA<f32>, Vec<f32>> {
dynamic_map!(self, ref p, p.cast_in_color_space())
}
#[must_use]
pub fn into_rgb8(self) -> RgbImage {
match self {
DynamicImage::ImageRgb8(x) => x,
x => x.to_rgb8(),
}
}
#[must_use]
pub fn into_rgb16(self) -> Rgb16Image {
match self {
DynamicImage::ImageRgb16(x) => x,
x => x.to_rgb16(),
}
}
#[must_use]
pub fn into_rgb32f(self) -> Rgb32FImage {
match self {
DynamicImage::ImageRgb32F(x) => x,
x => x.to_rgb32f(),
}
}
#[must_use]
pub fn into_rgba8(self) -> RgbaImage {
match self {
DynamicImage::ImageRgba8(x) => x,
x => x.to_rgba8(),
}
}
#[must_use]
pub fn into_rgba16(self) -> Rgba16Image {
match self {
DynamicImage::ImageRgba16(x) => x,
x => x.to_rgba16(),
}
}
#[must_use]
pub fn into_rgba32f(self) -> Rgba32FImage {
match self {
DynamicImage::ImageRgba32F(x) => x,
x => x.to_rgba32f(),
}
}
#[must_use]
pub fn into_luma8(self) -> GrayImage {
match self {
DynamicImage::ImageLuma8(x) => x,
x => x.to_luma8(),
}
}
#[must_use]
pub fn into_luma16(self) -> Gray16Image {
match self {
DynamicImage::ImageLuma16(x) => x,
x => x.to_luma16(),
}
}
#[must_use]
pub fn into_luma_alpha8(self) -> GrayAlphaImage {
match self {
DynamicImage::ImageLumaA8(x) => x,
x => x.to_luma_alpha8(),
}
}
#[must_use]
pub fn into_luma_alpha16(self) -> GrayAlpha16Image {
match self {
DynamicImage::ImageLumaA16(x) => x,
x => x.to_luma_alpha16(),
}
}
#[must_use]
pub fn crop(&mut self, x: u32, y: u32, width: u32, height: u32) -> DynamicImage {
dynamic_map!(*self, ref mut p => imageops::crop(p, x, y, width, height).to_image())
}
#[must_use]
pub fn crop_imm(&self, x: u32, y: u32, width: u32, height: u32) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::crop_imm(p, x, y, width, height).to_image())
}
#[must_use]
pub fn as_rgb8(&self) -> Option<&RgbImage> {
match *self {
DynamicImage::ImageRgb8(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_rgb8(&mut self) -> Option<&mut RgbImage> {
match *self {
DynamicImage::ImageRgb8(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_rgba8(&self) -> Option<&RgbaImage> {
match *self {
DynamicImage::ImageRgba8(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_rgba8(&mut self) -> Option<&mut RgbaImage> {
match *self {
DynamicImage::ImageRgba8(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_luma8(&self) -> Option<&GrayImage> {
match *self {
DynamicImage::ImageLuma8(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_luma8(&mut self) -> Option<&mut GrayImage> {
match *self {
DynamicImage::ImageLuma8(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_luma_alpha8(&self) -> Option<&GrayAlphaImage> {
match *self {
DynamicImage::ImageLumaA8(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_luma_alpha8(&mut self) -> Option<&mut GrayAlphaImage> {
match *self {
DynamicImage::ImageLumaA8(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_rgb16(&self) -> Option<&Rgb16Image> {
match *self {
DynamicImage::ImageRgb16(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_rgb16(&mut self) -> Option<&mut Rgb16Image> {
match *self {
DynamicImage::ImageRgb16(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_rgba16(&self) -> Option<&Rgba16Image> {
match *self {
DynamicImage::ImageRgba16(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_rgba16(&mut self) -> Option<&mut Rgba16Image> {
match *self {
DynamicImage::ImageRgba16(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_rgb32f(&self) -> Option<&Rgb32FImage> {
match *self {
DynamicImage::ImageRgb32F(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_rgb32f(&mut self) -> Option<&mut Rgb32FImage> {
match *self {
DynamicImage::ImageRgb32F(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_rgba32f(&self) -> Option<&Rgba32FImage> {
match *self {
DynamicImage::ImageRgba32F(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_rgba32f(&mut self) -> Option<&mut Rgba32FImage> {
match *self {
DynamicImage::ImageRgba32F(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_luma16(&self) -> Option<&Gray16Image> {
match *self {
DynamicImage::ImageLuma16(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_luma16(&mut self) -> Option<&mut Gray16Image> {
match *self {
DynamicImage::ImageLuma16(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_luma_alpha16(&self) -> Option<&GrayAlpha16Image> {
match *self {
DynamicImage::ImageLumaA16(ref p) => Some(p),
_ => None,
}
}
pub fn as_mut_luma_alpha16(&mut self) -> Option<&mut GrayAlpha16Image> {
match *self {
DynamicImage::ImageLumaA16(ref mut p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_flat_samples_u8(&self) -> Option<FlatSamples<&[u8]>> {
match *self {
DynamicImage::ImageLuma8(ref p) => Some(p.as_flat_samples()),
DynamicImage::ImageLumaA8(ref p) => Some(p.as_flat_samples()),
DynamicImage::ImageRgb8(ref p) => Some(p.as_flat_samples()),
DynamicImage::ImageRgba8(ref p) => Some(p.as_flat_samples()),
_ => None,
}
}
#[must_use]
pub fn as_flat_samples_u16(&self) -> Option<FlatSamples<&[u16]>> {
match *self {
DynamicImage::ImageLuma16(ref p) => Some(p.as_flat_samples()),
DynamicImage::ImageLumaA16(ref p) => Some(p.as_flat_samples()),
DynamicImage::ImageRgb16(ref p) => Some(p.as_flat_samples()),
DynamicImage::ImageRgba16(ref p) => Some(p.as_flat_samples()),
_ => None,
}
}
#[must_use]
pub fn as_flat_samples_f32(&self) -> Option<FlatSamples<&[f32]>> {
match *self {
DynamicImage::ImageRgb32F(ref p) => Some(p.as_flat_samples()),
DynamicImage::ImageRgba32F(ref p) => Some(p.as_flat_samples()),
_ => None,
}
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
dynamic_map!(
*self,
ref image_buffer,
bytemuck::cast_slice(image_buffer.as_raw())
)
}
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
dynamic_map!(self, image_buffer, {
match bytemuck::allocation::try_cast_vec(image_buffer.into_raw()) {
Ok(vec) => vec,
Err((_, vec)) => {
bytemuck::cast_slice(&vec).to_owned()
}
}
})
}
#[must_use]
pub fn color(&self) -> color::ColorType {
match *self {
DynamicImage::ImageLuma8(_) => color::ColorType::L8,
DynamicImage::ImageLumaA8(_) => color::ColorType::La8,
DynamicImage::ImageRgb8(_) => color::ColorType::Rgb8,
DynamicImage::ImageRgba8(_) => color::ColorType::Rgba8,
DynamicImage::ImageLuma16(_) => color::ColorType::L16,
DynamicImage::ImageLumaA16(_) => color::ColorType::La16,
DynamicImage::ImageRgb16(_) => color::ColorType::Rgb16,
DynamicImage::ImageRgba16(_) => color::ColorType::Rgba16,
DynamicImage::ImageRgb32F(_) => color::ColorType::Rgb32F,
DynamicImage::ImageRgba32F(_) => color::ColorType::Rgba32F,
}
}
#[must_use]
pub fn width(&self) -> u32 {
dynamic_map!(*self, ref p, { p.width() })
}
#[must_use]
pub fn height(&self) -> u32 {
dynamic_map!(*self, ref p, { p.height() })
}
pub fn set_rgb_primaries(&mut self, color: CicpColorPrimaries) {
dynamic_map!(self, ref mut p, p.set_rgb_primaries(color));
}
pub fn set_transfer_function(&mut self, tf: CicpTransferCharacteristics) {
dynamic_map!(self, ref mut p, p.set_transfer_function(tf));
}
pub fn color_space(&self) -> Cicp {
dynamic_map!(self, ref p, p.color_space())
}
pub fn set_color_space(&mut self, cicp: Cicp) -> ImageResult<()> {
dynamic_map!(self, ref mut p, p.set_color_space(cicp))
}
#[must_use]
pub fn has_alpha(&self) -> bool {
self.color().has_alpha()
}
#[must_use]
pub fn grayscale(&self) -> DynamicImage {
match *self {
DynamicImage::ImageLuma8(ref p) => DynamicImage::ImageLuma8(p.clone()),
DynamicImage::ImageLumaA8(ref p) => {
DynamicImage::ImageLumaA8(imageops::grayscale_alpha(p))
}
DynamicImage::ImageRgb8(ref p) => DynamicImage::ImageLuma8(imageops::grayscale(p)),
DynamicImage::ImageRgba8(ref p) => {
DynamicImage::ImageLumaA8(imageops::grayscale_alpha(p))
}
DynamicImage::ImageLuma16(ref p) => DynamicImage::ImageLuma16(p.clone()),
DynamicImage::ImageLumaA16(ref p) => {
DynamicImage::ImageLumaA16(imageops::grayscale_alpha(p))
}
DynamicImage::ImageRgb16(ref p) => DynamicImage::ImageLuma16(imageops::grayscale(p)),
DynamicImage::ImageRgba16(ref p) => {
DynamicImage::ImageLumaA16(imageops::grayscale_alpha(p))
}
DynamicImage::ImageRgb32F(ref p) => {
DynamicImage::ImageRgb32F(imageops::grayscale_with_type(p))
}
DynamicImage::ImageRgba32F(ref p) => {
DynamicImage::ImageRgba32F(imageops::grayscale_with_type_alpha(p))
}
}
}
pub fn invert(&mut self) {
dynamic_map!(*self, ref mut p, imageops::invert(p));
}
#[must_use]
pub fn resize(&self, nwidth: u32, nheight: u32, filter: imageops::FilterType) -> DynamicImage {
if (nwidth, nheight) == self.dimensions() {
return self.clone();
}
let (width2, height2) =
resize_dimensions(self.width(), self.height(), nwidth, nheight, false);
self.resize_exact(width2, height2, filter)
}
#[must_use]
pub fn resize_exact(
&self,
nwidth: u32,
nheight: u32,
filter: imageops::FilterType,
) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::resize(p, nwidth, nheight, filter))
}
#[must_use]
pub fn thumbnail(&self, nwidth: u32, nheight: u32) -> DynamicImage {
let (width2, height2) =
resize_dimensions(self.width(), self.height(), nwidth, nheight, false);
self.thumbnail_exact(width2, height2)
}
#[must_use]
pub fn thumbnail_exact(&self, nwidth: u32, nheight: u32) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::thumbnail(p, nwidth, nheight))
}
#[must_use]
pub fn resize_to_fill(
&self,
nwidth: u32,
nheight: u32,
filter: imageops::FilterType,
) -> DynamicImage {
let (width2, height2) =
resize_dimensions(self.width(), self.height(), nwidth, nheight, true);
let mut intermediate = self.resize_exact(width2, height2, filter);
let (iwidth, iheight) = intermediate.dimensions();
let ratio = u64::from(iwidth) * u64::from(nheight);
let nratio = u64::from(nwidth) * u64::from(iheight);
if nratio > ratio {
intermediate.crop(0, (iheight - nheight) / 2, nwidth, nheight)
} else {
intermediate.crop((iwidth - nwidth) / 2, 0, nwidth, nheight)
}
}
#[must_use]
pub fn blur(&self, sigma: f32) -> DynamicImage {
gaussian_blur_dyn_image(
self,
GaussianBlurParameters::new_from_sigma(if sigma == 0.0 { 0.8 } else { sigma }),
)
}
#[must_use]
pub fn blur_advanced(&self, parameters: GaussianBlurParameters) -> DynamicImage {
gaussian_blur_dyn_image(self, parameters)
}
#[must_use]
pub fn fast_blur(&self, sigma: f32) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::fast_blur(p, sigma))
}
#[must_use]
pub fn unsharpen(&self, sigma: f32, threshold: i32) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::unsharpen(p, sigma, threshold))
}
#[must_use]
pub fn filter3x3(&self, kernel: &[f32]) -> DynamicImage {
assert_eq!(9, kernel.len(), "filter must be 3 x 3");
dynamic_map!(*self, ref p => imageops::filter3x3(p, kernel))
}
#[must_use]
pub fn adjust_contrast(&self, c: f32) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::contrast(p, c))
}
#[must_use]
pub fn brighten(&self, value: i32) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::brighten(p, value))
}
#[must_use]
pub fn huerotate(&self, value: i32) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::huerotate(p, value))
}
#[must_use]
pub fn flipv(&self) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::flip_vertical(p))
}
fn flipv_in_place(&mut self) {
dynamic_map!(*self, ref mut p, imageops::flip_vertical_in_place(p))
}
#[must_use]
pub fn fliph(&self) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::flip_horizontal(p))
}
fn fliph_in_place(&mut self) {
dynamic_map!(*self, ref mut p, imageops::flip_horizontal_in_place(p))
}
#[must_use]
pub fn rotate90(&self) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::rotate90(p))
}
#[must_use]
pub fn rotate180(&self) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::rotate180(p))
}
fn rotate180_in_place(&mut self) {
dynamic_map!(*self, ref mut p, imageops::rotate180_in_place(p))
}
#[must_use]
pub fn rotate270(&self) -> DynamicImage {
dynamic_map!(*self, ref p => imageops::rotate270(p))
}
pub fn apply_orientation(&mut self, orientation: Orientation) {
let image = self;
match orientation {
Orientation::NoTransforms => (),
Orientation::Rotate90 => *image = image.rotate90(),
Orientation::Rotate180 => image.rotate180_in_place(),
Orientation::Rotate270 => *image = image.rotate270(),
Orientation::FlipHorizontal => image.fliph_in_place(),
Orientation::FlipVertical => image.flipv_in_place(),
Orientation::Rotate90FlipH => {
let mut new_image = image.rotate90();
new_image.fliph_in_place();
*image = new_image;
}
Orientation::Rotate270FlipH => {
let mut new_image = image.rotate270();
new_image.fliph_in_place();
*image = new_image;
}
}
}
pub fn copy_from_color_space(
&mut self,
other: &DynamicImage,
mut options: ConvertColorOptions,
) -> ImageResult<()> {
if self.color_space() == other.color_space() {
dynamic_map!(
self,
ref mut p,
*p = dynamic_map!(other, ref o, o.cast_in_color_space())
);
return Ok(());
}
match (&mut *self, other) {
(DynamicImage::ImageRgb8(img), DynamicImage::ImageRgb8(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgb8(img), DynamicImage::ImageRgba8(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgba8(img), DynamicImage::ImageRgb8(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgba8(img), DynamicImage::ImageRgba8(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgb16(img), DynamicImage::ImageRgb16(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgb16(img), DynamicImage::ImageRgba16(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgba16(img), DynamicImage::ImageRgb16(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgba16(img), DynamicImage::ImageRgba16(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgb32F(img), DynamicImage::ImageRgb32F(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgb32F(img), DynamicImage::ImageRgba32F(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgba32F(img), DynamicImage::ImageRgb32F(other)) => {
return img.copy_from_color_space(other, options);
}
(DynamicImage::ImageRgba32F(img), DynamicImage::ImageRgba32F(other)) => {
return img.copy_from_color_space(other, options);
}
_ => {}
};
let cicp = options.as_transform(other.color_space(), self.color_space())?;
cicp.transform_dynamic(self, other);
Ok(())
}
pub fn apply_color_space(
&mut self,
cicp: Cicp,
options: ConvertColorOptions,
) -> ImageResult<()> {
if self.color_space() == cicp {
return Ok(());
}
let mut target = self.clone();
target.set_color_space(cicp)?;
target.copy_from_color_space(self, options)?;
*self = target;
Ok(())
}
pub fn convert_color_space(
&mut self,
cicp: Cicp,
options: ConvertColorOptions,
color: color::ColorType,
) -> ImageResult<()> {
if self.color() == color {
return self.apply_color_space(cicp, options);
}
let rgb = cicp.try_into_rgb()?;
let mut target = DynamicImage::new(self.width(), self.height(), color);
dynamic_map!(target, ref mut p, p.set_rgb_color_space(rgb));
target.copy_from_color_space(self, options)?;
*self = target;
Ok(())
}
fn write_with_encoder_impl<'a>(
&self,
encoder: Box<dyn ImageEncoderBoxed + 'a>,
) -> ImageResult<()> {
let converted = encoder.make_compatible_img(crate::io::encoder::MethodSealedToImage, self);
let img = converted.as_ref().unwrap_or(self);
encoder.write_image(
img.as_bytes(),
img.width(),
img.height(),
img.color().into(),
)
}
pub fn write_to<W: Write + Seek>(&self, mut w: W, format: ImageFormat) -> ImageResult<()> {
let encoder = encoder_for_format(format, &mut w)?;
self.write_with_encoder_impl(encoder)
}
pub fn write_with_encoder(&self, encoder: impl ImageEncoder) -> ImageResult<()> {
self.write_with_encoder_impl(Box::new(encoder))
}
pub fn save<Q>(&self, path: Q) -> ImageResult<()>
where
Q: AsRef<Path>,
{
let format = ImageFormat::from_path(path.as_ref())?;
self.save_with_format(path, format)
}
pub fn save_with_format<Q>(&self, path: Q, format: ImageFormat) -> ImageResult<()>
where
Q: AsRef<Path>,
{
let file = &mut BufWriter::new(File::create(path)?);
let encoder = encoder_for_format(format, file)?;
self.write_with_encoder_impl(encoder)
}
}
impl From<GrayImage> for DynamicImage {
fn from(image: GrayImage) -> Self {
DynamicImage::ImageLuma8(image)
}
}
impl From<GrayAlphaImage> for DynamicImage {
fn from(image: GrayAlphaImage) -> Self {
DynamicImage::ImageLumaA8(image)
}
}
impl From<RgbImage> for DynamicImage {
fn from(image: RgbImage) -> Self {
DynamicImage::ImageRgb8(image)
}
}
impl From<RgbaImage> for DynamicImage {
fn from(image: RgbaImage) -> Self {
DynamicImage::ImageRgba8(image)
}
}
impl From<Gray16Image> for DynamicImage {
fn from(image: Gray16Image) -> Self {
DynamicImage::ImageLuma16(image)
}
}
impl From<GrayAlpha16Image> for DynamicImage {
fn from(image: GrayAlpha16Image) -> Self {
DynamicImage::ImageLumaA16(image)
}
}
impl From<Rgb16Image> for DynamicImage {
fn from(image: Rgb16Image) -> Self {
DynamicImage::ImageRgb16(image)
}
}
impl From<Rgba16Image> for DynamicImage {
fn from(image: Rgba16Image) -> Self {
DynamicImage::ImageRgba16(image)
}
}
impl From<Rgb32FImage> for DynamicImage {
fn from(image: Rgb32FImage) -> Self {
DynamicImage::ImageRgb32F(image)
}
}
impl From<Rgba32FImage> for DynamicImage {
fn from(image: Rgba32FImage) -> Self {
DynamicImage::ImageRgba32F(image)
}
}
impl From<ImageBuffer<Luma<f32>, Vec<f32>>> for DynamicImage {
fn from(image: ImageBuffer<Luma<f32>, Vec<f32>>) -> Self {
DynamicImage::ImageRgb32F(image.convert())
}
}
impl From<ImageBuffer<LumaA<f32>, Vec<f32>>> for DynamicImage {
fn from(image: ImageBuffer<LumaA<f32>, Vec<f32>>) -> Self {
DynamicImage::ImageRgba32F(image.convert())
}
}
#[allow(deprecated)]
impl GenericImageView for DynamicImage {
type Pixel = color::Rgba<u8>;
fn dimensions(&self) -> (u32, u32) {
dynamic_map!(*self, ref p, p.dimensions())
}
fn get_pixel(&self, x: u32, y: u32) -> color::Rgba<u8> {
dynamic_map!(*self, ref p, p.get_pixel(x, y).to_rgba().into_color())
}
}
#[allow(deprecated)]
impl GenericImage for DynamicImage {
fn put_pixel(&mut self, x: u32, y: u32, pixel: color::Rgba<u8>) {
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()),
}
}
fn blend_pixel(&mut self, x: u32, y: u32, pixel: color::Rgba<u8>) {
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 get_pixel_mut(&mut self, _: u32, _: u32) -> &mut color::Rgba<u8> {
unimplemented!()
}
}
impl Default for DynamicImage {
fn default() -> Self {
Self::ImageRgba8(Default::default())
}
}
fn decoder_to_image<I: ImageDecoder>(decoder: I) -> ImageResult<DynamicImage> {
let (w, h) = decoder.dimensions();
let color_type = decoder.color_type();
let mut image = match color_type {
color::ColorType::Rgb8 => {
let buf = free_functions::decoder_to_vec(decoder)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb8)
}
color::ColorType::Rgba8 => {
let buf = free_functions::decoder_to_vec(decoder)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba8)
}
color::ColorType::L8 => {
let buf = free_functions::decoder_to_vec(decoder)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma8)
}
color::ColorType::La8 => {
let buf = free_functions::decoder_to_vec(decoder)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA8)
}
color::ColorType::Rgb16 => {
let buf = free_functions::decoder_to_vec(decoder)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb16)
}
color::ColorType::Rgba16 => {
let buf = free_functions::decoder_to_vec(decoder)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba16)
}
color::ColorType::Rgb32F => {
let buf = free_functions::decoder_to_vec(decoder)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb32F)
}
color::ColorType::Rgba32F => {
let buf = free_functions::decoder_to_vec(decoder)?;
ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba32F)
}
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)
}
}
.ok_or_else(|| {
ImageError::Parameter(ParameterError::from_kind(
ParameterErrorKind::DimensionMismatch,
))
})?;
image.set_rgb_primaries(Cicp::SRGB.primaries);
image.set_transfer_function(Cicp::SRGB.transfer);
Ok(image)
}
pub fn open<P>(path: P) -> ImageResult<DynamicImage>
where
P: AsRef<Path>,
{
ImageReader::open(path)?.decode()
}
pub fn image_dimensions<P>(path: P) -> ImageResult<(u32, u32)>
where
P: AsRef<Path>,
{
ImageReader::open(path)?.into_dimensions()
}
pub fn write_buffer_with_format<W: Write + Seek>(
buffered_writer: &mut W,
buf: &[u8],
width: u32,
height: u32,
color: impl Into<ExtendedColorType>,
format: ImageFormat,
) -> ImageResult<()> {
let encoder = encoder_for_format(format, buffered_writer)?;
encoder.write_image(buf, width, height, color.into())
}
pub fn load_from_memory(buffer: &[u8]) -> ImageResult<DynamicImage> {
let format = free_functions::guess_format(buffer)?;
load_from_memory_with_format(buffer, format)
}
#[inline(always)]
pub fn load_from_memory_with_format(buf: &[u8], format: ImageFormat) -> ImageResult<DynamicImage> {
let b = io::Cursor::new(buf);
free_functions::load(b, format)
}
#[cfg(test)]
mod bench {
#[bench]
#[cfg(feature = "benchmarks")]
fn bench_conversion(b: &mut test::Bencher) {
let a = super::DynamicImage::ImageRgb8(crate::ImageBuffer::new(1000, 1000));
b.iter(|| a.to_luma8());
b.bytes = 1000 * 1000 * 3;
}
}
#[cfg(test)]
mod test {
use crate::metadata::{CicpColorPrimaries, CicpTransform};
use crate::ConvertColorOptions;
use crate::{color::ColorType, images::dynimage::Gray16Image};
use crate::{metadata::Cicp, ImageBuffer, Luma, Rgb, Rgba};
#[test]
fn test_empty_file() {
assert!(super::load_from_memory(b"").is_err());
}
#[cfg(feature = "jpeg")]
#[test]
fn image_dimensions() {
let im_path = "./tests/images/jpg/progressive/cat.jpg";
let dims = super::image_dimensions(im_path).unwrap();
assert_eq!(dims, (320, 240));
}
#[cfg(feature = "png")]
#[test]
fn open_16bpc_png() {
let im_path = "./tests/images/png/16bpc/basn6a16.png";
let image = super::open(im_path).unwrap();
assert_eq!(image.color(), ColorType::Rgba16);
}
fn test_grayscale(mut img: super::DynamicImage, alpha_discarded: bool) {
use crate::{GenericImage as _, GenericImageView as _};
img.put_pixel(0, 0, Rgba([255, 0, 0, 100]));
let expected_alpha = if alpha_discarded { 255 } else { 100 };
assert_eq!(
img.grayscale().get_pixel(0, 0),
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() {
#[derive(Default)]
#[allow(dead_code)]
struct Foo {
_image: super::DynamicImage,
}
}
#[test]
fn test_to_vecu8() {
let _ = super::DynamicImage::new_luma8(1, 1).into_bytes();
let _ = super::DynamicImage::new_luma16(1, 1).into_bytes();
}
#[test]
fn issue_1705_can_turn_16bit_image_into_bytes() {
let pixels = vec![65535u16; 64 * 64];
let img = ImageBuffer::from_vec(64, 64, pixels).unwrap();
let img = super::DynamicImage::ImageLuma16(img);
assert!(img.as_luma16().is_some());
let bytes: Vec<u8> = img.into_bytes();
assert_eq!(bytes, vec![0xFF; 64 * 64 * 2]);
}
#[test]
fn test_convert_to() {
use crate::Luma;
let image_luma8 = super::DynamicImage::new_luma8(1, 1);
let image_luma16 = super::DynamicImage::new_luma16(1, 1);
assert_eq!(image_luma8.to_luma16(), image_luma16.to_luma16());
let conv: Gray16Image = image_luma8.to();
assert_eq!(image_luma8.to_luma16(), conv);
let converted = image_luma8.to::<Luma<u16>>();
assert_eq!(image_luma8.to_luma16(), converted);
}
#[test]
fn color_conversion_srgb_p3() {
let mut source = super::DynamicImage::ImageRgb8({
ImageBuffer::from_fn(128, 128, |_, _| Rgb([255, 0, 0]))
});
let mut target = super::DynamicImage::ImageRgba8({
ImageBuffer::from_fn(128, 128, |_, _| Rgba(Default::default()))
});
source.set_rgb_primaries(Cicp::SRGB.primaries);
source.set_transfer_function(Cicp::SRGB.transfer);
target.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
target.set_transfer_function(Cicp::DISPLAY_P3.transfer);
let result = target.copy_from_color_space(&source, Default::default());
assert!(result.is_ok(), "{result:?}");
let target = target.as_rgba8().expect("Sample type unchanged");
assert_eq!(target[(0, 0)], Rgba([234u8, 51, 35, 255]));
}
#[test]
fn color_conversion_preserves_sample() {
let mut source = super::DynamicImage::ImageRgb16({
ImageBuffer::from_fn(128, 128, |_, _| Rgb([u16::MAX, 0, 0]))
});
let mut target = super::DynamicImage::ImageRgba8({
ImageBuffer::from_fn(128, 128, |_, _| Rgba(Default::default()))
});
source.set_rgb_primaries(Cicp::SRGB.primaries);
source.set_transfer_function(Cicp::SRGB.transfer);
target.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
target.set_transfer_function(Cicp::DISPLAY_P3.transfer);
let result = target.copy_from_color_space(&source, Default::default());
assert!(result.is_ok(), "{result:?}");
let target = target.as_rgba8().expect("Sample type unchanged");
assert_eq!(target[(0, 0)], Rgba([234u8, 51, 35, 255]));
}
#[test]
fn color_conversion_preserves_sample_in_fastpath() {
let source = super::DynamicImage::ImageRgb16({
ImageBuffer::from_fn(128, 128, |_, _| Rgb([u16::MAX, 0, 0]))
});
let mut target = super::DynamicImage::ImageRgba8({
ImageBuffer::from_fn(128, 128, |_, _| Rgba(Default::default()))
});
let result = target.copy_from_color_space(&source, Default::default());
assert!(result.is_ok(), "{result:?}");
let target = target.as_rgba8().expect("Sample type unchanged");
assert_eq!(target[(0, 0)], Rgba([255u8, 0, 0, 255]));
}
#[test]
fn color_conversion_rgb_to_luma() {
let source = super::DynamicImage::ImageRgb16({
ImageBuffer::from_fn(128, 128, |_, _| Rgb([0, u16::MAX, 0]))
});
let mut target = super::DynamicImage::ImageLuma8({
ImageBuffer::from_fn(128, 128, |_, _| Luma(Default::default()))
});
let result = target.copy_from_color_space(&source, Default::default());
assert!(result.is_ok(), "{result:?}");
target.as_luma8().expect("Sample type unchanged");
}
#[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");
for from in TYPES {
for to in TYPES {
let mut source = super::DynamicImage::new(16, 16, from);
let mut target = super::DynamicImage::new(16, 16, to);
source.set_rgb_primaries(Cicp::SRGB.primaries);
source.set_transfer_function(Cicp::SRGB.transfer);
target.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
target.set_transfer_function(Cicp::DISPLAY_P3.transfer);
target
.copy_from_color_space(
&source,
ConvertColorOptions {
transform: Some(transform.clone()),
..Default::default()
},
)
.expect("Failed to convert color space");
}
}
}
#[test]
fn apply_color_space() {
let mut buffer = super::DynamicImage::ImageRgb8({
ImageBuffer::from_fn(128, 128, |_, _| Rgb([u8::MAX, 0, 0]))
});
buffer.set_rgb_primaries(Cicp::SRGB.primaries);
buffer.set_transfer_function(Cicp::SRGB.transfer);
buffer
.apply_color_space(Cicp::DISPLAY_P3, Default::default())
.unwrap();
let target = buffer.as_rgb8().expect("Sample type unchanged");
assert_eq!(target[(0, 0)], Rgb([234u8, 51, 35]));
}
#[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");
for buffer in TYPES {
let mut buffer = super::DynamicImage::new(16, 16, buffer);
buffer.set_rgb_primaries(Cicp::SRGB.primaries);
buffer.set_transfer_function(Cicp::SRGB.transfer);
buffer
.apply_color_space(
Cicp::DISPLAY_P3,
ConvertColorOptions {
transform: Some(transform.clone()),
..Default::default()
},
)
.expect("Failed to convert color space");
}
}
#[test]
fn convert_color_space() {
let mut buffer = super::DynamicImage::ImageRgb16({
ImageBuffer::from_fn(128, 128, |_, _| Rgb([u16::MAX, 0, 0]))
});
buffer.set_rgb_primaries(Cicp::SRGB.primaries);
buffer.set_transfer_function(Cicp::SRGB.transfer);
buffer
.convert_color_space(Cicp::DISPLAY_P3, Default::default(), ColorType::Rgb8)
.unwrap();
let target = buffer.as_rgb8().expect("Sample type now rgb8");
assert_eq!(target[(0, 0)], Rgb([234u8, 51, 35]));
}
#[test]
fn into_luma_is_color_space_aware() {
let mut buffer = super::DynamicImage::ImageRgb16({
ImageBuffer::from_fn(128, 128, |_, _| Rgb([u16::MAX, 0, 0]))
});
buffer.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
buffer.set_transfer_function(Cicp::DISPLAY_P3.transfer);
let luma8 = buffer.clone().into_luma8();
assert_eq!(luma8[(0, 0)], Luma([58u8]));
assert_eq!(luma8.color_space(), Cicp::DISPLAY_P3);
buffer.set_rgb_primaries(Cicp::SRGB.primaries);
let luma8 = buffer.clone().into_luma8();
assert_eq!(luma8[(0, 0)], Luma([54u8]));
assert_ne!(luma8.color_space(), Cicp::DISPLAY_P3);
}
#[test]
fn from_luma_is_color_space_aware() {
let mut buffer = super::DynamicImage::ImageLuma16({
ImageBuffer::from_fn(128, 128, |_, _| Luma([u16::MAX]))
});
buffer.set_rgb_primaries(Cicp::DISPLAY_P3.primaries);
buffer.set_transfer_function(Cicp::DISPLAY_P3.transfer);
let rgb8 = buffer.clone().into_rgb8();
assert_eq!(rgb8[(0, 0)], Rgb([u8::MAX; 3]));
assert_eq!(rgb8.color_space(), Cicp::DISPLAY_P3);
buffer.set_rgb_primaries(Cicp::SRGB.primaries);
let rgb8 = buffer.clone().into_rgb8();
assert_eq!(rgb8[(0, 0)], Rgb([u8::MAX; 3]));
assert_ne!(rgb8.color_space(), Cicp::DISPLAY_P3);
}
#[test]
fn from_luma_for_all_chromaticities() {
const CHROMA: &[CicpColorPrimaries] = &[
(CicpColorPrimaries::SRgb),
(CicpColorPrimaries::RgbM),
(CicpColorPrimaries::RgbB),
(CicpColorPrimaries::Bt601),
(CicpColorPrimaries::Rgb240m),
(CicpColorPrimaries::GenericFilm),
(CicpColorPrimaries::Rgb2020),
(CicpColorPrimaries::Xyz),
(CicpColorPrimaries::SmpteRp431),
(CicpColorPrimaries::SmpteRp432),
(CicpColorPrimaries::Industry22),
(CicpColorPrimaries::Unspecified),
];
let mut buffer = super::DynamicImage::ImageLuma16({
ImageBuffer::from_fn(128, 128, |_, _| Luma([u16::MAX]))
});
for &chroma in CHROMA {
buffer.set_rgb_primaries(chroma);
let rgb = buffer.to_rgb8();
assert_eq!(
rgb[(0, 0)],
Rgb([u8::MAX; 3]),
"Failed for chroma: {chroma:?}"
);
}
}
#[test]
fn from_rgb_for_all_chromaticities() {
const CHROMA: &[(CicpColorPrimaries, [u8; 3], u8)] = &[
(CicpColorPrimaries::SRgb, [54, 182, 18], 143),
(CicpColorPrimaries::RgbM, [76, 150, 29], 114),
(CicpColorPrimaries::RgbB, [57, 180, 18], 141),
(CicpColorPrimaries::Bt601, [54, 179, 22], 139),
(CicpColorPrimaries::Rgb240m, [54, 179, 22], 139),
(CicpColorPrimaries::GenericFilm, [65, 173, 17], 135),
(CicpColorPrimaries::Rgb2020, [67, 173, 15], 136),
(CicpColorPrimaries::Xyz, [0, 255, 0], 255),
(CicpColorPrimaries::SmpteRp431, [53, 184, 18], 145),
(CicpColorPrimaries::SmpteRp432, [58, 176, 20], 137),
(CicpColorPrimaries::Industry22, [59, 171, 24], 131),
(CicpColorPrimaries::Unspecified, [54, 182, 18], 143),
];
let mut buffer = super::DynamicImage::ImageRgb8({
ImageBuffer::from_fn(128, 128, |_, _| Rgb([u8::MAX; 3]))
});
for &(chroma, rgb, luma) in CHROMA {
buffer.set_rgb_primaries(chroma);
for px in buffer.as_mut_rgb8().unwrap().pixels_mut() {
px.0 = rgb;
}
let buf = buffer.to_luma8();
assert_eq!(buf[(0, 0)], Luma([luma]), "Failed for chroma: {chroma:?}");
}
}
#[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");
for from in TYPES {
for to in TYPES {
let mut buffer = super::DynamicImage::new(16, 16, from);
buffer.set_rgb_primaries(Cicp::SRGB.primaries);
buffer.set_transfer_function(Cicp::SRGB.transfer);
let options = ConvertColorOptions {
transform: Some(transform.clone()),
..Default::default()
};
buffer
.convert_color_space(Cicp::DISPLAY_P3, options, to)
.expect("Failed to convert color space");
}
}
}
#[cfg(feature = "png")]
#[test]
fn color_space_independent_imageops() {
let im_path = "./tests/images/png/16bpc/basn6a16.png";
let mut image = super::open(im_path).unwrap();
let mut clone = image.clone();
image.set_color_space(Cicp::SRGB).unwrap();
clone.set_color_space(Cicp::DISPLAY_P3).unwrap();
const IMAGEOPS: &[&dyn Fn(&super::DynamicImage) -> super::DynamicImage] = &[
&|img| img.resize(32, 32, crate::imageops::FilterType::Lanczos3),
&|img| img.resize_exact(32, 32, crate::imageops::FilterType::Lanczos3),
&|img| img.thumbnail(8, 8),
&|img| img.thumbnail_exact(8, 8),
&|img| img.resize_to_fill(32, 32, crate::imageops::FilterType::Lanczos3),
&|img| img.blur(1.0),
&|img| {
img.blur_advanced(
crate::imageops::GaussianBlurParameters::new_anisotropic_kernel_size(1.0, 2.0),
)
},
&|img| img.fast_blur(1.0),
&|img| img.unsharpen(1.0, 3),
&|img| img.filter3x3(&[0.0, -1.0, 0.0, -1.0, 5.0, -1.0, 0.0, -1.0, 0.0]),
&|img| img.adjust_contrast(0.5),
&|img| img.brighten(10),
&|img| img.huerotate(180),
];
for (idx, &op) in IMAGEOPS.iter().enumerate() {
let result_a = op(&image);
let result_b = op(&clone);
assert_eq!(result_a.color_space(), image.color_space(), "{idx}");
assert_eq!(result_b.color_space(), clone.color_space(), "{idx}");
assert_ne!(result_a, result_b, "{idx}");
assert_eq!(result_a.as_bytes(), result_b.as_bytes(), "{idx}");
}
}
}