diff --git a/Cargo.toml b/Cargo.toml index 360ef137b4..7eabff3040 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,13 +20,14 @@ name = "image" path = "./src/lib.rs" [dependencies] -byteorder = "1.2.1" +bytemuck = "1" +byteorder = "1.3.2" num-iter = "0.1.32" num-rational = { version = "0.2.1", default-features = false } num-traits = "0.2.0" gif = { version = "0.10.0", optional = true } jpeg = { package = "jpeg-decoder", version = "0.1", default-features = false, optional = true } -png = { version = "0.15", optional = true } +png = { version = "0.15.2", optional = true } scoped_threadpool = { version = "0.1", optional = true } tiff = { version = "0.4.0", optional = true } diff --git a/src/bmp/encoder.rs b/src/bmp/encoder.rs index 69d3483285..186a04c4e3 100644 --- a/src/bmp/encoder.rs +++ b/src/bmp/encoder.rs @@ -2,7 +2,7 @@ use byteorder::{LittleEndian, WriteBytesExt}; use std::io::{self, Write}; use color; -use image::{ImageError, ImageResult}; +use image::{ImageEncoder, ImageError, ImageResult}; const BITMAPFILEHEADER_SIZE: u32 = 14; const BITMAPINFOHEADER_SIZE: u32 = 40; @@ -213,6 +213,18 @@ impl<'a, W: Write + 'a> BMPEncoder<'a, W> { } } +impl<'a, W: Write> ImageEncoder for BMPEncoder<'a, W> { + fn write_image( + mut self, + buf: &[u8], + width: u32, + height: u32, + color_type: color::ColorType, + ) -> ImageResult<()> { + self.encode(buf, width, height, color_type) + } +} + fn get_unsupported_error_message(c: color::ColorType) -> String { format!( "Unsupported color type {:?}. Supported types: RGB(8), RGBA(8), Gray(8), GrayA(8).", diff --git a/src/buffer.rs b/src/buffer.rs index bad4fc93f7..b1a8d308e9 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -8,7 +8,7 @@ use color::{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 traits::{EncodableLayout, Primitive}; use utils::expand_packed; /// A generalized pixel. @@ -745,8 +745,9 @@ where impl ImageBuffer where - P: Pixel + 'static, - Container: Deref, + P: Pixel + 'static, + [P::Subpixel]: EncodableLayout, + Container: Deref, { /// Saves the buffer to a file at the path specified. /// @@ -759,7 +760,7 @@ where // This is valid as the subpixel is u8. save_buffer( path, - self, + self.as_bytes(), self.width(), self.height(),

