diff --git a/Cargo.toml b/Cargo.toml index ad06e5387d..fa6b8938ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,10 +29,11 @@ name = "image" path = "./src/lib.rs" [dependencies] -byteorder = "1.2.1" +byteorder = "1.3.2" num-iter = "0.1.32" num-rational = { version = "0.2.1", default-features = false } num-traits = "0.2.0" +zerocopy = "0.2.8" [dependencies.gif] version = "0.10.0" diff --git a/src/buffer.rs b/src/buffer.rs index f10cc39e25..a288000fd8 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -4,12 +4,14 @@ use std::ops::{Deref, DerefMut, Index, IndexMut, Range}; use std::path::Path; use std::slice::{Chunks, ChunksMut}; -use color::{ColorType, FromColor, Luma, LumaA, Rgb, Rgba, Bgr, Bgra}; +use color::{ChannelsType, ColorType, FromColor, Luma, LumaA, Rgb, Rgba, Bgr, Bgra}; use flat::{FlatSamples, SampleLayout}; use dynimage::{save_buffer, save_buffer_with_format}; use image::{GenericImage, GenericImageView, ImageFormat, ImageResult}; use traits::Primitive; use utils::expand_packed; +use zerocopy::AsBytes; + /// A generalized pixel. /// @@ -42,13 +44,10 @@ pub trait Pixel: Copy + Clone { Self::COLOR_MODEL } - /// ColorType for this pixel format - const COLOR_TYPE: ColorType; + /// Channels for this pixel format + const CHANNELS_TYPE: ChannelsType; /// Returns the ColorType for this pixel format - #[deprecated(note="please use COLOR_TYPE associated constant")] - fn color_type() -> ColorType { - Self::COLOR_TYPE - } + fn color_type() -> ColorType; /// Returns the channels of this pixel as a 4 tuple. If the pixel /// has less than 4 channels the remainder is filled with the maximum value @@ -655,7 +654,7 @@ where FlatSamples { samples: self.data, layout, - color_hint: Some(P::COLOR_TYPE), + color_hint: Some(P::color_type()), } } @@ -669,7 +668,7 @@ where FlatSamples { samples: self.data.as_ref(), layout, - color_hint: Some(P::COLOR_TYPE), + color_hint: Some(P::color_type()), } } } @@ -745,8 +744,9 @@ where impl ImageBuffer where - P: Pixel + 'static, - Container: Deref, + P: Pixel + 'static, + P::Subpixel: AsBytes, + Container: Deref, { /// Saves the buffer to a file at the path specified. /// @@ -759,18 +759,19 @@ where // This is valid as the subpixel is u8. save_buffer( path, - self, + self.as_bytes(), self.width(), self.height(), -

::COLOR_TYPE, +

