diff options
Diffstat (limited to 'vendor/image/src/codecs/tga')
-rw-r--r-- | vendor/image/src/codecs/tga/decoder.rs | 502 | ||||
-rw-r--r-- | vendor/image/src/codecs/tga/encoder.rs | 215 | ||||
-rw-r--r-- | vendor/image/src/codecs/tga/header.rs | 150 | ||||
-rw-r--r-- | vendor/image/src/codecs/tga/mod.rs | 17 |
4 files changed, 884 insertions, 0 deletions
diff --git a/vendor/image/src/codecs/tga/decoder.rs b/vendor/image/src/codecs/tga/decoder.rs new file mode 100644 index 0000000..16243ce --- /dev/null +++ b/vendor/image/src/codecs/tga/decoder.rs @@ -0,0 +1,502 @@ +use super::header::{Header, ImageType, ALPHA_BIT_MASK, SCREEN_ORIGIN_BIT_MASK}; +use crate::{ + color::{ColorType, ExtendedColorType}, + error::{ + ImageError, ImageResult, LimitError, LimitErrorKind, UnsupportedError, UnsupportedErrorKind, + }, + image::{ImageDecoder, ImageFormat, ImageReadBuffer}, +}; +use byteorder::ReadBytesExt; +use std::{ + convert::TryFrom, + io::{self, Read, Seek}, + mem, +}; + +struct ColorMap { + /// sizes in bytes + start_offset: usize, + entry_size: usize, + bytes: Vec<u8>, +} + +impl ColorMap { + pub(crate) fn from_reader( + r: &mut dyn Read, + start_offset: u16, + num_entries: u16, + bits_per_entry: u8, + ) -> ImageResult<ColorMap> { + let bytes_per_entry = (bits_per_entry as usize + 7) / 8; + + let mut bytes = vec![0; bytes_per_entry * num_entries as usize]; + r.read_exact(&mut bytes)?; + + Ok(ColorMap { + entry_size: bytes_per_entry, + start_offset: start_offset as usize, + bytes, + }) + } + + /// Get one entry from the color map + pub(crate) fn get(&self, index: usize) -> Option<&[u8]> { + let entry = self.start_offset + self.entry_size * index; + self.bytes.get(entry..entry + self.entry_size) + } +} + +/// The representation of a TGA decoder +pub struct TgaDecoder<R> { + r: R, + + width: usize, + height: usize, + bytes_per_pixel: usize, + has_loaded_metadata: bool, + + image_type: ImageType, + color_type: ColorType, + original_color_type: Option<ExtendedColorType>, + + header: Header, + color_map: Option<ColorMap>, + + // Used in read_scanline + line_read: Option<usize>, + line_remain_buff: Vec<u8>, +} + +impl<R: Read + Seek> TgaDecoder<R> { + /// Create a new decoder that decodes from the stream `r` + pub fn new(r: R) -> ImageResult<TgaDecoder<R>> { + let mut decoder = TgaDecoder { + r, + + width: 0, + height: 0, + bytes_per_pixel: 0, + has_loaded_metadata: false, + + image_type: ImageType::Unknown, + color_type: ColorType::L8, + original_color_type: None, + + header: Header::default(), + color_map: None, + + line_read: None, + line_remain_buff: Vec::new(), + }; + decoder.read_metadata()?; + Ok(decoder) + } + + fn read_header(&mut self) -> ImageResult<()> { + self.header = Header::from_reader(&mut self.r)?; + self.image_type = ImageType::new(self.header.image_type); + self.width = self.header.image_width as usize; + self.height = self.header.image_height as usize; + self.bytes_per_pixel = (self.header.pixel_depth as usize + 7) / 8; + Ok(()) + } + + fn read_metadata(&mut self) -> ImageResult<()> { + if !self.has_loaded_metadata { + self.read_header()?; + self.read_image_id()?; + self.read_color_map()?; + self.read_color_information()?; + self.has_loaded_metadata = true; + } + Ok(()) + } + + /// Loads the color information for the decoder + /// + /// To keep things simple, we won't handle bit depths that aren't divisible + /// by 8 and are larger than 32. + fn read_color_information(&mut self) -> ImageResult<()> { + if self.header.pixel_depth % 8 != 0 || self.header.pixel_depth > 32 { + // Bit depth must be divisible by 8, and must be less than or equal + // to 32. + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Tga.into(), + UnsupportedErrorKind::Color(ExtendedColorType::Unknown( + self.header.pixel_depth, + )), + ), + )); + } + + let num_alpha_bits = self.header.image_desc & ALPHA_BIT_MASK; + + let other_channel_bits = if self.header.map_type != 0 { + self.header.map_entry_size + } else { + if num_alpha_bits > self.header.pixel_depth { + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Tga.into(), + UnsupportedErrorKind::Color(ExtendedColorType::Unknown( + self.header.pixel_depth, + )), + ), + )); + } + + self.header.pixel_depth - num_alpha_bits + }; + let color = self.image_type.is_color(); + + 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::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, + (8, 0, false) => { + // alpha-only image is treated as L8 + self.color_type = ColorType::L8; + self.original_color_type = Some(ExtendedColorType::A8); + } + _ => { + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Tga.into(), + UnsupportedErrorKind::Color(ExtendedColorType::Unknown( + self.header.pixel_depth, + )), + ), + )) + } + } + Ok(()) + } + + /// Read the image id field + /// + /// We're not interested in this field, so this function skips it if it + /// is present + fn read_image_id(&mut self) -> ImageResult<()> { + self.r + .seek(io::SeekFrom::Current(i64::from(self.header.id_length)))?; + Ok(()) + } + + fn read_color_map(&mut self) -> ImageResult<()> { + if self.header.map_type == 1 { + // FIXME: we could reverse the map entries, which avoids having to reverse all pixels + // in the final output individually. + self.color_map = Some(ColorMap::from_reader( + &mut self.r, + self.header.map_origin, + self.header.map_length, + self.header.map_entry_size, + )?); + } + Ok(()) + } + + /// Expands indices into its mapped color + fn expand_color_map(&self, pixel_data: &[u8]) -> io::Result<Vec<u8>> { + #[inline] + fn bytes_to_index(bytes: &[u8]) -> usize { + let mut result = 0usize; + for byte in bytes.iter() { + result = result << 8 | *byte as usize; + } + result + } + + 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); + + if self.bytes_per_pixel == 0 { + return Err(io::ErrorKind::Other.into()); + } + + let color_map = self + .color_map + .as_ref() + .ok_or_else(|| io::Error::from(io::ErrorKind::Other))?; + + for chunk in pixel_data.chunks(self.bytes_per_pixel) { + let index = bytes_to_index(chunk); + if let Some(color) = color_map.get(index) { + result.extend_from_slice(color); + } else { + return Err(io::ErrorKind::Other.into()); + } + } + + Ok(result) + } + + /// Reads a run length encoded data for given number of bytes + fn read_encoded_data(&mut self, num_bytes: usize) -> io::Result<Vec<u8>> { + let mut pixel_data = Vec::with_capacity(num_bytes); + let mut repeat_buf = Vec::with_capacity(self.bytes_per_pixel); + + while pixel_data.len() < num_bytes { + let run_packet = self.r.read_u8()?; + // If the highest bit in `run_packet` is set, then we repeat pixels + // + // Note: the TGA format adds 1 to both counts because having a count + // of 0 would be pointless. + if (run_packet & 0x80) != 0 { + // high bit set, so we will repeat the data + let repeat_count = ((run_packet & !0x80) + 1) as usize; + self.r + .by_ref() + .take(self.bytes_per_pixel as u64) + .read_to_end(&mut repeat_buf)?; + + // get the repeating pixels from the bytes of the pixel stored in `repeat_buf` + let data = repeat_buf + .iter() + .cycle() + .take(repeat_count * self.bytes_per_pixel); + pixel_data.extend(data); + repeat_buf.clear(); + } else { + // not set, so `run_packet+1` is the number of non-encoded pixels + let num_raw_bytes = (run_packet + 1) as usize * self.bytes_per_pixel; + self.r + .by_ref() + .take(num_raw_bytes as u64) + .read_to_end(&mut pixel_data)?; + } + } + + if pixel_data.len() > num_bytes { + // FIXME: the last packet contained more data than we asked for! + // This is at least a warning. We truncate the data since some methods rely on the + // length to be accurate in the success case. + pixel_data.truncate(num_bytes); + } + + Ok(pixel_data) + } + + /// Reads a run length encoded packet + fn read_all_encoded_data(&mut self) -> ImageResult<Vec<u8>> { + let num_bytes = self.width * self.height * self.bytes_per_pixel; + + Ok(self.read_encoded_data(num_bytes)?) + } + + /// Reads a run length encoded line + fn read_encoded_line(&mut self) -> io::Result<Vec<u8>> { + let line_num_bytes = self.width * self.bytes_per_pixel; + let remain_len = self.line_remain_buff.len(); + + if remain_len >= line_num_bytes { + // `Vec::split_to` if std had it + let bytes = { + let bytes_after = self.line_remain_buff.split_off(line_num_bytes); + mem::replace(&mut self.line_remain_buff, bytes_after) + }; + + return Ok(bytes); + } + + let num_bytes = line_num_bytes - remain_len; + + let line_data = self.read_encoded_data(num_bytes)?; + + let mut pixel_data = Vec::with_capacity(line_num_bytes); + pixel_data.append(&mut self.line_remain_buff); + pixel_data.extend_from_slice(&line_data[..num_bytes]); + + // put the remain data to line_remain_buff. + // expects `self.line_remain_buff` to be empty from + // the above `pixel_data.append` call + debug_assert!(self.line_remain_buff.is_empty()); + self.line_remain_buff + .extend_from_slice(&line_data[num_bytes..]); + + Ok(pixel_data) + } + + /// Reverse from BGR encoding to RGB encoding + /// + /// TGA files are stored in the BGRA encoding. This function swaps + /// the blue and red bytes in the `pixels` array. + fn reverse_encoding_in_output(&mut self, pixels: &mut [u8]) { + // We only need to reverse the encoding of color images + match self.color_type { + ColorType::Rgb8 | ColorType::Rgba8 => { + for chunk in pixels.chunks_mut(self.color_type.bytes_per_pixel().into()) { + chunk.swap(0, 2); + } + } + _ => {} + } + } + + /// Flip the image vertically depending on the screen origin bit + /// + /// The bit in position 5 of the image descriptor byte is the screen origin bit. + /// If it's 1, the origin is in the top left corner. + /// If it's 0, the origin is in the bottom left corner. + /// This function checks the bit, and if it's 0, flips the image vertically. + fn flip_vertically(&mut self, pixels: &mut [u8]) { + if self.is_flipped_vertically() { + if self.height == 0 { + return; + } + + let num_bytes = pixels.len(); + + let width_bytes = num_bytes / self.height; + + // Flip the image vertically. + for vertical_index in 0..(self.height / 2) { + let vertical_target = (self.height - vertical_index) * width_bytes - width_bytes; + + for horizontal_index in 0..width_bytes { + let source = vertical_index * width_bytes + horizontal_index; + let target = vertical_target + horizontal_index; + + pixels.swap(target, source); + } + } + } + } + + /// Check whether the image is vertically flipped + /// + /// The bit in position 5 of the image descriptor byte is the screen origin bit. + /// If it's 1, the origin is in the top left corner. + /// If it's 0, the origin is in the bottom left corner. + /// This function checks the bit, and if it's 0, flips the image vertically. + fn is_flipped_vertically(&self) -> bool { + let screen_origin_bit = SCREEN_ORIGIN_BIT_MASK & self.header.image_desc != 0; + !screen_origin_bit + } + + fn read_scanline(&mut self, buf: &mut [u8]) -> io::Result<usize> { + if let Some(line_read) = self.line_read { + if line_read == self.height { + return Ok(0); + } + } + + // read the pixels from the data region + let mut pixel_data = if self.image_type.is_encoded() { + self.read_encoded_line()? + } else { + let num_raw_bytes = self.width * 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_in_output(&mut pixel_data); + + // copy to the output buffer + buf[..pixel_data.len()].copy_from_slice(&pixel_data); + + self.line_read = Some(self.line_read.unwrap_or(0) + 1); + + Ok(pixel_data.len()) + } +} + +impl<'a, R: 'a + Read + Seek> ImageDecoder<'a> for TgaDecoder<R> { + type Reader = TGAReader<R>; + + fn dimensions(&self) -> (u32, u32) { + (self.width as u32, self.height as u32) + } + + fn color_type(&self) -> ColorType { + self.color_type + } + + fn original_color_type(&self) -> ExtendedColorType { + self.original_color_type + .unwrap_or_else(|| self.color_type().into()) + } + + fn scanline_bytes(&self) -> u64 { + // 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<Self::Reader> { + Ok(TGAReader { + buffer: ImageReadBuffer::new(self.scanline_bytes(), self.total_bytes()), + decoder: self, + }) + } + + fn read_image(mut self, buf: &mut [u8]) -> ImageResult<()> { + assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); + + // In indexed images, we might need more bytes than pixels to read them. That's nonsensical + // to encode but we'll not want to crash. + let mut fallback_buf = vec![]; + // read the pixels from the data region + let rawbuf = if self.image_type.is_encoded() { + let pixel_data = self.read_all_encoded_data()?; + if self.bytes_per_pixel <= usize::from(self.color_type.bytes_per_pixel()) { + buf[..pixel_data.len()].copy_from_slice(&pixel_data); + &buf[..pixel_data.len()] + } else { + fallback_buf = pixel_data; + &fallback_buf[..] + } + } else { + let num_raw_bytes = self.width * self.height * self.bytes_per_pixel; + if self.bytes_per_pixel <= usize::from(self.color_type.bytes_per_pixel()) { + self.r.by_ref().read_exact(&mut buf[..num_raw_bytes])?; + &buf[..num_raw_bytes] + } else { + fallback_buf.resize(num_raw_bytes, 0u8); + self.r + .by_ref() + .read_exact(&mut fallback_buf[..num_raw_bytes])?; + &fallback_buf[..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(rawbuf)?; + // not enough data to fill the buffer, or would overflow the buffer + if pixel_data.len() != buf.len() { + return Err(ImageError::Limits(LimitError::from_kind( + LimitErrorKind::DimensionError, + ))); + } + buf.copy_from_slice(&pixel_data); + } + + self.reverse_encoding_in_output(buf); + + self.flip_vertically(buf); + + Ok(()) + } +} + +pub struct TGAReader<R> { + buffer: ImageReadBuffer, + decoder: TgaDecoder<R>, +} +impl<R: Read + Seek> Read for TGAReader<R> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + let decoder = &mut self.decoder; + self.buffer.read(buf, |buf| decoder.read_scanline(buf)) + } +} diff --git a/vendor/image/src/codecs/tga/encoder.rs b/vendor/image/src/codecs/tga/encoder.rs new file mode 100644 index 0000000..cf34984 --- /dev/null +++ b/vendor/image/src/codecs/tga/encoder.rs @@ -0,0 +1,215 @@ +use super::header::Header; +use crate::{error::EncodingError, ColorType, ImageEncoder, ImageError, ImageFormat, ImageResult}; +use std::{convert::TryFrom, error, fmt, io::Write}; + +/// Errors that can occur during encoding and saving of a TGA image. +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +enum EncoderError { + /// Invalid TGA width. + WidthInvalid(u32), + + /// Invalid TGA height. + HeightInvalid(u32), +} + +impl fmt::Display for EncoderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + EncoderError::WidthInvalid(s) => f.write_fmt(format_args!("Invalid TGA width: {}", s)), + EncoderError::HeightInvalid(s) => { + f.write_fmt(format_args!("Invalid TGA height: {}", s)) + } + } + } +} + +impl From<EncoderError> for ImageError { + fn from(e: EncoderError) -> ImageError { + ImageError::Encoding(EncodingError::new(ImageFormat::Tga.into(), e)) + } +} + +impl error::Error for EncoderError {} + +/// TGA encoder. +pub struct TgaEncoder<W: Write> { + writer: W, +} + +impl<W: Write> TgaEncoder<W> { + /// Create a new encoder that writes its output to ```w```. + pub fn new(w: W) -> TgaEncoder<W> { + TgaEncoder { writer: w } + } + + /// Encodes the image ```buf``` that has dimensions ```width``` + /// and ```height``` and ```ColorType``` ```color_type```. + /// + /// The dimensions of the image must be between 0 and 65535 (inclusive) or + /// an error will be returned. + pub fn encode( + mut self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + // Validate dimensions. + let width = u16::try_from(width) + .map_err(|_| ImageError::from(EncoderError::WidthInvalid(width)))?; + + let height = u16::try_from(height) + .map_err(|_| ImageError::from(EncoderError::HeightInvalid(height)))?; + + // Write out TGA header. + let header = Header::from_pixel_info(color_type, width, height)?; + header.write_to(&mut self.writer)?; + + // Write out Bgr(a)8 or L(a)8 image data. + match color_type { + ColorType::Rgb8 | ColorType::Rgba8 => { + let mut image = Vec::from(buf); + + for chunk in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) { + chunk.swap(0, 2); + } + + self.writer.write_all(&image)?; + } + _ => { + self.writer.write_all(buf)?; + } + } + + Ok(()) + } +} + +impl<W: Write> ImageEncoder for TgaEncoder<W> { + fn write_image( + self, + buf: &[u8], + width: u32, + height: u32, + color_type: ColorType, + ) -> ImageResult<()> { + self.encode(buf, width, height, color_type) + } +} + +#[cfg(test)] +mod tests { + use super::{EncoderError, TgaEncoder}; + use crate::{codecs::tga::TgaDecoder, ColorType, ImageDecoder, ImageError}; + use std::{error::Error, io::Cursor}; + + fn round_trip_image(image: &[u8], width: u32, height: u32, c: ColorType) -> Vec<u8> { + let mut encoded_data = Vec::new(); + { + let encoder = TgaEncoder::new(&mut encoded_data); + encoder + .encode(&image, width, height, c) + .expect("could not encode image"); + } + + let decoder = TgaDecoder::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 test_image_width_too_large() { + // TGA cannot encode images larger than 65,535×65,535 + // create a 65,536×1 8-bit black image buffer + let size = usize::from(u16::MAX) + 1; + let dimension = size as u32; + let img = vec![0u8; size]; + // Try to encode an image that is too large + let mut encoded = Vec::new(); + let encoder = TgaEncoder::new(&mut encoded); + let result = encoder.encode(&img, dimension, 1, ColorType::L8); + match result { + Err(ImageError::Encoding(err)) => { + let err = err + .source() + .unwrap() + .downcast_ref::<EncoderError>() + .unwrap(); + assert_eq!(*err, EncoderError::WidthInvalid(dimension)); + } + other => panic!( + "Encoding an image that is too wide should return a InvalidWidth \ + it returned {:?} instead", + other + ), + } + } + + #[test] + fn test_image_height_too_large() { + // TGA cannot encode images larger than 65,535×65,535 + // create a 65,536×1 8-bit black image buffer + let size = usize::from(u16::MAX) + 1; + let dimension = size as u32; + let img = vec![0u8; size]; + // Try to encode an image that is too large + let mut encoded = Vec::new(); + let encoder = TgaEncoder::new(&mut encoded); + let result = encoder.encode(&img, 1, dimension, ColorType::L8); + match result { + Err(ImageError::Encoding(err)) => { + let err = err + .source() + .unwrap() + .downcast_ref::<EncoderError>() + .unwrap(); + assert_eq!(*err, EncoderError::HeightInvalid(dimension)); + } + other => panic!( + "Encoding an image that is too tall should return a InvalidHeight \ + it returned {:?} instead", + other + ), + } + } + + #[test] + fn round_trip_single_pixel_rgb() { + let image = [0, 1, 2]; + let decoded = round_trip_image(&image, 1, 1, ColorType::Rgb8); + assert_eq!(decoded.len(), image.len()); + assert_eq!(decoded.as_slice(), image); + } + + #[test] + fn round_trip_single_pixel_rgba() { + let image = [0, 1, 2, 3]; + let decoded = round_trip_image(&image, 1, 1, ColorType::Rgba8); + assert_eq!(decoded.len(), image.len()); + assert_eq!(decoded.as_slice(), image); + } + + #[test] + fn round_trip_gray() { + let image = [0, 1, 2]; + let decoded = round_trip_image(&image, 3, 1, ColorType::L8); + assert_eq!(decoded.len(), image.len()); + assert_eq!(decoded.as_slice(), image); + } + + #[test] + fn round_trip_graya() { + let image = [0, 1, 2, 3, 4, 5]; + let decoded = round_trip_image(&image, 1, 3, ColorType::La8); + assert_eq!(decoded.len(), image.len()); + assert_eq!(decoded.as_slice(), image); + } + + #[test] + fn round_trip_3px_rgb() { + let image = [0; 3 * 3 * 3]; // 3x3 pixels, 3 bytes per pixel + let _decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8); + } +} diff --git a/vendor/image/src/codecs/tga/header.rs b/vendor/image/src/codecs/tga/header.rs new file mode 100644 index 0000000..83ba7a3 --- /dev/null +++ b/vendor/image/src/codecs/tga/header.rs @@ -0,0 +1,150 @@ +use crate::{ + error::{UnsupportedError, UnsupportedErrorKind}, + ColorType, ImageError, ImageFormat, ImageResult, +}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Read, Write}; + +pub(crate) const ALPHA_BIT_MASK: u8 = 0b1111; +pub(crate) const SCREEN_ORIGIN_BIT_MASK: u8 = 0b10_0000; + +pub(crate) enum ImageType { + NoImageData = 0, + /// Uncompressed images. + RawColorMap = 1, + RawTrueColor = 2, + RawGrayScale = 3, + /// Run length encoded images. + RunColorMap = 9, + RunTrueColor = 10, + RunGrayScale = 11, + Unknown, +} + +impl ImageType { + /// Create a new image type from a u8. + pub(crate) fn new(img_type: u8) -> ImageType { + match img_type { + 0 => ImageType::NoImageData, + + 1 => ImageType::RawColorMap, + 2 => ImageType::RawTrueColor, + 3 => ImageType::RawGrayScale, + + 9 => ImageType::RunColorMap, + 10 => ImageType::RunTrueColor, + 11 => ImageType::RunGrayScale, + + _ => ImageType::Unknown, + } + } + + /// Check if the image format uses colors as opposed to gray scale. + pub(crate) fn is_color(&self) -> bool { + matches! { *self, + ImageType::RawColorMap + | ImageType::RawTrueColor + | ImageType::RunTrueColor + | ImageType::RunColorMap + } + } + + /// Does the image use a color map. + pub(crate) fn is_color_mapped(&self) -> bool { + matches! { *self, ImageType::RawColorMap | ImageType::RunColorMap } + } + + /// Is the image run length encoded. + pub(crate) fn is_encoded(&self) -> bool { + matches! {*self, ImageType::RunColorMap | ImageType::RunTrueColor | ImageType::RunGrayScale } + } +} + +/// Header used by TGA image files. +#[derive(Debug, Default)] +pub(crate) struct Header { + pub(crate) id_length: u8, // length of ID string + pub(crate) map_type: u8, // color map type + pub(crate) image_type: u8, // image type code + pub(crate) map_origin: u16, // starting index of map + pub(crate) map_length: u16, // length of map + pub(crate) map_entry_size: u8, // size of map entries in bits + pub(crate) x_origin: u16, // x-origin of image + pub(crate) y_origin: u16, // y-origin of image + pub(crate) image_width: u16, // width of image + pub(crate) image_height: u16, // height of image + pub(crate) pixel_depth: u8, // bits per pixel + pub(crate) image_desc: u8, // image descriptor +} + +impl Header { + /// Load the header with values from pixel information. + pub(crate) fn from_pixel_info( + color_type: ColorType, + width: u16, + height: u16, + ) -> ImageResult<Self> { + let mut header = Self::default(); + + if width > 0 && height > 0 { + let (num_alpha_bits, other_channel_bits, image_type) = match color_type { + ColorType::Rgba8 => (8, 24, ImageType::RawTrueColor), + ColorType::Rgb8 => (0, 24, ImageType::RawTrueColor), + ColorType::La8 => (8, 8, ImageType::RawGrayScale), + ColorType::L8 => (0, 8, ImageType::RawGrayScale), + _ => { + return Err(ImageError::Unsupported( + UnsupportedError::from_format_and_kind( + ImageFormat::Tga.into(), + UnsupportedErrorKind::Color(color_type.into()), + ), + )) + } + }; + + header.image_type = image_type as u8; + header.image_width = width; + header.image_height = height; + header.pixel_depth = num_alpha_bits + other_channel_bits; + header.image_desc = num_alpha_bits & ALPHA_BIT_MASK; + header.image_desc |= SCREEN_ORIGIN_BIT_MASK; // Upper left origin. + } + + Ok(header) + } + + /// Load the header with values from the reader. + pub(crate) fn from_reader(r: &mut dyn Read) -> ImageResult<Self> { + Ok(Self { + id_length: r.read_u8()?, + map_type: r.read_u8()?, + image_type: r.read_u8()?, + map_origin: r.read_u16::<LittleEndian>()?, + map_length: r.read_u16::<LittleEndian>()?, + map_entry_size: r.read_u8()?, + x_origin: r.read_u16::<LittleEndian>()?, + y_origin: r.read_u16::<LittleEndian>()?, + image_width: r.read_u16::<LittleEndian>()?, + image_height: r.read_u16::<LittleEndian>()?, + pixel_depth: r.read_u8()?, + image_desc: r.read_u8()?, + }) + } + + /// Write out the header values. + pub(crate) fn write_to(&self, w: &mut dyn Write) -> ImageResult<()> { + w.write_u8(self.id_length)?; + w.write_u8(self.map_type)?; + w.write_u8(self.image_type)?; + w.write_u16::<LittleEndian>(self.map_origin)?; + w.write_u16::<LittleEndian>(self.map_length)?; + w.write_u8(self.map_entry_size)?; + w.write_u16::<LittleEndian>(self.x_origin)?; + w.write_u16::<LittleEndian>(self.y_origin)?; + w.write_u16::<LittleEndian>(self.image_width)?; + w.write_u16::<LittleEndian>(self.image_height)?; + w.write_u8(self.pixel_depth)?; + w.write_u8(self.image_desc)?; + Ok(()) + } +} diff --git a/vendor/image/src/codecs/tga/mod.rs b/vendor/image/src/codecs/tga/mod.rs new file mode 100644 index 0000000..fdc2f0c --- /dev/null +++ b/vendor/image/src/codecs/tga/mod.rs @@ -0,0 +1,17 @@ +//! Decoding of TGA Images +//! +//! # Related Links +//! <http://googlesites.inequation.org/tgautilities> + +/// A decoder for TGA images +/// +/// Currently this decoder does not support 8, 15 and 16 bit color images. +pub use self::decoder::TgaDecoder; + +//TODO add 8, 15, 16 bit color support + +pub use self::encoder::TgaEncoder; + +mod decoder; +mod encoder; +mod header; |