::COLOR_TYPE, @@ -769,8 +770,9 @@ where impl ImageBuffer where - P: Pixel + 'static, - Container: Deref, + P: Pixel + 'static, + [P::Subpixel]: EncodableLayout, + Container: Deref, { /// Saves the buffer to a file at the specified path in /// the specified format. @@ -784,7 +786,7 @@ where // This is valid as the subpixel is u8. save_buffer_with_format( path, - self, + self.as_bytes(), self.width(), self.height(),

::COLOR_TYPE, @@ -1076,6 +1078,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(crate) type Rgb16Image = ImageBuffer, Vec>; +/// Sendable 16-bit Rgb + alpha channel image buffer +pub(crate) type Rgba16Image = ImageBuffer, Vec>; +/// Sendable 16-bit grayscale image buffer +pub(crate) type Gray16Image = ImageBuffer, Vec>; +/// Sendable 16-bit grayscale + alpha channel image buffer +pub(crate) type GrayAlpha16Image = ImageBuffer, Vec>; #[cfg(test)] mod test { diff --git a/src/color.rs b/src/color.rs index b465c37e9a..d1e678a6a1 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,4 +1,4 @@ -use num_traits::{NumCast, Zero}; +use num_traits::{NumCast, ToPrimitive, Zero}; use std::ops::{Index, IndexMut}; use buffer::Pixel; @@ -161,7 +161,8 @@ macro_rules! define_colors { $channels: expr, $alphas: expr, $interpretation: expr, - $color_type: expr, + $color_type_u8: expr, + $color_type_u16: expr, #[$doc:meta]; )*} => { @@ -180,7 +181,8 @@ impl Pixel for $ident { const COLOR_MODEL: &'static str = $interpretation; - const COLOR_TYPE: ColorType = $color_type; + const COLOR_TYPE: ColorType = + [$color_type_u8, $color_type_u16][(std::mem::size_of::() > 1) as usize]; #[inline(always)] fn channels(&self) -> &[T] { @@ -314,18 +316,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", ColorType::Rgb8, ColorType::Rgb16, #[doc = "RGB colors"]; + Bgr, 3, 0, "BGR", ColorType::Bgr8, ColorType::Bgr8, #[doc = "BGR colors"]; + Luma, 1, 0, "Y", ColorType::L8, ColorType::L16, #[doc = "Grayscale colors"]; + Rgba, 4, 1, "RGBA", ColorType::Rgba8, ColorType::Rgba16, #[doc = "RGB colors + alpha channel"]; + Bgra, 4, 1, "BGRA", ColorType::Bgra8, ColorType::Bgra8, #[doc = "BGR colors + alpha channel"]; + LumaA, 2, 1, "YA", ColorType::La8, ColorType::La16, #[doc = "Grayscale colors + alpha channel"]; } /// Provides color conversions for the different pixel types. @@ -341,15 +349,63 @@ impl FromColor for A { } } -/// `FromColor` for Luma +/// 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: ::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); } } @@ -357,9 +413,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); } } @@ -367,39 +421,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]; } } @@ -408,9 +495,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]; } } @@ -419,9 +504,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(); } } @@ -430,9 +513,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(); } } @@ -445,7 +526,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) { @@ -480,7 +582,6 @@ impl FromColor> for Rgba { } } - impl FromColor> for Rgba { fn from_color(&mut self, other: &LumaA) { let rgba = self.channels_mut(); @@ -492,8 +593,6 @@ impl FromColor> for Rgba { } } - - impl FromColor> for Rgba { fn from_color(&mut self, gray: &Luma) { let rgba = self.channels_mut(); @@ -505,8 +604,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) { @@ -519,7 +640,6 @@ impl FromColor> for Bgra { } } - impl FromColor> for Bgra { fn from_color(&mut self, other: &Bgr) { let bgra = self.channels_mut(); @@ -531,7 +651,6 @@ impl FromColor> for Bgra { } } - impl FromColor> for Bgra { fn from_color(&mut self, other: &Rgba) { let bgra = self.channels_mut(); @@ -566,8 +685,7 @@ impl FromColor> for Bgra { } - -/// `FromColor` for RGB +// `FromColor` for RGB impl FromColor> for Rgb { fn from_color(&mut self, other: &Rgba) { @@ -579,7 +697,6 @@ impl FromColor> for Rgb { } } - impl FromColor> for Rgb { fn from_color(&mut self, other: &Bgra) { let rgb = self.channels_mut(); @@ -620,6 +737,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 { @@ -673,6 +807,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 { @@ -924,7 +1095,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() { @@ -1081,4 +1252,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 80da673c16..60badd5922 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -17,10 +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 color::{self, IntoColor}; use flat::FlatSamples; use image; use image::{ @@ -49,6 +50,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 +73,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 +88,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 +103,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 +118,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 +158,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 +329,97 @@ 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 a view on the raw sample buffer for 8 bit per channel images. + pub fn as_flat_samples_u8(&self) -> Option> { + 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()), + DynamicImage::ImageBgr8(ref p) => Some(p.as_flat_samples()), + DynamicImage::ImageBgra8(ref p) => Some(p.as_flat_samples()), + _ => None, + } + } + + /// Return a view on the raw sample buffer for 16 bit per channel images. + pub fn as_flat_samples_u16(&self) -> Option> { + 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, + } + } + + /// 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 +431,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 +447,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 +611,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 +734,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 +754,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 +767,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 +790,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 +851,8 @@ fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult Vec { + use traits::EncodableLayout; + match *image { // TODO: consider transmuting DynamicImage::ImageLuma8(ref a) => a.iter().cloned().collect(), @@ -691,6 +866,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 +1085,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/ico/encoder.rs b/src/ico/encoder.rs index 76aa325adc..8b98b11ca7 100644 --- a/src/ico/encoder.rs +++ b/src/ico/encoder.rs @@ -2,7 +2,7 @@ use byteorder::{LittleEndian, WriteBytesExt}; use std::io::{self, Write}; use color::ColorType; -use image::ImageResult; +use image::{ImageEncoder, ImageResult}; use png::PNGEncoder; @@ -51,6 +51,18 @@ impl ICOEncoder { } } +impl ImageEncoder for ICOEncoder { + fn write_image( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + self.encode(buf, width, height, color_type) + } +} + fn write_icondir(w: &mut W, num_images: u16) -> io::Result<()> { // Reserved field (must be zero): w.write_u16::(0)?; diff --git a/src/image.rs b/src/image.rs index f130c17440..994efa62a4 100644 --- a/src/image.rs +++ b/src/image.rs @@ -366,13 +366,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 + bytemuck::Pod, +{ + let mut buf = vec![num_traits::Zero::zero(); usize::try_from(decoder.total_bytes()).unwrap() / std::mem::size_of::()]; + decoder.read_image(bytemuck::cast_slice_mut(buf.as_mut_slice()))?; Ok(buf) } @@ -535,6 +538,27 @@ pub trait AnimationDecoder<'a> { fn into_frames(self) -> Frames<'a>; } +/// The trait all encoders implement +pub trait ImageEncoder { + /// Writes all the bytes in an image to the encoder. + /// + /// This function takes a slice of bytes of the pixel data of the image + /// and encodes them. Unlike particular format encoders inherent impl encode + /// methods where endianness is not specified, here image data bytes should + /// always be in native endian. The implementor will reorder the endianess + /// as necessary for the target encoding format. + /// + /// See also `ImageDecoder::read_image` which reads byte buffers into + /// native endian. + fn write_image( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()>; +} + /// Immutable pixel iterator pub struct Pixels<'a, I: ?Sized + 'a> { image: &'a I, diff --git a/src/io/free_functions.rs b/src/io/free_functions.rs index 03d3d3dea1..c566a1004f 100644 --- a/src/io/free_functions.rs +++ b/src/io/free_functions.rs @@ -28,7 +28,7 @@ use webp; use color; use image; use dynimage::DynamicImage; -use image::{ImageDecoder, ImageFormat, ImageResult, ImageError}; +use image::{ImageDecoder, ImageEncoder, ImageFormat, ImageResult, ImageError}; /// Internal error type for guessing format from path. pub(crate) enum PathError { @@ -142,30 +142,30 @@ pub(crate) fn save_buffer_impl( match &*ext { #[cfg(feature = "ico")] - "ico" => ico::ICOEncoder::new(fout).encode(buf, width, height, color), + "ico" => ico::ICOEncoder::new(fout).write_image(buf, width, height, color), #[cfg(feature = "jpeg")] - "jpg" | "jpeg" => jpeg::JPEGEncoder::new(fout).encode(buf, width, height, color), + "jpg" | "jpeg" => jpeg::JPEGEncoder::new(fout).write_image(buf, width, height, color), #[cfg(feature = "png")] - "png" => png::PNGEncoder::new(fout).encode(buf, width, height, color), + "png" => png::PNGEncoder::new(fout).write_image(buf, width, height, color), #[cfg(feature = "pnm")] "pbm" => pnm::PNMEncoder::new(fout) .with_subtype(pnm::PNMSubtype::Bitmap(pnm::SampleEncoding::Binary)) - .encode(buf, width, height, color), + .write_image(buf, width, height, color), #[cfg(feature = "pnm")] "pgm" => pnm::PNMEncoder::new(fout) .with_subtype(pnm::PNMSubtype::Graymap(pnm::SampleEncoding::Binary)) - .encode(buf, width, height, color), + .write_image(buf, width, height, color), #[cfg(feature = "pnm")] "ppm" => pnm::PNMEncoder::new(fout) .with_subtype(pnm::PNMSubtype::Pixmap(pnm::SampleEncoding::Binary)) - .encode(buf, width, height, color), + .write_image(buf, width, height, color), #[cfg(feature = "pnm")] - "pam" => pnm::PNMEncoder::new(fout).encode(buf, width, height, color), + "pam" => pnm::PNMEncoder::new(fout).write_image(buf, width, height, color), #[cfg(feature = "bmp")] - "bmp" => bmp::BMPEncoder::new(fout).encode(buf, width, height, color), + "bmp" => bmp::BMPEncoder::new(fout).write_image(buf, width, height, color), #[cfg(feature = "tiff")] "tif" | "tiff" => tiff::TiffEncoder::new(fout) - .encode(buf, width, height, color), + .write_image(buf, width, height, color), format => Err(ImageError::UnsupportedError(format!("Unsupported image format image/{:?}", format))), } } @@ -182,16 +182,16 @@ pub(crate) fn save_buffer_with_format_impl( match format { #[cfg(feature = "ico")] - image::ImageFormat::Ico => ico::ICOEncoder::new(fout).encode(buf, width, height, color), + image::ImageFormat::Ico => ico::ICOEncoder::new(fout).write_image(buf, width, height, color), #[cfg(feature = "jpeg")] - image::ImageFormat::Jpeg => jpeg::JPEGEncoder::new(fout).encode(buf, width, height, color), + image::ImageFormat::Jpeg => jpeg::JPEGEncoder::new(fout).write_image(buf, width, height, color), #[cfg(feature = "png")] - image::ImageFormat::Png => png::PNGEncoder::new(fout).encode(buf, width, height, color), + image::ImageFormat::Png => png::PNGEncoder::new(fout).write_image(buf, width, height, color), #[cfg(feature = "bmp")] - image::ImageFormat::Bmp => bmp::BMPEncoder::new(fout).encode(buf, width, height, color), + image::ImageFormat::Bmp => bmp::BMPEncoder::new(fout).write_image(buf, width, height, color), #[cfg(feature = "tiff")] image::ImageFormat::Tiff => tiff::TiffEncoder::new(fout) - .encode(buf, width, height, color), + .write_image(buf, width, height, color), _ => Err(ImageError::UnsupportedError(format!("Unsupported image format image/{:?}", format))), } } diff --git a/src/jpeg/encoder.rs b/src/jpeg/encoder.rs index 97ddfb4a34..7e281321aa 100644 --- a/src/jpeg/encoder.rs +++ b/src/jpeg/encoder.rs @@ -7,6 +7,7 @@ use num_iter::range_step; use std::io::{self, Write}; use color; +use image::ImageEncoder; use super::entropy::build_huff_lut; use super::transform; @@ -631,6 +632,18 @@ impl<'a, W: Write> JPEGEncoder<'a, W> { } } +impl<'a, W: Write> ImageEncoder for JPEGEncoder<'a, W> { + fn write_image( + mut self, + buf: &[u8], + width: u32, + height: u32, + color_type: color::ColorType, + ) -> ImageResult<()> { + self.encode(buf, width, height, color_type) + } +} + fn build_jfif_header(m: &mut Vec, density: PixelDensity) { m.clear(); diff --git a/src/lib.rs b/src/lib.rs index 8fa5f6d68d..d518a572f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ // it's a bit of a pain otherwise #![allow(clippy::many_single_char_names)] +extern crate bytemuck; extern crate byteorder; extern crate num_iter; extern crate num_rational; @@ -35,6 +36,7 @@ pub use image::{AnimationDecoder, GenericImageView, ImageDecoder, ImageDecoderExt, + ImageEncoder, ImageError, ImageFormat, ImageOutputFormat, @@ -51,7 +53,8 @@ pub use buffer::{ConvertBuffer, ImageBuffer, Pixel, RgbImage, - RgbaImage}; + RgbaImage, + }; pub use flat::FlatSamples; diff --git a/src/png.rs b/src/png.rs index 38ab88c2dc..30ea08bc3d 100644 --- a/src/png.rs +++ b/src/png.rs @@ -12,7 +12,7 @@ use std::convert::TryFrom; use std::io::{self, Read, Write}; use color::{ColorType, ExtendedColorType}; -use image::{ImageDecoder, ImageError, ImageResult}; +use image::{ImageDecoder, ImageEncoder, ImageError, ImageResult}; /// PNG Reader /// @@ -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,23 @@ 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`. + // TODO: assumes equal channel bit depth. + let bpc = self.color_type().bytes_per_pixel() / self.color_type().channel_count(); + match bpc { + 1 => (), // No reodering necessary for u8 + 2 => buf.chunks_mut(2).for_each(|c| { + let v = BigEndian::read_u16(c); + NativeEndian::write_u16(c, v) + }), + _ => unreachable!(), + } Ok(()) } @@ -213,6 +232,38 @@ impl PNGEncoder { } } +impl ImageEncoder for PNGEncoder { + fn write_image( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + use byteorder::{BigEndian, ByteOrder, NativeEndian}; + + // PNG images are big endian. For 16 bit per channel and larger types, + // the buffer may need to be reordered to big endian per the + // contract of `write_image`. + // TODO: assumes equal channel bit depth. + let bpc = color_type.bytes_per_pixel() / color_type.channel_count(); + match bpc { + 1 => self.encode(buf, width, height, color_type), // No reodering necessary for u8 + 2 => { + // Because the buffer is immutable and the PNG encoder does not + // yet take Write/Read traits, create a temporary buffer for + // big endian reordering. + let mut reordered = vec![0; buf.len()]; + buf.chunks(2) + .zip(reordered.chunks_mut(2)) + .for_each(|(b, r)| BigEndian::write_u16(r, NativeEndian::read_u16(b))); + self.encode(&reordered, width, height, color_type) + }, + _ => unreachable!(), + } + } +} + impl ImageError { fn from_png(err: png::DecodingError) -> ImageError { use self::png::DecodingError::*; diff --git a/src/pnm/encoder.rs b/src/pnm/encoder.rs index 00fc5e8961..44a01dfb73 100644 --- a/src/pnm/encoder.rs +++ b/src/pnm/encoder.rs @@ -8,7 +8,7 @@ use super::AutoBreak; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PNMHeader, PNMSubtype, SampleEncoding}; use color::{ColorType, ExtendedColorType}; -use image::{ImageError, ImageResult}; +use image::{ImageEncoder, ImageError, ImageResult}; use byteorder::{BigEndian, WriteBytesExt}; @@ -272,6 +272,18 @@ impl PNMEncoder { } } +impl ImageEncoder for PNMEncoder { + fn write_image( + mut self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + self.encode(buf, width, height, color_type) + } +} + impl<'a> CheckedImageBuffer<'a> { fn check( image: FlatSamples<'a>, diff --git a/src/tiff.rs b/src/tiff.rs index e2a986fa13..30be7e2341 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -16,7 +16,7 @@ use std::mem; use byteorder::{NativeEndian, ByteOrder}; use color::{ColorType, ExtendedColorType}; -use image::{ImageDecoder, ImageResult, ImageError}; +use image::{ImageDecoder, ImageEncoder, ImageResult, ImageError}; use utils::vec_u16_into_u8; /// Decoder for TIFF images. @@ -129,6 +129,13 @@ pub struct TiffEncoder { w: W, } +// Utility to simplify and deduplicate error handling during 16-bit encoding. +fn u8_slice_as_u16(buf: &[u8]) -> ImageResult<&[u16]> { + bytemuck::try_cast_slice(buf) + // If the buffer is not aligned or the correct length for a u16 slice, err. + .map_err(|_| ImageError::IoError(std::io::ErrorKind::InvalidData.into())) +} + impl TiffEncoder { /// Create a new encoder that writes its output to `w` pub fn new(w: W) -> TiffEncoder { @@ -137,16 +144,31 @@ impl TiffEncoder { /// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`. /// - /// 16-bit color types are not yet supported. + /// 16-bit types assume the buffer is native endian. pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { let mut encoder = tiff::encoder::TiffEncoder::new(self.w).map_err(ImageError::from_tiff)?; match color { ColorType::L8 => encoder.write_image::(width, height, data), ColorType::Rgb8 => encoder.write_image::(width, height, data), ColorType::Rgba8 => encoder.write_image::(width, height, data), + ColorType::L16 => encoder.write_image::(width, height, &u8_slice_as_u16(data)?), + ColorType::Rgb16 => encoder.write_image::(width, height, &u8_slice_as_u16(data)?), + ColorType::Rgba16 => encoder.write_image::(width, height, &u8_slice_as_u16(data)?), _ => return Err(ImageError::UnsupportedColor(color.into())) }.map_err(ImageError::from_tiff)?; Ok(()) } } + +impl ImageEncoder for TiffEncoder { + fn write_image( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + self.encode(buf, width, height, color_type) + } +} diff --git a/src/traits.rs b/src/traits.rs index 11d25ec46b..6e558d4a13 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -5,6 +5,25 @@ use num_traits::{Bounded, Num, NumCast}; use std::ops::AddAssign; +/// Types which are safe to treat as an immutable byte slice in a pixel layout +/// for image encoding. +pub trait EncodableLayout: seals::EncodableLayout { + /// Get the bytes of this value. + fn as_bytes(&self) -> &[u8]; +} + +impl EncodableLayout for [u8] { + fn as_bytes(&self) -> &[u8] { + bytemuck::cast_slice(self) + } +} + +impl EncodableLayout for [u16] { + fn as_bytes(&self) -> &[u8] { + bytemuck::cast_slice(self) + } +} + /// Primitive trait from old stdlib pub trait Primitive: Copy + NumCast + Num + PartialOrd + Clone + Bounded {} @@ -45,3 +64,12 @@ impl Enlargeable for u16 { impl Enlargeable for u32 { type Larger = u64; } + + +/// Private module for supertraits of sealed traits. +mod seals { + pub trait EncodableLayout {} + + impl EncodableLayout for [u8] {} + impl EncodableLayout for [u16] {} +} 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