use super::header::Header;
use crate::{
codecs::tga::header::ImageType, error::EncodingError, ColorType, ImageEncoder, ImageError,
ImageFormat, ImageResult,
};
use std::{error, fmt, io::Write};
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum EncoderError {
WidthInvalid(u32),
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 {}
pub struct TgaEncoder<W: Write> {
writer: W,
use_rle: bool,
}
const MAX_RUN_LENGTH: u8 = 128;
#[derive(Debug, Eq, PartialEq)]
enum PacketType {
Raw,
Rle,
}
impl<W: Write> TgaEncoder<W> {
pub fn new(w: W) -> TgaEncoder<W> {
TgaEncoder {
writer: w,
use_rle: true,
}
}
pub fn disable_rle(mut self) -> TgaEncoder<W> {
self.use_rle = false;
self
}
fn write_raw_packet(&mut self, pixels: &[u8], counter: u8) -> ImageResult<()> {
let header = counter - 1;
self.writer.write_all(&[header])?;
self.writer.write_all(pixels)?;
Ok(())
}
fn write_rle_encoded_packet(&mut self, pixel: &[u8], counter: u8) -> ImageResult<()> {
let header = 0x80 | (counter - 1);
self.writer.write_all(&[header])?;
self.writer.write_all(pixel)?;
Ok(())
}
fn run_length_encode(&mut self, image: &[u8], color_type: ColorType) -> ImageResult<()> {
use PacketType::*;
let bytes_per_pixel = color_type.bytes_per_pixel();
let capacity_in_bytes = usize::from(MAX_RUN_LENGTH) * usize::from(bytes_per_pixel);
let mut buf = Vec::with_capacity(capacity_in_bytes);
let mut counter = 0;
let mut prev_pixel = None;
let mut packet_type = Rle;
for pixel in image.chunks(usize::from(bytes_per_pixel)) {
if let Some(prev) = prev_pixel {
if pixel == prev {
if packet_type == Raw && counter > 0 {
self.write_raw_packet(&buf, counter)?;
counter = 0;
buf.clear();
}
packet_type = Rle;
} else if packet_type == Rle && counter > 0 {
self.write_rle_encoded_packet(prev, counter)?;
counter = 0;
packet_type = Raw;
buf.clear();
}
}
counter += 1;
buf.extend_from_slice(pixel);
debug_assert!(buf.len() <= capacity_in_bytes);
if counter == MAX_RUN_LENGTH {
match packet_type {
Rle => self.write_rle_encoded_packet(prev_pixel.unwrap(), counter),
Raw => self.write_raw_packet(&buf, counter),
}?;
counter = 0;
packet_type = Rle;
buf.clear();
}
prev_pixel = Some(pixel);
}
if counter > 0 {
match packet_type {
Rle => self.write_rle_encoded_packet(prev_pixel.unwrap(), counter),
Raw => self.write_raw_packet(&buf, counter),
}?;
}
Ok(())
}
#[track_caller]
pub fn encode(
mut self,
buf: &[u8],
width: u32,
height: u32,
color_type: ColorType,
) -> ImageResult<()> {
let expected_buffer_len =
(width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64);
assert_eq!(
expected_buffer_len,
buf.len() as u64,
"Invalid buffer length: expected {expected_buffer_len} got {} for {width}x{height} image",
buf.len(),
);
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)))?;
let header = Header::from_pixel_info(color_type, width, height, self.use_rle)?;
header.write_to(&mut self.writer)?;
let image_type = ImageType::new(header.image_type);
match image_type {
ImageType::RunTrueColor | ImageType::RunGrayScale => {
match color_type {
ColorType::Rgb8 | ColorType::Rgba8 => {
let mut image = Vec::from(buf);
for pixel in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) {
pixel.swap(0, 2);
}
self.run_length_encode(&image, color_type)?;
}
_ => {
self.run_length_encode(buf, color_type)?;
}
}
}
_ => {
match color_type {
ColorType::Rgb8 | ColorType::Rgba8 => {
let mut image = Vec::from(buf);
for pixel in image.chunks_mut(usize::from(color_type.bytes_per_pixel())) {
pixel.swap(0, 2);
}
self.writer.write_all(&image)?;
}
_ => {
self.writer.write_all(buf)?;
}
}
}
}
Ok(())
}
}
impl<W: Write> ImageEncoder for TgaEncoder<W> {
#[track_caller]
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};
#[test]
fn test_image_width_too_large() {
let size = usize::from(u16::MAX) + 1;
let dimension = size as u32;
let img = vec![0u8; size];
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() {
let size = usize::from(u16::MAX) + 1;
let dimension = size as u32;
let img = vec![0u8; size];
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 test_compression_diff() {
let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2];
let uncompressed_bytes = {
let mut encoded_data = Vec::new();
let encoder = TgaEncoder::new(&mut encoded_data).disable_rle();
encoder
.encode(&image, 5, 1, ColorType::Rgb8)
.expect("could not encode image");
encoded_data
};
let compressed_bytes = {
let mut encoded_data = Vec::new();
let encoder = TgaEncoder::new(&mut encoded_data);
encoder
.encode(&image, 5, 1, ColorType::Rgb8)
.expect("could not encode image");
encoded_data
};
assert!(uncompressed_bytes.len() > compressed_bytes.len());
}
mod compressed {
use super::*;
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 mixed_packets() {
let image = [
255, 255, 255, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255,
];
let decoded = round_trip_image(&image, 5, 1, ColorType::Rgb8);
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_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_three_pixel_rgb() {
let image = [0, 1, 2, 0, 1, 2, 0, 1, 2];
let decoded = round_trip_image(&image, 3, 1, ColorType::Rgb8);
assert_eq!(decoded.len(), image.len());
assert_eq!(decoded.as_slice(), image);
}
#[test]
fn round_trip_3px_rgb() {
let image = [0; 3 * 3 * 3]; let decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8);
assert_eq!(decoded.len(), image.len());
assert_eq!(decoded.as_slice(), image);
}
#[test]
fn round_trip_different() {
let image = [0, 1, 2, 0, 1, 3, 0, 1, 4];
let decoded = round_trip_image(&image, 3, 1, ColorType::Rgb8);
assert_eq!(decoded.len(), image.len());
assert_eq!(decoded.as_slice(), image);
}
#[test]
fn round_trip_different_2() {
let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4];
let decoded = round_trip_image(&image, 4, 1, ColorType::Rgb8);
assert_eq!(decoded.len(), image.len());
assert_eq!(decoded.as_slice(), image);
}
#[test]
fn round_trip_different_3() {
let image = [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 4, 0, 1, 2];
let decoded = round_trip_image(&image, 5, 1, ColorType::Rgb8);
assert_eq!(decoded.len(), image.len());
assert_eq!(decoded.as_slice(), image);
}
#[test]
fn round_trip_bw() {
let image = crate::open("tests/images/tga/encoding/black_white.tga").unwrap();
let (width, height) = (image.width(), image.height());
let image = image.as_rgb8().unwrap().to_vec();
let decoded = round_trip_image(&image, width, height, ColorType::Rgb8);
assert_eq!(decoded.len(), image.len());
assert_eq!(decoded.as_slice(), image);
}
}
mod uncompressed {
use super::*;
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).disable_rle();
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 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]; let decoded = round_trip_image(&image, 3, 3, ColorType::Rgb8);
assert_eq!(decoded.len(), image.len());
assert_eq!(decoded.as_slice(), image);
}
}
}