diff --git a/.travis.yml b/.travis.yml index 10c2b721b1..1bb3a6c9f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,12 @@ env: matrix: - DEFAULT_FEATURES='yes' - FEATURES='' - - FEATURES='gif_codec' + - FEATURES='gif' - FEATURES='jpeg' - - FEATURES='png_codec' + - FEATURES='png' - FEATURES='pnm' - FEATURES='tga' + - FEATURES='dds' - FEATURES='tiff' - FEATURES='webp' - FEATURES='hdr' @@ -51,6 +52,14 @@ matrix: script: - rustup component add clippy - cargo clippy + - os: linux + rust: nightly + name: "Public private dependencies" + script: + - mv ./Cargo.toml.public-private-dependencies ./Cargo.toml + - echo "#![deny(exported_private_dependencies)]" | cat - src/lib.rs > src/lib.rs.0 + - mv src/lib.rs.0 src/lib.rs + - cargo check script: - if [ -n "${FEATURES+exists}" ]; then cargo build -v --no-default-features --features "$FEATURES" && @@ -60,3 +69,7 @@ script: - if [ -n "${DEFAULT_FEATURES+exists}" ]; then cargo test -v; fi + - if [ "$TRAVIS_RUST_VERSION" == "nightly" ] && [ -n "${DEFAULT_FEATURES+exists}" ]; then + cargo build -v --features=benchmarks && + cargo build -v --benches --features=benchmarks; + fi diff --git a/CHANGES.md b/CHANGES.md index 00fee8f461..252284871b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,54 @@ Rust image aims to be a pure-Rust implementation of various popular image format ## Changes +### Version 0.23.0-preview.0 + +This major release intends to improve the interface with regards to handling of +color format data for both decoding and encoding. This necessitated many +breaking changes anyways so it was used to improve the compliance to the +interface guidelines such as outstanding renaming. + +WIP: This preview version is intended to allow an evaluation and feedback on +the new interface. It is not yet perfect with regards to color spaces but it +was designed mainly as an improvement over the current interface with regards +to in-memory color formats, first. We'll get to color spaces in a later major +version. + +- Heavily reworked `ColorType`: + - This type is now used for denoting formats for which we support operations + on buffers in these memory representations. Particularly, all channels in + pixel types are assumed to be an integer number of bytes (In terms of the + Rust type system, these are `Sized` and one can crate slices of channel + values). + - An `ExtendedColorType` (WIP: do You have better name?) is used to express + more generic color formats for which the library has limited support but + can be converted/scaled/mapped into a `ColorType` buffer. This operation + might be fallible but, for example, includes sources with 1/2/4-bit + components. + - Both types are non-exhaustive to add more formats in a minor release. + - A work-in-progress (#1085) will further separate the color model from the + specific channel instantiation, e.g. both `8-bit RGB` and `16-bit BGR` + are instantiations of `RGB` color model. +- Reworked the `ImageDecoder` trait: + - `read_image` takes an output buffer argument instead of allocating all + memory on its own. + - The return type of `dimensions` now aligns with `GenericImage` sizes. + - The `colortype` method was renamed to `color_type` for conformity. +- The result of `encode` operations is now uniformly an `ImageResult<()>`. +- Removed public converters from some `tiff` and `png` types. This allows + upgrading the dependency across major versions without a major release in + `image` itself. +- The enums `ColorType`, `DynamicImage`, `imageops::FilterType`, `ImageFormat` + no longer re-export all of their variants in the top-level of the crate. This + removes the growing pollution in the documentation and usage. +- The capitalization of types such as `HDRDecoder` etc. has been adjusted to + adhere to the API guidelines. These are now spelled `HdrDecoder` etc. The + same change has been made on `ImageFormat` and other enum variants. +- The `Progress` type has finally received public access functions. Strange + that no one reported them missing. +- Introduced `PixelDensity` and `PixelDensityUnit` to store DPI information in + formats that support encoding this form of metadata (e.g. in `jpeg`). + ### Version 0.22.5 - Added `GenericImage::copy_within`, specialized for `ImageBuffer` diff --git a/Cargo.toml b/Cargo.toml index 8e417e85fb..c3db9d47ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,13 @@ [package] name = "image" -version = "0.22.5" +version = "0.23.0-preview.0" +edition = "2018" license = "MIT" description = "Imaging library written in Rust. Provides basic filters and decoders for the most common image formats." -authors = [ - "ccgn", - "bvssvni ", - "nwin", - "TyOverby ", - "HeroicKatora", - "Calum", - "CensoredUsername ", - "fintelia " -] +authors = ["The image-rs Developers"] readme = "README.md" documentation = "https://docs.rs/image" -repository = "https://github.com/image-rs/image.git" +repository = "https://github.com/image-rs/image" homepage = "https://github.com/image-rs/image" categories = ["multimedia::images", "multimedia::encoding"] exclude = [ @@ -29,31 +21,16 @@ 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" - -[dependencies.gif] -version = "0.10.0" -optional = true - -[dependencies.jpeg-decoder] -version = "0.1" -default-features = false -optional = true - -[dependencies.png] -version = "0.15" -optional = true - -[dependencies.scoped_threadpool] -version = "0.1" -optional = true - -[dependencies.tiff] -version = "0.3.1" -optional = true +gif = { version = "0.10.0", optional = true } +jpeg = { package = "jpeg-decoder", version = "0.1", default-features = false, optional = true } +png = { version = "0.15.2", optional = true } +scoped_threadpool = { version = "0.1", optional = true } +tiff = { version = "0.4.0", optional = true } [dev-dependencies] crc32fast = "1.2.0" @@ -62,18 +39,16 @@ glob = "0.3" quickcheck = "0.9" [features] -default = ["gif_codec", "jpeg", "ico", "png_codec", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "jpeg_rayon"] +default = ["gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "jpeg_rayon"] -gif_codec = ["gif"] -ico = ["bmp", "png_codec"] -jpeg = ["jpeg-decoder"] -png_codec = ["png"] +ico = ["bmp", "png"] pnm = [] tga = [] webp = [] bmp = [] hdr = ["scoped_threadpool"] dxt = [] -jpeg_rayon = ["jpeg-decoder/rayon"] +dds = ["dxt"] +jpeg_rayon = ["jpeg/rayon"] benchmarks = [] diff --git a/Cargo.toml.public-private-dependencies b/Cargo.toml.public-private-dependencies new file mode 100644 index 0000000000..3d0a4462c7 --- /dev/null +++ b/Cargo.toml.public-private-dependencies @@ -0,0 +1,58 @@ +cargo-features = ["public-dependency"] + +[package] +name = "image" +version = "0.23.0-preview.0" +edition = "2018" +license = "MIT" +description = "Imaging library written in Rust. Provides basic filters and decoders for the most common image formats." +authors = ["The image-rs Developers"] +readme = "README.md" +documentation = "https://docs.rs/image" +repository = "https://github.com/image-rs/image" +homepage = "https://github.com/image-rs/image" +categories = ["multimedia::images", "multimedia::encoding"] +exclude = [ + "src/png/testdata/*", + "examples/*", + "tests/*", +] + +[lib] +name = "image" +path = "./src/lib.rs" + +[dependencies] +# Not yet public. +bytemuck = "1" +byteorder = "1.3.2" +num-iter = "0.1.32" +num-rational = "0.2.1" +# Public due to Pixel, otherwise quite useless. +num-traits = { version = "0.2.0", public = true } +gif = { version = "0.10.0", optional = true } +jpeg = { package = "jpeg-decoder", version = "0.1", default-features = false, optional = true } +png = { version = "0.15.2", optional = true } +scoped_threadpool = { version = "0.1", optional = true } +tiff = { version = "0.4.0", optional = true } + +[dev-dependencies] +crc32fast = "1.2.0" +num-complex = "0.2.0" +glob = "0.3" +quickcheck = "0.9" + +[features] +default = ["gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "jpeg_rayon"] + +ico = ["bmp", "png"] +pnm = [] +tga = [] +webp = [] +bmp = [] +hdr = ["scoped_threadpool"] +dxt = [] +dds = ["dxt"] +jpeg_rayon = ["jpeg/rayon"] + +benchmarks = [] diff --git a/README.md b/README.md index 50af64e85a..5133aa16b6 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,17 @@ https://docs.rs/image | WebP | Lossy(Luma channel only) | No | | PNM | PBM, PGM, PPM, standard PAM | Yes | -### 2.2 The ```ImageDecoder``` Trait -All image format decoders implement the ```ImageDecoder``` trait which provides the following methods: -+ **dimensions**: Return a tuple containing the width and height of the image -+ **colortype**: Return the color type of the image. -+ **row_len**: Returns the length in bytes of one decoded row of the image -+ **read_scanline**: Read one row from the image into buf Returns the row index -+ **read_image**: Decode the entire image and return it as a Vector -+ **load_rect**: Decode a specific region of the image +### 2.2 The ```ImageDecoder``` and ```ImageDecoderExt` Traits + +All image format decoders implement the ```ImageDecoder``` trait which provide +basic methods for getting image metadata and decoding images. Some formats +additionally provide ```ImageDecoderExt``` implementations which allow for +decoding only part of an image at once. + +The most important methods for decoders are... ++ **dimensions**: Return a tuple containing the width and height of the image. ++ **color_type**: Return the color type of the image data produced by this decoder. ++ **read_image**: Decode the entire image and return it as a Vec of bytes. ## 3 Pixels ```image``` provides the following pixel types: @@ -249,7 +252,7 @@ fn main() { let buffer: &[u8] = unimplemented!(); // Generate the image data // Save the buffer as "image.png" - image::save_buffer("image.png", buffer, 800, 600, image::RGB(8)).unwrap() + image::save_buffer("image.png", buffer, 800, 600, image::ColorType::Rgb8).unwrap() } ``` diff --git a/benches/encode_jpeg.rs b/benches/encode_jpeg.rs index 2bc6cf8f5f..33e2586111 100644 --- a/benches/encode_jpeg.rs +++ b/benches/encode_jpeg.rs @@ -21,10 +21,10 @@ fn run_benchmark(b: &mut Bencher, color_type: image::ColorType) { #[bench] fn bench_rgb(b: &mut Bencher) { - run_benchmark(b, image::RGB(8)); + run_benchmark(b, image::ColorType::Rgb8); } #[bench] fn bench_gray(b: &mut Bencher) { - run_benchmark(b, image::Gray(8)); + run_benchmark(b, image::ColorType::L8); } diff --git a/benches/load.rs b/benches/load.rs index 7ffd80cbd3..4b2d79e3e1 100644 --- a/benches/load.rs +++ b/benches/load.rs @@ -16,7 +16,7 @@ struct BenchDef<'a> { const IMAGE_DIR: [&'static str; 3] = [".", "tests", "images"]; const BMP: BenchDef<'static> = BenchDef { dir: &["bmp", "images"], - format: ImageFormat::BMP, + format: ImageFormat::Bmp, }; fn bench_load(b: &mut test::Bencher, def: &BenchDef, filename: &str) { diff --git a/examples/opening.rs b/examples/opening.rs index 3f485ab3d6..111bedcc5f 100644 --- a/examples/opening.rs +++ b/examples/opening.rs @@ -5,7 +5,7 @@ use std::env; use std::fs::File; use std::path::Path; -use image::GenericImageView; +use image::{ImageFormat, GenericImageView}; fn main() { let file = if env::args().count() == 2 { @@ -27,5 +27,5 @@ fn main() { let fout = &mut File::create(&Path::new(&format!("{}.png", file))).unwrap(); // Write the contents of this image to the Writer in PNG format. - im.write_to(fout, image::PNG).unwrap(); + im.write_to(fout, ImageFormat::Png).unwrap(); } diff --git a/examples/scaledown/main.rs b/examples/scaledown/main.rs index a05a31e908..416096bf7b 100644 --- a/examples/scaledown/main.rs +++ b/examples/scaledown/main.rs @@ -1,6 +1,7 @@ extern crate image; -use image::{FilterType, PNG}; +use image::ImageFormat; +use image::imageops::FilterType; use std::fmt; use std::fs::File; use std::time::{Duration, Instant}; @@ -39,7 +40,7 @@ fn main() { let scaled = img.resize(400, 400, filter); println!("Scaled by {} in {}", name, Elapsed::from(&timer)); let mut output = File::create(&format!("test-{}.png", name)).unwrap(); - scaled.write_to(&mut output, PNG).unwrap(); + scaled.write_to(&mut output, ImageFormat::Png).unwrap(); } for size in &[20_u32, 40, 100, 200, 400] { @@ -47,6 +48,6 @@ fn main() { let scaled = img.thumbnail(*size, *size); println!("Thumbnailed to {} in {}", size, Elapsed::from(&timer)); let mut output = File::create(format!("test-thumb{}.png", size)).unwrap(); - scaled.write_to(&mut output, PNG).unwrap(); + scaled.write_to(&mut output, ImageFormat::Png).unwrap(); } } diff --git a/examples/scaleup/main.rs b/examples/scaleup/main.rs index 98cada5508..67de1f40ec 100644 --- a/examples/scaleup/main.rs +++ b/examples/scaleup/main.rs @@ -1,6 +1,7 @@ extern crate image; -use image::{FilterType, PNG}; +use image::ImageFormat; +use image::imageops::FilterType; use std::fmt; use std::fs::File; use std::time::{Duration, Instant}; @@ -39,12 +40,12 @@ fn main() { let scaled = tiny.resize(32, 32, filter); println!("Scaled by {} in {}", name, Elapsed::from(&timer)); let mut output = File::create(&format!("up2-{}.png", name)).unwrap(); - scaled.write_to(&mut output, PNG).unwrap(); + scaled.write_to(&mut output, ImageFormat::Png).unwrap(); let timer = Instant::now(); let scaled = tiny.resize(48, 48, filter); println!("Scaled by {} in {}", name, Elapsed::from(&timer)); let mut output = File::create(&format!("up3-{}.png", name)).unwrap(); - scaled.write_to(&mut output, PNG).unwrap(); + scaled.write_to(&mut output, ImageFormat::Png).unwrap(); } } diff --git a/src/animation.rs b/src/animation.rs index cc100b2c17..a10c0f611f 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -1,9 +1,10 @@ use std::iter::Iterator; +use std::time::Duration; use num_rational::Ratio; -use buffer::RgbaImage; -use image::ImageResult; +use crate::buffer::RgbaImage; +use crate::error::ImageResult; /// An implementation dependent iterator, reading the frames as requested pub struct Frames<'a> { @@ -36,8 +37,8 @@ impl<'a> Iterator for Frames<'a> { /// A single animation frame #[derive(Clone)] pub struct Frame { - /// Delay between the frames in s - delay: Ratio, + /// Delay between the frames in milliseconds + delay: Delay, /// x offset left: u32, /// y offset @@ -45,11 +46,17 @@ pub struct Frame { buffer: RgbaImage, } +/// The delay of a frame relative to the previous one. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd)] +pub struct Delay { + ratio: Ratio, +} + impl Frame { - /// Contructs a new frame + /// Contructs a new frame without any delay. pub fn new(buffer: RgbaImage) -> Frame { Frame { - delay: Ratio::from_integer(0), + delay: Delay::from_ratio(Ratio::from_integer(0)), left: 0, top: 0, buffer, @@ -57,7 +64,7 @@ impl Frame { } /// Contructs a new frame - pub fn from_parts(buffer: RgbaImage, left: u32, top: u32, delay: Ratio) -> Frame { + pub fn from_parts(buffer: RgbaImage, left: u32, top: u32, delay: Delay) -> Frame { Frame { delay, left, @@ -67,7 +74,7 @@ impl Frame { } /// Delay of this frame - pub fn delay(&self) -> Ratio { + pub fn delay(&self) -> Delay { self.delay } @@ -91,3 +98,233 @@ impl Frame { self.top } } + +impl Delay { + /// Create a delay from a ratio of milliseconds. + /// + /// # Examples + /// + /// ``` + /// use image::Delay; + /// let delay_10ms = Delay::from_numer_denom_ms(10, 1); + /// ``` + pub fn from_numer_denom_ms(numerator: u32, denominator: u32) -> Self { + Delay { ratio: Ratio::new_raw(numerator, denominator) } + } + + /// Convert from a duration, clamped between 0 and an implemented defined maximum. + /// + /// The maximum is *at least* `i32::MAX` milliseconds. It should be noted that the accuracy of + /// the result may be relative and very large delays have a coarse resolution. + /// + /// # Examples + /// + /// ``` + /// use std::time::Duration; + /// use image::Delay; + /// + /// let duration = Duration::from_millis(20); + /// let delay = Delay::from_saturating_duration(duration); + /// ``` + pub fn from_saturating_duration(duration: Duration) -> Self { + // A few notes: The largest number we can represent as a ratio is u32::MAX but we can + // sometimes represent much smaller numbers. + // + // We can represent duration as `millis+a/b` (where a < b, b > 0). + // We must thus bound b with `b·millis + (b-1) <= u32::MAX` or + // > `0 < b <= (u32::MAX + 1)/(millis + 1)` + // Corollary: millis <= u32::MAX + + const MILLIS_BOUND: u128 = u32::max_value() as u128; + + let millis = duration.as_millis().min(MILLIS_BOUND); + let submillis = (duration.as_nanos() % 1_000_000) as u32; + + let max_b = if millis > 0 { + ((MILLIS_BOUND + 1)/(millis + 1)) as u32 + } else { + MILLIS_BOUND as u32 + }; + let millis = millis as u32; + + let (a, b) = Self::closest_bounded_fraction(max_b, submillis, 1_000_000); + Self::from_numer_denom_ms(a + b*millis, b) + } + + /// The numerator and denominator of the delay in milliseconds. + /// + /// This is guaranteed to be an exact conversion if the `Delay` was previously created with the + /// `from_numer_denom_ms` constructor. + pub fn numer_denom_ms(self) -> (u32, u32) { + (*self.ratio.numer(), *self.ratio.denom()) + } + + pub(crate) fn from_ratio(ratio: Ratio) -> Self { + Delay { ratio } + } + + pub(crate) fn into_ratio(self) -> Ratio { + self.ratio + } + + /// Given some fraction, compute an approximation with denominator bounded. + /// + /// Note that `denom_bound` bounds nominator and denominator of all intermediate + /// approximations and the end result. + fn closest_bounded_fraction(denom_bound: u32, nom: u32, denom: u32) -> (u32, u32) { + use std::cmp::Ordering::{self, *}; + assert!(0 < denom); + assert!(0 < denom_bound); + assert!(nom < denom); + + // Avoid a few type troubles. All intermediate results are bounded by `denom_bound` which + // is in turn bounded by u32::MAX. Representing with u64 allows multiplication of any two + // values without fears of overflow. + + // Compare two fractions whose parts fit into a u32. + fn compare_fraction((an, ad): (u64, u64), (bn, bd): (u64, u64)) -> Ordering { + (an*bd).cmp(&(bn*ad)) + } + + // Computes the nominator of the absolute difference between two such fractions. + fn abs_diff_nom((an, ad): (u64, u64), (bn, bd): (u64, u64)) -> u64 { + let c0 = an*bd; + let c1 = ad*bn; + + let d0 = c0.max(c1); + let d1 = c0.min(c1); + d0 - d1 + } + + let exact = (u64::from(nom), u64::from(denom)); + // The lower bound fraction, numerator and denominator. + let mut lower = (0u64, 1u64); + // The upper bound fraction, numerator and denominator. + let mut upper = (1u64, 1u64); + // The closest approximation for now. + let mut guess = (u64::from(nom*2 > denom), 1u64); + + // loop invariant: ad, bd <= denom_bound + // iterates the Farey sequence. + loop { + // Break if we are done. + if compare_fraction(guess, exact) == Equal { + break; + } + + // Break if next Farey number is out-of-range. + if u64::from(denom_bound) - lower.1 < upper.1 { + break; + } + + // Next Farey approximation n between a and b + let next = (lower.0 + upper.0, lower.1 + upper.1); + // if F < n then replace the upper bound, else replace lower. + if compare_fraction(exact, next) == Less { + upper = next; + } else { + lower = next; + } + + // Now correct the closest guess. + // In other words, if |c - f| > |n - f| then replace it with the new guess. + // This favors the guess with smaller denominator on equality. + + // |g - f| = |g_diff_nom|/(gd*fd); + let g_diff_nom = abs_diff_nom(guess, exact); + // |n - f| = |n_diff_nom|/(nd*fd); + let n_diff_nom = abs_diff_nom(next, exact); + + // The difference |n - f| is smaller than |g - f| if either the integral part of the + // fraction |n_diff_nom|/nd is smaller than the one of |g_diff_nom|/gd or if they are + // the same but the fractional part is larger. + if match (n_diff_nom/next.1).cmp(&(g_diff_nom/guess.1)) { + Less => true, + Greater => false, + // Note that the nominator for the fractional part is smaller than its denominator + // which is smaller than u32 and can't overflow the multiplication with the other + // denominator, that is we can compare these fractions by multiplication with the + // respective other denominator. + Equal => compare_fraction((n_diff_nom%next.1, next.1), (g_diff_nom%guess.1, guess.1)) == Less, + } { + guess = next; + } + } + + (guess.0 as u32, guess.1 as u32) + } +} + +impl From for Duration { + fn from(delay: Delay) -> Self { + let ratio = delay.into_ratio(); + let ms = ratio.to_integer(); + let rest = ratio.numer() % ratio.denom(); + let nanos = (u64::from(rest) * 1_000_000) / u64::from(*ratio.denom()); + Duration::from_millis(ms.into()) + Duration::from_nanos(nanos) + } +} + +#[cfg(test)] +mod tests { + use super::{Delay, Duration, Ratio}; + + #[test] + fn simple() { + let second = Delay::from_numer_denom_ms(1000, 1); + assert_eq!(Duration::from(second), Duration::from_secs(1)); + } + + #[test] + fn fps_30() { + let thirtieth = Delay::from_numer_denom_ms(1000, 30); + let duration = Duration::from(thirtieth); + assert_eq!(duration.as_secs(), 0); + assert_eq!(duration.subsec_millis(), 33); + assert_eq!(duration.subsec_nanos(), 33_333_333); + } + + #[test] + fn duration_outlier() { + let oob = Duration::from_secs(0xFFFF_FFFF); + let delay = Delay::from_saturating_duration(oob); + assert_eq!(delay.numer_denom_ms(), (0xFFFF_FFFF, 1)); + } + + #[test] + fn duration_approx() { + let oob = Duration::from_millis(0xFFFF_FFFF) + Duration::from_micros(1); + let delay = Delay::from_saturating_duration(oob); + assert_eq!(delay.numer_denom_ms(), (0xFFFF_FFFF, 1)); + + let inbounds = Duration::from_millis(0xFFFF_FFFF) - Duration::from_micros(1); + let delay = Delay::from_saturating_duration(inbounds); + assert_eq!(delay.numer_denom_ms(), (0xFFFF_FFFF, 1)); + + let fine = Duration::from_millis(0xFFFF_FFFF/1000) + Duration::from_micros(0xFFFF_FFFF%1000); + let delay = Delay::from_saturating_duration(fine); + // Funnily, 0xFFFF_FFFF is divisble by 5, thus we compare with a `Ratio`. + assert_eq!(delay.into_ratio(), Ratio::new(0xFFFF_FFFF, 1000)); + } + + #[test] + fn precise() { + // The ratio has only 32 bits in the numerator, too imprecise to get more than 11 digits + // correct. But it may be expressed as 1_000_000/3 instead. + let exceed = Duration::from_secs(333) + Duration::from_nanos(333_333_333); + let delay = Delay::from_saturating_duration(exceed); + assert_eq!(Duration::from(delay), exceed); + } + + + #[test] + fn small() { + // Not quite a delay of `1 ms`. + let delay = Delay::from_numer_denom_ms(1 << 16, (1 << 16) + 1); + let duration = Duration::from(delay); + assert_eq!(duration.as_millis(), 0); + // Not precisely the original but should be smaller than 0. + let delay = Delay::from_saturating_duration(duration); + assert_eq!(delay.into_ratio().to_integer(), 0); + } +} diff --git a/src/bmp/decoder.rs b/src/bmp/decoder.rs index 47f8dab70a..3b32d0810c 100644 --- a/src/bmp/decoder.rs +++ b/src/bmp/decoder.rs @@ -1,3 +1,4 @@ +use std::convert::TryFrom; use std::io::{self, Cursor, Read, Seek, SeekFrom}; use std::iter::{repeat, Iterator, Rev}; use std::marker::PhantomData; @@ -7,8 +8,9 @@ use std::cmp::Ordering; use byteorder::{LittleEndian, ReadBytesExt}; -use color::ColorType; -use image::{self, ImageDecoder, ImageDecoderExt, ImageError, ImageResult, Progress}; +use crate::color::ColorType; +use crate::error::{ImageError, ImageResult}; +use crate::image::{self, ImageDecoder, ImageDecoderExt, Progress}; const BITMAPCOREHEADER_SIZE: u32 = 12; const BITMAPINFOHEADER_SIZE: u32 = 40; @@ -425,7 +427,7 @@ impl Bitfields { } /// A bmp decoder -pub struct BMPDecoder { +pub struct BmpDecoder { reader: R, bmp_header_type: BMPHeaderType, @@ -510,10 +512,10 @@ impl<'a, R: Read> Iterator for RLEInsnIterator<'a, R> { } } -impl BMPDecoder { +impl BmpDecoder { /// Create a new decoder that decodes from the stream ```r``` - pub fn new(reader: R) -> ImageResult> { - let mut decoder = BMPDecoder { + pub fn new(reader: R) -> ImageResult> { + let mut decoder = BmpDecoder { reader, bmp_header_type: BMPHeaderType::Info, @@ -538,8 +540,8 @@ impl BMPDecoder { } #[cfg(feature = "ico")] - pub(crate) fn new_with_ico_format(reader: R) -> ImageResult> { - let mut decoder = BMPDecoder { + pub(crate) fn new_with_ico_format(reader: R) -> ImageResult> { + let mut decoder = BmpDecoder { reader, bmp_header_type: BMPHeaderType::Info, @@ -1254,8 +1256,8 @@ impl BMPDecoder { /// Read the actual data of the image. This function is deliberately not public because it /// cannot be called multiple times without seeking back the underlying reader in between. - pub(crate) fn read_image_data(&mut self) -> ImageResult> { - match self.image_type { + pub(crate) fn read_image_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { + let data = match self.image_type { ImageType::Palette => self.read_palettized_pixel_data(), ImageType::RGB16 => self.read_16_bit_pixel_data(Some(&R5_G5_B5_COLOR_MASK)), ImageType::RGB24 => self.read_full_byte_pixel_data(&FormatFullBytes::RGB24), @@ -1278,7 +1280,10 @@ impl BMPDecoder { "Missing 32-bit bitfield masks".to_string(), )), }, - } + }?; + + buf.copy_from_slice(&data); + Ok(()) } } @@ -1298,58 +1303,45 @@ impl Read for BmpReader { } } -impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for BMPDecoder { +impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for BmpDecoder { type Reader = BmpReader; - fn dimensions(&self) -> (u64, u64) { - (self.width as u64, self.height as u64) + fn dimensions(&self) -> (u32, u32) { + (self.width as u32, self.height as u32) } - fn colortype(&self) -> ColorType { + fn color_type(&self) -> ColorType { if self.add_alpha_channel { - ColorType::RGBA(8) + ColorType::Rgba8 } else { - ColorType::RGB(8) + ColorType::Rgb8 } } fn into_reader(self) -> ImageResult { - Ok(BmpReader(Cursor::new(self.read_image()?), PhantomData)) + Ok(BmpReader(Cursor::new(image::decoder_to_vec(self)?), PhantomData)) } - fn read_image(mut self) -> ImageResult> { - self.read_image_data() + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + self.read_image_data(buf) } } -impl<'a, R: 'a + Read + Seek> ImageDecoderExt<'a> for BMPDecoder { +impl<'a, R: 'a + Read + Seek> ImageDecoderExt<'a> for BmpDecoder { fn read_rect_with_progress( &mut self, - x: u64, - y: u64, - width: u64, - height: u64, + x: u32, + y: u32, + width: u32, + height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()> { let start = self.reader.seek(SeekFrom::Current(0))?; - let data = self.read_image_data(); + image::load_rect(x, y, width, height, buf, progress_callback, self, |_, _| unreachable!(), + |s, buf| { s.read_image_data(buf).map(|_| buf.len()) })?; self.reader.seek(SeekFrom::Start(start))?; - - let data = data?; - - #[rustfmt::skip] - image::load_rect( - x, y, width, height, - buf, - progress_callback, - self, - |_, _| unreachable!(), - |_, buf| { - buf.copy_from_slice(&data); - Ok(buf.len()) - }, - )?; Ok(()) } } diff --git a/src/bmp/encoder.rs b/src/bmp/encoder.rs index 347633a008..cceffef320 100644 --- a/src/bmp/encoder.rs +++ b/src/bmp/encoder.rs @@ -1,7 +1,9 @@ use byteorder::{LittleEndian, WriteBytesExt}; use std::io::{self, Write}; -use color; +use crate::color; +use crate::error::{ImageError, ImageResult}; +use crate::image::ImageEncoder; const BITMAPFILEHEADER_SIZE: u32 = 14; const BITMAPINFOHEADER_SIZE: u32 = 40; @@ -27,21 +29,18 @@ impl<'a, W: Write + 'a> BMPEncoder<'a, W> { width: u32, height: u32, c: color::ColorType, - ) -> io::Result<()> { + ) -> ImageResult<()> { let bmp_header_size = BITMAPFILEHEADER_SIZE; let (dib_header_size, written_pixel_size, palette_color_count) = get_pixel_info(c)?; let row_pad_size = (4 - (width * written_pixel_size) % 4) % 4; // each row must be padded to a multiple of 4 bytes - - let make_invalid_input_err = - || io::Error::new(io::ErrorKind::InvalidInput, "Overflowing dimensions"); let image_size = width .checked_mul(height) - .ok_or_else(make_invalid_input_err)? + .ok_or(ImageError::DimensionError)? .checked_mul(written_pixel_size) - .ok_or_else(make_invalid_input_err)? + .ok_or(ImageError::DimensionError)? .checked_add(height * row_pad_size) - .ok_or_else(make_invalid_input_err)?; + .ok_or(ImageError::DimensionError)?; let palette_size = palette_color_count * 4; // all palette colors are BGRA let file_size = bmp_header_size + dib_header_size + palette_size + image_size; @@ -88,17 +87,23 @@ impl<'a, W: Write + 'a> BMPEncoder<'a, W> { // write image data match c { - color::ColorType::RGB(8) => self.encode_rgb(image, width, height, row_pad_size, 3)?, - color::ColorType::RGBA(8) => self.encode_rgba(image, width, height, row_pad_size, 4)?, - color::ColorType::Gray(8) => self.encode_gray(image, width, height, row_pad_size, 1)?, - color::ColorType::GrayA(8) => { + color::ColorType::Rgb8 => { + self.encode_rgb(image, width, height, row_pad_size, 3)? + } + color::ColorType::Rgba8 => { + self.encode_rgba(image, width, height, row_pad_size, 4)? + } + color::ColorType::L8 => { + self.encode_gray(image, width, height, row_pad_size, 1)? + } + color::ColorType::La8 => { self.encode_gray(image, width, height, row_pad_size, 2)? } _ => { - return Err(io::Error::new( + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, &get_unsupported_error_message(c)[..], - )) + ))) } } @@ -214,6 +219,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).", @@ -224,10 +241,10 @@ fn get_unsupported_error_message(c: color::ColorType) -> String { /// Returns a tuple representing: (dib header size, written pixel size, palette color count). fn get_pixel_info(c: color::ColorType) -> io::Result<(u32, u32, u32)> { let sizes = match c { - color::ColorType::RGB(8) => (BITMAPINFOHEADER_SIZE, 3, 0), - color::ColorType::RGBA(8) => (BITMAPV4HEADER_SIZE, 4, 0), - color::ColorType::Gray(8) => (BITMAPINFOHEADER_SIZE, 1, 256), - color::ColorType::GrayA(8) => (BITMAPINFOHEADER_SIZE, 1, 256), + color::ColorType::Rgb8 => (BITMAPINFOHEADER_SIZE, 3, 0), + color::ColorType::Rgba8 => (BITMAPV4HEADER_SIZE, 4, 0), + color::ColorType::L8 => (BITMAPINFOHEADER_SIZE, 1, 256), + color::ColorType::La8 => (BITMAPINFOHEADER_SIZE, 1, 256), _ => { return Err(io::Error::new( io::ErrorKind::InvalidInput, @@ -241,10 +258,10 @@ fn get_pixel_info(c: color::ColorType) -> io::Result<(u32, u32, u32)> { #[cfg(test)] mod tests { - use super::super::BMPDecoder; + use super::super::BmpDecoder; use super::BMPEncoder; - use color::ColorType; - use image::ImageDecoder; + use crate::color::ColorType; + use crate::image::ImageDecoder; use std::io::Cursor; fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec { @@ -256,14 +273,17 @@ mod tests { .expect("could not encode image"); } - let decoder = BMPDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode"); - decoder.read_image().expect("failed to decode") + let decoder = BmpDecoder::new(Cursor::new(&encoded_data)).expect("failed to decode"); + + let mut buf = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut buf).expect("failed to decode"); + buf } #[test] fn round_trip_single_pixel_rgb() { let image = [255u8, 0, 0]; // single red pixel - let decoded = round_trip_image(&image, 1, 1, ColorType::RGB(8)); + let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); assert_eq!(3, decoded.len()); assert_eq!(255, decoded[0]); assert_eq!(0, decoded[1]); @@ -275,27 +295,27 @@ mod tests { let mut encoded_data = Vec::new(); let image = vec![0u8; 3 * 40_000 * 40_000]; // 40_000x40_000 pixels, 3 bytes per pixel, allocated on the heap let mut encoder = BMPEncoder::new(&mut encoded_data); - let result = encoder.encode(&image, 40_000, 40_000, ColorType::RGB(8)); + let result = encoder.encode(&image, 40_000, 40_000, ColorType::Rgb8); assert!(result.is_err()); } #[test] fn round_trip_single_pixel_rgba() { let image = [1, 2, 3, 4]; - let decoded = round_trip_image(&image, 1, 1, ColorType::RGBA(8)); + let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8); assert_eq!(&decoded[..], &image[..]); } #[test] fn round_trip_3px_rgb() { let image = [0u8; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel - let _decoded = round_trip_image(&image, 3, 3, ColorType::RGB(8)); + let _decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8); } #[test] fn round_trip_gray() { let image = [0u8, 1, 2]; // 3 pixels - let decoded = round_trip_image(&image, 3, 1, ColorType::Gray(8)); + let decoded = round_trip_image(&image, 3, 1, ColorType::L8); // should be read back as 3 RGB pixels assert_eq!(9, decoded.len()); assert_eq!(0, decoded[0]); @@ -312,7 +332,7 @@ mod tests { #[test] fn round_trip_graya() { let image = [0u8, 0, 1, 0, 2, 0]; // 3 pixels, each with an alpha channel - let decoded = round_trip_image(&image, 1, 3, ColorType::GrayA(8)); + let decoded = round_trip_image(&image, 1, 3, ColorType::La8); // should be read back as 3 RGB pixels assert_eq!(9, decoded.len()); assert_eq!(0, decoded[0]); diff --git a/src/bmp/mod.rs b/src/bmp/mod.rs index ada8755017..09f80ba3d2 100644 --- a/src/bmp/mod.rs +++ b/src/bmp/mod.rs @@ -7,7 +7,7 @@ //! * //! -pub use self::decoder::BMPDecoder; +pub use self::decoder::BmpDecoder; pub use self::encoder::BMPEncoder; mod decoder; diff --git a/src/buffer.rs b/src/buffer.rs index 6b436ec61b..051fb2af25 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,17 +1,17 @@ use num_traits::Zero; -use std::io; use std::marker::PhantomData; 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 flat::{FlatSamples, SampleLayout}; -use dynimage::{save_buffer, save_buffer_with_format}; -use image::{GenericImage, GenericImageView, ImageFormat}; -use math::Rect; -use traits::Primitive; -use utils::expand_packed; +use crate::color::{ColorType, FromColor, Luma, LumaA, Rgb, Rgba, Bgr, Bgra}; +use crate::flat::{FlatSamples, SampleLayout}; +use crate::dynimage::{save_buffer, save_buffer_with_format}; +use crate::error::ImageResult; +use crate::image::{GenericImage, GenericImageView, ImageFormat}; +use crate::math::Rect; +use crate::traits::{EncodableLayout, Primitive}; +use crate::utils::expand_packed; /// A generalized pixel. /// @@ -747,21 +747,22 @@ 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. /// /// The image format is derived from the file extension. /// Currently only jpeg and png files are supported. - pub fn save(&self, path: Q) -> io::Result<()> + pub fn save(&self, path: Q) -> ImageResult<()> where Q: AsRef, { // This is valid as the subpixel is u8. save_buffer( path, - self, + self.as_bytes(), self.width(), self.height(),

