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