::color_type(), ) } } impl ImageBuffer where - P: Pixel + 'static, - Container: Deref, + P: Pixel + 'static, + P::Subpixel: AsBytes, + Container: Deref, { /// Saves the buffer to a file at the specified path in /// the specified format. @@ -784,10 +785,10 @@ where // This is valid as the subpixel is u8. save_buffer_with_format( path, - self, + self.as_bytes(), self.width(), self.height(), -

::COLOR_TYPE, +

::color_type(), format, ) } @@ -1076,6 +1077,14 @@ pub type GrayAlphaImage = ImageBuffer, Vec>; pub(crate) type BgrImage = ImageBuffer, Vec>; /// Sendable Bgr + alpha channel image buffer pub(crate) type BgraImage = ImageBuffer, Vec>; +/// Sendable 16-bit Rgb image buffer +pub type Rgb16Image = ImageBuffer, Vec>; +/// Sendable 16-bit Rgb + alpha channel image buffer +pub type Rgba16Image = ImageBuffer, Vec>; +/// Sendable 16-bit grayscale image buffer +pub type Gray16Image = ImageBuffer, Vec>; +/// Sendable 16-bit grayscale + alpha channel image buffer +pub type GrayAlpha16Image = ImageBuffer, Vec>; #[cfg(test)] mod test { diff --git a/src/color.rs b/src/color.rs index 64e074ddde..d90212bbcb 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,9 +1,39 @@ -use num_traits::{NumCast, Zero}; +use num_traits::{NumCast, ToPrimitive, Zero}; use std::ops::{Index, IndexMut}; use buffer::Pixel; use traits::Primitive; +/// Supported ordered sets of channels +#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] +pub enum ChannelsType { + /// Luminance + L, + /// Luminance and alpha + La, + /// Red, green and blue + Rgb, + /// Red, green, blue and alpha + Rgba, + /// Blue, green and red + Bgr, + /// Blue, green, red and alpha + Bgra, +} + +impl ChannelsType { + pub fn channel_count(self) -> u8 { + match self { + Self::L => 1, + Self::La => 2, + Self::Rgb => 3, + Self::Rgba => 4, + Self::Bgr => 3, + Self::Bgra => 4, + } + } +} + /// An enumeration over supported color types and bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] pub enum ColorType { @@ -48,6 +78,11 @@ impl ColorType { } } + /// Returns the number of bytes contained in a channel per pixel of `ColorType` ```c``` + pub fn bytes_per_channel(self) -> u8 { + self.bytes_per_pixel() / self.channel_count() + } + /// 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 { @@ -96,34 +131,38 @@ pub enum ExtendedColorType { } impl ExtendedColorType { - pub fn channel_count(self) -> u8 { + pub fn channels_type(self) -> ChannelsType { match self { ExtendedColorType::L1 | ExtendedColorType::L2 | ExtendedColorType::L4 | ExtendedColorType::L8 | ExtendedColorType::L16 | - ExtendedColorType::Unknown(_) => 1, + ExtendedColorType::Unknown(_) => ChannelsType::L, ExtendedColorType::La1 | ExtendedColorType::La2 | ExtendedColorType::La4 | ExtendedColorType::La8 | - ExtendedColorType::La16 => 2, + ExtendedColorType::La16 => ChannelsType::La, ExtendedColorType::Rgb1 | ExtendedColorType::Rgb2 | ExtendedColorType::Rgb4 | ExtendedColorType::Rgb8 | - ExtendedColorType::Rgb16 | - ExtendedColorType::Bgr8 => 3, + ExtendedColorType::Rgb16 => ChannelsType::Rgb, + ExtendedColorType::Bgr8 => ChannelsType::Bgr, ExtendedColorType::Rgba1 | ExtendedColorType::Rgba2 | ExtendedColorType::Rgba4 | ExtendedColorType::Rgba8 | - ExtendedColorType::Rgba16 | - ExtendedColorType::Bgra8 => 4, + ExtendedColorType::Rgba16 => ChannelsType::Rgba, + ExtendedColorType::Bgra8 => ChannelsType::Bgra, ExtendedColorType::__Nonexhaustive => unreachable!(), + } } + pub fn channel_count(self) -> u8 { + self.channels_type().channel_count() + } } impl From for ExtendedColorType { fn from(c: ColorType) -> Self { @@ -149,7 +188,7 @@ macro_rules! define_colors { $channels: expr, $alphas: expr, $interpretation: expr, - $color_type: expr, + $channels_type: expr, #[$doc:meta]; )*} => { @@ -168,7 +207,22 @@ impl Pixel for $ident { const COLOR_MODEL: &'static str = $interpretation; - const COLOR_TYPE: ColorType = $color_type; + const CHANNELS_TYPE: ChannelsType = $channels_type; + + fn color_type() -> ColorType { + match (Self::CHANNELS_TYPE, std::mem::size_of::()) { + (ChannelsType::L, 1) => ColorType::L8, + (ChannelsType::L, _) => ColorType::L16, + (ChannelsType::La, 1) => ColorType::La8, + (ChannelsType::La, _) => ColorType::La16, + (ChannelsType::Rgb, 1) => ColorType::Rgb8, + (ChannelsType::Rgb, _) => ColorType::Rgb16, + (ChannelsType::Rgba, 1) => ColorType::Rgba8, + (ChannelsType::Rgba, _) => ColorType::Rgba16, + (ChannelsType::Bgr, _) => ColorType::Bgr8, + (ChannelsType::Bgra, _) => ColorType::Bgra8, + } + } #[inline(always)] fn channels(&self) -> &[T] { @@ -302,18 +356,24 @@ impl IndexMut for $ident { } } +impl From<[T; $channels]> for $ident { + fn from(c: [T; $channels]) -> Self { + Self(c) + } +} + )* // END Structure definitions } } define_colors! { - Rgb, 3, 0, "RGB", ColorType::Rgb8, #[doc = "RGB colors"]; - Bgr, 3, 0, "BGR", ColorType::Bgr8, #[doc = "BGR colors"]; - Luma, 1, 0, "Y", ColorType::L8, #[doc = "Grayscale colors"]; - Rgba, 4, 1, "RGBA", ColorType::Rgba8, #[doc = "RGB colors + alpha channel"]; - Bgra, 4, 1, "BGRA", ColorType::Bgra8, #[doc = "BGR colors + alpha channel"]; - LumaA, 2, 1, "YA", ColorType::La8, #[doc = "Grayscale colors + alpha channel"]; + Rgb, 3, 0, "RGB", ChannelsType::Rgb, #[doc = "RGB colors"]; + Bgr, 3, 0, "BGR", ChannelsType::Bgr, #[doc = "BGR colors"]; + Luma, 1, 0, "Y", ChannelsType::L, #[doc = "Grayscale colors"]; + Rgba, 4, 1, "RGBA", ChannelsType::Rgba, #[doc = "RGB colors + alpha channel"]; + Bgra, 4, 1, "BGRA", ChannelsType::Bgra, #[doc = "BGR colors + alpha channel"]; + LumaA, 2, 1, "YA", ChannelsType::La, #[doc = "Grayscale colors + alpha channel"]; } /// Provides color conversions for the different pixel types. @@ -329,15 +389,61 @@ impl FromColor for A { } } -/// `FromColor` for Luma +/// Copy-based conversions to target pixel types using `FromColor`. +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: ::buffer::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. + 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: [f32; 3] = [0.2126, 0.7152, 0.0722]; + +#[inline] +fn rgb_to_luma(rgb: &[T]) -> T { + let l = SRGB_LUMA[0] * rgb[0].to_f32().unwrap() + + SRGB_LUMA[1] * rgb[1].to_f32().unwrap() + + SRGB_LUMA[2] * rgb[2].to_f32().unwrap(); + NumCast::from(l).unwrap() +} + +#[inline] +fn bgr_to_luma(bgr: &[T]) -> T { + let l = SRGB_LUMA[0] * bgr[2].to_f32().unwrap() + + SRGB_LUMA[1] * bgr[1].to_f32().unwrap() + + SRGB_LUMA[2] * bgr[0].to_f32().unwrap(); + NumCast::from(l).unwrap() +} + +#[inline] +fn downcast_channel(c16: u16) -> u8 { + NumCast::from(c16.to_u64().unwrap() >> 8).unwrap() +} + +#[inline] +fn upcast_channel(c8: u8) -> u16 { + NumCast::from(c8.to_u64().unwrap() << 8).unwrap() +} + + +// `FromColor` for Luma impl FromColor> for Luma { fn from_color(&mut self, other: &Rgba) { let gray = self.channels_mut(); - let rgb = other.channels(); - let l = 0.2126f32 * rgb[0].to_f32().unwrap() + 0.7152f32 * rgb[1].to_f32().unwrap() - + 0.0722f32 * rgb[2].to_f32().unwrap(); - gray[0] = NumCast::from(l).unwrap() + let rgba = other.channels(); + gray[0] = rgb_to_luma(rgba); } } @@ -345,9 +451,7 @@ impl FromColor> for Luma { fn from_color(&mut self, other: &Bgra) { let gray = self.channels_mut(); let bgra = other.channels(); - let l = 0.2126f32 * bgra[2].to_f32().unwrap() + 0.7152f32 * bgra[1].to_f32().unwrap() - + 0.0722f32 * bgra[0].to_f32().unwrap(); - gray[0] = NumCast::from(l).unwrap() + gray[0] = bgr_to_luma(bgra); } } @@ -355,39 +459,72 @@ impl FromColor> for Luma { fn from_color(&mut self, other: &Rgb) { let gray = self.channels_mut(); let rgb = other.channels(); - let l = 0.2126f32 * rgb[0].to_f32().unwrap() + 0.7152f32 * rgb[1].to_f32().unwrap() - + 0.0722f32 * rgb[2].to_f32().unwrap(); - gray[0] = NumCast::from(l).unwrap() + gray[0] = rgb_to_luma(rgb); } } - impl FromColor> for Luma { fn from_color(&mut self, other: &Bgr) { let gray = self.channels_mut(); let bgr = other.channels(); - let l = 0.2126f32 * bgr[2].to_f32().unwrap() + 0.7152f32 * bgr[1].to_f32().unwrap() - + 0.0722f32 * bgr[0].to_f32().unwrap(); - gray[0] = NumCast::from(l).unwrap() + gray[0] = bgr_to_luma(bgr); } } - impl FromColor> for Luma { fn from_color(&mut self, other: &LumaA) { self.channels_mut()[0] = other.channels()[0] } } -/// `FromColor` for LumA + +impl FromColor> for Luma { + fn from_color(&mut self, other: &Rgba) { + let gray = self.channels_mut(); + let rgb = other.channels(); + let l = rgb_to_luma(rgb); + gray[0] = downcast_channel(l); + } +} + +impl FromColor> for Luma { + fn from_color(&mut self, other: &Rgb) { + let gray = self.channels_mut(); + let rgb = other.channels(); + let l = rgb_to_luma(rgb); + gray[0] = downcast_channel(l); + } +} + +impl FromColor> for Luma { + fn from_color(&mut self, other: &Luma) { + let l = other.channels()[0]; + self.channels_mut()[0] = downcast_channel(l); + } +} + +impl FromColor> for Luma { + fn from_color(&mut self, other: &Luma) { + let l = other.channels()[0]; + self.channels_mut()[0] = upcast_channel(l); + } +} + +impl FromColor> for Luma { + fn from_color(&mut self, other: &LumaA) { + let l = other.channels()[0]; + self.channels_mut()[0] = downcast_channel(l); + } +} + + +// `FromColor` for LumaA impl FromColor> for LumaA { fn from_color(&mut self, other: &Rgba) { let gray_a = self.channels_mut(); let rgba = other.channels(); - let l = 0.2126f32 * rgba[0].to_f32().unwrap() + 0.7152f32 * rgba[1].to_f32().unwrap() - + 0.0722f32 * rgba[2].to_f32().unwrap(); - gray_a[0] = NumCast::from(l).unwrap(); + gray_a[0] = rgb_to_luma(rgba); gray_a[1] = rgba[3]; } } @@ -396,9 +533,7 @@ impl FromColor> for LumaA { fn from_color(&mut self, other: &Bgra) { let gray_a = self.channels_mut(); let bgra = other.channels(); - let l = 0.2126f32 * bgra[2].to_f32().unwrap() + 0.7152f32 * bgra[1].to_f32().unwrap() - + 0.0722f32 * bgra[0].to_f32().unwrap(); - gray_a[0] = NumCast::from(l).unwrap(); + gray_a[0] = bgr_to_luma(bgra); gray_a[1] = bgra[3]; } } @@ -407,9 +542,7 @@ impl FromColor> for LumaA { fn from_color(&mut self, other: &Rgb) { let gray_a = self.channels_mut(); let rgb = other.channels(); - let l = 0.2126f32 * rgb[0].to_f32().unwrap() + 0.7152f32 * rgb[1].to_f32().unwrap() - + 0.0722f32 * rgb[2].to_f32().unwrap(); - gray_a[0] = NumCast::from(l).unwrap(); + gray_a[0] = rgb_to_luma(rgb); gray_a[1] = T::max_value(); } } @@ -418,9 +551,7 @@ impl FromColor> for LumaA { fn from_color(&mut self, other: &Bgr) { let gray_a = self.channels_mut(); let bgr = other.channels(); - let l = 0.2126f32 * bgr[2].to_f32().unwrap() + 0.7152f32 * bgr[1].to_f32().unwrap() - + 0.0722f32 * bgr[0].to_f32().unwrap(); - gray_a[0] = NumCast::from(l).unwrap(); + gray_a[0] = bgr_to_luma(bgr); gray_a[1] = T::max_value(); } } @@ -433,7 +564,28 @@ impl FromColor> for LumaA { } } -/// `FromColor` for RGBA +impl FromColor> for LumaA { + fn from_color(&mut self, other: &LumaA) { + let la8 = self.channels_mut(); + let gray = other.channels()[0]; + let alpha = other.channels()[1]; + la8[0] = downcast_channel(gray); + la8[1] = downcast_channel(alpha); + } +} + +impl FromColor> for LumaA { + fn from_color(&mut self, other: &LumaA) { + let la8 = self.channels_mut(); + let gray = other.channels()[0]; + let alpha = other.channels()[1]; + la8[0] = upcast_channel(gray); + la8[1] = upcast_channel(alpha); + } +} + + +// `FromColor` for RGBA impl FromColor> for Rgba { fn from_color(&mut self, other: &Rgb) { @@ -468,7 +620,6 @@ impl FromColor> for Rgba { } } - impl FromColor> for Rgba { fn from_color(&mut self, other: &LumaA) { let rgba = self.channels_mut(); @@ -480,8 +631,6 @@ impl FromColor> for Rgba { } } - - impl FromColor> for Rgba { fn from_color(&mut self, gray: &Luma) { let rgba = self.channels_mut(); @@ -493,8 +642,30 @@ impl FromColor> for Rgba { } } +impl FromColor> for Rgba { + fn from_color(&mut self, other: &Rgba) { + let rgba = self.channels_mut(); + let rgba16 = other.channels(); + rgba[0] = downcast_channel(rgba16[0]); + rgba[1] = downcast_channel(rgba16[1]); + rgba[2] = downcast_channel(rgba16[2]); + rgba[3] = downcast_channel(rgba16[3]); + } +} -/// `FromColor` for BGRA +impl FromColor> for Rgba { + fn from_color(&mut self, other: &Rgba) { + let rgba = self.channels_mut(); + let rgba8 = other.channels(); + rgba[0] = upcast_channel(rgba8[0]); + rgba[1] = upcast_channel(rgba8[1]); + rgba[2] = upcast_channel(rgba8[2]); + rgba[3] = upcast_channel(rgba8[3]); + } +} + + +// `FromColor` for BGRA impl FromColor> for Bgra { fn from_color(&mut self, other: &Rgb) { @@ -507,7 +678,6 @@ impl FromColor> for Bgra { } } - impl FromColor> for Bgra { fn from_color(&mut self, other: &Bgr) { let bgra = self.channels_mut(); @@ -519,7 +689,6 @@ impl FromColor> for Bgra { } } - impl FromColor> for Bgra { fn from_color(&mut self, other: &Rgba) { let bgra = self.channels_mut(); @@ -554,8 +723,7 @@ impl FromColor> for Bgra { } - -/// `FromColor` for RGB +// `FromColor` for RGB impl FromColor> for Rgb { fn from_color(&mut self, other: &Rgba) { @@ -567,7 +735,6 @@ impl FromColor> for Rgb { } } - impl FromColor> for Rgb { fn from_color(&mut self, other: &Bgra) { let rgb = self.channels_mut(); @@ -608,6 +775,23 @@ impl FromColor> for Rgb { } } +impl FromColor> for Rgb { + fn from_color(&mut self, other: &Rgb) { + for (c8, &c16) in self.channels_mut().iter_mut().zip(other.channels()) { + *c8 = downcast_channel(c16); + } + } +} + +impl FromColor> for Rgb { + fn from_color(&mut self, other: &Rgb) { + for (c8, &c16) in self.channels_mut().iter_mut().zip(other.channels()) { + *c8 = upcast_channel(c16); + } + } +} + + /// `FromColor` for BGR impl FromColor> for Bgr { @@ -661,6 +845,43 @@ impl FromColor> for Bgr { } } +macro_rules! downcast_bit_depth_early { + ($src:ident, $intermediate:ident, $dst:ident) => { + impl FromColor<$src> for $dst { + fn from_color(&mut self, other: &$src) { + let mut intermediate: $intermediate = $intermediate([Zero::zero(); <$intermediate as Pixel>::CHANNEL_COUNT as usize]); + intermediate.from_color(other); + self.from_color(&intermediate); + } + } + }; +} + + +// Downcasts +// LumaA +downcast_bit_depth_early!(Luma, Luma, LumaA); +downcast_bit_depth_early!(Rgb, Rgb, LumaA); +downcast_bit_depth_early!(Rgba, Rgba, LumaA); +// Rgb +downcast_bit_depth_early!(Luma, Luma, Rgb); +downcast_bit_depth_early!(LumaA, LumaA, Rgb); +downcast_bit_depth_early!(Rgba, Rgba, Rgb); +// Rgba +downcast_bit_depth_early!(Luma, Luma, Rgba); +downcast_bit_depth_early!(LumaA, LumaA, Rgba); +downcast_bit_depth_early!(Rgb, Rgb, Rgba); +// Bgr +downcast_bit_depth_early!(Luma, Luma, Bgr); +downcast_bit_depth_early!(LumaA, LumaA, Bgr); +downcast_bit_depth_early!(Rgb, Rgb, Bgr); +downcast_bit_depth_early!(Rgba, Rgba, Bgr); +// Bgra +downcast_bit_depth_early!(Luma, Luma, Bgra); +downcast_bit_depth_early!(LumaA, LumaA, Bgra); +downcast_bit_depth_early!(Rgb, Rgb, Bgra); +downcast_bit_depth_early!(Rgba, Rgba, Bgra); + /// Blends a color inter another one pub(crate) trait Blend { @@ -912,7 +1133,7 @@ impl Invert for Bgr { #[cfg(test)] mod tests { - use super::{LumaA, Pixel, Rgb, Rgba, Bgr, Bgra}; + use super::{Luma, LumaA, Pixel, Rgb, Rgba, Bgr, Bgra}; #[test] fn test_apply_with_alpha_rgba() { @@ -1069,4 +1290,25 @@ mod tests { let bgra = Bgra([0, 0, 0, 0]).map_without_alpha(|s| s + 1); assert_eq!(bgra, Bgra([1, 1, 1, 0])); } + + macro_rules! test_lossless_conversion { + ($a:ty, $b:ty, $c:ty) => { + let a: $a = [<$a as Pixel>::Subpixel::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; + + test_lossless_conversion!(Bgr, Rgba, Bgr); + test_lossless_conversion!(Bgra, Rgba, Bgra); + test_lossless_conversion!(Luma, Luma, Luma); + test_lossless_conversion!(LumaA, LumaA, LumaA); + test_lossless_conversion!(Rgb, Rgb, Rgb); + test_lossless_conversion!(Rgba, Rgba, Rgba); + } } diff --git a/src/dynimage.rs b/src/dynimage.rs index 1985dfb4d3..5ea4f1f364 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -17,11 +17,11 @@ use png; use pnm; use buffer::{ - BgrImage, BgraImage, ConvertBuffer, GrayAlphaImage, GrayImage, ImageBuffer, Pixel, RgbImage, - RgbaImage, + BgrImage, BgraImage, ConvertBuffer, GrayAlphaImage, GrayAlpha16Image, + GrayImage, Gray16Image, ImageBuffer, Pixel, RgbImage, Rgb16Image, + RgbaImage, Rgba16Image, }; -use color; -use flat::FlatSamples; +use color::{self, IntoColor}; use image; use image::{ GenericImage, GenericImageView, ImageDecoder, ImageError, ImageFormat, ImageOutputFormat, ImageResult, @@ -49,6 +49,18 @@ pub enum DynamicImage { /// Each pixel in this image is 8-bit Bgr with alpha ImageBgra8(BgraImage), + + /// Each pixel in this image is 16-bit Luma + ImageLuma16(Gray16Image), + + /// Each pixel in this image is 16-bit Luma with alpha + ImageLumaA16(GrayAlpha16Image), + + /// Each pixel in this image is 16-bit Rgb + ImageRgb16(Rgb16Image), + + /// Each pixel in this image is 16-bit Rgb with alpha + ImageRgba16(Rgba16Image), } macro_rules! dynamic_map( @@ -60,6 +72,10 @@ macro_rules! dynamic_map( DynamicImage::ImageRgba8(ref $image) => DynamicImage::ImageRgba8($action), DynamicImage::ImageBgr8(ref $image) => DynamicImage::ImageBgr8($action), DynamicImage::ImageBgra8(ref $image) => DynamicImage::ImageBgra8($action), + DynamicImage::ImageLuma16(ref $image) => DynamicImage::ImageLuma16($action), + DynamicImage::ImageLumaA16(ref $image) => DynamicImage::ImageLumaA16($action), + DynamicImage::ImageRgb16(ref $image) => DynamicImage::ImageRgb16($action), + DynamicImage::ImageRgba16(ref $image) => DynamicImage::ImageRgba16($action), } ); @@ -71,6 +87,10 @@ macro_rules! dynamic_map( DynamicImage::ImageRgba8(ref mut $image) => DynamicImage::ImageRgba8($action), DynamicImage::ImageBgr8(ref mut $image) => DynamicImage::ImageBgr8($action), DynamicImage::ImageBgra8(ref mut $image) => DynamicImage::ImageBgra8($action), + DynamicImage::ImageLuma16(ref mut $image) => DynamicImage::ImageLuma16($action), + DynamicImage::ImageLumaA16(ref mut $image) => DynamicImage::ImageLumaA16($action), + DynamicImage::ImageRgb16(ref mut $image) => DynamicImage::ImageRgb16($action), + DynamicImage::ImageRgba16(ref mut $image) => DynamicImage::ImageRgba16($action), } ); @@ -82,6 +102,10 @@ macro_rules! dynamic_map( DynamicImage::ImageRgba8(ref $image) => $action, DynamicImage::ImageBgr8(ref $image) => $action, DynamicImage::ImageBgra8(ref $image) => $action, + DynamicImage::ImageLuma16(ref $image) => $action, + DynamicImage::ImageLumaA16(ref $image) => $action, + DynamicImage::ImageRgb16(ref $image) => $action, + DynamicImage::ImageRgba16(ref $image) => $action, } ); @@ -93,6 +117,10 @@ macro_rules! dynamic_map( DynamicImage::ImageRgba8(ref mut $image) => $action, DynamicImage::ImageBgr8(ref mut $image) => $action, DynamicImage::ImageBgra8(ref mut $image) => $action, + DynamicImage::ImageLuma16(ref mut $image) => $action, + DynamicImage::ImageLumaA16(ref mut $image) => $action, + DynamicImage::ImageRgb16(ref mut $image) => $action, + DynamicImage::ImageRgba16(ref mut $image) => $action, } ); ); @@ -129,6 +157,27 @@ impl DynamicImage { DynamicImage::ImageBgr8(ImageBuffer::new(w, h)) } + /// Creates a dynamic image backed by a buffer of grey pixels. + pub fn new_luma16(w: u32, h: u32) -> DynamicImage { + DynamicImage::ImageLuma16(ImageBuffer::new(w, h)) + } + + /// Creates a dynamic image backed by a buffer of grey + /// pixels with transparency. + pub fn new_luma_a16(w: u32, h: u32) -> DynamicImage { + DynamicImage::ImageLumaA16(ImageBuffer::new(w, h)) + } + + /// Creates a dynamic image backed by a buffer of RGB pixels. + pub fn new_rgb16(w: u32, h: u32) -> DynamicImage { + DynamicImage::ImageRgb16(ImageBuffer::new(w, h)) + } + + /// Creates a dynamic image backed by a buffer of RGBA pixels. + pub fn new_rgba16(w: u32, h: u32) -> DynamicImage { + DynamicImage::ImageRgba16(ImageBuffer::new(w, h)) + } + /// Decodes an encoded image into a dynamic image. pub fn from_decoder<'a>(decoder: impl ImageDecoder<'a>) -> ImageResult @@ -279,14 +328,73 @@ impl DynamicImage { } } - /// Return this image's pixels as a byte vector. - pub fn raw_pixels(&self) -> Vec { - image_to_bytes(self) + /// Return a reference to an 16bit RGB image + pub fn as_rgb16(&self) -> Option<&Rgb16Image> { + match *self { + DynamicImage::ImageRgb16(ref p) => Some(p), + _ => None, + } + } + + /// Return a mutable reference to an 16bit RGB image + pub fn as_mut_rgb16(&mut self) -> Option<&mut Rgb16Image> { + match *self { + DynamicImage::ImageRgb16(ref mut p) => Some(p), + _ => None, + } + } + + /// Return a reference to an 16bit RGBA image + pub fn as_rgba16(&self) -> Option<&Rgba16Image> { + match *self { + DynamicImage::ImageRgba16(ref p) => Some(p), + _ => None, + } + } + + /// Return a mutable reference to an 16bit RGBA image + pub fn as_mut_rgba16(&mut self) -> Option<&mut Rgba16Image> { + match *self { + DynamicImage::ImageRgba16(ref mut p) => Some(p), + _ => None, + } + } + + /// Return a reference to an 16bit Grayscale image + pub fn as_luma16(&self) -> Option<&Gray16Image> { + match *self { + DynamicImage::ImageLuma16(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 { + DynamicImage::ImageLuma16(ref mut p) => Some(p), + _ => None, + } + } + + /// Return a reference to an 16bit Grayscale image with an alpha channel + pub fn as_luma_alpha16(&self) -> Option<&GrayAlpha16Image> { + match *self { + DynamicImage::ImageLumaA16(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 { + DynamicImage::ImageLumaA16(ref mut p) => Some(p), + _ => None, + } } - /// Return a view on the raw sample buffer. - pub fn as_flat_samples(&self) -> FlatSamples<&[u8]> { - dynamic_map!(*self, ref p -> p.as_flat_samples()) + /// Return this image's pixels as a byte vector. + pub fn to_bytes(&self) -> Vec { + image_to_bytes(self) } /// Return this image's color type. @@ -298,6 +406,10 @@ impl DynamicImage { DynamicImage::ImageRgba8(_) => color::ColorType::Rgba8, DynamicImage::ImageBgra8(_) => color::ColorType::Bgra8, DynamicImage::ImageBgr8(_) => color::ColorType::Bgr8, + DynamicImage::ImageLuma16(_) => color::ColorType::L16, + DynamicImage::ImageLumaA16(_) => color::ColorType::La16, + DynamicImage::ImageRgb16(_) => color::ColorType::Rgb16, + DynamicImage::ImageRgba16(_) => color::ColorType::Rgba16, } } @@ -310,6 +422,10 @@ impl DynamicImage { DynamicImage::ImageRgba8(ref p) => DynamicImage::ImageLuma8(imageops::grayscale(p)), DynamicImage::ImageBgr8(ref p) => DynamicImage::ImageLuma8(imageops::grayscale(p)), DynamicImage::ImageBgra8(ref p) => DynamicImage::ImageLuma8(imageops::grayscale(p)), + DynamicImage::ImageLuma16(ref p) => DynamicImage::ImageLuma16(p.clone()), + DynamicImage::ImageLumaA16(ref p) => DynamicImage::ImageLuma16(imageops::grayscale(p)), + DynamicImage::ImageRgb16(ref p) => DynamicImage::ImageLuma16(imageops::grayscale(p)), + DynamicImage::ImageRgba16(ref p) => DynamicImage::ImageLuma16(imageops::grayscale(p)), } } @@ -470,7 +586,7 @@ impl DynamicImage { w: &mut W, format: F, ) -> ImageResult<()> { - let mut bytes = self.raw_pixels(); + let mut bytes = self.to_bytes(); let (width, height) = self.dimensions(); let mut color = self.color(); let format = format.into(); @@ -593,7 +709,7 @@ impl GenericImageView for DynamicImage { } fn get_pixel(&self, x: u32, y: u32) -> color::Rgba { - dynamic_map!(*self, ref p -> p.get_pixel(x, y).to_rgba()) + dynamic_map!(*self, ref p -> p.get_pixel(x, y).to_rgba().into_color()) } fn inner(&self) -> &Self::InnerImageView { @@ -613,6 +729,10 @@ impl GenericImage for DynamicImage { DynamicImage::ImageRgba8(ref mut p) => p.put_pixel(x, y, pixel), DynamicImage::ImageBgr8(ref mut p) => p.put_pixel(x, y, pixel.to_bgr()), DynamicImage::ImageBgra8(ref mut p) => p.put_pixel(x, y, pixel.to_bgra()), + 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()), } } /// DEPRECATED: Use iterator `pixels_mut` to blend the pixels directly. @@ -622,9 +742,12 @@ impl GenericImage for DynamicImage { 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::ImageBgr8(ref mut p) => p.blend_pixel(x, y, pixel.to_bgr()), DynamicImage::ImageBgra8(ref mut p) => p.blend_pixel(x, y, pixel.to_bgra()), + 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()), } } @@ -642,32 +765,57 @@ impl GenericImage for DynamicImage { fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult { let (w, h) = decoder.dimensions(); let color_type = decoder.color_type(); - let buf = image::decoder_to_vec(decoder)?; let image = match color_type { color::ColorType::Rgb8 => { + let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb8) } color::ColorType::Rgba8 => { + let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba8) } color::ColorType::Bgr8 => { + let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageBgr8) } color::ColorType::Bgra8 => { + let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageBgra8) } color::ColorType::L8 => { + let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma8) } color::ColorType::La8 => { + let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA8) } + + color::ColorType::Rgb16 => { + let buf = image::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb16) + } + + color::ColorType::Rgba16 => { + let buf = image::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba16) + } + + color::ColorType::L16 => { + let buf = image::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma16) + } + + color::ColorType::La16 => { + let buf = image::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA16) + } _ => return Err(ImageError::UnsupportedColor(color_type.into())), }; match image { @@ -678,6 +826,8 @@ fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult Vec { + use zerocopy::AsBytes; + match *image { // TODO: consider transmuting DynamicImage::ImageLuma8(ref a) => a.iter().cloned().collect(), @@ -691,6 +841,14 @@ fn image_to_bytes(image: &DynamicImage) -> Vec { DynamicImage::ImageBgr8(ref a) => a.iter().cloned().collect(), DynamicImage::ImageBgra8(ref a) => a.iter().cloned().collect(), + + DynamicImage::ImageLuma16(ref a) => a.as_bytes().to_vec(), + + DynamicImage::ImageLumaA16(ref a) => a.as_bytes().to_vec(), + + DynamicImage::ImageRgb16(ref a) => a.as_bytes().to_vec(), + + DynamicImage::ImageRgba16(ref a) => a.as_bytes().to_vec(), } } @@ -902,4 +1060,12 @@ mod test { 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(), super::color::ColorType::Rgba16); + } } diff --git a/src/flat.rs b/src/flat.rs index 316c2564d8..994e5de0d3 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -560,7 +560,7 @@ impl FlatSamples { where P: Pixel, Buffer: AsRef<[P::Subpixel]>, { if self.layout.channels != P::CHANNEL_COUNT { - return Err(Error::WrongColor(P::COLOR_TYPE)) + return Err(Error::WrongColor(P::color_type())) } let as_ref = self.samples.as_ref(); @@ -597,7 +597,7 @@ impl FlatSamples { where P: Pixel, Buffer: AsMut<[P::Subpixel]>, { if self.layout.channels != P::CHANNEL_COUNT { - return Err(Error::WrongColor(P::COLOR_TYPE)) + return Err(Error::WrongColor(P::color_type())) } let as_mut = self.samples.as_mut(); @@ -634,7 +634,7 @@ impl FlatSamples { } if self.layout.channels != P::CHANNEL_COUNT { - return Err(Error::WrongColor(P::COLOR_TYPE)) + return Err(Error::WrongColor(P::color_type())) } let as_mut = self.samples.as_mut(); @@ -718,7 +718,7 @@ impl FlatSamples { } if self.layout.channels != P::CHANNEL_COUNT { - return Err((Error::WrongColor(P::COLOR_TYPE), self)) + return Err((Error::WrongColor(P::color_type()), self)) } if !self.fits(self.samples.deref().len()) { diff --git a/src/image.rs b/src/image.rs index b22e5e17de..729dadf093 100644 --- a/src/image.rs +++ b/src/image.rs @@ -10,6 +10,7 @@ use std::ops::{Deref, DerefMut}; use buffer::{ImageBuffer, Pixel}; use color::{ColorType, ExtendedColorType}; +use zerocopy::{AsBytes, FromBytes}; use animation::Frames; @@ -366,13 +367,16 @@ pub(crate) fn load_rect<'a, D, F, F1, F2, E>(x: u32, y: u32, width: u32, height: Ok(seek_scanline(decoder, 0)?) } -/// Reads all of the bytes of a decoder into a Vec. No particular alignment +/// Reads all of the bytes of a decoder into a Vec. No particular alignment /// of the output buffer is guaranteed. /// /// Panics if there isn't enough memory to decode the image. -pub(crate) fn decoder_to_vec<'a>(decoder: impl ImageDecoder<'a>) -> ImageResult> { - let mut buf = vec![0; usize::try_from(decoder.total_bytes()).unwrap()]; - decoder.read_image(&mut buf)?; +pub(crate) fn decoder_to_vec<'a, T>(decoder: impl ImageDecoder<'a>) -> ImageResult> +where + T: ::traits::Primitive + AsBytes + FromBytes, +{ + let mut buf = vec![num_traits::Zero::zero(); usize::try_from(decoder.total_bytes()).unwrap() / std::mem::size_of::()]; + decoder.read_image(buf.as_bytes_mut())?; Ok(buf) } diff --git a/src/lib.rs b/src/lib.rs index 56590bc1a9..dc61e827ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ extern crate num_traits; extern crate scoped_threadpool; #[cfg(all(test, feature = "benchmarks"))] extern crate test; +extern crate zerocopy; #[cfg(test)] #[macro_use] @@ -52,7 +53,12 @@ pub use buffer::{ConvertBuffer, ImageBuffer, Pixel, RgbImage, - RgbaImage}; + RgbaImage, + Rgb16Image, + Rgba16Image, + Gray16Image, + GrayAlpha16Image, + }; pub use flat::FlatSamples; diff --git a/src/png.rs b/src/png.rs index 38ab88c2dc..b9d48a588f 100644 --- a/src/png.rs +++ b/src/png.rs @@ -101,7 +101,11 @@ impl PngDecoder { let limits = png::Limits { bytes: usize::max_value(), }; - let decoder = png::Decoder::new_with_limits(r, limits); + let mut decoder = png::Decoder::new_with_limits(r, limits); + // By default the PNG decoder will scale 16 bpc to 8 bpc, so custom + // transformations must be set. EXPAND preserves the default behavior + // expanding bpc < 8 to 8 bpc. + decoder.set_transformations(png::Transformations::EXPAND); let (_, mut reader) = decoder.read_info().map_err(ImageError::from_png)?; let (color_type, bits) = reader.output_color_type(); let color_type = match (color_type, bits) { @@ -165,8 +169,18 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { } fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + use byteorder::{BigEndian, ByteOrder, NativeEndian}; + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); self.reader.next_frame(buf).map_err(ImageError::from_png)?; + // PNG images are big endian. For 16 bit per channel and larger types, + // the buffer may need to be reordered to native endianness per the + // contract of `read_image`. + match self.color_type().bytes_per_channel() { + 1 => (), // No reodering necessary for u8 + 2 => buf.chunks_mut(2).for_each(|c| NativeEndian::write_u16(c, BigEndian::read_u16(c))), + _ => unreachable!(), + } Ok(()) } diff --git a/tests/images/png/16bpc/basn6a16.png b/tests/images/png/16bpc/basn6a16.png new file mode 100644 index 0000000000..984a99525f Binary files /dev/null and b/tests/images/png/16bpc/basn6a16.png differ diff --git a/tests/images/tiff/testsuite/rgb-3c-16b.tiff b/tests/images/tiff/testsuite/rgb-3c-16b.tiff new file mode 100644 index 0000000000..f1a0279a19 Binary files /dev/null and b/tests/images/tiff/testsuite/rgb-3c-16b.tiff differ diff --git a/tests/reference/png/16bpc/basn6a16.png.285be560.png b/tests/reference/png/16bpc/basn6a16.png.285be560.png new file mode 100644 index 0000000000..23de9945d7 Binary files /dev/null and b/tests/reference/png/16bpc/basn6a16.png.285be560.png differ diff --git a/tests/reference/tiff/testsuite/rgb-3c-16b.tiff.6456fd3a.png b/tests/reference/tiff/testsuite/rgb-3c-16b.tiff.6456fd3a.png new file mode 100644 index 0000000000..01223025d5 Binary files /dev/null and b/tests/reference/tiff/testsuite/rgb-3c-16b.tiff.6456fd3a.png differ