::COLOR_TYPE, @@ -771,22 +772,23 @@ 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. /// /// See [`save_buffer_with_format`](fn.save_buffer_with_format.html) for /// supported types. - pub fn save_with_format(&self, path: Q, format: ImageFormat) -> io::Result<()> + pub fn save_with_format(&self, path: Q, format: ImageFormat) -> ImageResult<()> where Q: AsRef, { // This is valid as the subpixel is u8. save_buffer_with_format( path, - self, + self.as_bytes(), self.width(), self.height(),

::COLOR_TYPE, @@ -1128,14 +1130,22 @@ 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 { - use super::{ImageBuffer, RgbImage, GrayImage}; - use super::GenericImage; - use color; - use math::Rect; + use super::{GrayImage, ImageBuffer, RgbImage}; + use crate::image::GenericImage; + use crate::color; + use crate::math::Rect; #[cfg(feature = "benchmarks")] use test; @@ -1170,9 +1180,9 @@ mod test { #[bench] #[cfg(feature = "benchmarks")] fn bench_conversion(b: &mut test::Bencher) { - use buffer::{ConvertBuffer, GrayImage, Pixel}; + use crate::buffer::{ConvertBuffer, GrayImage, Pixel}; let mut a: RgbImage = ImageBuffer::new(1000, 1000); - for mut p in a.pixels_mut() { + for p in a.pixels_mut() { let rgb = p.channels_mut(); rgb[0] = 255; rgb[1] = 23; @@ -1191,10 +1201,10 @@ mod test { #[bench] #[cfg(feature = "benchmarks")] fn bench_image_access_row_by_row(b: &mut test::Bencher) { - use buffer::{ImageBuffer, Pixel}; + use crate::buffer::{ImageBuffer, Pixel}; let mut a: RgbImage = ImageBuffer::new(1000, 1000); - for mut p in a.pixels_mut() { + for p in a.pixels_mut() { let rgb = p.channels_mut(); rgb[0] = 255; rgb[1] = 23; @@ -1221,10 +1231,10 @@ mod test { #[bench] #[cfg(feature = "benchmarks")] fn bench_image_access_col_by_col(b: &mut test::Bencher) { - use buffer::{ImageBuffer, Pixel}; + use crate::buffer::{ImageBuffer, Pixel}; let mut a: RgbImage = ImageBuffer::new(1000, 1000); - for mut p in a.pixels_mut() { + for p in a.pixels_mut() { let rgb = p.channels_mut(); rgb[0] = 255; rgb[1] = 23; diff --git a/src/color.rs b/src/color.rs index 001d934187..63fd9fb33b 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,65 +1,157 @@ -use num_traits::{NumCast, Zero}; -use std::mem; +use num_traits::{NumCast, ToPrimitive, Zero}; use std::ops::{Index, IndexMut}; -use buffer::Pixel; -use traits::Primitive; +use crate::buffer::Pixel; +use crate::traits::Primitive; -/// An enumeration over supported color types and their bit depths +/// An enumeration over supported color types and bit depths #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] pub enum ColorType { - /// Pixel is grayscale - Gray(u8), - - /// Pixel contains R, G and B channels - RGB(u8), - - /// Pixel is an index into a color palette - Palette(u8), - - /// Pixel is grayscale with an alpha channel - GrayA(u8), - - /// Pixel is RGB with an alpha channel - RGBA(u8), - - /// Pixel contains B, G and R channels - BGR(u8), - - /// Pixel is BGR with an alpha channel - BGRA(u8), - + /// Pixel is 8-bit luminance + L8, + /// Pixel is 8-bit luminance with an alpha channel + La8, + /// Pixel contains 8-bit R, G and B channels + Rgb8, + /// Pixel is 8-bit RGB with an alpha channel + Rgba8, + + /// Pixel is 16-bit luminance + L16, + /// Pixel is 16-bit luminance with an alpha channel + La16, + /// Pixel is 16-bit RGB + Rgb16, + /// Pixel is 16-bit RGBA + Rgba16, + + /// Pixel contains 8-bit B, G and R channels + Bgr8, + /// Pixel is 8-bit BGR with an alpha channel + Bgra8, + + #[doc(hidden)] + __NonExhaustive(crate::utils::NonExhaustiveMarker), } impl ColorType { - /// Returns the number of bits contained in a pixel of this `ColorType` - pub fn bits_per_pixel(&self) -> u16 { - bits_per_pixel(*self) + /// Returns the number of bytes contained in a pixel of `ColorType` ```c``` + pub fn bytes_per_pixel(self) -> u8 { + match self { + ColorType::L8 => 1, + ColorType::L16 | ColorType::La8 => 2, + ColorType::Rgb8 | ColorType::Bgr8 => 3, + ColorType::Rgba8 | ColorType::Bgra8 | ColorType::La16 => 4, + ColorType::Rgb16 => 6, + ColorType::Rgba16 => 8, + ColorType::__NonExhaustive(marker) => match marker._private {}, + } + } + + /// Returns the number of bits contained in a pixel of `ColorType` ```c``` (which will always be + /// a multiple of 8). + pub fn bits_per_pixel(self) -> u16 { + >::from(self.bytes_per_pixel()) * 8 } /// Returns the number of color channels that make up this pixel - pub fn channel_count(&self) -> u8 { - channel_count(*self) + pub fn channel_count(self) -> u8 { + let e: ExtendedColorType = self.into(); + e.channel_count() } } -/// Returns the number of bits contained in a pixel of `ColorType` ```c``` -pub(crate) fn bits_per_pixel(c: ColorType) -> u16 { - match c { - ColorType::Gray(n) => n as u16, - ColorType::GrayA(n) => 2 * n as u16, - ColorType::RGB(n) | ColorType::Palette(n)| ColorType::BGR(n) => 3 * n as u16, - ColorType::RGBA(n) | ColorType::BGRA(n) => 4 * n as u16, - } +/// An enumeration of color types encountered in image formats. +/// +/// This is not exhaustive over all existing image formats but should be granular enough to allow +/// round tripping of decoding and encoding as much as possible. The variants will be extended as +/// necessary to enable this. +/// +/// Another purpose is to advise users of a rough estimate of the accuracy and effort of the +/// decoding from and encoding to such an image format. +#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] +pub enum ExtendedColorType { + L1, + La1, + Rgb1, + Rgba1, + L2, + La2, + Rgb2, + Rgba2, + L4, + La4, + Rgb4, + Rgba4, + L8, + La8, + Rgb8, + Rgba8, + L16, + La16, + Rgb16, + Rgba16, + Bgr8, + Bgra8, + + /// Pixel is of unknown color type with the specified bits per pixel. This can apply to pixels + /// which are associated with an external palette. In that case, the pixel value is an index + /// into the palette. + Unknown(u8), + + #[doc(hidden)] + __NonExhaustive(crate::utils::NonExhaustiveMarker), } -/// Returns the number of color channels that make up this pixel -pub(crate) fn channel_count(c: ColorType) -> u8 { - match c { - ColorType::Gray(_) => 1, - ColorType::GrayA(_) => 2, - ColorType::RGB(_) | ColorType::Palette(_) | ColorType::BGR(_)=> 3, - ColorType::RGBA(_) | ColorType::BGRA(_) => 4, +impl ExtendedColorType { + /// Get the number of channels for colors of this type. + /// + /// Note that the `Unknown` variant returns a value of `1` since pixels can only be treated as + /// an opaque datum by the library. + pub fn channel_count(self) -> u8 { + match self { + ExtendedColorType::L1 | + ExtendedColorType::L2 | + ExtendedColorType::L4 | + ExtendedColorType::L8 | + ExtendedColorType::L16 | + ExtendedColorType::Unknown(_) => 1, + ExtendedColorType::La1 | + ExtendedColorType::La2 | + ExtendedColorType::La4 | + ExtendedColorType::La8 | + ExtendedColorType::La16 => 2, + ExtendedColorType::Rgb1 | + ExtendedColorType::Rgb2 | + ExtendedColorType::Rgb4 | + ExtendedColorType::Rgb8 | + ExtendedColorType::Rgb16 | + ExtendedColorType::Bgr8 => 3, + ExtendedColorType::Rgba1 | + ExtendedColorType::Rgba2 | + ExtendedColorType::Rgba4 | + ExtendedColorType::Rgba8 | + ExtendedColorType::Rgba16 | + ExtendedColorType::Bgra8 => 4, + ExtendedColorType::__NonExhaustive(marker) => match marker._private {}, + } + } +} +impl From for ExtendedColorType { + fn from(c: ColorType) -> Self { + match c { + ColorType::L8 => ExtendedColorType::L8, + ColorType::La8 => ExtendedColorType::La8, + ColorType::Rgb8 => ExtendedColorType::Rgb8, + ColorType::Rgba8 => ExtendedColorType::Rgba8, + ColorType::L16 => ExtendedColorType::L16, + ColorType::La16 => ExtendedColorType::La16, + ColorType::Rgb16 => ExtendedColorType::Rgb16, + ColorType::Rgba16 => ExtendedColorType::Rgba16, + ColorType::Bgr8 => ExtendedColorType::Bgr8, + ColorType::Bgra8 => ExtendedColorType::Bgra8, + ColorType::__NonExhaustive(marker) => match marker._private {}, + } } } @@ -69,7 +161,8 @@ macro_rules! define_colors { $channels: expr, $alphas: expr, $interpretation: expr, - $color_type: ident, + $color_type_u8: expr, + $color_type_u16: expr, #[$doc:meta]; )*} => { @@ -82,14 +175,14 @@ $( // START Structure definitions pub struct $ident (pub [T; $channels]); impl Pixel for $ident { - type Subpixel = T; const CHANNEL_COUNT: u8 = $channels; const COLOR_MODEL: &'static str = $interpretation; - const COLOR_TYPE: ColorType = ColorType::$color_type(mem::size_of::() as u8 * 8); + const COLOR_TYPE: ColorType = + [$color_type_u8, $color_type_u16][(std::mem::size_of::() > 1) as usize]; #[inline(always)] fn channels(&self) -> &[T] { @@ -223,24 +316,30 @@ 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", RGB, #[doc = "RGB colors"]; - Bgr, 3, 0, "BGR", BGR, #[doc = "BGR colors"]; - Luma, 1, 0, "Y", Gray, #[doc = "Grayscale colors"]; - Rgba, 4, 1, "RGBA", RGBA, #[doc = "RGB colors + alpha channel"]; - Bgra, 4, 1, "BGRA", BGRA, #[doc = "BGR colors + alpha channel"]; - LumaA, 2, 1, "YA", GrayA, #[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. pub trait FromColor { /// Changes `self` to represent `Other` in the color space of `Self` - fn from_color(&mut self, &Other); + fn from_color(&mut self, _: &Other); } // Self->Self: just copy @@ -250,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: 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); } } @@ -266,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); } } @@ -276,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]; } } @@ -317,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]; } } @@ -328,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(); } } @@ -339,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(); } } @@ -354,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) { @@ -389,7 +582,6 @@ impl FromColor> for Rgba { } } - impl FromColor> for Rgba { fn from_color(&mut self, other: &LumaA) { let rgba = self.channels_mut(); @@ -401,8 +593,6 @@ impl FromColor> for Rgba { } } - - impl FromColor> for Rgba { fn from_color(&mut self, gray: &Luma) { let rgba = self.channels_mut(); @@ -414,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) { @@ -428,7 +640,6 @@ impl FromColor> for Bgra { } } - impl FromColor> for Bgra { fn from_color(&mut self, other: &Bgr) { let bgra = self.channels_mut(); @@ -440,7 +651,6 @@ impl FromColor> for Bgra { } } - impl FromColor> for Bgra { fn from_color(&mut self, other: &Rgba) { let bgra = self.channels_mut(); @@ -475,8 +685,7 @@ impl FromColor> for Bgra { } - -/// `FromColor` for RGB +// `FromColor` for RGB impl FromColor> for Rgb { fn from_color(&mut self, other: &Rgba) { @@ -488,7 +697,6 @@ impl FromColor> for Rgb { } } - impl FromColor> for Rgb { fn from_color(&mut self, other: &Bgra) { let rgb = self.channels_mut(); @@ -529,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 { @@ -582,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 { @@ -833,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() { @@ -990,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/dds.rs b/src/dds.rs new file mode 100644 index 0000000000..09071bd31f --- /dev/null +++ b/src/dds.rs @@ -0,0 +1,170 @@ +//! Decoding of DDS images +//! +//! DDS (DirectDraw Surface) is a container format for storing DXT (S3TC) compressed images. +//! +//! # Related Links +//! * - Description of the DDS format. + +use std::io::Read; + +use byteorder::{LittleEndian, ReadBytesExt}; + +use crate::color::ColorType; +use crate::dxt::{DxtDecoder, DXTReader, DXTVariant}; +use crate::error::{ImageError, ImageResult}; +use crate::image::ImageDecoder; + + +/// Header used by DDS image files +#[derive(Debug)] +struct Header { + flags: u32, + height: u32, + width: u32, + pitch_or_linear_size: u32, + depth: u32, + mipmap_count: u32, + pixel_format: PixelFormat, + caps: u32, + caps2: u32, +} + +/// DDS pixel format +#[derive(Debug)] +struct PixelFormat { + flags: u32, + fourcc: [u8; 4], + rgb_bit_count: u32, + r_bit_mask: u32, + g_bit_mask: u32, + b_bit_mask: u32, + a_bit_mask: u32, +} + +impl PixelFormat { + fn from_reader(r: &mut dyn Read) -> ImageResult { + let size = r.read_u32::()?; + if size != 32 { + return Err(ImageError::FormatError("Invalid DDS PixelFormat size".to_string())) + } + + Ok(Self { + flags: r.read_u32::()?, + fourcc: { + let mut v = [0; 4]; + r.read_exact(&mut v)?; + v + }, + rgb_bit_count: r.read_u32::()?, + r_bit_mask: r.read_u32::()?, + g_bit_mask: r.read_u32::()?, + b_bit_mask: r.read_u32::()?, + a_bit_mask: r.read_u32::()?, + }) + } +} + +impl Header { + fn from_reader(r: &mut dyn Read) -> ImageResult { + let size = r.read_u32::()?; + if size != 124 { + return Err(ImageError::FormatError("Invalid DDS header size".to_string())) + } + + const REQUIRED_FLAGS: u32 = 0x1 | 0x2 | 0x4 | 0x1000; + const VALID_FLAGS: u32 = 0x1 | 0x2 | 0x4 | 0x8 | 0x1000 | 0x20000 | 0x80000 | 0x800000; + let flags = r.read_u32::()?; + if flags & (REQUIRED_FLAGS | !VALID_FLAGS) != REQUIRED_FLAGS { + return Err(ImageError::FormatError("Invalid DDS header flags".to_string())) + } + + let height = r.read_u32::()?; + let width = r.read_u32::()?; + let pitch_or_linear_size = r.read_u32::()?; + let depth = r.read_u32::()?; + let mipmap_count = r.read_u32::()?; + // Skip `dwReserved1` + { + let mut skipped = [0; 4 * 11]; + r.read_exact(&mut skipped)?; + } + let pixel_format = PixelFormat::from_reader(r)?; + let caps = r.read_u32::()?; + let caps2 = r.read_u32::()?; + // Skip `dwCaps3`, `dwCaps4`, `dwReserved2` (unused) + { + let mut skipped = [0; 4 + 4 + 4]; + r.read_exact(&mut skipped)?; + } + + Ok(Self { + flags, + height, + width, + pitch_or_linear_size, + depth, + mipmap_count, + pixel_format, + caps, + caps2, + }) + } +} + + +/// The representation of a DDS decoder +pub struct DdsDecoder { + inner: DxtDecoder, +} + +impl DdsDecoder { + /// Create a new decoder that decodes from the stream `r` + pub fn new(mut r: R) -> ImageResult { + let mut magic = [0; 4]; + r.read_exact(&mut magic)?; + if magic != b"DDS "[..] { + return Err(ImageError::FormatError("DDS signature not found".to_string())) + } + + let header = Header::from_reader(&mut r)?; + + if header.pixel_format.flags & 0x4 != 0 { + let variant = match &header.pixel_format.fourcc { + b"DXT1" => DXTVariant::DXT1, + b"DXT3" => DXTVariant::DXT3, + b"DXT5" => DXTVariant::DXT5, + _ => return Err(ImageError::FormatError("Unsupported DDS FourCC".to_string())), + }; + let inner = DxtDecoder::new(r, header.width, header.height, variant)?; + Ok(Self { inner }) + } else { + // For now, supports only DXT variants + Err(ImageError::FormatError("DDS format not supported".to_string())) + } + } +} + +impl<'a, R: 'a + Read> ImageDecoder<'a> for DdsDecoder { + type Reader = DXTReader; + + fn dimensions(&self) -> (u32, u32) { + self.inner.dimensions() + } + + fn color_type(&self) -> ColorType { + self.inner.color_type() + } + + fn scanline_bytes(&self) -> u64 { + self.inner.scanline_bytes() + } + + fn into_reader(self) -> ImageResult { + self.inner.into_reader() + } + + fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { + self.inner.read_image(buf) + } +} + diff --git a/src/dxt.rs b/src/dxt.rs index 5e0915e5b1..a76a10f6db 100644 --- a/src/dxt.rs +++ b/src/dxt.rs @@ -7,10 +7,12 @@ //! //! Note: this module only implements bare DXT encoding/decoding, it does not parse formats that can contain DXT files like .dds +use std::convert::TryFrom; use std::io::{self, Read, Seek, SeekFrom, Write}; -use color::ColorType; -use image::{self, ImageDecoder, ImageDecoderExt, ImageError, ImageReadBuffer, ImageResult, Progress}; +use crate::color::ColorType; +use crate::error::{ImageError, ImageResult}; +use crate::image::{self, ImageDecoder, ImageDecoderExt, ImageReadBuffer, Progress}; /// What version of DXT compression are we using? /// Note that DXT2 and DXT4 are left away as they're @@ -46,17 +48,17 @@ impl DXTVariant { } } - /// Returns the colortype that is stored in this DXT variant - pub fn colortype(self) -> ColorType { + /// Returns the color type that is stored in this DXT variant + pub fn color_type(self) -> ColorType { match self { - DXTVariant::DXT1 => ColorType::RGB(8), - DXTVariant::DXT3 | DXTVariant::DXT5 => ColorType::RGBA(8), + DXTVariant::DXT1 => ColorType::Rgb8, + DXTVariant::DXT3 | DXTVariant::DXT5 => ColorType::Rgba8, } } } /// DXT decoder -pub struct DXTDecoder { +pub struct DxtDecoder { inner: R, width_blocks: u32, height_blocks: u32, @@ -64,7 +66,7 @@ pub struct DXTDecoder { row: u32, } -impl DXTDecoder { +impl DxtDecoder { /// Create a new DXT decoder that decodes from the stream ```r```. /// As DXT is often stored as raw buffers with the width/height /// somewhere else the width and height of the image need @@ -77,13 +79,13 @@ impl DXTDecoder { width: u32, height: u32, variant: DXTVariant, - ) -> Result, ImageError> { + ) -> Result, ImageError> { if width % 4 != 0 || height % 4 != 0 { return Err(ImageError::DimensionError); } let width_blocks = width / 4; let height_blocks = height / 4; - Ok(DXTDecoder { + Ok(DxtDecoder { inner: r, width_blocks, height_blocks, @@ -93,7 +95,7 @@ impl DXTDecoder { } fn read_scanline(&mut self, buf: &mut [u8]) -> io::Result { - assert_eq!(buf.len() as u64, self.scanline_bytes()); + assert_eq!(u64::try_from(buf.len()), Ok(self.scanline_bytes())); let mut src = vec![0u8; self.variant.encoded_bytes_per_block() * self.width_blocks as usize]; @@ -110,15 +112,15 @@ impl DXTDecoder { // Note that, due to the way that DXT compression works, a scanline is considered to consist out of // 4 lines of pixels. -impl<'a, R: 'a + Read> ImageDecoder<'a> for DXTDecoder { +impl<'a, R: 'a + Read> ImageDecoder<'a> for DxtDecoder { type Reader = DXTReader; - fn dimensions(&self) -> (u64, u64) { - (u64::from(self.width_blocks) * 4, u64::from(self.height_blocks) * 4) + fn dimensions(&self) -> (u32, u32) { + (self.width_blocks * 4, self.height_blocks * 4) } - fn colortype(&self) -> ColorType { - self.variant.colortype() + fn color_type(&self) -> ColorType { + self.variant.color_type() } fn scanline_bytes(&self) -> u64 { @@ -126,36 +128,29 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for DXTDecoder { } fn into_reader(self) -> ImageResult { - if self.total_bytes() > usize::max_value() as u64 { - return Err(ImageError::InsufficientMemory); - } - Ok(DXTReader { - buffer: ImageReadBuffer::new(self.scanline_bytes() as usize, self.total_bytes() as usize), + buffer: ImageReadBuffer::new(self.scanline_bytes(), self.total_bytes()), decoder: self, }) } - fn read_image(mut self) -> ImageResult> { - if self.total_bytes() > usize::max_value() as u64 { - return Err(ImageError::InsufficientMemory); - } + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); - let mut dest = vec![0u8; self.total_bytes() as usize]; - for chunk in dest.chunks_mut(self.scanline_bytes() as usize) { + for chunk in buf.chunks_mut(self.scanline_bytes() as usize) { self.read_scanline(chunk)?; } - Ok(dest) + Ok(()) } } -impl<'a, R: 'a + Read + Seek> ImageDecoderExt<'a> for DXTDecoder { +impl<'a, R: 'a + Read + Seek> ImageDecoderExt<'a> for DxtDecoder { fn read_rect_with_progress( &mut self, - x: u64, - y: u64, - width: u64, - height: u64, + x: u32, + y: u32, + width: u32, + height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()> { @@ -177,7 +172,7 @@ impl<'a, R: 'a + Read + Seek> ImageDecoderExt<'a> for DXTDecoder { /// DXT reader pub struct DXTReader { buffer: ImageReadBuffer, - decoder: DXTDecoder, + decoder: DxtDecoder, } impl Read for DXTReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { @@ -200,7 +195,7 @@ impl DXTEncoder { /// Encodes the image data ```data``` /// that has dimensions ```width``` and ```height``` /// in ```DXTVariant``` ```variant``` - /// data is assumed to be in variant.colortype() + /// data is assumed to be in variant.color_type() pub fn encode( mut self, data: &[u8], diff --git a/src/dynimage.rs b/src/dynimage.rs index 2f63c46b44..141de48563 100644 --- a/src/dynimage.rs +++ b/src/dynimage.rs @@ -1,34 +1,33 @@ -use num_iter; use std::io; use std::io::Write; use std::path::Path; use std::u32; #[cfg(feature = "bmp")] -use bmp; -#[cfg(feature = "gif_codec")] -use gif; +use crate::bmp; +#[cfg(feature = "gif")] +use crate::gif; #[cfg(feature = "ico")] -use ico; +use crate::ico; #[cfg(feature = "jpeg")] -use jpeg; -#[cfg(feature = "png_codec")] -use png; +use crate::jpeg; +#[cfg(feature = "png")] +use crate::png; #[cfg(feature = "pnm")] -use pnm; +use crate::pnm; -use buffer::{ - BgrImage, BgraImage, ConvertBuffer, GrayAlphaImage, GrayImage, ImageBuffer, Pixel, RgbImage, - RgbaImage, +use crate::buffer::{ + BgrImage, BgraImage, ConvertBuffer, GrayAlphaImage, GrayAlpha16Image, + GrayImage, Gray16Image, ImageBuffer, Pixel, RgbImage, Rgb16Image, + RgbaImage, Rgba16Image, }; -use color; -use flat::FlatSamples; -use image; -use image::{ - GenericImage, GenericImageView, ImageDecoder, ImageFormat, ImageOutputFormat, ImageResult, -}; -use io::free_functions; -use imageops; +use crate::color::{self, IntoColor}; +use crate::error::{ImageError, ImageResult}; +use crate::flat::FlatSamples; +use crate::image; +use crate::image::{GenericImage, GenericImageView, ImageDecoder, ImageFormat, ImageOutputFormat}; +use crate::io::free_functions; +use crate::imageops; /// A Dynamic Image #[derive(Clone)] @@ -50,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( @@ -61,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), } ); @@ -72,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), } ); @@ -83,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, } ); @@ -94,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, } ); ); @@ -130,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 @@ -346,25 +394,112 @@ 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 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 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 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. pub fn color(&self) -> color::ColorType { match *self { - DynamicImage::ImageLuma8(_) => color::ColorType::Gray(8), - DynamicImage::ImageLumaA8(_) => color::ColorType::GrayA(8), - DynamicImage::ImageRgb8(_) => color::ColorType::RGB(8), - DynamicImage::ImageRgba8(_) => color::ColorType::RGBA(8), - DynamicImage::ImageBgra8(_) => color::ColorType::BGRA(8), - DynamicImage::ImageBgr8(_) => color::ColorType::BGR(8), + DynamicImage::ImageLuma8(_) => color::ColorType::L8, + DynamicImage::ImageLumaA8(_) => color::ColorType::La8, + DynamicImage::ImageRgb8(_) => color::ColorType::Rgb8, + 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, } } @@ -377,6 +512,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)), } } @@ -537,24 +676,24 @@ 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(); #[allow(deprecated)] match format { - #[cfg(feature = "png_codec")] - image::ImageOutputFormat::PNG => { + #[cfg(feature = "png")] + image::ImageOutputFormat::Png => { let p = png::PNGEncoder::new(w); match *self { DynamicImage::ImageBgra8(_) => { bytes = self.to_rgba().iter().cloned().collect(); - color = color::ColorType::RGBA(8); + color = color::ColorType::Rgba8; } DynamicImage::ImageBgr8(_) => { bytes = self.to_rgb().iter().cloned().collect(); - color = color::ColorType::RGB(8); + color = color::ColorType::Rgb8; } _ => {} } @@ -562,16 +701,16 @@ impl DynamicImage { Ok(()) } #[cfg(feature = "pnm")] - image::ImageOutputFormat::PNM(subtype) => { + image::ImageOutputFormat::Pnm(subtype) => { let mut p = pnm::PNMEncoder::new(w).with_subtype(subtype); match *self { DynamicImage::ImageBgra8(_) => { bytes = self.to_rgba().iter().cloned().collect(); - color = color::ColorType::RGBA(8); + color = color::ColorType::Rgba8; } DynamicImage::ImageBgr8(_) => { bytes = self.to_rgb().iter().cloned().collect(); - color = color::ColorType::RGB(8); + color = color::ColorType::Rgb8; } _ => {} } @@ -579,27 +718,22 @@ impl DynamicImage { Ok(()) } #[cfg(feature = "jpeg")] - image::ImageOutputFormat::JPEG(quality) => { + image::ImageOutputFormat::Jpeg(quality) => { let mut j = jpeg::JPEGEncoder::new_with_quality(w, quality); j.encode(&bytes, width, height, color)?; Ok(()) } - #[cfg(feature = "gif_codec")] - image::ImageOutputFormat::GIF => { + #[cfg(feature = "gif")] + image::ImageOutputFormat::Gif => { let mut g = gif::Encoder::new(w); - - g.encode(&gif::Frame::from_rgba( - width as u16, - height as u16, - &mut *self.to_rgba().iter().cloned().collect::>(), - ))?; + g.encode_frame(crate::animation::Frame::new(self.to_rgba()))?; Ok(()) } #[cfg(feature = "ico")] - image::ImageOutputFormat::ICO => { + image::ImageOutputFormat::Ico => { let i = ico::ICOEncoder::new(w); i.encode(&bytes, width, height, color)?; @@ -607,22 +741,24 @@ impl DynamicImage { } #[cfg(feature = "bmp")] - image::ImageOutputFormat::BMP => { + image::ImageOutputFormat::Bmp => { let mut b = bmp::BMPEncoder::new(w); b.encode(&bytes, width, height, color)?; Ok(()) } image::ImageOutputFormat::Unsupported(msg) => { - Err(image::ImageError::UnsupportedError(msg)) - } + Err(ImageError::UnsupportedError(msg)) + }, + + image::ImageOutputFormat::__NonExhaustive(marker) => match marker._private {}, } } /// Saves the buffer to a file at the path specified. /// /// The image format is derived from the file extension. - pub fn save(&self, path: Q) -> io::Result<()> + pub fn save(&self, path: Q) -> ImageResult<()> where Q: AsRef, { @@ -636,7 +772,7 @@ impl DynamicImage { /// /// See [`save_buffer_with_format`](fn.save_buffer_with_format.html) for /// supported types. - pub fn save_with_format(&self, path: Q, format: ImageFormat) -> io::Result<()> + pub fn save_with_format(&self, path: Q, format: ImageFormat) -> ImageResult<()> where Q: AsRef, { @@ -660,7 +796,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 { @@ -680,6 +816,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. @@ -689,9 +829,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()), } } @@ -705,71 +848,72 @@ impl GenericImage for DynamicImage { } } -fn decoder_to_image<'a, I: ImageDecoder<'a>>(codec: I) -> ImageResult { - let color = codec.colortype(); - let (w, h) = codec.dimensions(); - let buf = codec.read_image()?; - - // TODO: Avoid this cast by having ImageBuffer use u64's - assert!(w <= u64::from(u32::max_value())); - assert!(h <= u64::from(u32::max_value())); - let (w, h) = (w as u32, h as u32); +/// Decodes an image and stores it into a dynamic image +fn decoder_to_image<'a, I: ImageDecoder<'a>>(decoder: I) -> ImageResult { + let (w, h) = decoder.dimensions(); + let color_type = decoder.color_type(); - let image = match color { - color::ColorType::RGB(8) => ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb8), + 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::RGBA(8) => ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba8), + color::ColorType::Rgba8 => { + let buf = image::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgba8) + } - color::ColorType::BGR(8) => ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageBgr8), + color::ColorType::Bgr8 => { + let buf = image::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageBgr8) + } - color::ColorType::BGRA(8) => ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageBgra8), + color::ColorType::Bgra8 => { + let buf = image::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageBgra8) + } - color::ColorType::Gray(8) => ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma8), + color::ColorType::L8 => { + let buf = image::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLuma8) + } - color::ColorType::GrayA(8) => { + color::ColorType::La8 => { + let buf = image::decoder_to_vec(decoder)?; ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageLumaA8) } - color::ColorType::Gray(bit_depth) if bit_depth == 1 || bit_depth == 2 || bit_depth == 4 => { - gray_to_luma8(bit_depth, w, h, &buf).map(DynamicImage::ImageLuma8) + + color::ColorType::Rgb16 => { + let buf = image::decoder_to_vec(decoder)?; + ImageBuffer::from_raw(w, h, buf).map(DynamicImage::ImageRgb16) } - _ => return Err(image::ImageError::UnsupportedColor(color)), + + 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 { Some(image) => Ok(image), - None => Err(image::ImageError::DimensionError), + None => Err(ImageError::DimensionError), } } -fn gray_to_luma8(bit_depth: u8, w: u32, h: u32, buf: &[u8]) -> Option { - // Note: this conversion assumes that the scanlines begin on byte boundaries - let mask = (1u8 << bit_depth as usize) - 1; - let scaling_factor = 255 / ((1 << bit_depth as usize) - 1); - let bit_width = w * u32::from(bit_depth); - let skip = if bit_width % 8 == 0 { - 0 - } else { - (8 - bit_width % 8) / u32::from(bit_depth) - }; - let row_len = w + skip; - let mut p = Vec::new(); - let mut i = 0; - for v in buf { - for shift in num_iter::range_step_inclusive(8i8 - (bit_depth as i8), 0, -(bit_depth as i8)) - { - // skip the pixels that can be neglected because scanlines should - // start at byte boundaries - if i % (row_len as usize) < (w as usize) { - let pixel = (v & mask << shift as usize) >> shift as usize; - p.push(pixel * scaling_factor); - } - i += 1; - } - } - ImageBuffer::from_raw(w, h, p) -} - #[allow(deprecated)] fn image_to_bytes(image: &DynamicImage) -> Vec { + use crate::traits::EncodableLayout; + match *image { // TODO: consider transmuting DynamicImage::ImageLuma8(ref a) => a.iter().cloned().collect(), @@ -783,6 +927,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(), } } @@ -829,7 +981,7 @@ pub fn save_buffer

( width: u32, height: u32, color: color::ColorType, -) -> io::Result<()> +) -> ImageResult<()> where P: AsRef, { @@ -852,7 +1004,7 @@ pub fn save_buffer_with_format

( height: u32, color: color::ColorType, format: ImageFormat, -) -> io::Result<()> +) -> ImageResult<()> where P: AsRef, { @@ -936,7 +1088,7 @@ mod bench { #[bench] #[cfg(feature = "benchmarks")] fn bench_conversion(b: &mut test::Bencher) { - let a = super::DynamicImage::ImageRgb8(::ImageBuffer::new(1000, 1000)); + let a = super::DynamicImage::ImageRgb8(crate::ImageBuffer::new(1000, 1000)); b.iter(|| a.to_luma()); b.bytes = 1000 * 1000 * 3 } @@ -987,48 +1139,6 @@ mod test { assert!(result.1 == 100); } - #[test] - #[rustfmt::skip] - fn gray_to_luma8_skip() { - let check = |bit_depth, w, h, from, to| { - assert_eq!( - super::gray_to_luma8(bit_depth, w, h, from).map(super::GrayImage::into_raw), - Some(to) - ); - }; - // Bit depth 1, skip is more than half a byte - check( - 1, - 10, - 2, - &[0b11110000, 0b11000000, 0b00001111, 0b11000000], - vec![ - 255, 255, 255, 255, 0, - 0, 0, 0, 255, 255, - 0, 0, 0, 0, 255, - 255, 255, 255, 255, 255, - ], - ); - // Bit depth 2, skip is more than half a byte - check( - 2, - 5, - 2, - &[0b11110000, 0b11000000, 0b00001111, 0b11000000], - vec![255, 255, 0, 0, 255, 0, 0, 255, 255, 255], - ); - // Bit depth 2, skip is 0 - check( - 2, - 4, - 2, - &[0b11110000, 0b00001111], - vec![255, 255, 0, 0, 0, 0, 255, 255], - ); - // Bit depth 4, skip is half a byte - check(4, 1, 2, &[0b11110011, 0b00001100], vec![255, 0]); - } - #[cfg(feature = "jpeg")] #[test] fn image_dimensions() { @@ -1036,4 +1146,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/error.rs b/src/error.rs new file mode 100644 index 0000000000..5785db8e74 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,621 @@ +//! Contains detailed error representation. +//! +//! See the main [`ImageError`] which contains a variant for each specialized error type. The +//! subtypes used in each variant are opaque by design. They can be roughly inspected through their +//! respective `kind` methods which work similar to `std::io::Error::kind`. +//! +//! The error interface makes it possible to inspect the error of an underlying decoder or encoder, +//! through the `Error::source` method. Note that this is not part of the stable interface and you +//! may not rely on a particular error value for a particular operation. This means mainly that +//! `image` does not promise to remain on a particular version of its underlying decoders but if +//! you ensure to use the same version of the dependency (or at least of the error type) through +//! external means then you could inspect the error type in slightly more detail. +//! +//! [`ImageError`]: enum.ImageError.html + +use std::{fmt, io}; +use std::error::Error; + +use crate::color::ExtendedColorType; +use crate::image::ImageFormat; +use crate::utils::NonExhaustiveMarker; + +/// The generic error type for image operations. +/// +/// This high level enum allows, by variant matching, a rough separation of concerns between +/// underlying IO, the caller, format specifications, and the `image` implementation. +#[derive(Debug)] +pub enum ImageError { + /// An error was encountered while decoding. + /// + /// This means that the input data did not conform to the specification of some image format, + /// or that no format could be determined, or that it did not match format specific + /// requirements set by the caller. + Decoding(DecodingError), + + /// An error was encountered while encoding. + /// + /// The input image can not be encoded with the chosen format, for example because the + /// specification has no representation for its color space or because a necessary conversion + /// is ambiguous. In some cases it might also happen that the dimensions can not be used with + /// the format. + Encoding(EncodingError), + + /// An error was encountered in input arguments. + /// + /// This is a catch-all case for strictly internal operations such as scaling, conversions, + /// etc. that involve no external format specifications. + Parameter(ParameterError), + + /// Completing the operation would have required more resources than allowed. + /// + /// Errors of this type are limits set by the user or environment, *not* inherent in a specific + /// format or operation that was executed. + Limits(LimitError), + + /// An operation can not be completed by the chosen abstraction. + /// + /// This means that it might be possible for the operation to succeed in general but + /// * it requires a disabled feature, + /// * the implementation does not yet exist, or + /// * no abstraction for a lower level could be found. + Unsupported(UnsupportedError), + + /// An error occurred while interacting with the environment. + IoError(io::Error), +} + +/// The implementation for an operation was not provided. +/// +/// See the variant [`Unsupported`] for more documentation. +/// +/// [`Unsupported`]: enum.ImageError.html#variant.Unsupported +#[derive(Debug)] +pub struct UnsupportedError { + format: ImageFormatHint, + kind: UnsupportedErrorKind, +} + +/// Details what feature is not supported. +#[derive(Clone, Debug, Hash, PartialEq)] +pub enum UnsupportedErrorKind { + /// The required color type can not be handled. + Color(ExtendedColorType), + /// An image format is not supported. + Format(ImageFormatHint), + /// Some feature specified by string. + /// This is discouraged and is likely to get deprecated (but not removed). + GenericFeature(String), + #[doc(hidden)] + __NonExhaustive(NonExhaustiveMarker), +} + +/// An error was encountered while encoding an image. +/// +/// This is used as an opaque representation for the [`ImageError::Encoding`] variant. See its +/// documentation for more information. +/// +/// [`ImageError::Encoding`]: enum.ImageError.html#variant.Encoding +#[derive(Debug)] +pub struct EncodingError { + format: ImageFormatHint, + underlying: Option>, +} + + +/// An error was encountered in inputs arguments. +/// +/// This is used as an opaque representation for the [`ImageError::Parameter`] variant. See its +/// documentation for more information. +/// +/// [`ImageError::Parameter`]: enum.ImageError.html#variant.Parameter +#[derive(Debug)] +pub struct ParameterError { + kind: ParameterErrorKind, + underlying: Option>, +} + +/// Details how a parameter is malformed. +#[derive(Clone, Debug, Hash, PartialEq)] +pub enum ParameterErrorKind { + /// Repeated an operation for which error that could not be cloned was emitted already. + FailedAlready, + /// The dimensions passed are wrong. + DimensionMismatch, + /// A string describing the parameter. + /// This is discouraged and is likely to get deprecated (but not removed). + Generic(String), + #[doc(hidden)] + /// Do not use this, not part of stability guarantees. + __NonExhaustive(NonExhaustiveMarker), +} + +/// An error was encountered while decoding an image. +/// +/// This is used as an opaque representation for the [`ImageError::Decoding`] variant. See its +/// documentation for more information. +/// +/// [`ImageError::Decoding`]: enum.ImageError.html#variant.Decoding +#[derive(Debug)] +pub struct DecodingError { + format: ImageFormatHint, + message: Option>, + underlying: Option>, +} + +/// Completing the operation would have required more resources than allowed. +/// +/// This is used as an opaque representation for the [`ImageError::Limits`] variant. See its +/// documentation for more information. +/// +/// [`ImageError::Limits`]: enum.ImageError.html#variant.Limits +#[derive(Debug)] +pub struct LimitError { + kind: LimitErrorKind, + // do we need an underlying error? +} + +/// Indicates the limit that prevented an operation from completing. +/// +/// Note that this enumeration is not exhaustive and may in the future be extended to provide more +/// detailed information or to incorporate other resources types. +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +#[allow(missing_copy_implementations)] // Might be non-Copy in the future. +pub enum LimitErrorKind { + /// The resulting image exceed dimension limits in either direction. + DimensionError, + /// The operation would have performed an allocation larger than allowed. + InsufficientMemory, + #[doc(hidden)] + /// Do not use this, not part of stability guarantees. + __NonExhaustive(NonExhaustiveMarker), +} + +/// A best effort representation for image formats. +#[derive(Clone, Debug, Hash, PartialEq)] +pub enum ImageFormatHint { + /// The format is known exactly. + Exact(ImageFormat), + + /// The format can be identified by a name. + Name(String), + + /// A common path extension for the format is known. + PathExtension(std::path::PathBuf), + + /// The format is not known or could not be determined. + Unknown, + + #[doc(hidden)] + __NonExhaustive(NonExhaustiveMarker), +} + +// Internal implementation block for ImageError. +#[allow(non_upper_case_globals)] +#[allow(non_snake_case)] +impl ImageError { + pub(crate) const InsufficientMemory: Self = + ImageError::Limits(LimitError { + kind: LimitErrorKind::InsufficientMemory, + }); + + pub(crate) const DimensionError: Self = + ImageError::Parameter(ParameterError { + kind: ParameterErrorKind::DimensionMismatch, + underlying: None, + }); + + pub(crate) const ImageEnd: Self = + ImageError::Parameter(ParameterError { + kind: ParameterErrorKind::FailedAlready, + underlying: None, + }); + + pub(crate) fn UnsupportedError(message: String) -> Self { + ImageError::Unsupported(UnsupportedError::legacy_from_string(message)) + } + + pub(crate) fn UnsupportedColor(color: ExtendedColorType) -> Self { + ImageError::Unsupported(UnsupportedError::from_format_and_kind( + ImageFormatHint::Unknown, + UnsupportedErrorKind::Color(color), + )) + } + + pub(crate) fn FormatError(message: String) -> Self { + ImageError::Decoding(DecodingError::legacy_from_string(message)) + } +} + +impl UnsupportedError { + /// Create an `UnsupportedError` for an image with details on the unsupported feature. + /// + /// If the operation was not connected to a particular image format then the hint may be + /// `Unknown`. + pub fn from_format_and_kind(format: ImageFormatHint, kind: UnsupportedErrorKind) -> Self { + UnsupportedError { + format, + kind, + } + } + + /// A shorthand for a generic feature without an image format. + pub(crate) fn legacy_from_string(message: String) -> Self { + UnsupportedError { + format: ImageFormatHint::Unknown, + kind: UnsupportedErrorKind::GenericFeature(message), + } + } + + /// Returns the corresponding `UnsupportedErrorKind` of the error. + pub fn kind(&self) -> UnsupportedErrorKind { + self.kind.clone() + } + + /// Returns the image format associated with this error. + pub fn format_hint(&self) -> ImageFormatHint { + self.format.clone() + } +} + +impl DecodingError { + /// Create a `DecodingError` that stems from an arbitrary error of an underlying decoder. + pub fn new( + format: ImageFormatHint, + err: impl Into>, + ) -> Self { + DecodingError { + format, + message: None, + underlying: Some(err.into()), + } + } + + /// Create a `DecodingError` for an image format. + /// + /// The error will not contain any further information but is very easy to create. + pub fn from_format_hint(format: ImageFormatHint) -> Self { + DecodingError { + format, + message: None, + underlying: None, + } + } + + /// Returns the image format associated with this error. + pub fn format_hint(&self) -> ImageFormatHint { + self.format.clone() + } + + /// A shorthand for a string error without an image format. + pub(crate) fn legacy_from_string(message: String) -> Self { + DecodingError { + format: ImageFormatHint::Unknown, + message: Some(message.into_boxed_str()), + underlying: None, + } + } + + /// Not quite legacy but also highly discouraged. + /// This is just since the string typing is prevalent in the `image` decoders... + // TODO: maybe a Cow? A constructor from `&'static str` wouldn't be too bad. + pub(crate) fn with_message( + format: ImageFormatHint, + message: String, + ) -> Self { + DecodingError { + format, + message: Some(message.into_boxed_str()), + underlying: None, + } + } + + fn get_message_or_default(&self) -> &str { + match &self.message { + Some(st) => st, + None => "", + } + } +} + +impl EncodingError { + /// Create an `EncodingError` that stems from an arbitrary error of an underlying encoder. + pub fn new( + format: ImageFormatHint, + err: impl Into>, + ) -> Self { + EncodingError { + format, + underlying: Some(err.into()), + } + } + + /// Create a `DecodingError` for an image format. + /// + /// The error will not contain any further information but is very easy to create. + pub fn from_format_hint(format: ImageFormatHint) -> Self { + EncodingError { + format, + underlying: None, + } + } + + /// Return the image format associated with this error. + pub fn format_hint(&self) -> ImageFormatHint { + self.format.clone() + } +} + +impl ParameterError { + /// Construct a `ParameterError` directly from a corresponding kind. + pub fn from_kind(kind: ParameterErrorKind) -> Self { + ParameterError { + kind, + underlying: None, + } + } + + /// Returns the corresponding `ParameterErrorKind` of the error. + pub fn kind(&self) -> ParameterErrorKind { + self.kind.clone() + } +} + +impl LimitError { + /// Construct a generic `LimitError` directly from a corresponding kind. + pub fn from_kind(kind: LimitErrorKind) -> Self { + LimitError { + kind, + } + } + + /// Returns the corresponding `LimitErrorKind` of the error. + pub fn kind(&self) -> LimitErrorKind { + self.kind.clone() + } +} + +impl From for ImageError { + fn from(err: io::Error) -> ImageError { + ImageError::IoError(err) + } +} + +impl From for ImageFormatHint { + fn from(format: ImageFormat) -> Self { + ImageFormatHint::Exact(format) + } +} + +impl From<&'_ std::path::Path> for ImageFormatHint { + fn from(path: &'_ std::path::Path) -> Self { + match path.extension() { + Some(ext) => ImageFormatHint::PathExtension(ext.into()), + None => ImageFormatHint::Unknown, + } + } +} + +impl From for UnsupportedError { + fn from(hint: ImageFormatHint) -> Self { + UnsupportedError { + format: hint.clone(), + kind: UnsupportedErrorKind::Format(hint), + } + } +} + +/// Result of an image decoding/encoding process +pub type ImageResult = Result; + +impl fmt::Display for ImageError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + ImageError::IoError(err) => err.fmt(fmt), + ImageError::Decoding(err) => err.fmt(fmt), + ImageError::Encoding(err) => err.fmt(fmt), + ImageError::Parameter(err) => err.fmt(fmt), + ImageError::Limits(err) => err.fmt(fmt), + ImageError::Unsupported(err) => err.fmt(fmt), + } + } +} + +impl Error for ImageError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + ImageError::IoError(err) => err.source(), + ImageError::Decoding(err) => err.source(), + ImageError::Encoding(err) => err.source(), + ImageError::Parameter(err) => err.source(), + ImageError::Limits(err) => err.source(), + ImageError::Unsupported(err) => err.source(), + } + } +} + +impl fmt::Display for UnsupportedError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match &self.kind { + UnsupportedErrorKind::Format(ImageFormatHint::Unknown) => write!( + fmt, + "The image format could not be determined", + ), + UnsupportedErrorKind::Format(format @ ImageFormatHint::PathExtension(_)) => write!( + fmt, + "The file extension {} was not recognized as an image format", + format, + ), + UnsupportedErrorKind::Format(format) => write!( + fmt, + "The image format {} is not supported", + format, + ), + UnsupportedErrorKind::Color(color) => write!( + fmt, + "The decoder for {} does not support the color type `{:?}`", + self.format, + color, + ), + UnsupportedErrorKind::GenericFeature(message) => { + match &self.format { + ImageFormatHint::Unknown => write!( + fmt, + "The decoder does not support the format feature {}", + message, + ), + other => write!( + fmt, + "The decoder for {} does not support the format features {}", + other, + message, + ), + } + }, + UnsupportedErrorKind::__NonExhaustive(marker) => match marker._private {}, + } + } +} + +impl Error for UnsupportedError { } + +impl fmt::Display for ParameterError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match &self.kind { + ParameterErrorKind::DimensionMismatch => write!( + fmt, + "The Image's dimensions are either too \ + small or too large" + ), + ParameterErrorKind::FailedAlready => write!( + fmt, + "The end the image stream has been reached due to a previous error" + ), + ParameterErrorKind::Generic(message) => write!( + fmt, + "The parameter is malformed: {}", + message, + ), + ParameterErrorKind::__NonExhaustive(marker) => match marker._private {}, + }?; + + if let Some(underlying) = &self.underlying { + write!(fmt, "\n{}", underlying)?; + } + + Ok(()) + } +} + +impl Error for ParameterError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self.underlying { + None => None, + Some(source) => Some(&**source), + } + } +} + +impl fmt::Display for EncodingError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match &self.underlying { + Some(underlying) => write!( + fmt, + "Format error encoding {}:\n{}", + self.format, + underlying, + ), + None => write!( + fmt, + "Format error encoding {}", + self.format, + ), + } + } +} + +impl Error for EncodingError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self.underlying { + None => None, + Some(source) => Some(&**source), + } + } +} + +impl fmt::Display for DecodingError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match &self.underlying { + None => match self.format { + ImageFormatHint::Unknown => write!( + fmt, + "Format error: {}", + self.get_message_or_default(), + ), + _ => write!( + fmt, + "Format error decoding {}: {}", + self.format, + self.get_message_or_default(), + ), + }, + Some(underlying) => write!( + fmt, + "Format error decoding {}: {}\n{}", + self.format, + self.get_message_or_default(), + underlying, + ), + } + } +} + +impl Error for DecodingError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self.underlying { + None => None, + Some(source) => Some(&**source), + } + } +} + +impl fmt::Display for LimitError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self.kind { + LimitErrorKind::InsufficientMemory => write!(fmt, "Insufficient memory"), + LimitErrorKind::DimensionError => write!(fmt, "Image is too large"), + LimitErrorKind::__NonExhaustive(marker) => match marker._private {}, + } + } +} + +impl Error for LimitError { } + +impl fmt::Display for ImageFormatHint { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + ImageFormatHint::Exact(format) => write!(fmt, "{:?}", format), + ImageFormatHint::Name(name) => write!(fmt, "`{}`", name), + ImageFormatHint::PathExtension(ext) => write!(fmt, "`.{:?}`", ext), + ImageFormatHint::Unknown => write!(fmt, "`Unknown`"), + ImageFormatHint::__NonExhaustive(marker) => match marker._private {}, + } + } +} + +#[cfg(test)] +mod tests { + use std::mem; + use super::*; + + #[allow(dead_code)] + // This will fail to compile if the size of this type is large. + const ASSERT_SMALLISH: usize = [0][(mem::size_of::() >= 200) as usize]; + + #[test] + fn test_send_sync_stability() { + fn assert_send_sync() { } + + assert_send_sync::(); + } +} diff --git a/src/flat.rs b/src/flat.rs index be783f527c..6df78890cb 100644 --- a/src/flat.rs +++ b/src/flat.rs @@ -47,9 +47,10 @@ use std::marker::PhantomData; use num_traits::Zero; -use buffer::{ImageBuffer, Pixel}; -use color::ColorType; -use image::{GenericImage, GenericImageView, ImageError}; +use crate::buffer::{ImageBuffer, Pixel}; +use crate::color::ColorType; +use crate::error::ImageError; +use crate::image::{GenericImage, GenericImageView}; /// A flat buffer over a (multi channel) image. /// @@ -1076,7 +1077,8 @@ where /// `self.into_inner().as_view_mut()` and keeps the `View` alive on failure. /// /// ``` - /// # use image::{Rgb, RgbImage}; + /// # use image::RgbImage; + /// # use image::Rgb; /// let mut buffer = RgbImage::new(480, 640).into_flat_samples(); /// let view = buffer.as_view_with_mut_samples::>().unwrap(); /// @@ -1374,7 +1376,7 @@ impl From for ImageError { fn from(error: Error) -> ImageError { match error { Error::TooLarge => ImageError::DimensionError, - Error::WrongColor(color) => ImageError::UnsupportedColor(color), + Error::WrongColor(color) => ImageError::UnsupportedColor(color.into()), Error::NormalFormRequired(form) => ImageError::FormatError( format!("Required sample buffer in normal form {:?}", form)), } @@ -1417,8 +1419,8 @@ impl PartialOrd for NormalForm { #[cfg(test)] mod tests { use super::*; - use buffer::GrayAlphaImage; - use color::{LumaA, Rgb}; + use crate::buffer::GrayAlphaImage; + use crate::color::{LumaA, Rgb}; #[test] fn aliasing_view() { diff --git a/src/gif.rs b/src/gif.rs index 48fe5a1457..d191d41977 100644 --- a/src/gif.rs +++ b/src/gif.rs @@ -7,13 +7,13 @@ //! //! # Examples //! ```rust,no_run -//! use image::gif::{Decoder, Encoder}; +//! use image::gif::{GifDecoder, Encoder}; //! use image::{ImageDecoder, AnimationDecoder}; //! use std::fs::File; //! # fn main() -> std::io::Result<()> { //! // Decode a gif into frames //! let file_in = File::open("foo.gif")?; -//! let mut decoder = Decoder::new(file_in).unwrap(); +//! let mut decoder = GifDecoder::new(file_in).unwrap(); //! let frames = decoder.into_frames(); //! let frames = frames.collect_frames().expect("error decoding gif"); //! @@ -26,38 +26,37 @@ //! ``` #![allow(clippy::while_let_loop)] -extern crate gif; -extern crate num_rational; - use std::clone::Clone; +use std::convert::TryInto; use std::cmp::min; +use std::convert::TryFrom; use std::io::{self, Cursor, Read, Write}; use std::marker::PhantomData; use std::mem; -use self::gif::{ColorOutput, SetParameter}; -pub use self::gif::{DisposalMethod, Frame}; - -use animation; -use buffer::{ImageBuffer, Pixel}; -use color; -use color::Rgba; -use image::{AnimationDecoder, ImageDecoder, ImageError, ImageResult}; +use gif::{ColorOutput, SetParameter}; +use gif::{DisposalMethod, Frame}; use num_rational::Ratio; +use crate::animation; +use crate::buffer::{ImageBuffer, Pixel}; +use crate::color::{ColorType, Rgba}; +use crate::error::{ImageError, ImageResult}; +use crate::image::{self, AnimationDecoder, ImageDecoder}; + /// GIF decoder -pub struct Decoder { +pub struct GifDecoder { reader: gif::Reader, } -impl Decoder { +impl GifDecoder { /// Creates a new decoder that decodes the input steam ```r``` - pub fn new(r: R) -> ImageResult> { + pub fn new(r: R) -> ImageResult> { let mut decoder = gif::Decoder::new(r); decoder.set(ColorOutput::RGBA); - Ok(Decoder { - reader: decoder.read_info()?, + Ok(GifDecoder { + reader: decoder.read_info().map_err(ImageError::from_gif)?, }) } } @@ -78,25 +77,27 @@ impl Read for GifReader { } } -impl<'a, R: 'a + Read> ImageDecoder<'a> for Decoder { +impl<'a, R: 'a + Read> ImageDecoder<'a> for GifDecoder { type Reader = GifReader; - fn dimensions(&self) -> (u64, u64) { - (u64::from(self.reader.width()), u64::from(self.reader.height())) + fn dimensions(&self) -> (u32, u32) { + (u32::from(self.reader.width()), u32::from(self.reader.height())) } - fn colortype(&self) -> color::ColorType { - color::ColorType::RGBA(8) + fn color_type(&self) -> ColorType { + ColorType::Rgba8 } fn into_reader(self) -> ImageResult { - Ok(GifReader(Cursor::new(self.read_image()?), PhantomData)) + Ok(GifReader(Cursor::new(image::decoder_to_vec(self)?), PhantomData)) } - fn read_image(mut self) -> ImageResult> { + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + let (f_width, f_height, left, top); - if let Some(frame) = self.reader.next_frame_info()? { + if let Some(frame) = self.reader.next_frame_info().map_err(ImageError::from_gif)? { left = u32::from(frame.left); top = u32::from(frame.top); f_width = u32::from(frame.width); @@ -105,20 +106,38 @@ impl<'a, R: 'a + Read> ImageDecoder<'a> for Decoder { return Err(ImageError::ImageEnd); } - let mut buf = vec![0; self.reader.buffer_size()]; - self.reader.read_into_buffer(&mut buf)?; + self.reader.read_into_buffer(buf).map_err(ImageError::from_gif)?; - // See the comments inside `::next` about - // the error handling of `from_raw`. - let image_buffer_raw = ImageBuffer::from_raw(f_width, f_height, buf).ok_or_else(|| - ImageError::UnsupportedError("Image dimensions are too large".into()) - )?; - - // Recover the full image let (width, height) = (u32::from(self.reader.width()), u32::from(self.reader.height())); - let image_buffer = full_image_from_frame(width, height, image_buffer_raw, left, top); - - Ok(image_buffer.into_raw()) + if (left, top) != (0, 0) || (width, height) != (f_width, f_height) { + // This is somewhat of an annoying case. The image we read into `buf` doesn't take up + // the whole buffer and now we need to properly insert borders. For simplicity this code + // currently takes advantage of the `ImageBuffer::from_fn` function to make a second + // ImageBuffer that is properly positioned, and then copies it back into `buf`. + // + // TODO: Implement this without any allocation. + + // Recover the full image + let image_buffer = { + // See the comments inside `::next` about + // the error handling of `from_raw`. + let image = ImageBuffer::from_raw(f_width, f_height, &mut *buf).ok_or_else( + || ImageError::UnsupportedError("Image dimensions are too large".into()) + )?; + + ImageBuffer::from_fn(width, height, |x, y| { + let x = x.wrapping_sub(left); + let y = y.wrapping_sub(top); + if x < image.width() && y < image.height() { + *image.get_pixel(x, y) + } else { + Rgba([0, 0, 0, 0]) + } + }) + }; + buf.copy_from_slice(&mut image_buffer.into_raw()); + } + Ok(()) } } @@ -133,7 +152,7 @@ struct GifFrameIterator { impl GifFrameIterator { - fn new(decoder: Decoder) -> GifFrameIterator { + fn new(decoder: GifDecoder) -> GifFrameIterator { let (width, height) = decoder.dimensions(); // TODO: Avoid this cast @@ -170,19 +189,19 @@ impl Iterator for GifFrameIterator { f_height = u32::from(frame.height); // frame.delay is in units of 10ms so frame.delay*10 is in ms - delay = Ratio::new(frame.delay * 10, 1); + delay = Ratio::new(u32::from(frame.delay) * 10, 1); dispose = frame.dispose; } else { // no more frames return None; } }, - Err(err) => return Some(Err(err.into())), + Err(err) => return Some(Err(ImageError::from_gif(err))), } let mut vec = vec![0; self.reader.buffer_size()]; if let Err(err) = self.reader.read_into_buffer(&mut vec) { - return Some(Err(err.into())); + return Some(Err(ImageError::from_gif(err))); } // create the image buffer from the raw frame. @@ -225,7 +244,7 @@ impl Iterator for GifFrameIterator { } let frame = animation::Frame::from_parts( - image_buffer.clone(), 0, 0, delay + image_buffer.clone(), 0, 0, animation::Delay::from_ratio(delay), ); match dispose { @@ -282,7 +301,7 @@ fn full_image_from_frame( } } -impl<'a, R: Read + 'a> AnimationDecoder<'a> for Decoder { +impl<'a, R: Read + 'a> AnimationDecoder<'a> for GifDecoder { fn into_frames(self) -> animation::Frames<'a> { animation::Frames::new(Box::new(GifFrameIterator::new(self))) } @@ -302,18 +321,29 @@ impl Encoder { gif_encoder: None, } } - /// Encodes a frame. - pub fn encode(&mut self, frame: &Frame) -> ImageResult<()> { - let result; - if let Some(ref mut encoder) = self.gif_encoder { - result = encoder.write_frame(frame).map_err(|err| err.into()); - } else { - let writer = self.w.take().unwrap(); - let mut encoder = gif::Encoder::new(writer, frame.width, frame.height, &[])?; - result = encoder.write_frame(&frame).map_err(|err| err.into()); - self.gif_encoder = Some(encoder); + + /// Encode a single image. + pub fn encode( + &mut self, + data: &[u8], + width: u32, + height: u32, + color: ColorType, + ) -> ImageResult<()> { + let (width, height) = self.gif_dimensions(width, height)?; + match color { + ColorType::Rgb8 => self.encode_gif(Frame::from_rgb(width, height, data)), + ColorType::Rgba8 => { + self.encode_gif(Frame::from_rgb(width, height, &mut data.to_owned())) + }, + _ => Err(ImageError::UnsupportedColor(color.into())), } - result + } + + /// Encode one frame of animation. + pub fn encode_frame(&mut self, img_frame: animation::Frame) -> ImageResult<()> { + let frame = self.convert_frame(img_frame)?; + self.encode_gif(frame) } /// Encodes Frames. @@ -323,7 +353,7 @@ impl Encoder { F: IntoIterator { for img_frame in frames { - self.encode_single_frame(img_frame)?; + self.encode_frame(img_frame)?; } Ok(()) } @@ -336,29 +366,57 @@ impl Encoder { F: IntoIterator> { for img_frame in frames { - self.encode_single_frame(img_frame?)?; + self.encode_frame(img_frame?)?; } Ok(()) } - fn encode_single_frame(&mut self, img_frame: animation::Frame) -> ImageResult<()> { + pub(crate) fn convert_frame(&mut self, img_frame: animation::Frame) + -> ImageResult> + { // get the delay before converting img_frame - let frame_delay = img_frame.delay().to_integer(); + let frame_delay = img_frame.delay().into_ratio().to_integer(); // convert img_frame into RgbaImage - let rbga_frame = img_frame.into_buffer(); + let mut rbga_frame = img_frame.into_buffer(); + let (width, height) = self.gif_dimensions( + rbga_frame.width(), + rbga_frame.height())?; // Create the gif::Frame from the animation::Frame - let mut frame = Frame::from_rgba(rbga_frame.width() as u16, rbga_frame.height() as u16, &mut rbga_frame.into_raw()); - frame.delay = frame_delay; + let mut frame = Frame::from_rgba(width, height, &mut *rbga_frame); + frame.delay = (frame_delay / 10).try_into().map_err(|_|ImageError::DimensionError)?; + + Ok(frame) + } + + fn gif_dimensions(&self, width: u32, height: u32) -> ImageResult<(u16, u16)> { + fn inner_dimensions(width: u32, height: u32) -> Option<(u16, u16)> { + let width = u16::try_from(width).ok()?; + let height = u16::try_from(height).ok()?; + Some((width, height)) + } + + inner_dimensions(width, height).ok_or(ImageError::DimensionError) + } + + pub(crate) fn encode_gif(&mut self, frame: Frame) -> ImageResult<()> { + let gif_encoder; + if let Some(ref mut encoder) = self.gif_encoder { + gif_encoder = encoder; + } else { + let writer = self.w.take().unwrap(); + let encoder = gif::Encoder::new(writer, frame.width, frame.height, &[])?; + self.gif_encoder = Some(encoder); + gif_encoder = self.gif_encoder.as_mut().unwrap() + } - // encode the gif::Frame - self.encode(&frame) + gif_encoder.write_frame(&frame).map_err(|err| err.into()) } } -impl From for ImageError { - fn from(err: gif::DecodingError) -> ImageError { - use self::gif::DecodingError::*; +impl ImageError { + fn from_gif(err: gif::DecodingError) -> ImageError { + use gif::DecodingError::*; match err { Format(desc) | Internal(desc) => ImageError::FormatError(desc.into()), Io(io_err) => ImageError::IoError(io_err), diff --git a/src/hdr/decoder.rs b/src/hdr/decoder.rs index 01b1731c0a..1409e4683f 100644 --- a/src/hdr/decoder.rs +++ b/src/hdr/decoder.rs @@ -1,61 +1,56 @@ -use num_traits::cast::NumCast; use num_traits::identities::Zero; use scoped_threadpool::Pool; #[cfg(test)] use std::borrow::Cow; +use std::convert::TryFrom; use std::io::{self, BufRead, Cursor, Read, Seek}; use std::iter::Iterator; use std::marker::PhantomData; use std::mem; use std::path::Path; -use Primitive; +use crate::Primitive; -use color::{ColorType, Rgb}; -use image::{self, ImageDecoder, ImageDecoderExt, ImageError, ImageResult, Progress}; +use crate::color::{ColorType, Rgb}; +use crate::error::{ImageError, ImageResult}; +use crate::image::{self, ImageDecoder, ImageDecoderExt, Progress}; /// Adapter to conform to ```ImageDecoder``` trait #[derive(Debug)] pub struct HDRAdapter { - inner: Option>, - data: Option>, + inner: Option>, + // data: Option>, meta: HDRMetadata, } impl HDRAdapter { /// Creates adapter pub fn new(r: R) -> ImageResult> { - let decoder = HDRDecoder::new(r)?; + let decoder = HdrDecoder::new(r)?; let meta = decoder.metadata(); Ok(HDRAdapter { inner: Some(decoder), - data: None, meta, }) } /// Allows reading old Radiance HDR images pub fn new_nonstrict(r: R) -> ImageResult> { - let decoder = HDRDecoder::with_strictness(r, false)?; + let decoder = HdrDecoder::with_strictness(r, false)?; let meta = decoder.metadata(); Ok(HDRAdapter { inner: Some(decoder), - data: None, meta, }) } /// Read the actual data of the image, and store it in Self::data. - fn read_image_data(&mut self) -> ImageResult<()> { + fn read_image_data(&mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); match self.inner.take() { Some(decoder) => { let img: Vec> = decoder.read_image_ldr()?; - - let len = img.len() * mem::size_of::>(); // length in bytes - let target = self.data.get_or_insert_with(|| Vec::with_capacity(len)); - target.clear(); - - for Rgb(data) in img { - target.extend_from_slice(&data); + for (i, Rgb(data)) in img.into_iter().enumerate() { + buf[(i*3)..][..3].copy_from_slice(&data); } Ok(()) @@ -84,56 +79,35 @@ impl Read for HdrReader { impl<'a, R: 'a + BufRead> ImageDecoder<'a> for HDRAdapter { type Reader = HdrReader; - fn dimensions(&self) -> (u64, u64) { - (self.meta.width as u64, self.meta.height as u64) + fn dimensions(&self) -> (u32, u32) { + (self.meta.width, self.meta.height) } - fn colortype(&self) -> ColorType { - ColorType::RGB(8) + fn color_type(&self) -> ColorType { + ColorType::Rgb8 } fn into_reader(self) -> ImageResult { - Ok(HdrReader(Cursor::new(self.read_image()?), PhantomData)) + Ok(HdrReader(Cursor::new(image::decoder_to_vec(self)?), PhantomData)) } - fn read_image(mut self) -> ImageResult> { - if let Some(data) = self.data { - return Ok(data); - } - - self.read_image_data()?; - Ok(self.data.unwrap()) + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + self.read_image_data(buf) } } impl<'a, R: 'a + BufRead + Seek> ImageDecoderExt<'a> for HDRAdapter { fn read_rect_with_progress( &mut self, - x: u64, - y: u64, - width: u64, - height: u64, + x: u32, + y: u32, + width: u32, + height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()> { - if self.data.is_none() { - self.read_image_data()?; - } - - image::load_rect( - x, - y, - width, - height, - buf, - progress_callback, - self, - |_, _| unreachable!(), - |s, buf| { - buf.copy_from_slice(&*s.data.as_ref().unwrap()); - Ok(buf.len()) - }, - ) + image::load_rect(x, y, width, height, buf, progress_callback, self, |_, _| unreachable!(), + |s, buf| s.read_image_data(buf).map(|_| buf.len())) } } @@ -143,7 +117,7 @@ const SIGNATURE_LENGTH: usize = 10; /// An Radiance HDR decoder #[derive(Debug)] -pub struct HDRDecoder { +pub struct HdrDecoder { r: R, width: u32, height: u32, @@ -210,7 +184,7 @@ impl RGBE8Pixel { fn sg(v: f32, scale: f32, gamma: f32) -> T { let t_max = T::max_value(); // Disassembly shows that t_max_f32 is compiled into constant - let t_max_f32: f32 = NumCast::from(t_max) + let t_max_f32: f32 = num_traits::NumCast::from(t_max) .expect("to_ldr_scale_gamma: maximum value of type is not representable as f32"); let fv = f32::powf(v * scale, gamma) * t_max_f32 + 0.5; if fv < 0.0 { @@ -218,7 +192,7 @@ impl RGBE8Pixel { } else if fv > t_max_f32 { t_max } else { - NumCast::from(fv) + num_traits::NumCast::from(fv) .expect("to_ldr_scale_gamma: cannot convert f32 to target type. NaN?") } } @@ -230,22 +204,22 @@ impl RGBE8Pixel { } } -impl HDRDecoder { +impl HdrDecoder { /// Reads Radiance HDR image header from stream ```r``` - /// if the header is valid, creates HDRDecoder + /// if the header is valid, creates HdrDecoder /// strict mode is enabled - pub fn new(reader: R) -> ImageResult> { - HDRDecoder::with_strictness(reader, true) + pub fn new(reader: R) -> ImageResult> { + HdrDecoder::with_strictness(reader, true) } /// Reads Radiance HDR image header from stream ```reader```, - /// if the header is valid, creates ```HDRDecoder```. + /// if the header is valid, creates ```HdrDecoder```. /// /// strict enables strict mode /// /// Warning! Reading wrong file in non-strict mode /// could consume file size worth of memory in the process. - pub fn with_strictness(mut reader: R, strict: bool) -> ImageResult> { + pub fn with_strictness(mut reader: R, strict: bool) -> ImageResult> { let mut attributes = HDRMetadata::new(); { @@ -300,7 +274,7 @@ impl HDRDecoder { } }; - Ok(HDRDecoder { + Ok(HdrDecoder { r: reader, width, @@ -385,7 +359,7 @@ impl HDRDecoder { } } -impl IntoIterator for HDRDecoder { +impl IntoIterator for HdrDecoder { type Item = ImageResult; type IntoIter = HDRImageDecoderIterator; diff --git a/src/hdr/encoder.rs b/src/hdr/encoder.rs index f1760804f1..285a44526a 100644 --- a/src/hdr/encoder.rs +++ b/src/hdr/encoder.rs @@ -1,5 +1,6 @@ -use color::Rgb; -use hdr::{rgbe8, RGBE8Pixel, SIGNATURE}; +use crate::color::Rgb; +use crate::error::ImageResult; +use crate::hdr::{rgbe8, RGBE8Pixel, SIGNATURE}; use std::io::{Result, Write}; use std::cmp::Ordering; @@ -16,7 +17,7 @@ impl HDREncoder { /// Encodes the image ```data``` /// that has dimensions ```width``` and ```height``` - pub fn encode(mut self, data: &[Rgb], width: usize, height: usize) -> Result<()> { + pub fn encode(mut self, data: &[Rgb], width: usize, height: usize) -> ImageResult<()> { assert!(data.len() >= width * height); let w = &mut self.w; w.write_all(SIGNATURE)?; @@ -246,7 +247,7 @@ pub fn to_rgbe8(pix: Rgb) -> RGBE8Pixel { #[test] fn to_rgbe8_test() { - use hdr::rgbe8; + use crate::hdr::rgbe8; let test_cases = vec![rgbe8(0, 0, 0, 0), rgbe8(1, 1, 128, 128)]; for &pix in &test_cases { assert_eq!(pix, to_rgbe8(pix.to_hdr())); diff --git a/src/ico/decoder.rs b/src/ico/decoder.rs index 10269b7af8..0dfc44f112 100644 --- a/src/ico/decoder.rs +++ b/src/ico/decoder.rs @@ -1,28 +1,30 @@ use byteorder::{LittleEndian, ReadBytesExt}; +use std::convert::TryFrom; use std::io::{self, Cursor, Read, Seek, SeekFrom}; use std::marker::PhantomData; use std::mem; -use color::ColorType; -use image::{ImageDecoder, ImageError, ImageResult}; +use crate::color::ColorType; +use crate::error::{ImageError, ImageResult}; +use crate::image::{self, ImageDecoder}; use self::InnerDecoder::*; -use bmp::BMPDecoder; -use png::PNGDecoder; +use crate::bmp::BmpDecoder; +use crate::png::PngDecoder; // http://www.w3.org/TR/PNG-Structure.html // The first eight bytes of a PNG file always contain the following (decimal) values: const PNG_SIGNATURE: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; /// An ico decoder -pub struct ICODecoder { +pub struct IcoDecoder { selected_entry: DirEntry, inner_decoder: InnerDecoder, } enum InnerDecoder { - BMP(BMPDecoder), - PNG(PNGDecoder), + BMP(BmpDecoder), + PNG(PngDecoder), } #[derive(Clone, Copy, Default)] @@ -39,14 +41,14 @@ struct DirEntry { image_offset: u32, } -impl ICODecoder { +impl IcoDecoder { /// Create a new decoder that decodes from the stream ```r``` - pub fn new(mut r: R) -> ImageResult> { + pub fn new(mut r: R) -> ImageResult> { let entries = read_entries(&mut r)?; let entry = best_entry(entries)?; let decoder = entry.decoder(r)?; - Ok(ICODecoder { + Ok(IcoDecoder { selected_entry: entry, inner_decoder: decoder, }) @@ -129,8 +131,8 @@ impl DirEntry { } } - fn matches_dimensions(&self, width: u64, height: u64) -> bool { - u64::from(self.real_width()) == width && u64::from(self.real_height()) == height + fn matches_dimensions(&self, width: u32, height: u32) -> bool { + u32::from(self.real_width()) == width && u32::from(self.real_height()) == height } fn seek_to_start(&self, r: &mut R) -> ImageResult<()> { @@ -153,9 +155,9 @@ impl DirEntry { self.seek_to_start(&mut r)?; if is_png { - Ok(PNG(PNGDecoder::new(r)?)) + Ok(PNG(PngDecoder::new(r)?)) } else { - Ok(BMP(BMPDecoder::new_with_ico_format(r)?)) + Ok(BMP(BmpDecoder::new_with_ico_format(r)?)) } } } @@ -176,28 +178,29 @@ impl Read for IcoReader { } } -impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for ICODecoder { +impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for IcoDecoder { type Reader = IcoReader; - fn dimensions(&self) -> (u64, u64) { + fn dimensions(&self) -> (u32, u32) { match self.inner_decoder { BMP(ref decoder) => decoder.dimensions(), PNG(ref decoder) => decoder.dimensions(), } } - fn colortype(&self) -> ColorType { + fn color_type(&self) -> ColorType { match self.inner_decoder { - BMP(ref decoder) => decoder.colortype(), - PNG(ref decoder) => decoder.colortype(), + BMP(ref decoder) => decoder.color_type(), + PNG(ref decoder) => decoder.color_type(), } } fn into_reader(self) -> ImageResult { - Ok(IcoReader(Cursor::new(self.read_image()?), PhantomData)) + Ok(IcoReader(Cursor::new(image::decoder_to_vec(self)?), PhantomData)) } - fn read_image(self) -> ImageResult> { + fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); match self.inner_decoder { PNG(decoder) => { if self.selected_entry.image_length < PNG_SIGNATURE.len() as u32 { @@ -216,15 +219,15 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for ICODecoder { // Embedded PNG images can only be of the 32BPP RGBA format. // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/ - let color_type = decoder.colortype(); - if let ColorType::RGBA(8) = color_type { + let color_type = decoder.color_type(); + if let ColorType::Rgba8 = color_type { } else { return Err(ImageError::FormatError( "The PNG is not in RGBA format!".to_string(), )); } - decoder.read_image() + decoder.read_image(buf) } BMP(mut decoder) => { let (width, height) = decoder.dimensions(); @@ -235,13 +238,11 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for ICODecoder { } // The ICO decoder needs an alpha channel to apply the AND mask. - if decoder.colortype() != ColorType::RGBA(8) { - return Err(ImageError::UnsupportedError( - "Unsupported color type".to_string(), - )); + if decoder.color_type() != ColorType::Rgba8 { + return Err(ImageError::UnsupportedColor(decoder.color_type().into())); } - let mut pixel_data = decoder.read_image_data()?; + decoder.read_image_data(buf)?; // If there's an AND mask following the image, read and apply it. let r = decoder.reader(); @@ -253,7 +254,7 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for ICODecoder { if mask_length > 0 { // A mask row contains 1 bit per pixel, padded to 4 bytes. let mask_row_bytes = ((width + 31) / 32) * 4; - let expected_length = mask_row_bytes * height; + let expected_length = u64::from(mask_row_bytes) * u64::from(height); if mask_length < expected_length { return Err(ImageError::ImageEnd); } @@ -269,14 +270,14 @@ impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for ICODecoder { } if mask_byte & (1 << bit) != 0 { // Set alpha channel to transparent. - pixel_data[((height - y - 1) * width + x) as usize * 4 + 3] = 0; + buf[((height - y - 1) * width + x) as usize * 4 + 3] = 0; } x += 1; } } } } - Ok(pixel_data) + Ok(()) } } } diff --git a/src/ico/encoder.rs b/src/ico/encoder.rs index f3e9764aa9..57a14a1ce8 100644 --- a/src/ico/encoder.rs +++ b/src/ico/encoder.rs @@ -1,9 +1,11 @@ use byteorder::{LittleEndian, WriteBytesExt}; use std::io::{self, Write}; -use color::{bits_per_pixel, ColorType}; +use crate::color::ColorType; +use crate::error::ImageResult; +use crate::image::ImageEncoder; -use png::PNGEncoder; +use crate::png::PNGEncoder; // Enum value indicating an ICO image (as opposed to a CUR image): const ICO_IMAGE_TYPE: u16 = 1; @@ -32,7 +34,7 @@ impl ICOEncoder { width: u32, height: u32, color: ColorType, - ) -> io::Result<()> { + ) -> ImageResult<()> { let mut image_data: Vec = Vec::new(); PNGEncoder::new(&mut image_data).encode(data, width, height, color)?; @@ -50,6 +52,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)?; @@ -78,7 +92,7 @@ fn write_direntry( // Color planes: w.write_u16::(0)?; // Bits per pixel: - w.write_u16::(bits_per_pixel(color))?; + w.write_u16::(color.bits_per_pixel())?; // Image data size, in bytes: w.write_u32::(data_size)?; // Image data offset, in bytes: diff --git a/src/ico/mod.rs b/src/ico/mod.rs index c9a8b5f621..fd65df1f6e 100644 --- a/src/ico/mod.rs +++ b/src/ico/mod.rs @@ -6,7 +6,7 @@ //! * //! * -pub use self::decoder::ICODecoder; +pub use self::decoder::IcoDecoder; pub use self::encoder::ICOEncoder; mod decoder; diff --git a/src/image.rs b/src/image.rs index c2bee8502b..f761902b2a 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,142 +1,66 @@ #![allow(clippy::too_many_arguments)] - -use std::error::Error; -use std::fmt; +use std::convert::TryFrom; use std::io; use std::io::Read; use std::ops::{Deref, DerefMut}; use std::path::Path; -use buffer::{ImageBuffer, Pixel}; -use color; -use color::ColorType; -use math::Rect; +use crate::buffer::{ImageBuffer, Pixel}; +use crate::color::{ColorType, ExtendedColorType}; +use crate::error::{ImageError, ImageResult}; +use crate::math::Rect; -use animation::Frames; +use crate::animation::Frames; #[cfg(feature = "pnm")] -use pnm::PNMSubtype; - -/// An enumeration of Image errors -#[derive(Debug)] -pub enum ImageError { - /// The Image is not formatted properly - FormatError(String), - - /// The Image's dimensions are either too small or too large - DimensionError, - - /// The Decoder does not support this image format - UnsupportedError(String), - - /// The Decoder does not support this color type - UnsupportedColor(ColorType), - - /// Not enough data was provided to the Decoder - /// to decode the image - NotEnoughData, - - /// An I/O Error occurred while decoding the image - IoError(io::Error), - - /// The end of the image has been reached - ImageEnd, - - /// There is not enough memory to complete the given operation - InsufficientMemory, -} - -impl fmt::Display for ImageError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { - ImageError::FormatError(ref e) => write!(fmt, "Format error: {}", e), - ImageError::DimensionError => write!( - fmt, - "The Image's dimensions are either too \ - small or too large" - ), - ImageError::UnsupportedError(ref f) => write!( - fmt, - "The Decoder does not support the \ - image format `{}`", - f - ), - ImageError::UnsupportedColor(ref c) => write!( - fmt, - "The decoder does not support \ - the color type `{:?}`", - c - ), - ImageError::NotEnoughData => write!( - fmt, - "Not enough data was provided to the \ - Decoder to decode the image" - ), - ImageError::IoError(ref e) => e.fmt(fmt), - ImageError::ImageEnd => write!(fmt, "The end of the image has been reached"), - ImageError::InsufficientMemory => write!(fmt, "Insufficient memory"), - } - } -} - -impl Error for ImageError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - ImageError::IoError(ref e) => Some(e), - _ => None, - } - } -} - -impl From for ImageError { - fn from(err: io::Error) -> ImageError { - ImageError::IoError(err) - } -} - -/// Result of an image decoding/encoding process -pub type ImageResult = Result; +use crate::pnm::PNMSubtype; /// An enumeration of supported image formats. /// Not all formats support both encoding and decoding. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum ImageFormat { /// An Image in PNG Format - PNG, + Png, /// An Image in JPEG Format - JPEG, + Jpeg, /// An Image in GIF Format - GIF, + Gif, /// An Image in WEBP Format - WEBP, + WebP, /// An Image in general PNM Format - PNM, + Pnm, /// An Image in TIFF Format - TIFF, + Tiff, /// An Image in TGA Format - TGA, + Tga, + + /// An Image in DDS Format + Dds, /// An Image in BMP Format - BMP, + Bmp, /// An Image in ICO Format - ICO, + Ico, /// An Image in Radiance HDR Format - HDR, + Hdr, + + #[doc(hidden)] + __NonExhaustive(crate::utils::NonExhaustiveMarker), } impl ImageFormat { /// Return the image format specified by the path's file extension. pub fn from_path

(path: P) -> ImageResult where P : AsRef { // thin wrapper function to strip generics before calling from_path_impl - ::io::free_functions::guess_format_from_path_impl(path.as_ref()) + crate::io::free_functions::guess_format_from_path_impl(path.as_ref()) .map_err(Into::into) } } @@ -144,51 +68,54 @@ impl ImageFormat { /// An enumeration of supported image formats for encoding. #[derive(Clone, PartialEq, Eq, Debug)] pub enum ImageOutputFormat { - #[cfg(feature = "png_codec")] + #[cfg(feature = "png")] /// An Image in PNG Format - PNG, + Png, #[cfg(feature = "jpeg")] /// An Image in JPEG Format with specified quality - JPEG(u8), + Jpeg(u8), #[cfg(feature = "pnm")] /// An Image in one of the PNM Formats - PNM(PNMSubtype), + Pnm(PNMSubtype), - #[cfg(feature = "gif_codec")] + #[cfg(feature = "gif")] /// An Image in GIF Format - GIF, + Gif, #[cfg(feature = "ico")] /// An Image in ICO Format - ICO, + Ico, #[cfg(feature = "bmp")] /// An Image in BMP Format - BMP, + Bmp, /// A value for signalling an error: An unsupported format was requested // Note: When TryFrom is stabilized, this value should not be needed, and // a TryInto should be used instead of an Into. Unsupported(String), + + #[doc(hidden)] + __NonExhaustive(crate::utils::NonExhaustiveMarker), } impl From for ImageOutputFormat { fn from(fmt: ImageFormat) -> Self { match fmt { - #[cfg(feature = "png_codec")] - ImageFormat::PNG => ImageOutputFormat::PNG, + #[cfg(feature = "png")] + ImageFormat::Png => ImageOutputFormat::Png, #[cfg(feature = "jpeg")] - ImageFormat::JPEG => ImageOutputFormat::JPEG(75), + ImageFormat::Jpeg => ImageOutputFormat::Jpeg(75), #[cfg(feature = "pnm")] - ImageFormat::PNM => ImageOutputFormat::PNM(PNMSubtype::ArbitraryMap), - #[cfg(feature = "gif_codec")] - ImageFormat::GIF => ImageOutputFormat::GIF, + ImageFormat::Pnm => ImageOutputFormat::Pnm(PNMSubtype::ArbitraryMap), + #[cfg(feature = "gif")] + ImageFormat::Gif => ImageOutputFormat::Gif, #[cfg(feature = "ico")] - ImageFormat::ICO => ImageOutputFormat::ICO, + ImageFormat::Ico => ImageOutputFormat::Ico, #[cfg(feature = "bmp")] - ImageFormat::BMP => ImageOutputFormat::BMP, + ImageFormat::Bmp => ImageOutputFormat::Bmp, f => ImageOutputFormat::Unsupported(format!( "Image format {:?} not supported for encoding.", @@ -205,19 +132,25 @@ pub(crate) struct ImageReadBuffer { buffer: Vec, consumed: usize, - total_bytes: usize, - offset: usize, + total_bytes: u64, + offset: u64, } impl ImageReadBuffer { - pub(crate) fn new(scanline_bytes: usize, total_bytes: usize) -> Self { + /// Create a new ImageReadBuffer. + /// + /// Panics if scanline_bytes doesn't fit into a usize, because that would mean reading anything + /// from the image would take more RAM than the entire virtual address space. In other words, + /// actually using this struct would instantly OOM so just get it out of the way now. + pub(crate) fn new(scanline_bytes: u64, total_bytes: u64) -> Self { Self { - scanline_bytes, + scanline_bytes: usize::try_from(scanline_bytes).unwrap(), buffer: Vec::new(), consumed: 0, total_bytes, offset: 0, } } + pub(crate) fn read(&mut self, buf: &mut [u8], mut read_scanline: F) -> io::Result where F: FnMut(&mut [u8]) -> io::Result, @@ -229,7 +162,7 @@ impl ImageReadBuffer { // If there is nothing buffered and the user requested a full scanline worth of // data, skip buffering. let bytes_read = read_scanline(&mut buf[..self.scanline_bytes])?; - self.offset += bytes_read; + self.offset += u64::try_from(bytes_read).unwrap(); return Ok(bytes_read); } else { // Lazily allocate buffer the first time that read is called with a buffer smaller @@ -241,7 +174,7 @@ impl ImageReadBuffer { self.consumed = 0; let bytes_read = read_scanline(&mut self.buffer[..])?; self.buffer.resize(bytes_read, 0); - self.offset += bytes_read; + self.offset += u64::try_from(bytes_read).unwrap(); assert!(bytes_read == self.scanline_bytes || self.offset == self.total_bytes); } @@ -250,11 +183,11 @@ impl ImageReadBuffer { // Finally, copy bytes into output buffer. let bytes_buffered = self.buffer.len() - self.consumed; if bytes_buffered > buf.len() { - ::copy_memory(&self.buffer[self.consumed..][..buf.len()], &mut buf[..]); + crate::copy_memory(&self.buffer[self.consumed..][..buf.len()], &mut buf[..]); self.consumed += buf.len(); Ok(buf.len()) } else { - ::copy_memory(&self.buffer[self.consumed..], &mut buf[..bytes_buffered]); + crate::copy_memory(&self.buffer[self.consumed..], &mut buf[..bytes_buffered]); self.consumed = self.buffer.len(); Ok(bytes_buffered) } @@ -263,7 +196,7 @@ impl ImageReadBuffer { /// Decodes a specific region of the image, represented by the rectangle /// starting from ```x``` and ```y``` and having ```length``` and ```width``` -pub(crate) fn load_rect<'a, D, F, F1, F2>(x: u64, y: u64, width: u64, height: u64, buf: &mut [u8], +pub(crate) fn load_rect<'a, D, F, F1, F2, E>(x: u32, y: u32, width: u32, height: u32, buf: &mut [u8], progress_callback: F, decoder: &mut D, mut seek_scanline: F1, @@ -271,60 +204,58 @@ pub(crate) fn load_rect<'a, D, F, F1, F2>(x: u64, y: u64, width: u64, height: u6 where D: ImageDecoder<'a>, F: Fn(Progress), F1: FnMut(&mut D, u64) -> io::Result<()>, - F2: FnMut(&mut D, &mut [u8]) -> io::Result + F2: FnMut(&mut D, &mut [u8]) -> Result, + ImageError: From, { + let (x, y, width, height) = (u64::from(x), u64::from(y), u64::from(width), u64::from(height)); let dimensions = decoder.dimensions(); - let row_bytes = decoder.row_bytes(); + let bytes_per_pixel = u64::from(decoder.color_type().bytes_per_pixel()); + let row_bytes = bytes_per_pixel * u64::from(dimensions.0); let scanline_bytes = decoder.scanline_bytes(); - let bits_per_pixel = u64::from(color::bits_per_pixel(decoder.colortype())); - let total_bits = width * height * bits_per_pixel; + let total_bytes = width * height * bytes_per_pixel; - let mut bits_read = 0u64; + let mut bytes_read = 0u64; let mut current_scanline = 0; let mut tmp = Vec::new(); { - // Read a range of the image starting from bit number `start` and continuing until bit - // number `end`. Updates `current_scanline` and `bits_read` appropiately. + // Read a range of the image starting from byte number `start` and continuing until byte + // number `end`. Updates `current_scanline` and `bytes_read` appropiately. let mut read_image_range = |start: u64, end: u64| -> ImageResult<()> { - let target_scanline = start / (scanline_bytes * 8); + let target_scanline = start / scanline_bytes; if target_scanline != current_scanline { seek_scanline(decoder, target_scanline)?; current_scanline = target_scanline; } - let mut position = current_scanline * scanline_bytes * 8; + let mut position = current_scanline * scanline_bytes; while position < end { - if position >= start && end - position >= scanline_bytes * 8 && bits_read % 8 == 0 { - read_scanline(decoder, &mut buf[((bits_read/8) as usize)..] + if position >= start && end - position >= scanline_bytes { + read_scanline(decoder, &mut buf[(bytes_read as usize)..] [..(scanline_bytes as usize)])?; - bits_read += scanline_bytes * 8; + bytes_read += scanline_bytes; } else { tmp.resize(scanline_bytes as usize, 0u8); read_scanline(decoder, &mut tmp)?; let offset = start.saturating_sub(position); let len = (end - start) - .min(scanline_bytes * 8 - offset) + .min(scanline_bytes - offset) .min(end - position); - if bits_read % 8 == 0 && offset % 8 == 0 && len % 8 == 0 { - let o = (offset / 8) as usize; - let l = (len / 8) as usize; - buf[((bits_read/8) as usize)..][..l].copy_from_slice(&tmp[o..][..l]); - bits_read += len; - } else { - unimplemented!("Target rectangle not aligned on byte boundaries") - } + + buf[(bytes_read as usize)..][..len as usize] + .copy_from_slice(&tmp[offset as usize..][..len as usize]); + bytes_read += len; } current_scanline += 1; - position += scanline_bytes * 8; - progress_callback(Progress {current: bits_read, total: total_bits}); + position += scanline_bytes; + progress_callback(Progress {current: bytes_read, total: total_bytes}); } Ok(()) }; - if x + width > dimensions.0 || y + height > dimensions.0 + if x + width > u64::from(dimensions.0) || y + height > u64::from(dimensions.0) || width == 0 || height == 0 { return Err(ImageError::DimensionError); } @@ -332,15 +263,15 @@ pub(crate) fn load_rect<'a, D, F, F1, F2>(x: u64, y: u64, width: u64, height: u6 return Err(ImageError::InsufficientMemory); } - progress_callback(Progress {current: 0, total: total_bits}); - if x == 0 && width == dimensions.0 { - let start = x * bits_per_pixel + y * row_bytes * 8; - let end = (x + width) * bits_per_pixel + (y + height - 1) * row_bytes * 8; + progress_callback(Progress {current: 0, total: total_bytes}); + if x == 0 && width == u64::from(dimensions.0) { + let start = x * bytes_per_pixel + y * row_bytes; + let end = (x + width) * bytes_per_pixel + (y + height - 1) * row_bytes; read_image_range(start, end)?; } else { for row in y..(y+height) { - let start = x * bits_per_pixel + row * row_bytes * 8; - let end = (x + width) * bits_per_pixel + row * row_bytes * 8; + let start = x * bytes_per_pixel + row * row_bytes; + let end = (x + width) * bytes_per_pixel + row * row_bytes; read_image_range(start, end)?; } } @@ -350,38 +281,79 @@ pub(crate) fn load_rect<'a, D, F, F1, F2>(x: u64, y: u64, width: u64, height: u6 Ok(seek_scanline(decoder, 0)?) } +/// 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, T>(decoder: impl ImageDecoder<'a>) -> ImageResult> +where + T: crate::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) +} + /// Represents the progress of an image operation. +/// +/// Note that this is not necessarily accurate and no change to the values passed to the progress +/// function during decoding will be considered breaking. A decoder could in theory report the +/// progress `(0, 0)` if progress is unknown, without violating the interface contract of the type. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Progress { current: u64, total: u64, } +impl Progress { + /// A measure of completed decoding. + pub fn current(self) -> u64 { + self.current + } + + /// A measure of all necessary decoding work. + /// + /// This is in general greater or equal than `current`. + pub fn total(self) -> u64 { + self.total + } + + /// Calculate a measure for remaining decoding work. + pub fn remaining(self) -> u64 { + self.total.max(self.current) - self.current + } +} + /// The trait that all decoders implement pub trait ImageDecoder<'a>: Sized { /// The type of reader produced by `into_reader`. type Reader: Read + 'a; /// Returns a tuple containing the width and height of the image - fn dimensions(&self) -> (u64, u64); + fn dimensions(&self) -> (u32, u32); - /// Returns the color type of the image e.g. RGB(8) (8bit RGB) - fn colortype(&self) -> ColorType; + /// Returns the color type of the image data produced by this decoder + fn color_type(&self) -> ColorType; + + /// Retuns the color type of the image file before decoding + fn original_color_type(&self) -> ExtendedColorType { + self.color_type().into() + } /// Returns a reader that can be used to obtain the bytes of the image. For the best /// performance, always try to read at least `scanline_bytes` from the reader at a time. Reading /// fewer bytes will cause the reader to perform internal buffering. fn into_reader(self) -> ImageResult; - /// Returns the number of bytes in a single row of the image. All decoders will pad image rows - /// to a byte boundary. - fn row_bytes(&self) -> u64 { - (self.dimensions().0 * u64::from(color::bits_per_pixel(self.colortype())) + 7) / 8 - } - - /// Returns the total number of bytes in the image. + /// Returns the total number of bytes in the decoded image. + /// + /// This is the size of the buffer that must be passed to `read_image` or + /// `read_image_with_progress`. The returned value may exceed usize::MAX, in + /// which case it isn't actually possible to construct a buffer to decode all the image data + /// into. fn total_bytes(&self) -> u64 { - self.dimensions().1 * self.row_bytes() + let dimensions = self.dimensions(); + u64::from(dimensions.0) * u64::from(dimensions.1) * u64::from(self.color_type().bytes_per_pixel()) } /// Returns the minimum number of bytes that can be efficiently read from this decoder. This may @@ -391,22 +363,39 @@ pub trait ImageDecoder<'a>: Sized { } /// Returns all the bytes in the image. - fn read_image(self) -> ImageResult> { - self.read_image_with_progress(|_| {}) + /// + /// This function takes a slice of bytes and writes the pixel data of the image into it. + /// Although not required, for certain color types callers may want to pass buffers which are + /// aligned to 2 or 4 byte boundaries to the slice can be cast to a [u16] or [u32]. To accommodate + /// such casts, the returned contents will always be in native endian. + /// + /// # Panics + /// + /// This function panics if buf.len() != self.total_bytes(). + /// + /// # Examples + /// + /// ```no_build + /// use zerocopy::{AsBytes, FromBytes}; + /// fn read_16bit_image(decoder: impl ImageDecoder) -> Vec<16> { + /// let mut buf: Vec = vec![0; decoder.total_bytes()/2]; + /// decoder.read_image(buf.as_bytes()); + /// buf + /// } + fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { + self.read_image_with_progress(buf, |_| {}) } /// Same as `read_image` but periodically calls the provided callback to give updates on loading /// progress. fn read_image_with_progress( self, + buf: &mut [u8], progress_callback: F, - ) -> ImageResult> { - let total_bytes = self.total_bytes(); - if total_bytes > usize::max_value() as u64 { - return Err(ImageError::InsufficientMemory); - } + ) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); - let total_bytes = total_bytes as usize; + let total_bytes = self.total_bytes() as usize; let scanline_bytes = self.scanline_bytes() as usize; let target_read_size = if scanline_bytes < 4096 { (4096 / scanline_bytes) * scanline_bytes @@ -417,10 +406,9 @@ pub trait ImageDecoder<'a>: Sized { let mut reader = self.into_reader()?; let mut bytes_read = 0; - let mut contents = vec![0; total_bytes]; while bytes_read < total_bytes { let read_size = target_read_size.min(total_bytes - bytes_read); - reader.read_exact(&mut contents[bytes_read..][..read_size])?; + reader.read_exact(&mut buf[bytes_read..][..read_size])?; bytes_read += read_size; progress_callback(Progress { @@ -429,7 +417,7 @@ pub trait ImageDecoder<'a>: Sized { }); } - Ok(contents) + Ok(()) } } @@ -438,10 +426,10 @@ pub trait ImageDecoderExt<'a>: ImageDecoder<'a> + Sized { /// Read a rectangular section of the image. fn read_rect( &mut self, - x: u64, - y: u64, - width: u64, - height: u64, + x: u32, + y: u32, + width: u32, + height: u32, buf: &mut [u8], ) -> ImageResult<()> { self.read_rect_with_progress(x, y, width, height, buf, |_|{}) @@ -450,10 +438,10 @@ pub trait ImageDecoderExt<'a>: ImageDecoder<'a> + Sized { /// Read a rectangular section of the image, periodically reporting progress. fn read_rect_with_progress( &mut self, - x: u64, - y: u64, - width: u64, - height: u64, + x: u32, + y: u32, + width: u32, + height: u32, buf: &mut [u8], progress_callback: F, ) -> ImageResult<()>; @@ -465,6 +453,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, @@ -612,16 +621,15 @@ pub trait GenericImage: GenericImageView { /// In order to copy only a piece of the other image, use [`GenericImageView::view`]. /// /// # Returns - /// `true` if the copy was successful, `false` if the image could not - /// be copied due to size constraints. - fn copy_from(&mut self, other: &O, x: u32, y: u32) -> bool + /// Returns an error if the image is too large to be copied at the given position + fn copy_from(&mut self, other: &O, x: u32, y: u32) -> ImageResult<()> where O: GenericImageView, { // Do bounds checking here so we can use the non-bounds-checking // functions to copy pixels. if self.width() < other.width() + x || self.height() < other.height() + y { - return false; + return Err(ImageError::DimensionError); } for i in 0..other.width() { @@ -630,7 +638,7 @@ pub trait GenericImage: GenericImageView { self.put_pixel(i + x, k + y, p); } } - true + Ok(()) } /// Copies all of the pixels from one part of this image to another part of this image. @@ -829,9 +837,9 @@ mod tests { use std::path::Path; use super::{ColorType, ImageDecoder, ImageResult, GenericImage, GenericImageView, load_rect, ImageFormat}; - use buffer::{ImageBuffer, GrayImage}; - use color::Rgba; - use math::Rect; + use crate::buffer::{GrayImage, ImageBuffer}; + use crate::color::Rgba; + use crate::math::Rect; #[test] /// Test that alpha blending works as expected @@ -905,8 +913,8 @@ mod tests { struct MockDecoder {scanline_number: u64, scanline_bytes: u64} impl<'a> ImageDecoder<'a> for MockDecoder { type Reader = Box; - fn dimensions(&self) -> (u64, u64) {(5, 5)} - fn colortype(&self) -> ColorType { ColorType::Gray(8) } + fn dimensions(&self) -> (u32, u32) {(5, 5)} + fn color_type(&self) -> ColorType { ColorType::L8 } fn into_reader(self) -> ImageResult {unimplemented!()} fn scanline_bytes(&self) -> u64 { self.scanline_bytes } } @@ -969,22 +977,23 @@ mod tests { fn from_path(s: &str) -> ImageResult { ImageFormat::from_path(Path::new(s)) } - assert_eq!(from_path("./a.jpg").unwrap(), ImageFormat::JPEG); - assert_eq!(from_path("./a.jpeg").unwrap(), ImageFormat::JPEG); - assert_eq!(from_path("./a.JPEG").unwrap(), ImageFormat::JPEG); - assert_eq!(from_path("./a.pNg").unwrap(), ImageFormat::PNG); - assert_eq!(from_path("./a.gif").unwrap(), ImageFormat::GIF); - assert_eq!(from_path("./a.webp").unwrap(), ImageFormat::WEBP); - assert_eq!(from_path("./a.tiFF").unwrap(), ImageFormat::TIFF); - assert_eq!(from_path("./a.tif").unwrap(), ImageFormat::TIFF); - assert_eq!(from_path("./a.tga").unwrap(), ImageFormat::TGA); - assert_eq!(from_path("./a.bmp").unwrap(), ImageFormat::BMP); - assert_eq!(from_path("./a.Ico").unwrap(), ImageFormat::ICO); - assert_eq!(from_path("./a.hdr").unwrap(), ImageFormat::HDR); - assert_eq!(from_path("./a.pbm").unwrap(), ImageFormat::PNM); - assert_eq!(from_path("./a.pAM").unwrap(), ImageFormat::PNM); - assert_eq!(from_path("./a.Ppm").unwrap(), ImageFormat::PNM); - assert_eq!(from_path("./a.pgm").unwrap(), ImageFormat::PNM); + assert_eq!(from_path("./a.jpg").unwrap(), ImageFormat::Jpeg); + assert_eq!(from_path("./a.jpeg").unwrap(), ImageFormat::Jpeg); + assert_eq!(from_path("./a.JPEG").unwrap(), ImageFormat::Jpeg); + assert_eq!(from_path("./a.pNg").unwrap(), ImageFormat::Png); + assert_eq!(from_path("./a.gif").unwrap(), ImageFormat::Gif); + assert_eq!(from_path("./a.webp").unwrap(), ImageFormat::WebP); + assert_eq!(from_path("./a.tiFF").unwrap(), ImageFormat::Tiff); + assert_eq!(from_path("./a.tif").unwrap(), ImageFormat::Tiff); + assert_eq!(from_path("./a.tga").unwrap(), ImageFormat::Tga); + assert_eq!(from_path("./a.dds").unwrap(), ImageFormat::Dds); + assert_eq!(from_path("./a.bmp").unwrap(), ImageFormat::Bmp); + assert_eq!(from_path("./a.Ico").unwrap(), ImageFormat::Ico); + assert_eq!(from_path("./a.hdr").unwrap(), ImageFormat::Hdr); + assert_eq!(from_path("./a.pbm").unwrap(), ImageFormat::Pnm); + assert_eq!(from_path("./a.pAM").unwrap(), ImageFormat::Pnm); + assert_eq!(from_path("./a.Ppm").unwrap(), ImageFormat::Pnm); + assert_eq!(from_path("./a.pgm").unwrap(), ImageFormat::Pnm); assert!(from_path("./a.txt").is_err()); assert!(from_path("./a").is_err()); } diff --git a/src/imageops/affine.rs b/src/imageops/affine.rs index 28f1cd5aa0..adaa8b995b 100644 --- a/src/imageops/affine.rs +++ b/src/imageops/affine.rs @@ -1,7 +1,7 @@ //! Functions for performing affine transformations. -use buffer::{ImageBuffer, Pixel}; -use image::{GenericImage, GenericImageView}; +use crate::buffer::{ImageBuffer, Pixel}; +use crate::image::{GenericImage, GenericImageView}; /// Rotate an image 90 degrees clockwise. pub fn rotate90( @@ -245,8 +245,8 @@ mod test { flip_horizontal, flip_horizontal_in_place, flip_vertical, flip_vertical_in_place, rotate180, rotate180_in_place, rotate270, rotate90, }; - use buffer::{GrayImage, ImageBuffer, Pixel}; - use image::GenericImage; + use crate::buffer::{GrayImage, ImageBuffer, Pixel}; + use crate::image::GenericImage; macro_rules! assert_pixels_eq { ($actual:expr, $expected:expr) => {{ diff --git a/src/imageops/colorops.rs b/src/imageops/colorops.rs index 053b8271b2..2de5194957 100644 --- a/src/imageops/colorops.rs +++ b/src/imageops/colorops.rs @@ -1,13 +1,13 @@ //! Functions for altering and converting the color of pixelbufs -use buffer::{ImageBuffer, Pixel}; -use color::{Luma, Rgba}; -use image::{GenericImage, GenericImageView}; -use math::nq; -use math::utils::clamp; +use crate::buffer::{ImageBuffer, Pixel}; +use crate::color::{Luma, Rgba}; +use crate::image::{GenericImage, GenericImageView}; +use crate::math::nq; +use crate::math::utils::clamp; use num_traits::{Num, NumCast}; use std::f64::consts::PI; -use traits::Primitive; +use crate::traits::Primitive; type Subpixel = <::Pixel as Pixel>::Subpixel; @@ -312,7 +312,7 @@ where mod test { use super::*; - use ImageBuffer; + use crate::ImageBuffer; #[test] fn test_dither() { diff --git a/src/imageops/mod.rs b/src/imageops/mod.rs index f575bd3045..f0c7fe68c8 100644 --- a/src/imageops/mod.rs +++ b/src/imageops/mod.rs @@ -1,9 +1,9 @@ //! Image Processing Functions use std::cmp; -use image::{GenericImage, GenericImageView, SubImage}; +use crate::image::{GenericImage, GenericImageView, SubImage}; -use buffer::Pixel; +use crate::buffer::Pixel; pub use self::sample::FilterType; @@ -166,8 +166,8 @@ where mod tests { use super::overlay; - use buffer::ImageBuffer; - use color::Rgb; + use crate::buffer::ImageBuffer; + use crate::color::Rgb; #[test] /// Test that images written into other images works diff --git a/src/imageops/sample.rs b/src/imageops/sample.rs index a5f49198de..6f8a76da17 100644 --- a/src/imageops/sample.rs +++ b/src/imageops/sample.rs @@ -7,10 +7,10 @@ use std::f32; use num_traits::{NumCast, ToPrimitive, Zero}; -use buffer::{ImageBuffer, Pixel}; -use image::GenericImageView; -use math::utils::clamp; -use traits::{Enlargeable, Primitive}; +use crate::buffer::{ImageBuffer, Pixel}; +use crate::image::GenericImageView; +use crate::math::utils::clamp; +use crate::traits::{Enlargeable, Primitive}; /// Available Sampling Filters. /// @@ -817,17 +817,17 @@ where #[cfg(test)] mod tests { use super::{resize, FilterType}; - use buffer::{ImageBuffer, RgbImage}; + use crate::buffer::{ImageBuffer, RgbImage}; #[cfg(feature = "benchmarks")] use test; #[bench] - #[cfg(all(feature = "benchmarks", feature = "png_codec"))] + #[cfg(all(feature = "benchmarks", feature = "png"))] fn bench_resize(b: &mut test::Bencher) { use std::path::Path; - let img = ::open(&Path::new("./examples/fractal.png")).unwrap(); + let img = crate::open(&Path::new("./examples/fractal.png")).unwrap(); b.iter(|| { - test::black_box(resize(&img, 200, 200, ::Nearest)); + test::black_box(resize(&img, 200, 200, FilterType::Nearest)); }); b.bytes = 800 * 800 * 3 + 200 * 200 * 3; } @@ -841,8 +841,8 @@ mod tests { #[bench] #[cfg(all(feature = "benchmarks", feature = "tiff"))] fn bench_thumbnail(b: &mut test::Bencher) { - let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/lenna.tiff"); - let image = ::open(path).unwrap(); + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/mandrill.tiff"); + let image = crate::open(path).unwrap(); b.iter(|| { test::black_box(image.thumbnail(256, 256)); }); @@ -852,8 +852,8 @@ mod tests { #[bench] #[cfg(all(feature = "benchmarks", feature = "tiff"))] fn bench_thumbnail_upsize(b: &mut test::Bencher) { - let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/lenna.tiff"); - let image = ::open(path).unwrap().thumbnail(256, 256); + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/mandrill.tiff"); + let image = crate::open(path).unwrap().thumbnail(256, 256); b.iter(|| { test::black_box(image.thumbnail(512, 512)); }); @@ -863,8 +863,8 @@ mod tests { #[bench] #[cfg(all(feature = "benchmarks", feature = "tiff"))] fn bench_thumbnail_upsize_irregular(b: &mut test::Bencher) { - let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/lenna.tiff"); - let image = ::open(path).unwrap().thumbnail(193, 193); + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/images/tiff/testsuite/mandrill.tiff"); + let image = crate::open(path).unwrap().thumbnail(193, 193); b.iter(|| { test::black_box(image.thumbnail(256, 256)); }); diff --git a/src/io/free_functions.rs b/src/io/free_functions.rs index f42d057c8e..227864d77e 100644 --- a/src/io/free_functions.rs +++ b/src/io/free_functions.rs @@ -1,36 +1,37 @@ use std::ffi::OsString; use std::fs::File; -use std::io; use std::io::{BufRead, BufReader, BufWriter, Seek}; use std::path::Path; use std::u32; #[cfg(feature = "bmp")] -use bmp; -#[cfg(feature = "gif_codec")] -use gif; +use crate::bmp; +#[cfg(feature = "gif")] +use crate::gif; #[cfg(feature = "hdr")] -use hdr; +use crate::hdr; #[cfg(feature = "ico")] -use ico; +use crate::ico; #[cfg(feature = "jpeg")] -use jpeg; -#[cfg(feature = "png_codec")] -use png; +use crate::jpeg; +#[cfg(feature = "png")] +use crate::png; #[cfg(feature = "pnm")] -use pnm; +use crate::pnm; #[cfg(feature = "tga")] -use tga; +use crate::tga; +#[cfg(feature = "dds")] +use crate::dds; #[cfg(feature = "tiff")] -use tiff; +use crate::tiff; #[cfg(feature = "webp")] -use webp; +use crate::webp; -use color; -use image; -use dynimage::DynamicImage; -use image::{ImageDecoder, ImageFormat, ImageResult}; -use ImageError; +use crate::color; +use crate::image; +use crate::dynimage::DynamicImage; +use crate::error::{ImageError, ImageFormatHint, ImageResult}; +use crate::image::{ImageDecoder, ImageEncoder, ImageFormat}; /// Internal error type for guessing format from path. pub(crate) enum PathError { @@ -43,7 +44,7 @@ pub(crate) enum PathError { pub(crate) fn open_impl(path: &Path) -> ImageResult { let fin = match File::open(path) { Ok(f) => f, - Err(err) => return Err(image::ImageError::IoError(err)), + Err(err) => return Err(ImageError::IoError(err)), }; let fin = BufReader::new(fin); @@ -59,30 +60,29 @@ pub fn load(r: R, format: ImageFormat) -> ImageResult DynamicImage::from_decoder(png::PNGDecoder::new(r)?), - #[cfg(feature = "gif_codec")] - image::ImageFormat::GIF => DynamicImage::from_decoder(gif::Decoder::new(r)?), + #[cfg(feature = "png")] + image::ImageFormat::Png => DynamicImage::from_decoder(png::PngDecoder::new(r)?), + #[cfg(feature = "gif")] + image::ImageFormat::Gif => DynamicImage::from_decoder(gif::GifDecoder::new(r)?), #[cfg(feature = "jpeg")] - image::ImageFormat::JPEG => DynamicImage::from_decoder(jpeg::JPEGDecoder::new(r)?), + image::ImageFormat::Jpeg => DynamicImage::from_decoder(jpeg::JpegDecoder::new(r)?), #[cfg(feature = "webp")] - image::ImageFormat::WEBP => DynamicImage::from_decoder(webp::WebpDecoder::new(r)?), + image::ImageFormat::WebP => DynamicImage::from_decoder(webp::WebPDecoder::new(r)?), #[cfg(feature = "tiff")] - image::ImageFormat::TIFF => DynamicImage::from_decoder(tiff::TIFFDecoder::new(r)?), + image::ImageFormat::Tiff => DynamicImage::from_decoder(tiff::TiffDecoder::new(r)?), #[cfg(feature = "tga")] - image::ImageFormat::TGA => DynamicImage::from_decoder(tga::TGADecoder::new(r)?), + image::ImageFormat::Tga => DynamicImage::from_decoder(tga::TgaDecoder::new(r)?), + #[cfg(feature = "dds")] + image::ImageFormat::Dds => DynamicImage::from_decoder(dds::DdsDecoder::new(r)?), #[cfg(feature = "bmp")] - image::ImageFormat::BMP => DynamicImage::from_decoder(bmp::BMPDecoder::new(r)?), + image::ImageFormat::Bmp => DynamicImage::from_decoder(bmp::BmpDecoder::new(r)?), #[cfg(feature = "ico")] - image::ImageFormat::ICO => DynamicImage::from_decoder(ico::ICODecoder::new(r)?), + image::ImageFormat::Ico => DynamicImage::from_decoder(ico::IcoDecoder::new(r)?), #[cfg(feature = "hdr")] - image::ImageFormat::HDR => DynamicImage::from_decoder(hdr::HDRAdapter::new(BufReader::new(r))?), + image::ImageFormat::Hdr => DynamicImage::from_decoder(hdr::HDRAdapter::new(BufReader::new(r))?), #[cfg(feature = "pnm")] - image::ImageFormat::PNM => DynamicImage::from_decoder(pnm::PNMDecoder::new(BufReader::new(r))?), - _ => Err(image::ImageError::UnsupportedError(format!( - "A decoder for {:?} is not available.", - format - ))), + image::ImageFormat::Pnm => DynamicImage::from_decoder(pnm::PnmDecoder::new(BufReader::new(r))?), + _ => Err(ImageError::Unsupported(ImageFormatHint::Exact(format).into())), } } @@ -100,38 +100,33 @@ pub(crate) fn image_dimensions_with_format_impl(fin: R, forma { #[allow(unreachable_patterns)] // Default is unreachable if all features are supported. - let (w, h): (u64, u64) = match format { + Ok(match format { #[cfg(feature = "jpeg")] - image::ImageFormat::JPEG => jpeg::JPEGDecoder::new(fin)?.dimensions(), - #[cfg(feature = "png_codec")] - image::ImageFormat::PNG => png::PNGDecoder::new(fin)?.dimensions(), - #[cfg(feature = "gif_codec")] - image::ImageFormat::GIF => gif::Decoder::new(fin)?.dimensions(), + image::ImageFormat::Jpeg => jpeg::JpegDecoder::new(fin)?.dimensions(), + #[cfg(feature = "png")] + image::ImageFormat::Png => png::PngDecoder::new(fin)?.dimensions(), + #[cfg(feature = "gif")] + image::ImageFormat::Gif => gif::GifDecoder::new(fin)?.dimensions(), #[cfg(feature = "webp")] - image::ImageFormat::WEBP => webp::WebpDecoder::new(fin)?.dimensions(), + image::ImageFormat::WebP => webp::WebPDecoder::new(fin)?.dimensions(), #[cfg(feature = "tiff")] - image::ImageFormat::TIFF => tiff::TIFFDecoder::new(fin)?.dimensions(), + image::ImageFormat::Tiff => tiff::TiffDecoder::new(fin)?.dimensions(), #[cfg(feature = "tga")] - image::ImageFormat::TGA => tga::TGADecoder::new(fin)?.dimensions(), + image::ImageFormat::Tga => tga::TgaDecoder::new(fin)?.dimensions(), + #[cfg(feature = "dds")] + image::ImageFormat::Dds => dds::DdsDecoder::new(fin)?.dimensions(), #[cfg(feature = "bmp")] - image::ImageFormat::BMP => bmp::BMPDecoder::new(fin)?.dimensions(), + image::ImageFormat::Bmp => bmp::BmpDecoder::new(fin)?.dimensions(), #[cfg(feature = "ico")] - image::ImageFormat::ICO => ico::ICODecoder::new(fin)?.dimensions(), + image::ImageFormat::Ico => ico::IcoDecoder::new(fin)?.dimensions(), #[cfg(feature = "hdr")] - image::ImageFormat::HDR => hdr::HDRAdapter::new(fin)?.dimensions(), + image::ImageFormat::Hdr => hdr::HDRAdapter::new(fin)?.dimensions(), #[cfg(feature = "pnm")] - image::ImageFormat::PNM => { - pnm::PNMDecoder::new(fin)?.dimensions() + image::ImageFormat::Pnm => { + pnm::PnmDecoder::new(fin)?.dimensions() } - format => return Err(image::ImageError::UnsupportedError(format!( - "Image format image/{:?} is not supported.", - format - ))), - }; - if w >= u64::from(u32::MAX) || h >= u64::from(u32::MAX) { - return Err(image::ImageError::DimensionError); - } - Ok((w as u32, h as u32)) + format => return Err(ImageError::Unsupported(ImageFormatHint::Exact(format).into())), + }) } pub(crate) fn save_buffer_impl( @@ -140,45 +135,41 @@ pub(crate) fn save_buffer_impl( width: u32, height: u32, color: color::ColorType, -) -> io::Result<()> { +) -> ImageResult<()> { let fout = &mut BufWriter::new(File::create(path)?); let ext = path.extension() .and_then(|s| s.to_str()) .map_or("".to_string(), |s| s.to_ascii_lowercase()); match &*ext { - #[cfg(feature = "gif_codec")] - "gif" => gif::Encoder::new(fout).encode(&gif::Frame::from_rgb(width as u16, height as u16, &buf)) - .map_err(|e| io::Error::new(io::ErrorKind::Other, Box::new(e))), // FIXME: see https://github.com/image-rs/image/issues/921 + #[cfg(feature = "gif")] + "gif" => gif::Encoder::new(fout).encode(buf, width, height, color), #[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), - #[cfg(feature = "png_codec")] - "png" => png::PNGEncoder::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).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) - .map_err(|e| io::Error::new(io::ErrorKind::Other, Box::new(e))), // FIXME: see https://github.com/image-rs/image/issues/921 - format => Err(io::Error::new( - io::ErrorKind::InvalidInput, - &format!("Unsupported image format image/{:?}", format)[..], - )), + "tif" | "tiff" => tiff::TiffEncoder::new(fout) + .write_image(buf, width, height, color), + _ => Err(ImageError::Unsupported(ImageFormatHint::from(path).into())), } } @@ -189,30 +180,24 @@ pub(crate) fn save_buffer_with_format_impl( height: u32, color: color::ColorType, format: ImageFormat, -) -> io::Result<()> { +) -> ImageResult<()> { let fout = &mut BufWriter::new(File::create(path)?); match format { - #[cfg(feature = "gif_codec")] - image::ImageFormat::GIF => gif::Encoder::new(fout) - .encode(&gif::Frame::from_rgb(width as u16, height as u16, &buf)) - .map_err(|e| io::Error::new(io::ErrorKind::Other, Box::new(e))), // FIXME: see https://github.com/image-rs/image/issues/921 + #[cfg(feature = "gif")] + image::ImageFormat::Gif => gif::Encoder::new(fout).encode(buf, width, height, color), #[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), - #[cfg(feature = "png_codec")] - image::ImageFormat::PNG => png::PNGEncoder::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).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) - .map_err(|e| io::Error::new(io::ErrorKind::Other, Box::new(e))), - _ => Err(io::Error::new( - io::ErrorKind::InvalidInput, - &format!("Unsupported image format image/{:?}", format)[..], - )), + image::ImageFormat::Tiff => tiff::TiffEncoder::new(fout) + .write_image(buf, width, height, color), + format => return Err(ImageError::Unsupported(ImageFormatHint::Exact(format).into())), } } @@ -231,42 +216,44 @@ pub(crate) fn guess_format_from_path_impl(path: &Path) -> Result image::ImageFormat::JPEG, - Some("png") => image::ImageFormat::PNG, - Some("gif") => image::ImageFormat::GIF, - Some("webp") => image::ImageFormat::WEBP, - Some("tif") | Some("tiff") => image::ImageFormat::TIFF, - Some("tga") => image::ImageFormat::TGA, - Some("bmp") => image::ImageFormat::BMP, - Some("ico") => image::ImageFormat::ICO, - Some("hdr") => image::ImageFormat::HDR, - Some("pbm") | Some("pam") | Some("ppm") | Some("pgm") => image::ImageFormat::PNM, + Some("jpg") | Some("jpeg") => image::ImageFormat::Jpeg, + Some("png") => image::ImageFormat::Png, + Some("gif") => image::ImageFormat::Gif, + Some("webp") => image::ImageFormat::WebP, + Some("tif") | Some("tiff") => image::ImageFormat::Tiff, + Some("tga") => image::ImageFormat::Tga, + Some("dds") => image::ImageFormat::Dds, + Some("bmp") => image::ImageFormat::Bmp, + Some("ico") => image::ImageFormat::Ico, + Some("hdr") => image::ImageFormat::Hdr, + Some("pbm") | Some("pam") | Some("ppm") | Some("pgm") => image::ImageFormat::Pnm, // The original extension is used, instead of _format - _format => return match exact_ext { + _ => return match exact_ext { None => Err(PathError::NoExtension), Some(os) => Err(PathError::UnknownExtension(os.to_owned())), }, }) } -static MAGIC_BYTES: [(&[u8], ImageFormat); 17] = [ - (b"\x89PNG\r\n\x1a\n", ImageFormat::PNG), - (&[0xff, 0xd8, 0xff], ImageFormat::JPEG), - (b"GIF89a", ImageFormat::GIF), - (b"GIF87a", ImageFormat::GIF), - (b"RIFF", ImageFormat::WEBP), // TODO: better magic byte detection, see https://github.com/image-rs/image/issues/660 - (b"MM\x00*", ImageFormat::TIFF), - (b"II*\x00", ImageFormat::TIFF), - (b"BM", ImageFormat::BMP), - (&[0, 0, 1, 0], ImageFormat::ICO), - (b"#?RADIANCE", ImageFormat::HDR), - (b"P1", ImageFormat::PNM), - (b"P2", ImageFormat::PNM), - (b"P3", ImageFormat::PNM), - (b"P4", ImageFormat::PNM), - (b"P5", ImageFormat::PNM), - (b"P6", ImageFormat::PNM), - (b"P7", ImageFormat::PNM), +static MAGIC_BYTES: [(&'static [u8], ImageFormat); 18] = [ + (b"\x89PNG\r\n\x1a\n", ImageFormat::Png), + (&[0xff, 0xd8, 0xff], ImageFormat::Jpeg), + (b"GIF89a", ImageFormat::Gif), + (b"GIF87a", ImageFormat::Gif), + (b"RIFF", ImageFormat::WebP), // TODO: better magic byte detection, see https://github.com/image-rs/image/issues/660 + (b"MM\x00*", ImageFormat::Tiff), + (b"II*\x00", ImageFormat::Tiff), + (b"DDS ", ImageFormat::Dds), + (b"BM", ImageFormat::Bmp), + (&[0, 0, 1, 0], ImageFormat::Ico), + (b"#?RADIANCE", ImageFormat::Hdr), + (b"P1", ImageFormat::Pnm), + (b"P2", ImageFormat::Pnm), + (b"P3", ImageFormat::Pnm), + (b"P4", ImageFormat::Pnm), + (b"P5", ImageFormat::Pnm), + (b"P6", ImageFormat::Pnm), + (b"P7", ImageFormat::Pnm), ]; /// Guess image format from memory block @@ -277,9 +264,7 @@ static MAGIC_BYTES: [(&[u8], ImageFormat); 17] = [ pub fn guess_format(buffer: &[u8]) -> ImageResult { match guess_format_impl(buffer) { Some(format) => Ok(format), - None => Err(image::ImageError::UnsupportedError( - "Unsupported image format".to_string(), - )), + None => Err(ImageError::Unsupported(ImageFormatHint::Unknown.into())), } } @@ -295,11 +280,10 @@ pub(crate) fn guess_format_impl(buffer: &[u8]) -> Option { impl From for ImageError { fn from(path: PathError) -> Self { - match path { - PathError::NoExtension => ImageError::UnsupportedError( - "Image format could not be recognized: no extension present".into()), - PathError::UnknownExtension(ext) => ImageError::UnsupportedError(format!( - "Image format image/{} is not recognized.", Path::new(&ext).display())) - } + let format_hint = match path { + PathError::NoExtension => ImageFormatHint::Unknown, + PathError::UnknownExtension(ext) => ImageFormatHint::PathExtension(ext.into()), + }; + ImageError::Unsupported(format_hint.into()) } } diff --git a/src/io/reader.rs b/src/io/reader.rs index 9f0b5e4bfc..4da41f458b 100644 --- a/src/io/reader.rs +++ b/src/io/reader.rs @@ -2,9 +2,9 @@ use std::fs::File; use std::io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom}; use std::path::Path; -use dynimage::DynamicImage; -use image::ImageFormat; -use {ImageError, ImageResult}; +use crate::dynimage::DynamicImage; +use crate::image::ImageFormat; +use crate::{ImageError, ImageResult}; use super::free_functions; @@ -46,7 +46,7 @@ use super::free_functions; /// let mut reader = Reader::new(Cursor::new(raw_data)) /// .with_guessed_format() /// .expect("Cursor io never fails"); -/// assert_eq!(reader.format(), Some(ImageFormat::PNM)); +/// assert_eq!(reader.format(), Some(ImageFormat::Pnm)); /// /// let image = reader.decode()?; /// # Ok(()) } diff --git a/src/jpeg/decoder.rs b/src/jpeg/decoder.rs index 023183f57e..0403a3fbbd 100644 --- a/src/jpeg/decoder.rs +++ b/src/jpeg/decoder.rs @@ -1,32 +1,32 @@ -extern crate jpeg_decoder; - +use std::convert::TryFrom; use std::io::{self, Cursor, Read}; use std::marker::PhantomData; use std::mem; -use color::ColorType; -use image::{ImageDecoder, ImageError, ImageResult}; +use crate::color::ColorType; +use crate::image::ImageDecoder; +use crate::error::{ImageError, ImageResult}; /// JPEG decoder -pub struct JPEGDecoder { - decoder: jpeg_decoder::Decoder, - metadata: jpeg_decoder::ImageInfo, +pub struct JpegDecoder { + decoder: jpeg::Decoder, + metadata: jpeg::ImageInfo, } -impl JPEGDecoder { +impl JpegDecoder { /// Create a new decoder that decodes from the stream ```r``` - pub fn new(r: R) -> ImageResult> { - let mut decoder = jpeg_decoder::Decoder::new(r); + pub fn new(r: R) -> ImageResult> { + let mut decoder = jpeg::Decoder::new(r); - decoder.read_info()?; + decoder.read_info().map_err(ImageError::from_jpeg)?; let mut metadata = decoder.info().unwrap(); // We convert CMYK data to RGB before returning it to the user. - if metadata.pixel_format == jpeg_decoder::PixelFormat::CMYK32 { - metadata.pixel_format = jpeg_decoder::PixelFormat::RGB24; + if metadata.pixel_format == jpeg::PixelFormat::CMYK32 { + metadata.pixel_format = jpeg::PixelFormat::RGB24; } - Ok(JPEGDecoder { + Ok(JpegDecoder { decoder, metadata, }) @@ -49,29 +49,38 @@ impl Read for JpegReader { } } -impl<'a, R: 'a + Read> ImageDecoder<'a> for JPEGDecoder { +impl<'a, R: 'a + Read> ImageDecoder<'a> for JpegDecoder { type Reader = JpegReader; - fn dimensions(&self) -> (u64, u64) { - (u64::from(self.metadata.width), u64::from(self.metadata.height)) + fn dimensions(&self) -> (u32, u32) { + (u32::from(self.metadata.width), u32::from(self.metadata.height)) } - fn colortype(&self) -> ColorType { - self.metadata.pixel_format.into() + fn color_type(&self) -> ColorType { + ColorType::from_jpeg(self.metadata.pixel_format) } - fn into_reader(self) -> ImageResult { - Ok(JpegReader(Cursor::new(self.read_image()?), PhantomData)) + fn into_reader(mut self) -> ImageResult { + let mut data = self.decoder.decode().map_err(ImageError::from_jpeg)?; + data = match self.decoder.info().unwrap().pixel_format { + jpeg::PixelFormat::CMYK32 => cmyk_to_rgb(&data), + _ => data, + }; + + Ok(JpegReader(Cursor::new(data), PhantomData)) } - fn read_image(mut self) -> ImageResult> { - let mut data = self.decoder.decode()?; + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + + let mut data = self.decoder.decode().map_err(ImageError::from_jpeg)?; data = match self.decoder.info().unwrap().pixel_format { - jpeg_decoder::PixelFormat::CMYK32 => cmyk_to_rgb(&data), + jpeg::PixelFormat::CMYK32 => cmyk_to_rgb(&data), _ => data, }; - Ok(data) + buf.copy_from_slice(&data); + Ok(()) } } @@ -103,20 +112,20 @@ fn cmyk_to_rgb(input: &[u8]) -> Vec { output } -impl From for ColorType { - fn from(pixel_format: jpeg_decoder::PixelFormat) -> ColorType { - use self::jpeg_decoder::PixelFormat::*; +impl ColorType { + fn from_jpeg(pixel_format: jpeg::PixelFormat) -> ColorType { + use jpeg::PixelFormat::*; match pixel_format { - L8 => ColorType::Gray(8), - RGB24 => ColorType::RGB(8), + L8 => ColorType::L8, + RGB24 => ColorType::Rgb8, CMYK32 => panic!(), } } } -impl From for ImageError { - fn from(err: jpeg_decoder::Error) -> ImageError { - use self::jpeg_decoder::Error::*; +impl ImageError { + fn from_jpeg(err: jpeg::Error) -> ImageError { + use jpeg::Error::*; match err { Format(desc) => ImageError::FormatError(desc), Unsupported(desc) => ImageError::UnsupportedError(format!("{:?}", desc)), diff --git a/src/jpeg/encoder.rs b/src/jpeg/encoder.rs index cca3d0ec01..6b7ce5c270 100644 --- a/src/jpeg/encoder.rs +++ b/src/jpeg/encoder.rs @@ -1,11 +1,13 @@ #![allow(clippy::too_many_arguments)] use byteorder::{BigEndian, WriteBytesExt}; -use math::utils::clamp; +use crate::error::{ImageError, ImageResult}; +use crate::math::utils::clamp; use num_iter::range_step; use std::io::{self, Write}; -use color; +use crate::color; +use crate::image::ImageEncoder; use super::entropy::build_huff_lut; use super::transform; @@ -273,6 +275,59 @@ impl<'a, W: Write + 'a> BitWriter<'a, W> { } } +/// Represents a unit in which the density of an image is measured +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PixelDensityUnit { + /// Represents the absence of a unit, the values indicate only a + /// [pixel aspect ratio](https://en.wikipedia.org/wiki/Pixel_aspect_ratio) + PixelAspectRatio, + + /// Pixels per inch (2.54 cm) + Inches, + + /// Pixels per centimeter + Centimeters, +} + +/// Represents the pixel density of an image +/// +/// For example, a 300 DPI image is represented by: +/// +/// ```rust +/// use image::jpeg::*; +/// let hdpi = PixelDensity::dpi(300); +/// assert_eq!(hdpi, PixelDensity {density: (300,300), unit: PixelDensityUnit::Inches}) +/// ``` +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PixelDensity { + /// A couple of values for (Xdensity, Ydensity) + pub density: (u16, u16), + /// The unit in which the density is measured + pub unit: PixelDensityUnit, +} + +impl PixelDensity { + /// Creates the most common pixel density type: + /// the horizontal and the vertical density are equal, + /// and measured in pixels per inch. + pub fn dpi(density: u16) -> Self { + PixelDensity { + density: (density, density), + unit: PixelDensityUnit::Inches, + } + } +} + +impl Default for PixelDensity { + /// Returns a pixel density with a pixel aspect ratio of 1 + fn default() -> Self { + PixelDensity { + density: (1, 1), + unit: PixelDensityUnit::PixelAspectRatio, + } + } +} + /// The representation of a JPEG encoder pub struct JPEGEncoder<'a, W: 'a> { writer: BitWriter<'a, W>, @@ -284,6 +339,8 @@ pub struct JPEGEncoder<'a, W: 'a> { luma_actable: Vec<(u8, u16)>, chroma_dctable: Vec<(u8, u16)>, chroma_actable: Vec<(u8, u16)>, + + pixel_density: PixelDensity, } impl<'a, W: Write> JPEGEncoder<'a, W> { @@ -359,9 +416,18 @@ impl<'a, W: Write> JPEGEncoder<'a, W> { luma_actable: la, chroma_dctable: cd, chroma_actable: ca, + + pixel_density: PixelDensity::default(), } } + /// Set the pixel density of the images the encoder will encode. + /// If this method is not called, then a default pixel aspect ratio of 1x1 will be applied, + /// and no DPI information will be stored in the image. + pub fn set_pixel_density(&mut self, pixel_density: PixelDensity) { + self.pixel_density = pixel_density; + } + /// Encodes the image ```image``` /// that has dimensions ```width``` and ```height``` /// and ```ColorType``` ```c``` @@ -373,15 +439,15 @@ impl<'a, W: Write> JPEGEncoder<'a, W> { width: u32, height: u32, c: color::ColorType, - ) -> io::Result<()> { - let n = color::channel_count(c); + ) -> ImageResult<()> { + let n = c.channel_count(); let num_components = if n == 1 || n == 2 { 1 } else { 3 }; self.writer.write_segment(SOI, None)?; let mut buf = Vec::new(); - build_jfif_header(&mut buf); + build_jfif_header(&mut buf, self.pixel_density); self.writer.write_segment(APP0, Some(&buf))?; build_frame_header( @@ -443,26 +509,20 @@ impl<'a, W: Write> JPEGEncoder<'a, W> { self.writer.write_segment(SOS, Some(&buf))?; match c { - color::ColorType::RGB(8) => { + color::ColorType::Rgb8 => { self.encode_rgb(image, width as usize, height as usize, 3)? } - color::ColorType::RGBA(8) => { + color::ColorType::Rgba8 => { self.encode_rgb(image, width as usize, height as usize, 4)? } - color::ColorType::Gray(8) => { + color::ColorType::L8 => { self.encode_gray(image, width as usize, height as usize, 1)? } - color::ColorType::GrayA(8) => { + color::ColorType::La8 => { self.encode_gray(image, width as usize, height as usize, 2)? } _ => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - &format!( - "Unsupported color type {:?}. Use 8 bit per channel RGB(A) or Gray(A) instead.", - c - )[..], - )) + return Err(ImageError::UnsupportedColor(c.into())) } }; @@ -572,16 +632,32 @@ impl<'a, W: Write> JPEGEncoder<'a, W> { } } -fn build_jfif_header(m: &mut Vec) { +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(); let _ = write!(m, "JFIF"); let _ = m.write_all(&[0]); let _ = m.write_all(&[0x01]); let _ = m.write_all(&[0x02]); - let _ = m.write_all(&[0]); - let _ = m.write_u16::(1); - let _ = m.write_u16::(1); + let _ = m.write_all(&[match density.unit { + PixelDensityUnit::PixelAspectRatio => 0x00, + PixelDensityUnit::Inches => 0x01, + PixelDensityUnit::Centimeters => 0x02, + }]); + let _ = m.write_u16::(density.density.0); + let _ = m.write_u16::(density.density.1); let _ = m.write_all(&[0]); let _ = m.write_all(&[0]); } @@ -756,12 +832,21 @@ fn copy_blocks_gray( #[cfg(test)] mod tests { - use super::super::JPEGDecoder; - use super::JPEGEncoder; - use color::ColorType; - use image::ImageDecoder; + use super::super::JpegDecoder; + use super::{JPEGEncoder, PixelDensity, build_jfif_header}; + use crate::color::ColorType; + use crate::image::ImageDecoder; use std::io::Cursor; + fn decode(encoded: &[u8]) -> Vec { + let decoder = JpegDecoder::new(Cursor::new(encoded)) + .expect("Could not decode image"); + + let mut decoded = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut decoded).expect("Could not decode image"); + decoded + } + #[test] fn roundtrip_sanity_check() { // create a 1x1 8-bit image buffer containing a single red pixel @@ -772,15 +857,13 @@ mod tests { { let mut encoder = JPEGEncoder::new_with_quality(&mut encoded_img, 100); encoder - .encode(&img, 1, 1, ColorType::RGB(8)) + .encode(&img, 1, 1, ColorType::Rgb8) .expect("Could not encode image"); } // decode it from the memory buffer { - let decoder = JPEGDecoder::new(Cursor::new(&encoded_img)) - .expect("Could not decode image"); - let decoded = decoder.read_image().expect("Could not decode image"); + let decoded = decode(&encoded_img); // note that, even with the encode quality set to 100, we do not get the same image // back. Therefore, we're going to assert that it's at least red-ish: assert_eq!(3, decoded.len()); @@ -800,15 +883,13 @@ mod tests { { let mut encoder = JPEGEncoder::new_with_quality(&mut encoded_img, 100); encoder - .encode(&img, 2, 2, ColorType::Gray(8)) + .encode(&img, 2, 2, ColorType::L8) .expect("Could not encode image"); } // decode it from the memory buffer { - let decoder = JPEGDecoder::new(Cursor::new(&encoded_img)) - .expect("Could not decode image"); - let decoded = decoder.read_image().expect("Could not decode image"); + let decoded = decode(&encoded_img); // note that, even with the encode quality set to 100, we do not get the same image // back. Therefore, we're going to assert that the diagonal is at least white-ish: assert_eq!(4, decoded.len()); @@ -818,4 +899,19 @@ mod tests { assert!(decoded[3] > 0x80); } } + + #[test] + fn jfif_header_density_check() { + let mut buffer = Vec::new(); + build_jfif_header(&mut buffer, PixelDensity::dpi(300)); + assert_eq!(buffer, vec![ + b'J', b'F', b'I', b'F', + 0, 1, 2, // JFIF version 1.2 + 1, // density is in dpi + 300u16.to_be_bytes()[0], 300u16.to_be_bytes()[1], + 300u16.to_be_bytes()[0], 300u16.to_be_bytes()[1], + 0, 0, // No thumbnail + ] + ); + } } diff --git a/src/jpeg/mod.rs b/src/jpeg/mod.rs index 516a5ec47d..4d9bf7a8d6 100644 --- a/src/jpeg/mod.rs +++ b/src/jpeg/mod.rs @@ -7,8 +7,8 @@ //! * - The JPEG specification //! -pub use self::decoder::JPEGDecoder; -pub use self::encoder::JPEGEncoder; +pub use self::decoder::JpegDecoder; +pub use self::encoder::{JPEGEncoder, PixelDensity, PixelDensityUnit}; mod decoder; mod encoder; diff --git a/src/lib.rs b/src/lib.rs index 2ac34c39ff..4f232227ab 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; @@ -26,49 +27,51 @@ extern crate quickcheck; use std::io::Write; -pub use color::ColorType::{self, Gray, GrayA, Palette, RGB, RGBA, BGR, BGRA}; +pub use crate::color::{ColorType, ExtendedColorType}; -pub use color::{Luma, LumaA, Rgb, Rgba, Bgr, Bgra}; +pub use crate::color::{Luma, LumaA, Rgb, Rgba, Bgr, Bgra}; -pub use image::{AnimationDecoder, +pub use crate::error::{ImageError, ImageResult}; + +pub use crate::image::{AnimationDecoder, GenericImage, GenericImageView, ImageDecoder, ImageDecoderExt, - ImageError, - ImageResult, + ImageEncoder, + ImageFormat, + ImageOutputFormat, + Progress, // Iterators Pixels, SubImage}; -pub use imageops::FilterType::{self, CatmullRom, Gaussian, Lanczos3, Nearest, Triangle}; - -pub use image::ImageFormat::{self, BMP, GIF, ICO, JPEG, PNG, PNM, WEBP}; - -pub use image::ImageOutputFormat; - -pub use buffer::{ConvertBuffer, +pub use crate::buffer::{ConvertBuffer, GrayAlphaImage, GrayImage, // Image types ImageBuffer, Pixel, RgbImage, - RgbaImage}; + RgbaImage, + }; -pub use flat::{FlatSamples}; +pub use crate::flat::FlatSamples; // Traits -pub use traits::Primitive; +pub use crate::traits::Primitive; // Opening and loading images -pub use io::free_functions::{guess_format, load}; -pub use dynimage::{load_from_memory, load_from_memory_with_format, open, +pub use crate::io::free_functions::{guess_format, load}; +pub use crate::dynimage::{load_from_memory, load_from_memory_with_format, open, save_buffer, save_buffer_with_format, image_dimensions}; -pub use dynimage::DynamicImage::{self, ImageLuma8, ImageLumaA8, ImageRgb8, ImageRgba8, ImageBgr8, ImageBgra8}; +pub use crate::dynimage::DynamicImage; + +pub use crate::animation::{Delay, Frame, Frames}; -pub use animation::{Frame, Frames}; +// More detailed error type +pub mod error; // Math utils pub mod math; @@ -85,9 +88,11 @@ pub mod flat; // Image codecs #[cfg(feature = "bmp")] pub mod bmp; +#[cfg(feature = "dds")] +pub mod dds; #[cfg(feature = "dxt")] pub mod dxt; -#[cfg(feature = "gif_codec")] +#[cfg(feature = "gif")] pub mod gif; #[cfg(feature = "hdr")] pub mod hdr; @@ -95,7 +100,7 @@ pub mod hdr; pub mod ico; #[cfg(feature = "jpeg")] pub mod jpeg; -#[cfg(feature = "png_codec")] +#[cfg(feature = "png")] pub mod png; #[cfg(feature = "pnm")] pub mod pnm; diff --git a/src/math/nq.rs b/src/math/nq.rs index fdb49f706a..a6a502dffc 100644 --- a/src/math/nq.rs +++ b/src/math/nq.rs @@ -30,7 +30,7 @@ * */ -use math::utils::clamp; +use crate::math::utils::clamp; use std::cmp::{max, min}; const CHANNELS: usize = 4; diff --git a/src/png.rs b/src/png.rs index 653ccfa99e..d4b083e094 100644 --- a/src/png.rs +++ b/src/png.rs @@ -6,12 +6,12 @@ //! * - The PNG Specification //! -extern crate png; - +use std::convert::TryFrom; use std::io::{self, Read, Write}; -use color::ColorType; -use image::{ImageDecoder, ImageError, ImageResult}; +use crate::color::{ColorType, ExtendedColorType}; +use crate::error::{DecodingError, ImageError, ImageResult, ParameterError, ParameterErrorKind}; +use crate::image::{ImageDecoder, ImageEncoder, ImageFormat}; /// PNG Reader /// @@ -34,7 +34,7 @@ impl PNGReader { // as most interlaced images should fit in memory. let buffer = if reader.info().interlaced { let mut buffer = vec![0; len]; - reader.next_frame(&mut buffer)?; + reader.next_frame(&mut buffer).map_err(ImageError::from_png)?; buffer } else { Vec::new() @@ -89,46 +89,103 @@ impl Read for PNGReader { } /// PNG decoder -pub struct PNGDecoder { - colortype: ColorType, +pub struct PngDecoder { + color_type: ColorType, reader: png::Reader, } -impl PNGDecoder { +impl PngDecoder { /// Creates a new decoder that decodes from the stream ```r``` - pub fn new(r: R) -> ImageResult> { + pub fn new(r: R) -> ImageResult> { let limits = png::Limits { bytes: usize::max_value(), }; - let decoder = png::Decoder::new_with_limits(r, limits); - let (_, mut reader) = decoder.read_info()?; - let colortype = reader.output_color_type().into(); + 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) { + (png::ColorType::Grayscale, png::BitDepth::Eight) => ColorType::L8, + (png::ColorType::Grayscale, png::BitDepth::Sixteen) => ColorType::L16, + (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight) => ColorType::La8, + (png::ColorType::GrayscaleAlpha, png::BitDepth::Sixteen) => ColorType::La16, + (png::ColorType::RGB, png::BitDepth::Eight) => ColorType::Rgb8, + (png::ColorType::RGB, png::BitDepth::Sixteen) => ColorType::Rgb16, + (png::ColorType::RGBA, png::BitDepth::Eight) => ColorType::Rgba8, + (png::ColorType::RGBA, png::BitDepth::Sixteen) => ColorType::Rgba16, + + (png::ColorType::Grayscale, png::BitDepth::One) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::L1)), + (png::ColorType::GrayscaleAlpha, png::BitDepth::One) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::La1)), + (png::ColorType::RGB, png::BitDepth::One) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgb1)), + (png::ColorType::RGBA, png::BitDepth::One) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba1)), + + (png::ColorType::Grayscale, png::BitDepth::Two) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::L2)), + (png::ColorType::GrayscaleAlpha, png::BitDepth::Two) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::La2)), + (png::ColorType::RGB, png::BitDepth::Two) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgb2)), + (png::ColorType::RGBA, png::BitDepth::Two) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba2)), + + (png::ColorType::Grayscale, png::BitDepth::Four) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::L4)), + (png::ColorType::GrayscaleAlpha, png::BitDepth::Four) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::La4)), + (png::ColorType::RGB, png::BitDepth::Four) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgb4)), + (png::ColorType::RGBA, png::BitDepth::Four) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba4)), + + (png::ColorType::Indexed, bits) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Unknown(bits as u8))), + }; - Ok(PNGDecoder { colortype, reader }) + Ok(PngDecoder { color_type, reader }) } } -impl<'a, R: 'a + Read> ImageDecoder<'a> for PNGDecoder { +impl<'a, R: 'a + Read> ImageDecoder<'a> for PngDecoder { type Reader = PNGReader; - fn dimensions(&self) -> (u64, u64) { - let (w, h) = self.reader.info().size(); - (u64::from(w), u64::from(h)) + fn dimensions(&self) -> (u32, u32) { + self.reader.info().size() } - fn colortype(&self) -> ColorType { - self.colortype + fn color_type(&self) -> ColorType { + self.color_type } fn into_reader(self) -> ImageResult { PNGReader::new(self.reader) } - fn read_image(mut self) -> ImageResult> { - // This should be slightly faster than the default implementation - let mut data = vec![0; self.reader.output_buffer_size()]; - self.reader.next_frame(&mut data)?; - Ok(data) + 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(()) } fn scanline_bytes(&self) -> u64 { @@ -151,91 +208,102 @@ impl PNGEncoder { /// Encodes the image ```image``` /// that has dimensions ```width``` and ```height``` /// and ```ColorType``` ```c``` - pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> io::Result<()> { + pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> { let (ct, bits) = match color { - ColorType::Gray(bits) => Some((png::ColorType::Grayscale, bits)), - ColorType::RGB(bits) => Some((png::ColorType::RGB, bits)), - ColorType::Palette(bits) => Some((png::ColorType::Indexed, bits)), - ColorType::GrayA(bits) => Some((png::ColorType::GrayscaleAlpha, bits)), - ColorType::RGBA(bits) => Some((png::ColorType::RGBA, bits)), - _ => None, - } - .and_then(|(ct, bits)| Some((ct, png::BitDepth::from_u8(bits)?))) - // FIXME: After #1066 lands, return the ImageError directly. - .ok_or(io::Error::new(io::ErrorKind::InvalidInput, ImageError::UnsupportedColor(color)))?; + ColorType::L8 => (png::ColorType::Grayscale, png::BitDepth::Eight), + ColorType::L16 => (png::ColorType::Grayscale,png::BitDepth::Sixteen), + ColorType::La8 => (png::ColorType::GrayscaleAlpha, png::BitDepth::Eight), + ColorType::La16 => (png::ColorType::GrayscaleAlpha,png::BitDepth::Sixteen), + ColorType::Rgb8 => (png::ColorType::RGB, png::BitDepth::Eight), + ColorType::Rgb16 => (png::ColorType::RGB,png::BitDepth::Sixteen), + ColorType::Rgba8 => (png::ColorType::RGBA, png::BitDepth::Eight), + ColorType::Rgba16 => (png::ColorType::RGBA,png::BitDepth::Sixteen), + _ => return Err(ImageError::UnsupportedColor(color.into())), + }; let mut encoder = png::Encoder::new(self.w, width, height); encoder.set_color(ct); encoder.set_depth(bits); - let mut writer = encoder.write_header()?; - writer.write_image_data(data).map_err(|e| e.into()) + let mut writer = encoder.write_header().map_err(|e| ImageError::IoError(e.into()))?; + writer.write_image_data(data).map_err(|e| ImageError::IoError(e.into())) } } -impl From<(png::ColorType, png::BitDepth)> for ColorType { - fn from((ct, bits): (png::ColorType, png::BitDepth)) -> ColorType { - use self::png::ColorType::*; - let bits = bits as u8; - match ct { - Grayscale => ColorType::Gray(bits), - RGB => ColorType::RGB(bits), - Indexed => ColorType::Palette(bits), - GrayscaleAlpha => ColorType::GrayA(bits), - RGBA => ColorType::RGBA(bits), +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 From for (png::ColorType, png::BitDepth) { - fn from(ct: ColorType) -> (png::ColorType, png::BitDepth) { - use self::png::ColorType::*; - let (ct, bits) = match ct { - ColorType::Gray(bits) => (Grayscale, bits), - ColorType::RGB(bits) => (RGB, bits), - ColorType::Palette(bits) => (Indexed, bits), - ColorType::GrayA(bits) => (GrayscaleAlpha, bits), - ColorType::RGBA(bits) => (RGBA, bits), - ColorType::BGRA(bits) => (RGBA, bits), - ColorType::BGR(bits) => (RGB, bits), - }; - (ct, png::BitDepth::from_u8(bits).unwrap()) - } -} - -impl From for ImageError { - fn from(err: png::DecodingError) -> ImageError { - use self::png::DecodingError::*; +impl ImageError { + fn from_png(err: png::DecodingError) -> ImageError { + use png::DecodingError::*; match err { IoError(err) => ImageError::IoError(err), - Format(desc) => ImageError::FormatError(desc.into_owned()), - InvalidSignature => ImageError::FormatError("invalid signature".into()), - CrcMismatch { .. } => ImageError::FormatError("CRC error".into()), - Other(desc) => ImageError::FormatError(desc.into_owned()), - CorruptFlateStream => { - ImageError::FormatError("compressed data stream corrupted".into()) - } + Format(message) => ImageError::Decoding(DecodingError::with_message( + ImageFormat::Png.into(), + message.into_owned(), + )), LimitsExceeded => ImageError::InsufficientMemory, + // Other is used when the buffer to `Reader::next_frame` is too small. + Other(message) => ImageError::Parameter(ParameterError::from_kind( + ParameterErrorKind::Generic(message.into_owned()) + )), + err @ InvalidSignature + | err @ CrcMismatch { .. } + | err @ CorruptFlateStream => { + ImageError::Decoding(DecodingError::new( + ImageFormat::Png.into(), + err, + )) + } } } } #[cfg(test)] mod tests { - use image::ImageDecoder; + use crate::image::ImageDecoder; use std::io::Read; use super::*; #[test] fn ensure_no_decoder_off_by_one() { - let dec = PNGDecoder::new(std::fs::File::open("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png").unwrap()) + let dec = PngDecoder::new(std::fs::File::open("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png").unwrap()) .expect("Unable to read PNG file (does it exist?)"); assert_eq![(2000, 1000), dec.dimensions()]; assert_eq![ - ColorType::RGB(8), - dec.colortype(), - "Image MUST have the RGB(8) format" + ColorType::Rgb8, + dec.color_type(), + "Image MUST have the Rgb8 format" ]; let correct_bytes = dec @@ -247,4 +315,19 @@ mod tests { assert_eq![6_000_000, correct_bytes.len()]; } + + #[test] + fn underlying_error() { + use std::error::Error; + + let mut not_png = std::fs::read("tests/images/png/bugfixes/debug_triangle_corners_widescreen.png").unwrap(); + not_png[0] = 0; + + let error = PngDecoder::new(¬_png[..]).err().unwrap(); + let _ = error + .source() + .unwrap() + .downcast_ref::() + .expect("Caused by a png error"); + } } diff --git a/src/pnm/decoder.rs b/src/pnm/decoder.rs index 85a497fb14..85c8e43787 100644 --- a/src/pnm/decoder.rs +++ b/src/pnm/decoder.rs @@ -1,3 +1,4 @@ +use std::convert::TryFrom; use std::io::{self, BufRead, BufReader, Cursor, Read}; use std::str::{self, FromStr}; use std::fmt::Display; @@ -6,8 +7,10 @@ use std::mem; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PNMHeader, PNMSubtype, SampleEncoding}; -use color::ColorType; -use image::{ImageDecoder, ImageError, ImageResult}; +use crate::color::{ColorType, ExtendedColorType}; +use crate::error::{ImageError, ImageResult}; +use crate::image::{self, ImageDecoder}; +use crate::utils; use byteorder::{BigEndian, ByteOrder, NativeEndian}; @@ -43,15 +46,15 @@ trait DecodableImageHeader { } /// PNM decoder -pub struct PNMDecoder { +pub struct PnmDecoder { reader: BufReader, header: PNMHeader, tuple: TupleType, } -impl PNMDecoder { +impl PnmDecoder { /// Create a new decoder that decodes from the stream ```read``` - pub fn new(read: R) -> ImageResult> { + pub fn new(read: R) -> ImageResult> { let mut buf = BufReader::new(read); let magic = buf.read_magic_constant()?; if magic[0] != b'P' { @@ -76,10 +79,10 @@ impl PNMDecoder { }; match subtype { - PNMSubtype::Bitmap(enc) => PNMDecoder::read_bitmap_header(buf, enc), - PNMSubtype::Graymap(enc) => PNMDecoder::read_graymap_header(buf, enc), - PNMSubtype::Pixmap(enc) => PNMDecoder::read_pixmap_header(buf, enc), - PNMSubtype::ArbitraryMap => PNMDecoder::read_arbitrary_header(buf), + PNMSubtype::Bitmap(enc) => PnmDecoder::read_bitmap_header(buf, enc), + PNMSubtype::Graymap(enc) => PnmDecoder::read_graymap_header(buf, enc), + PNMSubtype::Pixmap(enc) => PnmDecoder::read_pixmap_header(buf, enc), + PNMSubtype::ArbitraryMap => PnmDecoder::read_arbitrary_header(buf), } } @@ -91,9 +94,9 @@ impl PNMDecoder { fn read_bitmap_header( mut reader: BufReader, encoding: SampleEncoding, - ) -> ImageResult> { + ) -> ImageResult> { let header = reader.read_bitmap_header(encoding)?; - Ok(PNMDecoder { + Ok(PnmDecoder { reader, tuple: TupleType::PbmBit, header: PNMHeader { @@ -106,10 +109,10 @@ impl PNMDecoder { fn read_graymap_header( mut reader: BufReader, encoding: SampleEncoding, - ) -> ImageResult> { + ) -> ImageResult> { let header = reader.read_graymap_header(encoding)?; let tuple_type = header.tuple_type()?; - Ok(PNMDecoder { + Ok(PnmDecoder { reader, tuple: tuple_type, header: PNMHeader { @@ -122,10 +125,10 @@ impl PNMDecoder { fn read_pixmap_header( mut reader: BufReader, encoding: SampleEncoding, - ) -> ImageResult> { + ) -> ImageResult> { let header = reader.read_pixmap_header(encoding)?; let tuple_type = header.tuple_type()?; - Ok(PNMDecoder { + Ok(PnmDecoder { reader, tuple: tuple_type, header: PNMHeader { @@ -135,10 +138,10 @@ impl PNMDecoder { }) } - fn read_arbitrary_header(mut reader: BufReader) -> ImageResult> { + fn read_arbitrary_header(mut reader: BufReader) -> ImageResult> { let header = reader.read_arbitrary_header()?; let tuple_type = header.tuple_type()?; - Ok(PNMDecoder { + Ok(PnmDecoder { reader, tuple: tuple_type, header: PNMHeader { @@ -424,48 +427,79 @@ impl Read for PnmReader { } } -impl<'a, R: 'a + Read> ImageDecoder<'a> for PNMDecoder { +impl<'a, R: 'a + Read> ImageDecoder<'a> for PnmDecoder { type Reader = PnmReader; - fn dimensions(&self) -> (u64, u64) { - (u64::from(self.header.width()), u64::from(self.header.height())) + fn dimensions(&self) -> (u32, u32) { + (self.header.width(), self.header.height()) } - fn colortype(&self) -> ColorType { - self.tuple.color() + fn color_type(&self) -> ColorType { + match self.tuple { + TupleType::PbmBit => ColorType::L8, + TupleType::BWBit => ColorType::L8, + TupleType::GrayU8 => ColorType::L8, + TupleType::GrayU16 => ColorType::L16, + TupleType::RGBU8 => ColorType::Rgb8, + TupleType::RGBU16 => ColorType::Rgb16, + } } - fn into_reader(self) -> ImageResult { - Ok(PnmReader(Cursor::new(self.read_image()?), PhantomData)) + fn original_color_type(&self) -> ExtendedColorType { + match self.tuple { + TupleType::PbmBit => ExtendedColorType::L1, + TupleType::BWBit => ExtendedColorType::L1, + TupleType::GrayU8 => ExtendedColorType::L8, + TupleType::GrayU16 => ExtendedColorType::L16, + TupleType::RGBU8 => ExtendedColorType::Rgb8, + TupleType::RGBU16 => ExtendedColorType::Rgb16, + } } - fn read_image(mut self) -> ImageResult> { - self.read() + fn into_reader(self) -> ImageResult { + Ok(PnmReader(Cursor::new(image::decoder_to_vec(self)?), PhantomData)) } -} -impl PNMDecoder { - fn read(&mut self) -> ImageResult> { - match self.tuple { + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + buf.copy_from_slice(&match self.tuple { TupleType::PbmBit => self.read_samples::(1), TupleType::BWBit => self.read_samples::(1), TupleType::RGBU8 => self.read_samples::(3), TupleType::RGBU16 => self.read_samples::(3), TupleType::GrayU8 => self.read_samples::(1), TupleType::GrayU16 => self.read_samples::(1), - } + }?); + Ok(()) } +} +fn err_input_is_too_short() -> ImageError { + return ImageError::FormatError( + "Not enough data was provided to the Decoder to decode the image".into() + ) +} + +impl PnmDecoder { fn read_samples(&mut self, components: u32) -> ImageResult> { match self.subtype().sample_encoding() { SampleEncoding::Binary => { let width = self.header.width(); let height = self.header.height(); let bytecount = S::bytelen(width, height, components)?; - let mut bytes = vec![0 as u8; bytecount]; - (&mut self.reader) - .read_exact(&mut bytes) - .map_err(|_| ImageError::NotEnoughData)?; + let mut bytes = vec![]; + + self.reader + .by_ref() + // This conversion is potentially lossy but unlikely and in that case we error + // later anyways. + .take(bytecount as u64) + .read_to_end(&mut bytes)?; + + if bytes.len() != bytecount { + return Err(err_input_is_too_short()); + } + let samples = S::from_bytes(&bytes, width, height, components)?; Ok(samples) } @@ -486,20 +520,6 @@ impl PNMDecoder { } } -impl TupleType { - fn color(self) -> ColorType { - use self::TupleType::*; - match self { - PbmBit => ColorType::Gray(1), - BWBit => ColorType::Gray(1), - GrayU8 => ColorType::Gray(8), - GrayU16 => ColorType::Gray(16), - RGBU8 => ColorType::RGB(8), - RGBU16 => ColorType::GrayA(16), - } - } -} - fn read_separated_ascii(reader: &mut dyn Read) -> ImageResult where T::Err: Display { @@ -536,14 +556,12 @@ impl Sample for U8 { fn from_bytes( bytes: &[u8], - _width: u32, - _height: u32, - _samples: u32, + width: u32, + height: u32, + samples: u32, ) -> ImageResult> { - let mut buffer = Vec::new(); - buffer.resize(bytes.len(), 0 as u8); - buffer.copy_from_slice(bytes); - Ok(buffer) + assert_eq!(bytes.len(), Self::bytelen(width, height, samples).unwrap()); + Ok(bytes.to_vec()) } fn from_ascii( @@ -569,7 +587,7 @@ impl Sample for U16 { height: u32, samples: u32, ) -> ImageResult> { - assert_eq!(bytes.len(), (width*height*samples*2) as usize); + assert_eq!(bytes.len(), Self::bytelen(width, height, samples).unwrap()); let mut buffer = bytes.to_vec(); for chunk in buffer.chunks_mut(2) { @@ -606,11 +624,17 @@ impl Sample for PbmBit { fn from_bytes( bytes: &[u8], - _width: u32, - _height: u32, - _samples: u32, + width: u32, + height: u32, + samples: u32, ) -> ImageResult> { - Ok(bytes.iter().map(|pixel| !pixel).collect()) + assert_eq!(bytes.len(), Self::bytelen(width, height, samples).unwrap()); + + let mut expanded = utils::expand_bits(1, width * samples, bytes); + for b in expanded.iter_mut() { + *b = !*b; + } + Ok(expanded) } fn from_ascii( @@ -639,7 +663,7 @@ impl Sample for PbmBit { .collect::>>()?; if raw_samples.len() < count { - return Err(ImageError::NotEnoughData) + return Err(err_input_is_too_short()) } Ok(raw_samples) @@ -658,6 +682,8 @@ impl Sample for BWBit { height: u32, samples: u32, ) -> ImageResult> { + assert_eq!(bytes.len(), Self::bytelen(width, height, samples).unwrap()); + let values = U8::from_bytes(bytes, width, height, samples)?; if let Some(val) = values.iter().find(|&val| *val > 1) { return Err(ImageError::FormatError( @@ -711,9 +737,9 @@ impl DecodableImageHeader for ArbitraryHeader { fn tuple_type(&self) -> ImageResult { match self.tupltype { None if self.depth == 1 => Ok(TupleType::GrayU8), - None if self.depth == 2 => Err(ImageError::UnsupportedColor(ColorType::GrayA(8))), + None if self.depth == 2 => Err(ImageError::UnsupportedColor(ExtendedColorType::La8)), None if self.depth == 3 => Ok(TupleType::RGBU8), - None if self.depth == 4 => Err(ImageError::UnsupportedColor(ColorType::RGBA(8))), + None if self.depth == 4 => Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba8)), Some(ArbitraryTuplType::BlackAndWhite) if self.maxval == 1 && self.depth == 1 => { Ok(TupleType::BWBit) @@ -742,14 +768,14 @@ impl DecodableImageHeader for ArbitraryHeader { "Invalid depth for tuple type RGB".to_string(), )), - Some(ArbitraryTuplType::BlackAndWhiteAlpha) => { - Err(ImageError::UnsupportedColor(ColorType::GrayA(1))) - } + Some(ArbitraryTuplType::BlackAndWhiteAlpha) => Err(ImageError::FormatError( + "Unsupported color type: BlackAndWhiteAlpha".to_string() + )), Some(ArbitraryTuplType::GrayscaleAlpha) => { - Err(ImageError::UnsupportedColor(ColorType::GrayA(8))) + Err(ImageError::UnsupportedColor(ExtendedColorType::La8)) } Some(ArbitraryTuplType::RGBAlpha) => { - Err(ImageError::UnsupportedColor(ColorType::RGBA(8))) + Err(ImageError::UnsupportedColor(ExtendedColorType::Rgba8)) } _ => Err(ImageError::FormatError( "Tuple type not recognized".to_string(), @@ -772,17 +798,20 @@ TUPLTYPE BLACKANDWHITE # Comment line ENDHDR \x01\x00\x00\x01\x01\x00\x00\x01\x01\x00\x00\x01\x01\x00\x00\x01"; - let decoder = PNMDecoder::new(&pamdata[..]).unwrap(); - assert_eq!(decoder.colortype(), ColorType::Gray(1)); + let decoder = PnmDecoder::new(&pamdata[..]).unwrap(); + assert_eq!(decoder.color_type(), ColorType::L8); + assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!(decoder.subtype(), PNMSubtype::ArbitraryMap); + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).unwrap(); assert_eq!( - decoder.read_image().unwrap(), + image, vec![0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01] ); - match PNMDecoder::new(&pamdata[..]).unwrap().into_inner() { + match PnmDecoder::new(&pamdata[..]).unwrap().into_inner() { ( _, PNMHeader { @@ -813,16 +842,19 @@ TUPLTYPE GRAYSCALE # Comment line ENDHDR \xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"; - let decoder = PNMDecoder::new(&pamdata[..]).unwrap(); - assert_eq!(decoder.colortype(), ColorType::Gray(8)); + let decoder = PnmDecoder::new(&pamdata[..]).unwrap(); + assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!(decoder.subtype(), PNMSubtype::ArbitraryMap); + + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).unwrap(); assert_eq!( - decoder.read_image().unwrap(), + image, vec![0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef] ); - match PNMDecoder::new(&pamdata[..]).unwrap().into_inner() { + match PnmDecoder::new(&pamdata[..]).unwrap().into_inner() { ( _, PNMHeader { @@ -853,14 +885,16 @@ WIDTH 2 HEIGHT 2 ENDHDR \xde\xad\xbe\xef\xde\xad\xbe\xef\xde\xad\xbe\xef"; - let decoder = PNMDecoder::new(&pamdata[..]).unwrap(); - assert_eq!(decoder.colortype(), ColorType::RGB(8)); + let decoder = PnmDecoder::new(&pamdata[..]).unwrap(); + assert_eq!(decoder.color_type(), ColorType::Rgb8); assert_eq!(decoder.dimensions(), (2, 2)); assert_eq!(decoder.subtype(), PNMSubtype::ArbitraryMap); - assert_eq!(decoder.read_image().unwrap(), + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).unwrap(); + assert_eq!(image, vec![0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef]); - match PNMDecoder::new(&pamdata[..]).unwrap().into_inner() { + match PnmDecoder::new(&pamdata[..]).unwrap().into_inner() { ( _, PNMHeader { @@ -884,15 +918,18 @@ ENDHDR // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. let pbmbinary = [&b"P4 6 2\n"[..], &[0b01101100 as u8, 0b10110111]].concat(); - let decoder = PNMDecoder::new(&pbmbinary[..]).unwrap(); - assert_eq!(decoder.colortype(), ColorType::Gray(1)); + let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); + assert_eq!(decoder.color_type(), ColorType::L8); + assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (6, 2)); assert_eq!( decoder.subtype(), PNMSubtype::Bitmap(SampleEncoding::Binary) ); - assert_eq!(decoder.read_image().unwrap(), vec![0b10010011, 0b01001000]); - match PNMDecoder::new(&pbmbinary[..]).unwrap().into_inner() { + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).unwrap(); + assert_eq!(image, vec![255, 0, 0, 255, 0, 0, 0, 255, 0, 0, 255, 0]); + match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PNMHeader { @@ -929,8 +966,9 @@ ENDHDR let pbmbinary = FailRead(Cursor::new(b"P1 1 1\n")); - PNMDecoder::new(pbmbinary).unwrap() - .read_image().expect_err("Image is malformed"); + let decoder = PnmDecoder::new(pbmbinary).unwrap(); + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).expect_err("Image is malformed"); } #[test] @@ -939,12 +977,16 @@ ENDHDR // comments on its format, see documentation of `impl SampleType for PbmBit`. Tests all // whitespace characters that should be allowed (the 6 characters according to POSIX). let pbmbinary = b"P1 6 2\n 0 1 1 0 1 1\n1 0 1 1 0\t\n\x0b\x0c\r1"; - let decoder = PNMDecoder::new(&pbmbinary[..]).unwrap(); - assert_eq!(decoder.colortype(), ColorType::Gray(1)); + let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); + assert_eq!(decoder.color_type(), ColorType::L8); + assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (6, 2)); assert_eq!(decoder.subtype(), PNMSubtype::Bitmap(SampleEncoding::Ascii)); - assert_eq!(decoder.read_image().unwrap(), vec![1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0]); - match PNMDecoder::new(&pbmbinary[..]).unwrap().into_inner() { + + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).unwrap(); + assert_eq!(image, vec![1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0]); + match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PNMHeader { @@ -967,12 +1009,16 @@ ENDHDR // it is completely within specification for the ascii data not to contain separating // whitespace for the pbm format or any mix. let pbmbinary = b"P1 6 2\n011011101101"; - let decoder = PNMDecoder::new(&pbmbinary[..]).unwrap(); - assert_eq!(decoder.colortype(), ColorType::Gray(1)); + let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); + assert_eq!(decoder.color_type(), ColorType::L8); + assert_eq!(decoder.original_color_type(), ExtendedColorType::L1); assert_eq!(decoder.dimensions(), (6, 2)); assert_eq!(decoder.subtype(), PNMSubtype::Bitmap(SampleEncoding::Ascii)); - assert_eq!(decoder.read_image().unwrap(), vec![1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0]); - match PNMDecoder::new(&pbmbinary[..]).unwrap().into_inner() { + + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).unwrap(); + assert_eq!(image, vec![1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0]); + match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PNMHeader { @@ -995,15 +1041,17 @@ ENDHDR // comments on its format, see documentation of `impl SampleType for PbmBit`. let elements = (0..16).collect::>(); let pbmbinary = [&b"P5 4 4 255\n"[..], &elements].concat(); - let decoder = PNMDecoder::new(&pbmbinary[..]).unwrap(); - assert_eq!(decoder.colortype(), ColorType::Gray(8)); + let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); + assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!( decoder.subtype(), PNMSubtype::Graymap(SampleEncoding::Binary) ); - assert_eq!(decoder.read_image().unwrap(), elements); - match PNMDecoder::new(&pbmbinary[..]).unwrap().into_inner() { + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).unwrap(); + assert_eq!(image, elements); + match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PNMHeader { @@ -1026,15 +1074,17 @@ ENDHDR // The data contains two rows of the image (each line is padded to the full byte). For // comments on its format, see documentation of `impl SampleType for PbmBit`. let pbmbinary = b"P2 4 4 255\n 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"; - let decoder = PNMDecoder::new(&pbmbinary[..]).unwrap(); - assert_eq!(decoder.colortype(), ColorType::Gray(8)); + let decoder = PnmDecoder::new(&pbmbinary[..]).unwrap(); + assert_eq!(decoder.color_type(), ColorType::L8); assert_eq!(decoder.dimensions(), (4, 4)); assert_eq!( decoder.subtype(), PNMSubtype::Graymap(SampleEncoding::Ascii) ); - assert_eq!(decoder.read_image().unwrap(), (0..16).collect::>()); - match PNMDecoder::new(&pbmbinary[..]).unwrap().into_inner() { + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).unwrap(); + assert_eq!(image, (0..16).collect::>()); + match PnmDecoder::new(&pbmbinary[..]).unwrap().into_inner() { ( _, PNMHeader { diff --git a/src/pnm/encoder.rs b/src/pnm/encoder.rs index 9fceeb22bb..5e9d2ed769 100644 --- a/src/pnm/encoder.rs +++ b/src/pnm/encoder.rs @@ -7,7 +7,9 @@ use std::io::Write; use super::AutoBreak; use super::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, PixmapHeader}; use super::{HeaderRecord, PNMHeader, PNMSubtype, SampleEncoding}; -use color::{channel_count, ColorType}; +use crate::color::{ColorType, ExtendedColorType}; +use crate::error::{ImageError, ImageResult}; +use crate::image::ImageEncoder; use byteorder::{BigEndian, WriteBytesExt}; @@ -35,7 +37,7 @@ struct CheckedImageBuffer<'a> { _image: FlatSamples<'a>, _width: u32, _height: u32, - _color: ColorType, + _color: ExtendedColorType, } // Check the header against the buffer. Each struct produces the next after a check. @@ -51,7 +53,7 @@ struct CheckedDimensions<'a> { struct CheckedHeaderColor<'a> { dimensions: CheckedDimensions<'a>, - color: ColorType, + color: ExtendedColorType, } struct CheckedHeader<'a> { @@ -140,18 +142,18 @@ impl PNMEncoder { width: u32, height: u32, color: ColorType, - ) -> io::Result<()> + ) -> ImageResult<()> where S: Into>, { let image = image.into(); match self.header { - HeaderStrategy::Dynamic => self.write_dynamic_header(image, width, height, color), + HeaderStrategy::Dynamic => self.write_dynamic_header(image, width, height, color.into()), HeaderStrategy::Subtype(subtype) => { - self.write_subtyped_header(subtype, image, width, height, color) + self.write_subtyped_header(subtype, image, width, height, color.into()) } HeaderStrategy::Chosen(ref header) => { - Self::write_with_header(&mut self.writer, header, image, width, height, color) + Self::write_with_header(&mut self.writer, header, image, width, height, color.into()) } } } @@ -164,21 +166,22 @@ impl PNMEncoder { image: FlatSamples, width: u32, height: u32, - color: ColorType, - ) -> io::Result<()> { - let depth = channel_count(color).into(); + color: ExtendedColorType, + ) -> ImageResult<()> { + let depth = u32::from(color.channel_count()); let (maxval, tupltype) = match color { - ColorType::Gray(1) => (1, ArbitraryTuplType::BlackAndWhite), - ColorType::GrayA(1) => (1, ArbitraryTuplType::BlackAndWhiteAlpha), - ColorType::Gray(n @ 2..=16) => ((1 << n) - 1, ArbitraryTuplType::Grayscale), - ColorType::GrayA(n @ 2..=16) => ((1 << n) - 1, ArbitraryTuplType::GrayscaleAlpha), - ColorType::RGB(n @ 1..=16) => ((1 << n) - 1, ArbitraryTuplType::RGB), - ColorType::RGBA(n @ 1..=16) => ((1 << n) - 1, ArbitraryTuplType::RGBAlpha), + ExtendedColorType::L1 => (1, ArbitraryTuplType::BlackAndWhite), + ExtendedColorType::L8 => (0xff, ArbitraryTuplType::Grayscale), + ExtendedColorType::L16 => (0xffff, ArbitraryTuplType::Grayscale), + ExtendedColorType::La1 => (1, ArbitraryTuplType::BlackAndWhiteAlpha), + ExtendedColorType::La8 => (0xff, ArbitraryTuplType::GrayscaleAlpha), + ExtendedColorType::La16 => (0xffff, ArbitraryTuplType::GrayscaleAlpha), + ExtendedColorType::Rgb8 => (0xff, ArbitraryTuplType::RGB), + ExtendedColorType::Rgb16 => (0xffff, ArbitraryTuplType::RGB), + ExtendedColorType::Rgba8 => (0xff, ArbitraryTuplType::RGBAlpha), + ExtendedColorType::Rgba16 => (0xffff, ArbitraryTuplType::RGBAlpha), _ => { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - &format!("Encoding colour type {:?} is not supported", color)[..], - )) + return Err(ImageError::UnsupportedColor(color)) } }; @@ -203,13 +206,13 @@ impl PNMEncoder { image: FlatSamples, width: u32, height: u32, - color: ColorType, - ) -> io::Result<()> { + color: ExtendedColorType, + ) -> ImageResult<()> { let header = match (subtype, color) { (PNMSubtype::ArbitraryMap, color) => { return self.write_dynamic_header(image, width, height, color) } - (PNMSubtype::Pixmap(encoding), ColorType::RGB(8)) => PNMHeader { + (PNMSubtype::Pixmap(encoding), ExtendedColorType::Rgb8) => PNMHeader { decoded: HeaderRecord::Pixmap(PixmapHeader { encoding, width, @@ -218,7 +221,7 @@ impl PNMEncoder { }), encoded: None, }, - (PNMSubtype::Graymap(encoding), ColorType::Gray(8)) => PNMHeader { + (PNMSubtype::Graymap(encoding), ExtendedColorType::L8) => PNMHeader { decoded: HeaderRecord::Graymap(GraymapHeader { encoding, width, @@ -227,8 +230,8 @@ impl PNMEncoder { }), encoded: None, }, - (PNMSubtype::Bitmap(encoding), ColorType::Gray(8)) - | (PNMSubtype::Bitmap(encoding), ColorType::Gray(1)) => PNMHeader { + (PNMSubtype::Bitmap(encoding), ExtendedColorType::L8) + | (PNMSubtype::Bitmap(encoding), ExtendedColorType::L1) => PNMHeader { decoded: HeaderRecord::Bitmap(BitmapHeader { encoding, width, @@ -237,10 +240,11 @@ impl PNMEncoder { encoded: None, }, (_, _) => { - return Err(io::Error::new( + // FIXME https://github.com/image-rs/image/issues/921 + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, "Color type can not be represented in the chosen format", - )) + ))) } }; @@ -256,8 +260,8 @@ impl PNMEncoder { image: FlatSamples, width: u32, height: u32, - color: ColorType, - ) -> io::Result<()> { + color: ExtendedColorType, + ) -> ImageResult<()> { let unchecked = UncheckedHeader { header }; unchecked @@ -269,48 +273,58 @@ 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>, width: u32, height: u32, - color: ColorType, - ) -> io::Result> { - let components = channel_count(color) as usize; + color: ExtendedColorType, + ) -> ImageResult> { + let components = color.channel_count() as usize; let uwidth = width as usize; let uheight = height as usize; match Some(components) .and_then(|v| v.checked_mul(uwidth)) .and_then(|v| v.checked_mul(uheight)) { - None => Err(io::Error::new( - io::ErrorKind::InvalidInput, - &format!( - "Image dimensions invalid: {}×{}×{} (w×h×d)", - width, height, components - )[..], - )), + None => Err(ImageError::DimensionError), Some(v) if v == image.len() => Ok(CheckedImageBuffer { _image: image, _width: width, _height: height, _color: color, }), - Some(_) => Err(io::Error::new( + Some(_) => Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, &"Image buffer does not correspond to size and colour".to_string()[..], - )), + ))), } } } impl<'a> UncheckedHeader<'a> { - fn check_header_dimensions(self, width: u32, height: u32) -> io::Result> { + fn check_header_dimensions( + self, + width: u32, + height: u32, + ) -> ImageResult> { if self.header.width() != width || self.header.height() != height { - return Err(io::Error::new( + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, "Chosen header does not match Image dimensions", - )); + ))); } Ok(CheckedDimensions { @@ -325,49 +339,44 @@ impl<'a> CheckedDimensions<'a> { // Check color compatibility with the header. This will only error when we are certain that // the comination is bogus (e.g. combining Pixmap and Palette) but allows uncertain // combinations (basically a ArbitraryTuplType::Custom with any color of fitting depth). - fn check_header_color(self, color: ColorType) -> io::Result> { - let components = match color { - ColorType::Gray(_) => 1, - ColorType::GrayA(_) => 2, - ColorType::Palette(_) | ColorType::RGB(_) | ColorType::BGR(_)=> 3, - ColorType::RGBA(_) | ColorType::BGRA(_) => 4, - }; + fn check_header_color(self, color: ExtendedColorType) -> ImageResult> { + let components = u32::from(color.channel_count()); match *self.unchecked.header { PNMHeader { decoded: HeaderRecord::Bitmap(_), .. } => match color { - ColorType::Gray(_) => (), + ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (), _ => { - return Err(io::Error::new( + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, - "PBM format only support ColorType::Gray", - )) + "PBM format only support luma color types", + ))) } }, PNMHeader { decoded: HeaderRecord::Graymap(_), .. } => match color { - ColorType::Gray(_) => (), + ExtendedColorType::L1 | ExtendedColorType::L8 | ExtendedColorType::L16 => (), _ => { - return Err(io::Error::new( + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, - "PGM format only support ColorType::Gray", - )) + "PGM format only support luma color types", + ))) } }, PNMHeader { decoded: HeaderRecord::Pixmap(_), .. } => match color { - ColorType::RGB(_) => (), + ExtendedColorType::Rgb8 => (), _ => { - return Err(io::Error::new( + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, - "PPM format only support ColorType::RGB", - )) + "PPM format only support ExtendedColorType::Rgb8", + ))) } }, PNMHeader { @@ -379,28 +388,30 @@ impl<'a> CheckedDimensions<'a> { }), .. } => match (tupltype, color) { - (&Some(ArbitraryTuplType::BlackAndWhite), ColorType::Gray(_)) => (), - (&Some(ArbitraryTuplType::BlackAndWhiteAlpha), ColorType::GrayA(_)) => (), + (&Some(ArbitraryTuplType::BlackAndWhite), ExtendedColorType::L1) => (), + (&Some(ArbitraryTuplType::BlackAndWhiteAlpha), ExtendedColorType::La8) => (), - (&Some(ArbitraryTuplType::Grayscale), ColorType::Gray(_)) => (), - (&Some(ArbitraryTuplType::GrayscaleAlpha), ColorType::GrayA(_)) => (), + (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L1) => (), + (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L8) => (), + (&Some(ArbitraryTuplType::Grayscale), ExtendedColorType::L16) => (), + (&Some(ArbitraryTuplType::GrayscaleAlpha), ExtendedColorType::La8) => (), - (&Some(ArbitraryTuplType::RGB), ColorType::RGB(_)) => (), - (&Some(ArbitraryTuplType::RGBAlpha), ColorType::RGBA(_)) => (), + (&Some(ArbitraryTuplType::RGB), ExtendedColorType::Rgb8) => (), + (&Some(ArbitraryTuplType::RGBAlpha), ExtendedColorType::Rgba8) => (), (&None, _) if depth == components => (), (&Some(ArbitraryTuplType::Custom(_)), _) if depth == components => (), _ if depth != components => { - return Err(io::Error::new( + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, format!("Depth mismatch: header {} vs. color {}", depth, components), - )) + ))) } _ => { - return Err(io::Error::new( + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, "Invalid color type for selected PAM color type", - )) + ))) } }, } @@ -413,7 +424,7 @@ impl<'a> CheckedDimensions<'a> { } impl<'a> CheckedHeaderColor<'a> { - fn check_sample_values(self, image: FlatSamples<'a>) -> io::Result> { + fn check_sample_values(self, image: FlatSamples<'a>) -> ImageResult> { let header_maxval = match self.dimensions.unchecked.header.decoded { HeaderRecord::Bitmap(_) => 1, HeaderRecord::Graymap(GraymapHeader { maxwhite, .. }) => maxwhite, @@ -423,35 +434,37 @@ impl<'a> CheckedHeaderColor<'a> { // We trust the image color bit count to be correct at least. let max_sample = match self.color { - // Protects against overflows from shifting and gives a better error. - ColorType::Gray(n) - | ColorType::GrayA(n) - | ColorType::Palette(n) - | ColorType::RGB(n) - | ColorType::RGBA(n) - | ColorType::BGR(n) - | ColorType::BGRA(n) if n > 16 => - { - return Err(io::Error::new( + ExtendedColorType::Unknown(n) if n <= 16 => (1 << n) - 1, + ExtendedColorType::L1 => 1, + ExtendedColorType::L8 + | ExtendedColorType::La8 + | ExtendedColorType::Rgb8 + | ExtendedColorType::Rgba8 + | ExtendedColorType::Bgr8 + | ExtendedColorType::Bgra8 + => 0xff, + ExtendedColorType::L16 + | ExtendedColorType::La16 + | ExtendedColorType::Rgb16 + | ExtendedColorType::Rgba16 + => 0xffff, + ExtendedColorType::__NonExhaustive(marker) => match marker._private {}, + _ => { + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, - "Encoding colors with a bit depth greater 16 not supported", - )) + "Unsupported target color type", + ))) } - ColorType::Gray(n) - | ColorType::GrayA(n) - | ColorType::Palette(n) - | ColorType::RGB(n) - | ColorType::RGBA(n) - | ColorType::BGR(n) - | ColorType::BGRA(n) => (1 << n) - 1, }; // Avoid the performance heavy check if possible, e.g. if the header has been chosen by us. if header_maxval < max_sample && !image.all_smaller(header_maxval) { - return Err(io::Error::new( + // FIXME https://github.com/image-rs/image/issues/921, No ImageError variant seems + // appropriate in this situation UnsupportedHeaderFormat maybe? + return Err(ImageError::IoError(io::Error::new( io::ErrorKind::InvalidInput, "Sample value greater than allowed for chosen header", - )); + ))); } let encoding = image.encoding_for(&self.dimensions.unchecked.header.decoded); @@ -472,7 +485,7 @@ impl<'a> CheckedHeaderColor<'a> { } impl<'a> CheckedHeader<'a> { - fn write_header(self, writer: &mut dyn Write) -> io::Result> { + fn write_header(self, writer: &mut dyn Write) -> ImageResult> { self.header().write(writer)?; Ok(self.encoding) } @@ -596,58 +609,45 @@ impl<'a> From<&'a [u16]> for FlatSamples<'a> { } impl<'a> TupleEncoding<'a> { - fn write_image(&self, writer: &mut dyn Write) -> io::Result<()> { + fn write_image(&self, writer: &mut dyn Write) -> ImageResult<()> { match *self { TupleEncoding::PbmBits { samples: FlatSamples::U8(samples), width, - } => SampleWriter(writer).write_pbm_bits(samples, width), + } => SampleWriter(writer) + .write_pbm_bits(samples, width) + .map_err(ImageError::IoError), TupleEncoding::PbmBits { samples: FlatSamples::U16(samples), width, - } => SampleWriter(writer).write_pbm_bits(samples, width), + } => SampleWriter(writer) + .write_pbm_bits(samples, width) + .map_err(ImageError::IoError), TupleEncoding::Bytes { samples: FlatSamples::U8(samples), - } => writer.write_all(samples), + } => writer.write_all(samples).map_err(ImageError::IoError), TupleEncoding::Bytes { samples: FlatSamples::U16(samples), } => samples .iter() - .map(|&sample| writer.write_u16::(sample)) + .map(|&sample| { + writer + .write_u16::(sample) + .map_err(ImageError::IoError) + }) .collect(), TupleEncoding::Ascii { samples: FlatSamples::U8(samples), - } => SampleWriter(writer).write_samples_ascii(samples.iter()), + } => SampleWriter(writer) + .write_samples_ascii(samples.iter()) + .map_err(ImageError::IoError), TupleEncoding::Ascii { samples: FlatSamples::U16(samples), - } => SampleWriter(writer).write_samples_ascii(samples.iter()), + } => SampleWriter(writer) + .write_samples_ascii(samples.iter()) + .map_err(ImageError::IoError), } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn custom_header_and_color() { - let data: [u8; 12] = [0, 0, 0, 1, 1, 1, 255, 255, 255, 0, 0, 0]; - - let header = ArbitraryHeader { - width: 2, - height: 2, - depth: 3, - maxval: 255, - tupltype: Some(ArbitraryTuplType::Custom("Palette".to_string())), - }; - - let mut output = Vec::new(); - - PNMEncoder::new(&mut output) - .with_header(header.into()) - .encode(&data[..], 2, 2, ColorType::Palette(8)) - .expect("Failed encoding custom color value"); - } -} diff --git a/src/pnm/mod.rs b/src/pnm/mod.rs index d8b0ac698c..f1c568f13a 100644 --- a/src/pnm/mod.rs +++ b/src/pnm/mod.rs @@ -4,7 +4,7 @@ //! `BLACKANDWHITE`, `GRAYSCALE` and `RGB` and explicitely recognizes but rejects their `_ALPHA` //! variants for now as alpha color types are unsupported. use self::autobreak::AutoBreak; -pub use self::decoder::PNMDecoder; +pub use self::decoder::PnmDecoder; pub use self::encoder::PNMEncoder; use self::header::HeaderRecord; pub use self::header::{ArbitraryHeader, ArbitraryTuplType, BitmapHeader, GraymapHeader, @@ -20,8 +20,8 @@ mod header; mod tests { use super::*; use byteorder::{ByteOrder, NativeEndian}; - use color::ColorType; - use image::ImageDecoder; + use crate::color::ColorType; + use crate::image::ImageDecoder; fn execute_roundtrip_default(buffer: &[u8], width: u32, height: u32, color: ColorType) { let mut encoded_buffer = Vec::new(); @@ -34,11 +34,12 @@ mod tests { } let (header, loaded_color, loaded_image) = { - let decoder = PNMDecoder::new(&encoded_buffer[..]).unwrap(); - let colortype = decoder.colortype(); - let image = decoder.read_image().expect("Failed to decode the image"); - let (_, header) = PNMDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); - (header, colortype, image) + let decoder = PnmDecoder::new(&encoded_buffer[..]).unwrap(); + let color_type = decoder.color_type(); + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).expect("Failed to decode the image"); + let (_, header) = PnmDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); + (header, color_type, image) }; assert_eq!(header.width(), width); @@ -64,11 +65,12 @@ mod tests { } let (header, loaded_color, loaded_image) = { - let decoder = PNMDecoder::new(&encoded_buffer[..]).unwrap(); - let colortype = decoder.colortype(); - let image = decoder.read_image().expect("Failed to decode the image"); - let (_, header) = PNMDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); - (header, colortype, image) + let decoder = PnmDecoder::new(&encoded_buffer[..]).unwrap(); + let color_type = decoder.color_type(); + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).expect("Failed to decode the image"); + let (_, header) = PnmDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); + (header, color_type, image) }; assert_eq!(header.width(), width); @@ -89,11 +91,12 @@ mod tests { } let (header, loaded_color, loaded_image) = { - let decoder = PNMDecoder::new(&encoded_buffer[..]).unwrap(); - let colortype = decoder.colortype(); - let image = decoder.read_image().expect("Failed to decode the image"); - let (_, header) = PNMDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); - (header, colortype, image) + let decoder = PnmDecoder::new(&encoded_buffer[..]).unwrap(); + let color_type = decoder.color_type(); + let mut image = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image).expect("Failed to decode the image"); + let (_, header) = PnmDecoder::new(&encoded_buffer[..]).unwrap().into_inner(); + (header, color_type, image) }; let mut buffer_u8 = vec![0; buffer.len() * 2]; @@ -119,20 +122,20 @@ mod tests { 255, 255, 255, 255, 255, 255, ]; - execute_roundtrip_default(&buf, 3, 3, ColorType::RGB(8)); - execute_roundtrip_with_subtype(&buf, 3, 3, ColorType::RGB(8), PNMSubtype::ArbitraryMap); + execute_roundtrip_default(&buf, 3, 3, ColorType::Rgb8); + execute_roundtrip_with_subtype(&buf, 3, 3, ColorType::Rgb8, PNMSubtype::ArbitraryMap); execute_roundtrip_with_subtype( &buf, 3, 3, - ColorType::RGB(8), + ColorType::Rgb8, PNMSubtype::Pixmap(SampleEncoding::Binary), ); execute_roundtrip_with_subtype( &buf, 3, 3, - ColorType::RGB(8), + ColorType::Rgb8, PNMSubtype::Pixmap(SampleEncoding::Ascii), ); } @@ -141,6 +144,6 @@ mod tests { fn roundtrip_u16() { let buf: [u16; 6] = [0, 1, 0xFFFF, 0x1234, 0x3412, 0xBEAF]; - execute_roundtrip_u16(&buf, 6, 1, ColorType::Gray(16)); + execute_roundtrip_u16(&buf, 6, 1, ColorType::L16); } } diff --git a/src/tga/decoder.rs b/src/tga/decoder.rs index 454834c681..29145e1fb3 100644 --- a/src/tga/decoder.rs +++ b/src/tga/decoder.rs @@ -1,9 +1,11 @@ use byteorder::{LittleEndian, ReadBytesExt}; +use std::convert::TryFrom; use std::io; use std::io::{Read, Seek}; -use color::ColorType; -use image::{ImageDecoder, ImageError, ImageReadBuffer, ImageResult}; +use crate::color::ColorType; +use crate::error::{ImageError, ImageResult}; +use crate::image::{ImageDecoder, ImageReadBuffer}; enum ImageType { NoImageData = 0, @@ -153,7 +155,7 @@ impl ColorMap { } /// The representation of a TGA decoder -pub struct TGADecoder { +pub struct TgaDecoder { r: R, width: usize, @@ -172,10 +174,10 @@ pub struct TGADecoder { line_remain_buff: Vec, } -impl TGADecoder { +impl TgaDecoder { /// Create a new decoder that decodes from the stream `r` - pub fn new(r: R) -> ImageResult> { - let mut decoder = TGADecoder { + pub fn new(r: R) -> ImageResult> { + let mut decoder = TgaDecoder { r, width: 0, @@ -184,7 +186,7 @@ impl TGADecoder { has_loaded_metadata: false, image_type: ImageType::Unknown, - color_type: ColorType::Gray(1), + color_type: ColorType::L8, header: Header::new(), color_map: None, @@ -249,12 +251,12 @@ impl TGADecoder { match (num_alpha_bits, other_channel_bits, color) { // really, the encoding is BGR and BGRA, this is fixed - // up with `TGADecoder::reverse_encoding`. - (0, 32, true) => self.color_type = ColorType::RGBA(8), - (8, 24, true) => self.color_type = ColorType::RGBA(8), - (0, 24, true) => self.color_type = ColorType::RGB(8), - (8, 8, false) => self.color_type = ColorType::GrayA(8), - (0, 8, false) => self.color_type = ColorType::Gray(8), + // up with `TgaDecoder::reverse_encoding`. + (0, 32, true) => self.color_type = ColorType::Rgba8, + (8, 24, true) => self.color_type = ColorType::Rgba8, + (0, 24, true) => self.color_type = ColorType::Rgb8, + (8, 8, false) => self.color_type = ColorType::La8, + (0, 8, false) => self.color_type = ColorType::L8, _ => { return Err(ImageError::UnsupportedError(format!( "Color format not supported. Bit depth: {}, Alpha bits: {}", @@ -301,10 +303,7 @@ impl TGADecoder { let bytes_per_entry = (self.header.map_entry_size as usize + 7) / 8; let mut result = Vec::with_capacity(self.width * self.height * bytes_per_entry); - let color_map = match self.color_map { - Some(ref color_map) => color_map, - None => unreachable!(), - }; + let color_map = self.color_map.as_ref().unwrap(); for chunk in pixel_data.chunks(self.bytes_per_pixel) { let index = bytes_to_index(chunk); @@ -314,29 +313,6 @@ impl TGADecoder { result } - fn read_image_data(&mut self) -> ImageResult> { - // read the pixels from the data region - let mut pixel_data = if self.image_type.is_encoded() { - self.read_all_encoded_data()? - } else { - let num_raw_bytes = self.width * self.height * self.bytes_per_pixel; - let mut buf = vec![0; num_raw_bytes]; - self.r.by_ref().read_exact(&mut buf)?; - buf - }; - - // expand the indices using the color map if necessary - if self.image_type.is_color_mapped() { - pixel_data = self.expand_color_map(&pixel_data) - } - - self.reverse_encoding(&mut pixel_data); - - self.flip_vertically(&mut pixel_data); - - Ok(pixel_data) - } - /// Reads a run length encoded data for given number of bytes fn read_encoded_data(&mut self, num_bytes: usize) -> io::Result> { let mut pixel_data = Vec::with_capacity(num_bytes); @@ -411,7 +387,7 @@ impl TGADecoder { fn reverse_encoding(&mut self, pixels: &mut [u8]) { // We only need to reverse the encoding of color images match self.color_type { - ColorType::RGB(8) | ColorType::RGBA(8) => { + ColorType::Rgb8 | ColorType::Rgba8 => { for chunk in pixels.chunks_mut(self.bytes_per_pixel) { chunk.swap(0, 2); } @@ -489,43 +465,61 @@ impl TGADecoder { } } -impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TGADecoder { +impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TgaDecoder { type Reader = TGAReader; - fn dimensions(&self) -> (u64, u64) { - (self.width as u64, self.height as u64) + fn dimensions(&self) -> (u32, u32) { + (self.width as u32, self.height as u32) } - fn colortype(&self) -> ColorType { + fn color_type(&self) -> ColorType { self.color_type } fn scanline_bytes(&self) -> u64 { - self.row_bytes() + // This cannot overflow because TGA has a maximum width of u16::MAX_VALUE and + // `bytes_per_pixel` is a u8. + u64::from(self.color_type.bytes_per_pixel()) * self.width as u64 } fn into_reader(self) -> ImageResult { - if self.total_bytes() > usize::max_value() as u64 { - return Err(ImageError::InsufficientMemory); - } - Ok(TGAReader { - buffer: ImageReadBuffer::new( - self.scanline_bytes() as usize, - self.total_bytes() as usize, - ), + buffer: ImageReadBuffer::new(self.scanline_bytes(), self.total_bytes()), decoder: self, }) } - fn read_image(mut self) -> ImageResult> { - self.read_image_data() + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + + // read the pixels from the data region + let len = if self.image_type.is_encoded() { + let pixel_data = self.read_all_encoded_data()?; + buf[0..pixel_data.len()].copy_from_slice(&pixel_data); + pixel_data.len() + } else { + let num_raw_bytes = self.width * self.height * self.bytes_per_pixel; + self.r.by_ref().read_exact(&mut buf[0..num_raw_bytes])?; + num_raw_bytes + }; + + // expand the indices using the color map if necessary + if self.image_type.is_color_mapped() { + let pixel_data = self.expand_color_map(&buf[0..len]); + buf.copy_from_slice(&pixel_data); + } + + self.reverse_encoding(buf); + + self.flip_vertically(buf); + + Ok(()) } } pub struct TGAReader { buffer: ImageReadBuffer, - decoder: TGADecoder, + decoder: TgaDecoder, } impl Read for TGAReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { diff --git a/src/tga/mod.rs b/src/tga/mod.rs index 6ccff58846..af185871e7 100644 --- a/src/tga/mod.rs +++ b/src/tga/mod.rs @@ -6,7 +6,7 @@ /// A decoder for TGA images /// /// Currently this decoder does not support 8, 15 and 16 bit color images. -pub use self::decoder::TGADecoder; +pub use self::decoder::TgaDecoder; //TODO add 8, 15, 16 bit color support diff --git a/src/tiff.rs b/src/tiff.rs index d6219f9182..6a556257f4 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -8,42 +8,64 @@ extern crate tiff; +use std::convert::TryFrom; use std::io::{self, Cursor, Read, Write, Seek}; use std::marker::PhantomData; use std::mem; -use color::ColorType; -use image::{ImageDecoder, ImageResult, ImageError}; -use utils::vec_u16_into_u8; +use byteorder::{NativeEndian, ByteOrder}; + +use crate::color::{ColorType, ExtendedColorType}; +use crate::error::{ImageError, ImageResult}; +use crate::image::{ImageDecoder, ImageEncoder}; +use crate::utils::vec_u16_into_u8; /// Decoder for TIFF images. -pub struct TIFFDecoder +pub struct TiffDecoder where R: Read + Seek { dimensions: (u32, u32), - colortype: ColorType, + color_type: ColorType, inner: tiff::decoder::Decoder, } -impl TIFFDecoder +impl TiffDecoder where R: Read + Seek { - /// Create a new TIFFDecoder. - pub fn new(r: R) -> Result, ImageError> { - let mut inner = tiff::decoder::Decoder::new(r)?; - let dimensions = inner.dimensions()?; - let colortype = inner.colortype()?.into(); - - Ok(TIFFDecoder { + /// Create a new TiffDecoder. + pub fn new(r: R) -> Result, ImageError> { + let mut inner = tiff::decoder::Decoder::new(r).map_err(ImageError::from_tiff)?; + let dimensions = inner.dimensions().map_err(ImageError::from_tiff)?; + let color_type = match inner.colortype().map_err(ImageError::from_tiff)? { + tiff::ColorType::Gray(8) => ColorType::L8, + tiff::ColorType::Gray(16) => ColorType::L16, + tiff::ColorType::GrayA(8) => ColorType::La8, + tiff::ColorType::GrayA(16) => ColorType::La16, + tiff::ColorType::RGB(8) => ColorType::Rgb8, + tiff::ColorType::RGB(16) => ColorType::Rgb16, + tiff::ColorType::RGBA(8) => ColorType::Rgba8, + tiff::ColorType::RGBA(16) => ColorType::Rgba16, + + tiff::ColorType::Palette(n) | tiff::ColorType::Gray(n) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Unknown(n))), + tiff::ColorType::GrayA(n) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Unknown(n*2))), + tiff::ColorType::RGB(n) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Unknown(n*3))), + tiff::ColorType::RGBA(n) | tiff::ColorType::CMYK(n) => + return Err(ImageError::UnsupportedColor(ExtendedColorType::Unknown(n*4))), + }; + + Ok(TiffDecoder { dimensions, - colortype, + color_type, inner, }) } } -impl From for ImageError { - fn from(err: tiff::TiffError) -> ImageError { +impl ImageError { + fn from_tiff(err: tiff::TiffError) -> ImageError { match err { tiff::TiffError::IoError(err) => ImageError::IoError(err), tiff::TiffError::FormatError(desc) => ImageError::FormatError(desc.to_string()), @@ -53,19 +75,6 @@ impl From for ImageError { } } -impl From for ColorType { - fn from(ct: tiff::ColorType) -> ColorType { - match ct { - tiff::ColorType::Gray(depth) => ColorType::Gray(depth), - tiff::ColorType::RGB(depth) => ColorType::RGB(depth), - tiff::ColorType::Palette(depth) => ColorType::Palette(depth), - tiff::ColorType::GrayA(depth) => ColorType::GrayA(depth), - tiff::ColorType::RGBA(depth) => ColorType::RGBA(depth), - tiff::ColorType::CMYK(_) => unimplemented!() - } - } -} - /// Wrapper struct around a `Cursor>` pub struct TiffReader(Cursor>, PhantomData); impl Read for TiffReader { @@ -82,26 +91,37 @@ impl Read for TiffReader { } } -impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TIFFDecoder { +impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TiffDecoder { type Reader = TiffReader; - fn dimensions(&self) -> (u64, u64) { - (u64::from(self.dimensions.0), u64::from(self.dimensions.1)) + fn dimensions(&self) -> (u32, u32) { + self.dimensions } - fn colortype(&self) -> ColorType { - self.colortype + fn color_type(&self) -> ColorType { + self.color_type } - fn into_reader(self) -> ImageResult { - Ok(TiffReader(Cursor::new(self.read_image()?), PhantomData)) + fn into_reader(mut self) -> ImageResult { + let buf = match self.inner.read_image().map_err(ImageError::from_tiff)? { + tiff::decoder::DecodingResult::U8(v) => v, + tiff::decoder::DecodingResult::U16(v) => vec_u16_into_u8(v), + }; + + Ok(TiffReader(Cursor::new(buf), PhantomData)) } - fn read_image(mut self) -> ImageResult> { - match self.inner.read_image()? { - tiff::decoder::DecodingResult::U8(v) => Ok(v), - tiff::decoder::DecodingResult::U16(v) => Ok(vec_u16_into_u8(v)), + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + match self.inner.read_image().map_err(ImageError::from_tiff)? { + tiff::decoder::DecodingResult::U8(v) => { + buf.copy_from_slice(&v); + } + tiff::decoder::DecodingResult::U16(v) => { + NativeEndian::write_u16_into(&v, buf); + } } + Ok(()) } } @@ -110,27 +130,46 @@ 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 { TiffEncoder { w } } - /// Encodes the image `image` - /// that has dimensions `width` and `height` - /// and `ColorType` `c`. + /// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`. /// - /// 16-bit colortypes 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<()> { - // TODO: 16bit support - let mut encoder = tiff::encoder::TiffEncoder::new(self.w)?; + let mut encoder = tiff::encoder::TiffEncoder::new(self.w).map_err(ImageError::from_tiff)?; match color { - ColorType::Gray(8) => encoder.write_image::(width, height, data)?, - ColorType::RGB(8) => encoder.write_image::(width, height, data)?, - ColorType::RGBA(8) => encoder.write_image::(width, height, data)?, - _ => return Err(ImageError::UnsupportedColor(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/src/utils/mod.rs b/src/utils/mod.rs index 8a9d764439..3728443646 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -34,6 +34,35 @@ where } } +/// Expand a buffer of packed 1, 2, or 4 bits integers into u8's. Assumes that +/// every `row_size` entries there are padding bits up to the next byte boundry. +pub(crate) fn expand_bits(bit_depth: u8, row_size: u32, buf: &[u8]) -> Vec { + // Note: this conversion assumes that the scanlines begin on byte boundaries + let mask = (1u8 << bit_depth as usize) - 1; + let scaling_factor = 255 / ((1 << bit_depth as usize) - 1); + let bit_width = row_size * u32::from(bit_depth); + let skip = if bit_width % 8 == 0 { + 0 + } else { + (8 - bit_width % 8) / u32::from(bit_depth) + }; + let row_len = row_size + skip; + let mut p = Vec::new(); + let mut i = 0; + for v in buf { + for shift in num_iter::range_step_inclusive(8i8-(bit_depth as i8), 0, -(bit_depth as i8)) { + // skip the pixels that can be neglected because scanlines should + // start at byte boundaries + if i % (row_len as usize) < (row_size as usize) { + let pixel = (v & mask << shift as usize) >> shift as usize; + p.push(pixel * scaling_factor); + } + i += 1; + } + } + p +} + pub(crate) fn vec_u16_into_u8(vec: Vec) -> Vec { // Do this way until we find a way to not alloc/dealloc but get llvm to realloc instead. vec_u16_copy_u8(&vec) @@ -44,3 +73,55 @@ pub(crate) fn vec_u16_copy_u8(vec: &[u16]) -> Vec { NativeEndian::write_u16_into(&vec[..], &mut new[..]); new } + + +/// A marker struct for __NonExhaustive enums. +/// +/// This is an empty type that can not be constructed. When an enum contains a tuple variant that +/// includes this type the optimizer can statically determined tha the branch is never taken while +/// at the same time the matching of the branch is required. +/// +/// The effect is thus very similar to the actual `#[non_exhaustive]` attribute with no runtime +/// costs. Also note that we use a dirty trick to not only hide this type from the doc but make it +/// inaccessible. The visibility in this module is pub but the module itself is not and the +/// top-level crate never exports the type. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct NonExhaustiveMarker { + /// Allows this crate, and this crate only, to match on the impossibility of this variant. + pub(crate) _private: Empty, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum Empty { } + +#[cfg(test)] +mod test { + #[test] + fn gray_to_luma8_skip() { + let check = |bit_depth, w, from, to| { + assert_eq!( + super::expand_bits(bit_depth, w, from), + to); + }; + // Bit depth 1, skip is more than half a byte + check( + 1, 10, + &[0b11110000, 0b11000000, 0b00001111, 0b11000000], + vec![255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255]); + // Bit depth 2, skip is more than half a byte + check( + 2, 5, + &[0b11110000, 0b11000000, 0b00001111, 0b11000000], + vec![255, 255, 0, 0, 255, 0, 0, 255, 255, 255]); + // Bit depth 2, skip is 0 + check( + 2, 4, + &[0b11110000, 0b00001111], + vec![255, 255, 0, 0, 0, 0, 255, 255]); + // Bit depth 4, skip is half a byte + check( + 4, 1, + &[0b11110011, 0b00001100], + vec![255, 0]); + } +} diff --git a/src/webp/decoder.rs b/src/webp/decoder.rs index 2fd35c3b9a..b67a16013a 100644 --- a/src/webp/decoder.rs +++ b/src/webp/decoder.rs @@ -1,33 +1,33 @@ use byteorder::{LittleEndian, ReadBytesExt}; +use std::convert::TryFrom; use std::default::Default; use std::io::{self, Cursor, Read}; use std::marker::PhantomData; use std::mem; -use image; -use image::ImageDecoder; -use image::ImageResult; +use crate::image::ImageDecoder; +use crate::error::{ImageError, ImageResult}; -use color; +use crate::color; use super::vp8::Frame; -use super::vp8::VP8Decoder; +use super::vp8::Vp8Decoder; -/// Webp Image format decoder. Currently only supportes the luma channel (meaning that decoded +/// WebP Image format decoder. Currently only supportes the luma channel (meaning that decoded /// images will be grayscale). -pub struct WebpDecoder { +pub struct WebPDecoder { r: R, frame: Frame, have_frame: bool, } -impl WebpDecoder { - /// Create a new WebpDecoder from the Reader ```r```. +impl WebPDecoder { + /// Create a new WebPDecoder from the Reader ```r```. /// This function takes ownership of the Reader. - pub fn new(r: R) -> ImageResult> { + pub fn new(r: R) -> ImageResult> { let f: Frame = Default::default(); - let mut decoder = WebpDecoder { + let mut decoder = WebPDecoder { r, have_frame: false, frame: f, @@ -44,13 +44,13 @@ impl WebpDecoder { self.r.by_ref().take(4).read_to_end(&mut webp)?; if &*riff != b"RIFF" { - return Err(image::ImageError::FormatError( + return Err(ImageError::FormatError( "Invalid RIFF signature.".to_string(), )); } if &*webp != b"WEBP" { - return Err(image::ImageError::FormatError( + return Err(ImageError::FormatError( "Invalid WEBP signature.".to_string(), )); } @@ -63,7 +63,7 @@ impl WebpDecoder { self.r.by_ref().take(4).read_to_end(&mut vp8)?; if &*vp8 != b"VP8 " { - return Err(image::ImageError::FormatError( + return Err(ImageError::FormatError( "Invalid VP8 signature.".to_string(), )); } @@ -78,7 +78,7 @@ impl WebpDecoder { self.r.read_to_end(&mut framedata)?; let m = io::Cursor::new(framedata); - let mut v = VP8Decoder::new(m); + let mut v = Vp8Decoder::new(m); let frame = v.decode_frame()?; self.frame = frame.clone(); @@ -115,22 +115,24 @@ impl Read for WebpReader { } } -impl<'a, R: 'a + Read> ImageDecoder<'a> for WebpDecoder { +impl<'a, R: 'a + Read> ImageDecoder<'a> for WebPDecoder { type Reader = WebpReader; - fn dimensions(&self) -> (u64, u64) { - (u64::from(self.frame.width), u64::from(self.frame.height)) + fn dimensions(&self) -> (u32, u32) { + (u32::from(self.frame.width), u32::from(self.frame.height)) } - fn colortype(&self) -> color::ColorType { - color::ColorType::Gray(8) + fn color_type(&self) -> color::ColorType { + color::ColorType::L8 } fn into_reader(self) -> ImageResult { Ok(WebpReader(Cursor::new(self.frame.ybuf), PhantomData)) } - fn read_image(self) -> ImageResult> { - Ok(self.frame.ybuf) + fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + buf.copy_from_slice(&self.frame.ybuf); + Ok(()) } } diff --git a/src/webp/mod.rs b/src/webp/mod.rs index dea5bd1ef8..24f57ee4d2 100644 --- a/src/webp/mod.rs +++ b/src/webp/mod.rs @@ -1,6 +1,6 @@ -//! Decoding of Webp Images +//! Decoding of WebP Images -pub use self::decoder::WebpDecoder; +pub use self::decoder::WebPDecoder; mod decoder; mod transform; diff --git a/src/webp/vp8.rs b/src/webp/vp8.rs index a8fe1bc2d6..ed1da1ee76 100644 --- a/src/webp/vp8.rs +++ b/src/webp/vp8.rs @@ -4,7 +4,7 @@ //! VP8 video format as defined in RFC-6386. //! //! It decodes Keyframes only sans Loop Filtering. -//! VP8 is the underpinning of the Webp image format +//! VP8 is the underpinning of the WebP image format //! //! # Related Links //! * [rfc-6386](http://tools.ietf.org/html/rfc6386) - The VP8 Data Format and Decoding Guide @@ -18,9 +18,9 @@ use std::cmp; use std::io::Read; use super::transform; -use ::{ImageError, ImageResult}; +use crate::{ImageError, ImageResult}; -use math::utils::clamp; +use crate::math::utils::clamp; const MAX_SEGMENTS: usize = 4; const NUM_DCT_TOKENS: usize = 12; @@ -835,7 +835,7 @@ struct Segment { /// VP8 Decoder /// /// Only decodes keyframes -pub struct VP8Decoder { +pub struct Vp8Decoder { r: R, b: BoolReader, @@ -867,15 +867,15 @@ pub struct VP8Decoder { left_border: Vec, } -impl VP8Decoder { +impl Vp8Decoder { /// Create a new decoder. /// The reader must present a raw vp8 bitstream to the decoder - pub fn new(r: R) -> VP8Decoder { + pub fn new(r: R) -> Vp8Decoder { let f = Frame::default(); let s = Segment::default(); let m = MacroBlock::default(); - VP8Decoder { + Vp8Decoder { r, b: BoolReader::new(), 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/png/interlaced/basi2c08.png b/tests/images/png/interlaced/basi2c08.png new file mode 100644 index 0000000000..2aab44d42b Binary files /dev/null and b/tests/images/png/interlaced/basi2c08.png differ diff --git a/tests/images/png/interlaced/lenna_fragment_interlaced.png b/tests/images/png/interlaced/lenna_fragment_interlaced.png deleted file mode 100644 index 15885f0c4b..0000000000 Binary files a/tests/images/png/interlaced/lenna_fragment_interlaced.png and /dev/null differ diff --git a/tests/images/tiff/testsuite/lenna.tiff b/tests/images/tiff/testsuite/lenna.tiff deleted file mode 100644 index ffe5c835d0..0000000000 Binary files a/tests/images/tiff/testsuite/lenna.tiff and /dev/null differ diff --git a/tests/images/tiff/testsuite/mandrill.tiff b/tests/images/tiff/testsuite/mandrill.tiff new file mode 100644 index 0000000000..017ce58f64 Binary files /dev/null and b/tests/images/tiff/testsuite/mandrill.tiff 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/png/interlaced/basi2c08.png.2fb54036.png b/tests/reference/png/interlaced/basi2c08.png.2fb54036.png new file mode 100644 index 0000000000..ce906ed256 Binary files /dev/null and b/tests/reference/png/interlaced/basi2c08.png.2fb54036.png differ diff --git a/tests/reference/png/interlaced/lenna_fragment_interlaced.png.ec750ce4.png b/tests/reference/png/interlaced/lenna_fragment_interlaced.png.ec750ce4.png deleted file mode 100644 index 2e6ea1fccd..0000000000 Binary files a/tests/reference/png/interlaced/lenna_fragment_interlaced.png.ec750ce4.png and /dev/null differ diff --git a/tests/reference/tiff/testsuite/lenna.tiff.e80eb1ce.png b/tests/reference/tiff/testsuite/lenna.tiff.e80eb1ce.png deleted file mode 100644 index d459fa9096..0000000000 Binary files a/tests/reference/tiff/testsuite/lenna.tiff.e80eb1ce.png and /dev/null differ diff --git a/tests/reference/tiff/testsuite/mandrill.tiff.694c777d.png b/tests/reference/tiff/testsuite/mandrill.tiff.694c777d.png new file mode 100644 index 0000000000..d34f4bd7e3 Binary files /dev/null and b/tests/reference/tiff/testsuite/mandrill.tiff.694c777d.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 diff --git a/tests/reference_images.rs b/tests/reference_images.rs index f54f32bc02..c4065cd143 100644 --- a/tests/reference_images.rs +++ b/tests/reference_images.rs @@ -50,7 +50,7 @@ fn render_images() { // Do not fail on unsupported error // This might happen because the testsuite contains unsupported images // or because a specific decoder included via a feature. - Err(image::ImageError::UnsupportedError(e)) => { + Err(image::ImageError::Unsupported(e)) => { println!("UNSUPPORTED {}: {}", path.display(), e); return; } @@ -161,7 +161,7 @@ fn check_references() { // Do not fail on unsupported error // This might happen because the testsuite contains unsupported images // or because a specific decoder included via a feature. - Err(image::ImageError::UnsupportedError(_)) => return, + Err(image::ImageError::Unsupported(_)) => return, Err(err) => panic!(format!("{}", err)), }; @@ -185,14 +185,14 @@ fn check_references() { match case.kind { ReferenceTestKind::AnimatedGifFrame { frame: frame_num } => { - #[cfg(feature = "gif_codec")] + #[cfg(feature = "gif")] { // Interpret the input file as an animation file use image::AnimationDecoder; let stream = io::BufReader::new(fs::File::open(&img_path).unwrap()); - let decoder = match image::gif::Decoder::new(stream) { + let decoder = match image::gif::GifDecoder::new(stream) { Ok(decoder) => decoder, - Err(image::ImageError::UnsupportedError(_)) => return, + Err(image::ImageError::Unsupported(_)) => return, Err(err) => { panic!(format!("decoding of {:?} failed with: {}", img_path, err)) } @@ -200,7 +200,7 @@ fn check_references() { let mut frames = match decoder.into_frames().collect_frames() { Ok(frames) => frames, - Err(image::ImageError::UnsupportedError(_)) => return, + Err(image::ImageError::Unsupported(_)) => return, Err(err) => panic!(format!( "collecting frames of {:?} failed with: {}", img_path, err @@ -214,7 +214,7 @@ fn check_references() { test_img = frame.into_buffer(); } - #[cfg(not(feature = "gif_codec"))] + #[cfg(not(feature = "gif"))] { println!("Skipping - GIF codec is not enabled"); return; @@ -228,7 +228,7 @@ fn check_references() { // Do not fail on unsupported error // This might happen because the testsuite contains unsupported images // or because a specific decoder included via a feature. - Err(image::ImageError::UnsupportedError(_)) => return, + Err(image::ImageError::Unsupported(_)) => return, Err(err) => panic!(format!("decoding of {:?} failed with: {}", img_path, err)), }; } @@ -284,7 +284,7 @@ fn check_hdr_references() { ref_path.set_extension("raw"); println!("{}", ref_path.display()); println!("{}", path.display()); - let decoder = image::hdr::HDRDecoder::new(io::BufReader::new( + let decoder = image::hdr::HdrDecoder::new(io::BufReader::new( fs::File::open(&path).unwrap(), )).unwrap(); let decoded = decoder.read_image_hdr().unwrap(); diff --git a/tests/save_jpeg.rs b/tests/save_jpeg.rs index 11fc6b422f..6108eb88b9 100644 --- a/tests/save_jpeg.rs +++ b/tests/save_jpeg.rs @@ -2,25 +2,25 @@ #![cfg(all(feature = "jpeg", feature = "tiff"))] extern crate image; -use image::{ImageOutputFormat, JPEG}; +use image::{ImageOutputFormat, ImageFormat}; #[test] fn jqeg_qualitys() { - let img = image::open("tests/images/tiff/testsuite/lenna.tiff").unwrap(); + let img = image::open("tests/images/tiff/testsuite/mandrill.tiff").unwrap(); let mut default = vec![]; - img.write_to(&mut default, JPEG).unwrap(); + img.write_to(&mut default, ImageFormat::Jpeg).unwrap(); assert_eq!(&[255, 216], &default[..2]); let mut small = vec![]; - img.write_to(&mut small, ImageOutputFormat::JPEG(10)) + img.write_to(&mut small, ImageOutputFormat::Jpeg(10)) .unwrap(); assert_eq!(&[255, 216], &small[..2]); assert!(small.len() < default.len()); let mut large = vec![]; - img.write_to(&mut large, ImageOutputFormat::JPEG(99)) + img.write_to(&mut large, ImageOutputFormat::Jpeg(99)) .unwrap(); assert_eq!(&[255, 216], &large[..2]);