aboutsummaryrefslogtreecommitdiff
path: root/vendor/image/src/codecs/bmp/encoder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/image/src/codecs/bmp/encoder.rs')
-rw-r--r--vendor/image/src/codecs/bmp/encoder.rs388
1 files changed, 388 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/bmp/encoder.rs b/vendor/image/src/codecs/bmp/encoder.rs
new file mode 100644
index 0000000..c90c063
--- /dev/null
+++ b/vendor/image/src/codecs/bmp/encoder.rs
@@ -0,0 +1,388 @@
+use byteorder::{LittleEndian, WriteBytesExt};
+use std::io::{self, Write};
+
+use crate::error::{
+ EncodingError, ImageError, ImageFormatHint, ImageResult, ParameterError, ParameterErrorKind,
+};
+use crate::image::ImageEncoder;
+use crate::{color, ImageFormat};
+
+const BITMAPFILEHEADER_SIZE: u32 = 14;
+const BITMAPINFOHEADER_SIZE: u32 = 40;
+const BITMAPV4HEADER_SIZE: u32 = 108;
+
+/// The representation of a BMP encoder.
+pub struct BmpEncoder<'a, W: 'a> {
+ writer: &'a mut W,
+}
+
+impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
+ /// Create a new encoder that writes its output to ```w```.
+ pub fn new(w: &'a mut W) -> Self {
+ BmpEncoder { writer: w }
+ }
+
+ /// Encodes the image ```image```
+ /// that has dimensions ```width``` and ```height```
+ /// and ```ColorType``` ```c```.
+ pub fn encode(
+ &mut self,
+ image: &[u8],
+ width: u32,
+ height: u32,
+ c: color::ColorType,
+ ) -> ImageResult<()> {
+ self.encode_with_palette(image, width, height, c, None)
+ }
+
+ /// Same as ```encode```, but allow a palette to be passed in.
+ /// The ```palette``` is ignored for color types other than Luma/Luma-with-alpha.
+ pub fn encode_with_palette(
+ &mut self,
+ image: &[u8],
+ width: u32,
+ height: u32,
+ c: color::ColorType,
+ palette: Option<&[[u8; 3]]>,
+ ) -> ImageResult<()> {
+ if palette.is_some() && c != color::ColorType::L8 && c != color::ColorType::La8 {
+ return Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ format!(
+ "Unsupported color type {:?} when using a non-empty palette. Supported types: Gray(8), GrayA(8).",
+ c
+ ),
+ )));
+ }
+
+ let bmp_header_size = BITMAPFILEHEADER_SIZE;
+
+ let (dib_header_size, written_pixel_size, palette_color_count) =
+ get_pixel_info(c, palette)?;
+ let row_pad_size = (4 - (width * written_pixel_size) % 4) % 4; // each row must be padded to a multiple of 4 bytes
+ let image_size = width
+ .checked_mul(height)
+ .and_then(|v| v.checked_mul(written_pixel_size))
+ .and_then(|v| v.checked_add(height * row_pad_size))
+ .ok_or_else(|| {
+ ImageError::Parameter(ParameterError::from_kind(
+ ParameterErrorKind::DimensionMismatch,
+ ))
+ })?;
+ let palette_size = palette_color_count * 4; // all palette colors are BGRA
+ let file_size = bmp_header_size
+ .checked_add(dib_header_size)
+ .and_then(|v| v.checked_add(palette_size))
+ .and_then(|v| v.checked_add(image_size))
+ .ok_or_else(|| {
+ ImageError::Encoding(EncodingError::new(
+ ImageFormatHint::Exact(ImageFormat::Bmp),
+ "calculated BMP header size larger than 2^32",
+ ))
+ })?;
+
+ // write BMP header
+ self.writer.write_u8(b'B')?;
+ self.writer.write_u8(b'M')?;
+ self.writer.write_u32::<LittleEndian>(file_size)?; // file size
+ self.writer.write_u16::<LittleEndian>(0)?; // reserved 1
+ self.writer.write_u16::<LittleEndian>(0)?; // reserved 2
+ self.writer
+ .write_u32::<LittleEndian>(bmp_header_size + dib_header_size + palette_size)?; // image data offset
+
+ // write DIB header
+ self.writer.write_u32::<LittleEndian>(dib_header_size)?;
+ self.writer.write_i32::<LittleEndian>(width as i32)?;
+ self.writer.write_i32::<LittleEndian>(height as i32)?;
+ self.writer.write_u16::<LittleEndian>(1)?; // color planes
+ self.writer
+ .write_u16::<LittleEndian>((written_pixel_size * 8) as u16)?; // bits per pixel
+ if dib_header_size >= BITMAPV4HEADER_SIZE {
+ // Assume BGRA32
+ self.writer.write_u32::<LittleEndian>(3)?; // compression method - bitfields
+ } else {
+ self.writer.write_u32::<LittleEndian>(0)?; // compression method - no compression
+ }
+ self.writer.write_u32::<LittleEndian>(image_size)?;
+ self.writer.write_i32::<LittleEndian>(0)?; // horizontal ppm
+ self.writer.write_i32::<LittleEndian>(0)?; // vertical ppm
+ self.writer.write_u32::<LittleEndian>(palette_color_count)?;
+ self.writer.write_u32::<LittleEndian>(0)?; // all colors are important
+ if dib_header_size >= BITMAPV4HEADER_SIZE {
+ // Assume BGRA32
+ self.writer.write_u32::<LittleEndian>(0xff << 16)?; // red mask
+ self.writer.write_u32::<LittleEndian>(0xff << 8)?; // green mask
+ self.writer.write_u32::<LittleEndian>(0xff)?; // blue mask
+ self.writer.write_u32::<LittleEndian>(0xff << 24)?; // alpha mask
+ self.writer.write_u32::<LittleEndian>(0x73524742)?; // colorspace - sRGB
+
+ // endpoints (3x3) and gamma (3)
+ for _ in 0..12 {
+ self.writer.write_u32::<LittleEndian>(0)?;
+ }
+ }
+
+ // write image data
+ match c {
+ 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, palette)?
+ }
+ color::ColorType::La8 => {
+ self.encode_gray(image, width, height, row_pad_size, 2, palette)?
+ }
+ _ => {
+ return Err(ImageError::IoError(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ &get_unsupported_error_message(c)[..],
+ )))
+ }
+ }
+
+ Ok(())
+ }
+
+ fn encode_rgb(
+ &mut self,
+ image: &[u8],
+ width: u32,
+ height: u32,
+ row_pad_size: u32,
+ bytes_per_pixel: u32,
+ ) -> io::Result<()> {
+ let width = width as usize;
+ let height = height as usize;
+ let x_stride = bytes_per_pixel as usize;
+ let y_stride = width * x_stride;
+ for row in (0..height).rev() {
+ // from the bottom up
+ let row_start = row * y_stride;
+ for px in image[row_start..][..y_stride].chunks_exact(x_stride) {
+ let r = px[0];
+ let g = px[1];
+ let b = px[2];
+ // written as BGR
+ self.writer.write_all(&[b, g, r])?;
+ }
+ self.write_row_pad(row_pad_size)?;
+ }
+
+ Ok(())
+ }
+
+ fn encode_rgba(
+ &mut self,
+ image: &[u8],
+ width: u32,
+ height: u32,
+ row_pad_size: u32,
+ bytes_per_pixel: u32,
+ ) -> io::Result<()> {
+ let width = width as usize;
+ let height = height as usize;
+ let x_stride = bytes_per_pixel as usize;
+ let y_stride = width * x_stride;
+ for row in (0..height).rev() {
+ // from the bottom up
+ let row_start = row * y_stride;
+ for px in image[row_start..][..y_stride].chunks_exact(x_stride) {
+ let r = px[0];
+ let g = px[1];
+ let b = px[2];
+ let a = px[3];
+ // written as BGRA
+ self.writer.write_all(&[b, g, r, a])?;
+ }
+ self.write_row_pad(row_pad_size)?;
+ }
+
+ Ok(())
+ }
+
+ fn encode_gray(
+ &mut self,
+ image: &[u8],
+ width: u32,
+ height: u32,
+ row_pad_size: u32,
+ bytes_per_pixel: u32,
+ palette: Option<&[[u8; 3]]>,
+ ) -> io::Result<()> {
+ // write grayscale palette
+ if let Some(palette) = palette {
+ for item in palette {
+ // each color is written as BGRA, where A is always 0
+ self.writer.write_all(&[item[2], item[1], item[0], 0])?;
+ }
+ } else {
+ for val in 0u8..=255 {
+ // each color is written as BGRA, where A is always 0 and since only grayscale is being written, B = G = R = index
+ self.writer.write_all(&[val, val, val, 0])?;
+ }
+ }
+
+ // write image data
+ let x_stride = bytes_per_pixel;
+ let y_stride = width * x_stride;
+ for row in (0..height).rev() {
+ // from the bottom up
+ let row_start = row * y_stride;
+ for col in 0..width {
+ let pixel_start = (row_start + (col * x_stride)) as usize;
+ // color value is equal to the palette index
+ self.writer.write_u8(image[pixel_start])?;
+ // alpha is never written as it's not widely supported
+ }
+
+ self.write_row_pad(row_pad_size)?;
+ }
+
+ Ok(())
+ }
+
+ fn write_row_pad(&mut self, row_pad_size: u32) -> io::Result<()> {
+ for _ in 0..row_pad_size {
+ self.writer.write_u8(0)?;
+ }
+
+ Ok(())
+ }
+}
+
+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).",
+ c
+ )
+}
+
+/// Returns a tuple representing: (dib header size, written pixel size, palette color count).
+fn get_pixel_info(c: color::ColorType, palette: Option<&[[u8; 3]]>) -> io::Result<(u32, u32, u32)> {
+ let sizes = match c {
+ color::ColorType::Rgb8 => (BITMAPINFOHEADER_SIZE, 3, 0),
+ color::ColorType::Rgba8 => (BITMAPV4HEADER_SIZE, 4, 0),
+ color::ColorType::L8 => (
+ BITMAPINFOHEADER_SIZE,
+ 1,
+ palette.map(|p| p.len()).unwrap_or(256) as u32,
+ ),
+ color::ColorType::La8 => (
+ BITMAPINFOHEADER_SIZE,
+ 1,
+ palette.map(|p| p.len()).unwrap_or(256) as u32,
+ ),
+ _ => {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ &get_unsupported_error_message(c)[..],
+ ))
+ }
+ };
+
+ Ok(sizes)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::BmpDecoder;
+ use super::BmpEncoder;
+ 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<u8> {
+ let mut encoded_data = Vec::new();
+ {
+ let mut encoder = BmpEncoder::new(&mut encoded_data);
+ encoder
+ .encode(&image, width, height, c)
+ .expect("could not encode image");
+ }
+
+ 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::Rgb8);
+ assert_eq!(3, decoded.len());
+ assert_eq!(255, decoded[0]);
+ assert_eq!(0, decoded[1]);
+ assert_eq!(0, decoded[2]);
+ }
+
+ #[test]
+ #[cfg(target_pointer_width = "64")]
+ fn huge_files_return_error() {
+ 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::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::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::Rgb8);
+ }
+
+ #[test]
+ fn round_trip_gray() {
+ let image = [0u8, 1, 2]; // 3 pixels
+ 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]);
+ assert_eq!(0, decoded[1]);
+ assert_eq!(0, decoded[2]);
+ assert_eq!(1, decoded[3]);
+ assert_eq!(1, decoded[4]);
+ assert_eq!(1, decoded[5]);
+ assert_eq!(2, decoded[6]);
+ assert_eq!(2, decoded[7]);
+ assert_eq!(2, decoded[8]);
+ }
+
+ #[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::La8);
+ // should be read back as 3 RGB pixels
+ assert_eq!(9, decoded.len());
+ assert_eq!(0, decoded[0]);
+ assert_eq!(0, decoded[1]);
+ assert_eq!(0, decoded[2]);
+ assert_eq!(1, decoded[3]);
+ assert_eq!(1, decoded[4]);
+ assert_eq!(1, decoded[5]);
+ assert_eq!(2, decoded[6]);
+ assert_eq!(2, decoded[7]);
+ assert_eq!(2, decoded[8]);
+ }
+}