use std::ops::{Index, IndexMut}; use num_traits::{NumCast, ToPrimitive, Zero}; use crate::traits::{Enlargeable, Pixel, Primitive}; /// An enumeration over supported color types and bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[non_exhaustive] pub enum ColorType { /// Pixel is 8-bit luminance L8, /// Pixel is 8-bit luminance with an alpha channel La8, /// Pixel contains 8-bit R, G and B channels Rgb8, /// Pixel is 8-bit RGB with an alpha channel Rgba8, /// Pixel is 16-bit luminance L16, /// Pixel is 16-bit luminance with an alpha channel La16, /// Pixel is 16-bit RGB Rgb16, /// Pixel is 16-bit RGBA Rgba16, /// Pixel is 32-bit float RGB Rgb32F, /// Pixel is 32-bit float RGBA Rgba32F, } impl ColorType { /// Returns the number of bytes contained in a pixel of `ColorType` ```c``` pub fn bytes_per_pixel(self) -> u8 { match self { ColorType::L8 => 1, ColorType::L16 | ColorType::La8 => 2, ColorType::Rgb8 => 3, ColorType::Rgba8 | ColorType::La16 => 4, ColorType::Rgb16 => 6, ColorType::Rgba16 => 8, ColorType::Rgb32F => 3 * 4, ColorType::Rgba32F => 4 * 4, } } /// Returns if there is an alpha channel. pub fn has_alpha(self) -> bool { use ColorType::*; match self { L8 | L16 | Rgb8 | Rgb16 | Rgb32F => false, La8 | Rgba8 | La16 | Rgba16 | Rgba32F => true, } } /// Returns false if the color scheme is grayscale, true otherwise. pub fn has_color(self) -> bool { use ColorType::*; match self { L8 | L16 | La8 | La16 => false, Rgb8 | Rgb16 | Rgba8 | Rgba16 | Rgb32F | Rgba32F => true, } } /// Returns the number of bits contained in a pixel of `ColorType` ```c``` (which will always be /// a multiple of 8). pub fn bits_per_pixel(self) -> u16 { >::from(self.bytes_per_pixel()) * 8 } /// Returns the number of color channels that make up this pixel pub fn channel_count(self) -> u8 { let e: ExtendedColorType = self.into(); e.channel_count() } } /// An enumeration of color types encountered in image formats. /// /// This is not exhaustive over all existing image formats but should be granular enough to allow /// round tripping of decoding and encoding as much as possible. The variants will be extended as /// necessary to enable this. /// /// Another purpose is to advise users of a rough estimate of the accuracy and effort of the /// decoding from and encoding to such an image format. #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[non_exhaustive] pub enum ExtendedColorType { /// Pixel is 8-bit alpha A8, /// Pixel is 1-bit luminance L1, /// Pixel is 1-bit luminance with an alpha channel La1, /// Pixel contains 1-bit R, G and B channels Rgb1, /// Pixel is 1-bit RGB with an alpha channel Rgba1, /// Pixel is 2-bit luminance L2, /// Pixel is 2-bit luminance with an alpha channel La2, /// Pixel contains 2-bit R, G and B channels Rgb2, /// Pixel is 2-bit RGB with an alpha channel Rgba2, /// Pixel is 4-bit luminance L4, /// Pixel is 4-bit luminance with an alpha channel La4, /// Pixel contains 4-bit R, G and B channels Rgb4, /// Pixel is 4-bit RGB with an alpha channel Rgba4, /// Pixel is 8-bit luminance L8, /// Pixel is 8-bit luminance with an alpha channel La8, /// Pixel contains 8-bit R, G and B channels Rgb8, /// Pixel is 8-bit RGB with an alpha channel Rgba8, /// Pixel is 16-bit luminance L16, /// Pixel is 16-bit luminance with an alpha channel La16, /// Pixel contains 16-bit R, G and B channels Rgb16, /// Pixel is 16-bit RGB with an alpha channel Rgba16, /// Pixel contains 8-bit B, G and R channels Bgr8, /// Pixel is 8-bit BGR with an alpha channel Bgra8, // TODO f16 types? /// Pixel is 32-bit float RGB Rgb32F, /// Pixel is 32-bit float RGBA Rgba32F, /// Pixel is of unknown color type with the specified bits per pixel. This can apply to pixels /// which are associated with an external palette. In that case, the pixel value is an index /// into the palette. Unknown(u8), } impl ExtendedColorType { /// Get the number of channels for colors of this type. /// /// Note that the `Unknown` variant returns a value of `1` since pixels can only be treated as /// an opaque datum by the library. pub fn channel_count(self) -> u8 { match self { ExtendedColorType::A8 | ExtendedColorType::L1 | ExtendedColorType::L2 | ExtendedColorType::L4 | ExtendedColorType::L8 | ExtendedColorType::L16 | ExtendedColorType::Unknown(_) => 1, ExtendedColorType::La1 | ExtendedColorType::La2 | ExtendedColorType::La4 | ExtendedColorType::La8 | ExtendedColorType::La16 => 2, ExtendedColorType::Rgb1 | ExtendedColorType::Rgb2 | ExtendedColorType::Rgb4 | ExtendedColorType::Rgb8 | ExtendedColorType::Rgb16 | ExtendedColorType::Rgb32F | ExtendedColorType::Bgr8 => 3, ExtendedColorType::Rgba1 | ExtendedColorType::Rgba2 | ExtendedColorType::Rgba4 | ExtendedColorType::Rgba8 | ExtendedColorType::Rgba16 | ExtendedColorType::Rgba32F | ExtendedColorType::Bgra8 => 4, } } } impl From for ExtendedColorType { fn from(c: ColorType) -> Self { match c { ColorType::L8 => ExtendedColorType::L8, ColorType::La8 => ExtendedColorType::La8, ColorType::Rgb8 => ExtendedColorType::Rgb8, ColorType::Rgba8 => ExtendedColorType::Rgba8, ColorType::L16 => ExtendedColorType::L16, ColorType::La16 => ExtendedColorType::La16, ColorType::Rgb16 => ExtendedColorType::Rgb16, ColorType::Rgba16 => ExtendedColorType::Rgba16, ColorType::Rgb32F => ExtendedColorType::Rgb32F, ColorType::Rgba32F => ExtendedColorType::Rgba32F, } } } macro_rules! define_colors { {$( $(#[$doc:meta])* pub struct $ident:ident([T; $channels:expr, $alphas:expr]) = $interpretation:literal; )*} => { $( // START Structure definitions $(#[$doc])* #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)] #[repr(C)] #[allow(missing_docs)] pub struct $ident (pub [T; $channels]); impl Pixel for $ident { type Subpixel = T; const CHANNEL_COUNT: u8 = $channels; #[inline(always)] fn channels(&self) -> &[T] { &self.0 } #[inline(always)] fn channels_mut(&mut self) -> &mut [T] { &mut self.0 } const COLOR_MODEL: &'static str = $interpretation; fn channels4(&self) -> (T, T, T, T) { const CHANNELS: usize = $channels; let mut channels = [T::DEFAULT_MAX_VALUE; 4]; channels[0..CHANNELS].copy_from_slice(&self.0); (channels[0], channels[1], channels[2], channels[3]) } fn from_channels(a: T, b: T, c: T, d: T,) -> $ident { const CHANNELS: usize = $channels; *<$ident as Pixel>::from_slice(&[a, b, c, d][..CHANNELS]) } fn from_slice(slice: &[T]) -> &$ident { assert_eq!(slice.len(), $channels); unsafe { &*(slice.as_ptr() as *const $ident) } } fn from_slice_mut(slice: &mut [T]) -> &mut $ident { assert_eq!(slice.len(), $channels); unsafe { &mut *(slice.as_mut_ptr() as *mut $ident) } } fn to_rgb(&self) -> Rgb { let mut pix = Rgb([Zero::zero(), Zero::zero(), Zero::zero()]); pix.from_color(self); pix } fn to_rgba(&self) -> Rgba { let mut pix = Rgba([Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()]); pix.from_color(self); pix } fn to_luma(&self) -> Luma { let mut pix = Luma([Zero::zero()]); pix.from_color(self); pix } fn to_luma_alpha(&self) -> LumaA { let mut pix = LumaA([Zero::zero(), Zero::zero()]); pix.from_color(self); pix } fn map(& self, f: F) -> $ident where F: FnMut(T) -> T { let mut this = (*self).clone(); this.apply(f); this } fn apply(&mut self, mut f: F) where F: FnMut(T) -> T { for v in &mut self.0 { *v = f(*v) } } fn map_with_alpha(&self, f: F, g: G) -> $ident where F: FnMut(T) -> T, G: FnMut(T) -> T { let mut this = (*self).clone(); this.apply_with_alpha(f, g); this } fn apply_with_alpha(&mut self, mut f: F, mut g: G) where F: FnMut(T) -> T, G: FnMut(T) -> T { const ALPHA: usize = $channels - $alphas; for v in self.0[..ALPHA].iter_mut() { *v = f(*v) } // The branch of this match is `const`. This way ensures that no subexpression fails the // `const_err` lint (the expression `self.0[ALPHA]` would). if let Some(v) = self.0.get_mut(ALPHA) { *v = g(*v) } } fn map2(&self, other: &Self, f: F) -> $ident where F: FnMut(T, T) -> T { let mut this = (*self).clone(); this.apply2(other, f); this } fn apply2(&mut self, other: &$ident, mut f: F) where F: FnMut(T, T) -> T { for (a, &b) in self.0.iter_mut().zip(other.0.iter()) { *a = f(*a, b) } } fn invert(&mut self) { Invert::invert(self) } fn blend(&mut self, other: &$ident) { Blend::blend(self, other) } } impl Index for $ident { type Output = T; #[inline(always)] fn index(&self, _index: usize) -> &T { &self.0[_index] } } impl IndexMut for $ident { #[inline(always)] fn index_mut(&mut self, _index: usize) -> &mut T { &mut self.0[_index] } } impl From<[T; $channels]> for $ident { fn from(c: [T; $channels]) -> Self { Self(c) } } )* // END Structure definitions } } define_colors! { /// RGB colors. /// /// For the purpose of color conversion, as well as blending, the implementation of `Pixel` /// assumes an `sRGB` color space of its data. pub struct Rgb([T; 3, 0]) = "RGB"; /// Grayscale colors. pub struct Luma([T; 1, 0]) = "Y"; /// RGB colors + alpha channel pub struct Rgba([T; 4, 1]) = "RGBA"; /// Grayscale colors + alpha channel pub struct LumaA([T; 2, 1]) = "YA"; } /// Convert from one pixel component type to another. For example, convert from `u8` to `f32` pixel values. pub trait FromPrimitive { /// Converts from any pixel component type to this type. fn from_primitive(component: Component) -> Self; } impl FromPrimitive for T { fn from_primitive(sample: T) -> Self { sample } } // from f32: // Note that in to-integer-conversion we are performing rounding but NumCast::from is implemented // as truncate towards zero. We emulate rounding by adding a bias. impl FromPrimitive for u8 { fn from_primitive(float: f32) -> Self { let inner = (float.clamp(0.0, 1.0) * u8::MAX as f32).round(); NumCast::from(inner).unwrap() } } impl FromPrimitive for u16 { fn from_primitive(float: f32) -> Self { let inner = (float.clamp(0.0, 1.0) * u16::MAX as f32).round(); NumCast::from(inner).unwrap() } } // from u16: impl FromPrimitive for u8 { fn from_primitive(c16: u16) -> Self { fn from(c: impl Into) -> u32 { c.into() } // The input c is the numerator of `c / u16::MAX`. // Derive numerator of `num / u8::MAX`, with rounding. // // This method is based on the inverse (see FromPrimitive for u16) and was tested // exhaustively in Python. It's the same as the reference function: // round(c * (2**8 - 1) / (2**16 - 1)) NumCast::from((from(c16) + 128) / 257).unwrap() } } impl FromPrimitive for f32 { fn from_primitive(int: u16) -> Self { (int as f32 / u16::MAX as f32).clamp(0.0, 1.0) } } // from u8: impl FromPrimitive for f32 { fn from_primitive(int: u8) -> Self { (int as f32 / u8::MAX as f32).clamp(0.0, 1.0) } } impl FromPrimitive for u16 { fn from_primitive(c8: u8) -> Self { let x = c8.to_u64().unwrap(); NumCast::from((x << 8) | x).unwrap() } } /// Provides color conversions for the different pixel types. pub trait FromColor { /// Changes `self` to represent `Other` in the color space of `Self` fn from_color(&mut self, _: &Other); } /// Copy-based conversions to target pixel types using `FromColor`. // FIXME: this trait should be removed and replaced with real color space models // rather than assuming sRGB. pub(crate) trait IntoColor { /// Constructs a pixel of the target type and converts this pixel into it. fn into_color(&self) -> Other; } impl IntoColor for S where O: Pixel + FromColor, { fn into_color(&self) -> O { // Note we cannot use Pixel::CHANNELS_COUNT here to directly construct // the pixel due to a current bug/limitation of consts. #[allow(deprecated)] let mut pix = O::from_channels(Zero::zero(), Zero::zero(), Zero::zero(), Zero::zero()); pix.from_color(self); pix } } /// Coefficients to transform from sRGB to a CIE Y (luminance) value. const SRGB_LUMA: [u32; 3] = [2126, 7152, 722]; const SRGB_LUMA_DIV: u32 = 10000; #[inline] fn rgb_to_luma(rgb: &[T]) -> T { let l = ::from(SRGB_LUMA[0]).unwrap() * rgb[0].to_larger() + ::from(SRGB_LUMA[1]).unwrap() * rgb[1].to_larger() + ::from(SRGB_LUMA[2]).unwrap() * rgb[2].to_larger(); T::clamp_from(l / ::from(SRGB_LUMA_DIV).unwrap()) } // `FromColor` for Luma impl FromColor> for Luma where T: FromPrimitive, { fn from_color(&mut self, other: &Luma) { let own = self.channels_mut(); let other = other.channels(); own[0] = T::from_primitive(other[0]); } } impl FromColor> for Luma where T: FromPrimitive, { fn from_color(&mut self, other: &LumaA) { self.channels_mut()[0] = T::from_primitive(other.channels()[0]) } } impl FromColor> for Luma where T: FromPrimitive, { fn from_color(&mut self, other: &Rgb) { let gray = self.channels_mut(); let rgb = other.channels(); gray[0] = T::from_primitive(rgb_to_luma(rgb)); } } impl FromColor> for Luma where T: FromPrimitive, { fn from_color(&mut self, other: &Rgba) { let gray = self.channels_mut(); let rgb = other.channels(); let l = rgb_to_luma(rgb); gray[0] = T::from_primitive(l); } } // `FromColor` for LumaA impl FromColor> for LumaA where T: FromPrimitive, { fn from_color(&mut self, other: &LumaA) { let own = self.channels_mut(); let other = other.channels(); own[0] = T::from_primitive(other[0]); own[1] = T::from_primitive(other[1]); } } impl FromColor> for LumaA where T: FromPrimitive, { fn from_color(&mut self, other: &Rgb) { let gray_a = self.channels_mut(); let rgb = other.channels(); gray_a[0] = T::from_primitive(rgb_to_luma(rgb)); gray_a[1] = T::DEFAULT_MAX_VALUE; } } impl FromColor> for LumaA where T: FromPrimitive, { fn from_color(&mut self, other: &Rgba) { let gray_a = self.channels_mut(); let rgba = other.channels(); gray_a[0] = T::from_primitive(rgb_to_luma(rgba)); gray_a[1] = T::from_primitive(rgba[3]); } } impl FromColor> for LumaA where T: FromPrimitive, { fn from_color(&mut self, other: &Luma) { let gray_a = self.channels_mut(); gray_a[0] = T::from_primitive(other.channels()[0]); gray_a[1] = T::DEFAULT_MAX_VALUE; } } // `FromColor` for RGBA impl FromColor> for Rgba where T: FromPrimitive, { fn from_color(&mut self, other: &Rgba) { let own = &mut self.0; let other = &other.0; own[0] = T::from_primitive(other[0]); own[1] = T::from_primitive(other[1]); own[2] = T::from_primitive(other[2]); own[3] = T::from_primitive(other[3]); } } impl FromColor> for Rgba where T: FromPrimitive, { fn from_color(&mut self, other: &Rgb) { let rgba = &mut self.0; let rgb = &other.0; rgba[0] = T::from_primitive(rgb[0]); rgba[1] = T::from_primitive(rgb[1]); rgba[2] = T::from_primitive(rgb[2]); rgba[3] = T::DEFAULT_MAX_VALUE; } } impl FromColor> for Rgba where T: FromPrimitive, { fn from_color(&mut self, gray: &LumaA) { let rgba = &mut self.0; let gray = &gray.0; rgba[0] = T::from_primitive(gray[0]); rgba[1] = T::from_primitive(gray[0]); rgba[2] = T::from_primitive(gray[0]); rgba[3] = T::from_primitive(gray[1]); } } impl FromColor> for Rgba where T: FromPrimitive, { fn from_color(&mut self, gray: &Luma) { let rgba = &mut self.0; let gray = gray.0[0]; rgba[0] = T::from_primitive(gray); rgba[1] = T::from_primitive(gray); rgba[2] = T::from_primitive(gray); rgba[3] = T::DEFAULT_MAX_VALUE; } } // `FromColor` for RGB impl FromColor> for Rgb where T: FromPrimitive, { fn from_color(&mut self, other: &Rgb) { let own = &mut self.0; let other = &other.0; own[0] = T::from_primitive(other[0]); own[1] = T::from_primitive(other[1]); own[2] = T::from_primitive(other[2]); } } impl FromColor> for Rgb where T: FromPrimitive, { fn from_color(&mut self, other: &Rgba) { let rgb = &mut self.0; let rgba = &other.0; rgb[0] = T::from_primitive(rgba[0]); rgb[1] = T::from_primitive(rgba[1]); rgb[2] = T::from_primitive(rgba[2]); } } impl FromColor> for Rgb where T: FromPrimitive, { fn from_color(&mut self, other: &LumaA) { let rgb = &mut self.0; let gray = other.0[0]; rgb[0] = T::from_primitive(gray); rgb[1] = T::from_primitive(gray); rgb[2] = T::from_primitive(gray); } } impl FromColor> for Rgb where T: FromPrimitive, { fn from_color(&mut self, other: &Luma) { let rgb = &mut self.0; let gray = other.0[0]; rgb[0] = T::from_primitive(gray); rgb[1] = T::from_primitive(gray); rgb[2] = T::from_primitive(gray); } } /// Blends a color inter another one pub(crate) trait Blend { /// Blends a color in-place. fn blend(&mut self, other: &Self); } impl Blend for LumaA { fn blend(&mut self, other: &LumaA) { let max_t = T::DEFAULT_MAX_VALUE; let max_t = max_t.to_f32().unwrap(); let (bg_luma, bg_a) = (self.0[0], self.0[1]); let (fg_luma, fg_a) = (other.0[0], other.0[1]); let (bg_luma, bg_a) = ( bg_luma.to_f32().unwrap() / max_t, bg_a.to_f32().unwrap() / max_t, ); let (fg_luma, fg_a) = ( fg_luma.to_f32().unwrap() / max_t, fg_a.to_f32().unwrap() / max_t, ); let alpha_final = bg_a + fg_a - bg_a * fg_a; if alpha_final == 0.0 { return; }; let bg_luma_a = bg_luma * bg_a; let fg_luma_a = fg_luma * fg_a; let out_luma_a = fg_luma_a + bg_luma_a * (1.0 - fg_a); let out_luma = out_luma_a / alpha_final; *self = LumaA([ NumCast::from(max_t * out_luma).unwrap(), NumCast::from(max_t * alpha_final).unwrap(), ]) } } impl Blend for Luma { fn blend(&mut self, other: &Luma) { *self = *other } } impl Blend for Rgba { fn blend(&mut self, other: &Rgba) { // http://stackoverflow.com/questions/7438263/alpha-compositing-algorithm-blend-modes#answer-11163848 if other.0[3].is_zero() { return; } if other.0[3] == T::DEFAULT_MAX_VALUE { *self = *other; return; } // First, as we don't know what type our pixel is, we have to convert to floats between 0.0 and 1.0 let max_t = T::DEFAULT_MAX_VALUE; let max_t = max_t.to_f32().unwrap(); let (bg_r, bg_g, bg_b, bg_a) = (self.0[0], self.0[1], self.0[2], self.0[3]); let (fg_r, fg_g, fg_b, fg_a) = (other.0[0], other.0[1], other.0[2], other.0[3]); let (bg_r, bg_g, bg_b, bg_a) = ( bg_r.to_f32().unwrap() / max_t, bg_g.to_f32().unwrap() / max_t, bg_b.to_f32().unwrap() / max_t, bg_a.to_f32().unwrap() / max_t, ); let (fg_r, fg_g, fg_b, fg_a) = ( fg_r.to_f32().unwrap() / max_t, fg_g.to_f32().unwrap() / max_t, fg_b.to_f32().unwrap() / max_t, fg_a.to_f32().unwrap() / max_t, ); // Work out what the final alpha level will be let alpha_final = bg_a + fg_a - bg_a * fg_a; if alpha_final == 0.0 { return; }; // We premultiply our channels by their alpha, as this makes it easier to calculate let (bg_r_a, bg_g_a, bg_b_a) = (bg_r * bg_a, bg_g * bg_a, bg_b * bg_a); let (fg_r_a, fg_g_a, fg_b_a) = (fg_r * fg_a, fg_g * fg_a, fg_b * fg_a); // Standard formula for src-over alpha compositing let (out_r_a, out_g_a, out_b_a) = ( fg_r_a + bg_r_a * (1.0 - fg_a), fg_g_a + bg_g_a * (1.0 - fg_a), fg_b_a + bg_b_a * (1.0 - fg_a), ); // Unmultiply the channels by our resultant alpha channel let (out_r, out_g, out_b) = ( out_r_a / alpha_final, out_g_a / alpha_final, out_b_a / alpha_final, ); // Cast back to our initial type on return *self = Rgba([ NumCast::from(max_t * out_r).unwrap(), NumCast::from(max_t * out_g).unwrap(), NumCast::from(max_t * out_b).unwrap(), NumCast::from(max_t * alpha_final).unwrap(), ]) } } impl Blend for Rgb { fn blend(&mut self, other: &Rgb) { *self = *other } } /// Invert a color pub(crate) trait Invert { /// Inverts a color in-place. fn invert(&mut self); } impl Invert for LumaA { fn invert(&mut self) { let l = self.0; let max = T::DEFAULT_MAX_VALUE; *self = LumaA([max - l[0], l[1]]) } } impl Invert for Luma { fn invert(&mut self) { let l = self.0; let max = T::DEFAULT_MAX_VALUE; let l1 = max - l[0]; *self = Luma([l1]) } } impl Invert for Rgba { fn invert(&mut self) { let rgba = self.0; let max = T::DEFAULT_MAX_VALUE; *self = Rgba([max - rgba[0], max - rgba[1], max - rgba[2], rgba[3]]) } } impl Invert for Rgb { fn invert(&mut self) { let rgb = self.0; let max = T::DEFAULT_MAX_VALUE; let r1 = max - rgb[0]; let g1 = max - rgb[1]; let b1 = max - rgb[2]; *self = Rgb([r1, g1, b1]) } } #[cfg(test)] mod tests { use super::{Luma, LumaA, Pixel, Rgb, Rgba}; #[test] fn test_apply_with_alpha_rgba() { let mut rgba = Rgba([0, 0, 0, 0]); rgba.apply_with_alpha(|s| s, |_| 0xFF); assert_eq!(rgba, Rgba([0, 0, 0, 0xFF])); } #[test] fn test_apply_with_alpha_rgb() { let mut rgb = Rgb([0, 0, 0]); rgb.apply_with_alpha(|s| s, |_| panic!("bug")); assert_eq!(rgb, Rgb([0, 0, 0])); } #[test] fn test_map_with_alpha_rgba() { let rgba = Rgba([0, 0, 0, 0]).map_with_alpha(|s| s, |_| 0xFF); assert_eq!(rgba, Rgba([0, 0, 0, 0xFF])); } #[test] fn test_map_with_alpha_rgb() { let rgb = Rgb([0, 0, 0]).map_with_alpha(|s| s, |_| panic!("bug")); assert_eq!(rgb, Rgb([0, 0, 0])); } #[test] fn test_blend_luma_alpha() { let ref mut a = LumaA([255 as u8, 255]); let b = LumaA([255 as u8, 255]); a.blend(&b); assert_eq!(a.0[0], 255); assert_eq!(a.0[1], 255); let ref mut a = LumaA([255 as u8, 0]); let b = LumaA([255 as u8, 255]); a.blend(&b); assert_eq!(a.0[0], 255); assert_eq!(a.0[1], 255); let ref mut a = LumaA([255 as u8, 255]); let b = LumaA([255 as u8, 0]); a.blend(&b); assert_eq!(a.0[0], 255); assert_eq!(a.0[1], 255); let ref mut a = LumaA([255 as u8, 0]); let b = LumaA([255 as u8, 0]); a.blend(&b); assert_eq!(a.0[0], 255); assert_eq!(a.0[1], 0); } #[test] fn test_blend_rgba() { let ref mut a = Rgba([255 as u8, 255, 255, 255]); let b = Rgba([255 as u8, 255, 255, 255]); a.blend(&b); assert_eq!(a.0, [255, 255, 255, 255]); let ref mut a = Rgba([255 as u8, 255, 255, 0]); let b = Rgba([255 as u8, 255, 255, 255]); a.blend(&b); assert_eq!(a.0, [255, 255, 255, 255]); let ref mut a = Rgba([255 as u8, 255, 255, 255]); let b = Rgba([255 as u8, 255, 255, 0]); a.blend(&b); assert_eq!(a.0, [255, 255, 255, 255]); let ref mut a = Rgba([255 as u8, 255, 255, 0]); let b = Rgba([255 as u8, 255, 255, 0]); a.blend(&b); assert_eq!(a.0, [255, 255, 255, 0]); } #[test] fn test_apply_without_alpha_rgba() { let mut rgba = Rgba([0, 0, 0, 0]); rgba.apply_without_alpha(|s| s + 1); assert_eq!(rgba, Rgba([1, 1, 1, 0])); } #[test] fn test_apply_without_alpha_rgb() { let mut rgb = Rgb([0, 0, 0]); rgb.apply_without_alpha(|s| s + 1); assert_eq!(rgb, Rgb([1, 1, 1])); } #[test] fn test_map_without_alpha_rgba() { let rgba = Rgba([0, 0, 0, 0]).map_without_alpha(|s| s + 1); assert_eq!(rgba, Rgba([1, 1, 1, 0])); } #[test] fn test_map_without_alpha_rgb() { let rgb = Rgb([0, 0, 0]).map_without_alpha(|s| s + 1); assert_eq!(rgb, Rgb([1, 1, 1])); } macro_rules! test_lossless_conversion { ($a:ty, $b:ty, $c:ty) => { let a: $a = [<$a as Pixel>::Subpixel::DEFAULT_MAX_VALUE >> 2; <$a as Pixel>::CHANNEL_COUNT as usize] .into(); let b: $b = a.into_color(); let c: $c = b.into_color(); assert_eq!(a.channels(), c.channels()); }; } #[test] fn test_lossless_conversions() { use super::IntoColor; use crate::traits::Primitive; test_lossless_conversion!(Luma, Luma, Luma); test_lossless_conversion!(LumaA, LumaA, LumaA); test_lossless_conversion!(Rgb, Rgb, Rgb); test_lossless_conversion!(Rgba, Rgba, Rgba); } #[test] fn accuracy_conversion() { use super::{Luma, Pixel, Rgb}; let pixel = Rgb::from([13, 13, 13]); let Luma([luma]) = pixel.to_luma(); assert_eq!(luma, 13); } }