Skip to content

Commit

Permalink
Support 16-bit encoded TGA images.
Browse files Browse the repository at this point in the history
The decoder does the same thing as the BMP decoder, and converts 16-bit images to 32-bit ones on load.
  • Loading branch information
red-001 committed Aug 6, 2024
1 parent e176cd4 commit e1e23f1
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 109 deletions.
119 changes: 17 additions & 102 deletions src/codecs/bmp/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,16 @@ use crate::error::{
use crate::image::{self, ImageDecoder, ImageFormat};
use crate::ImageDecoderRect;

use crate::utils::packed_color::Bitfield;
use crate::utils::packed_color::{BitfieldError, Bitfields};

const BITMAPCOREHEADER_SIZE: u32 = 12;
const BITMAPINFOHEADER_SIZE: u32 = 40;
const BITMAPV2HEADER_SIZE: u32 = 52;
const BITMAPV3HEADER_SIZE: u32 = 56;
const BITMAPV4HEADER_SIZE: u32 = 108;
const BITMAPV5HEADER_SIZE: u32 = 124;

static LOOKUP_TABLE_3_BIT_TO_8_BIT: [u8; 8] = [0, 36, 73, 109, 146, 182, 219, 255];
static LOOKUP_TABLE_4_BIT_TO_8_BIT: [u8; 16] = [
0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255,
];
static LOOKUP_TABLE_5_BIT_TO_8_BIT: [u8; 32] = [
0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173,
181, 189, 197, 206, 214, 222, 230, 239, 247, 255,
];
static LOOKUP_TABLE_6_BIT_TO_8_BIT: [u8; 64] = [
0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93,
97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170,
174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247,
251, 255,
];

static R5_G5_B5_COLOR_MASK: Bitfields = Bitfields {
r: Bitfield { len: 5, shift: 10 },
g: Bitfield { len: 5, shift: 5 },
Expand Down Expand Up @@ -120,14 +108,8 @@ enum DecoderError {
// Failed to decompress RLE data.
CorruptRleData,

/// The bitfield mask interleaves set and unset bits
BitfieldMaskNonContiguous,
/// Bitfield mask invalid (e.g. too long for specified type)
BitfieldMaskInvalid,
/// Bitfield (of the specified width – 16- or 32-bit) mask not present
BitfieldMaskMissing(u32),
/// Bitfield (of the specified width – 16- or 32-bit) masks not present
BitfieldMasksMissing(u32),
BitfieldError(BitfieldError),

/// BMP's "BM" signature wrong or missing
BmpSignatureInvalid,
Expand Down Expand Up @@ -164,13 +146,8 @@ impl fmt::Display for DecoderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecoderError::CorruptRleData => f.write_str("Corrupt RLE data"),
DecoderError::BitfieldMaskNonContiguous => f.write_str("Non-contiguous bitfield mask"),
DecoderError::BitfieldMaskInvalid => f.write_str("Invalid bitfield mask"),
DecoderError::BitfieldMaskMissing(bb) => {
f.write_fmt(format_args!("Missing {bb}-bit bitfield mask"))
}
DecoderError::BitfieldMasksMissing(bb) => {
f.write_fmt(format_args!("Missing {bb}-bit bitfield masks"))
DecoderError::BitfieldError(bb) => {
f.write_fmt(format_args!("Bitfield error: {bb}"))
}
DecoderError::BmpSignatureInvalid => f.write_str("BMP signature not found"),
DecoderError::MoreThanOnePlane => f.write_str("More than one plane"),
Expand Down Expand Up @@ -207,6 +184,15 @@ impl From<DecoderError> for ImageError {
}
}

impl From<BitfieldError> for ImageError {
fn from(e: BitfieldError) -> ImageError {
ImageError::Decoding(DecodingError::new(
ImageFormat::Bmp.into(),
DecoderError::BitfieldError(e),
))
}
}

impl error::Error for DecoderError {}

/// Distinct image types whose saved channel width can be invalid
Expand Down Expand Up @@ -397,77 +383,6 @@ fn set_1bit_pixel_run<'a, T: Iterator<Item = &'a u8>>(
}
}

#[derive(PartialEq, Eq)]
struct Bitfield {
shift: u32,
len: u32,
}

impl Bitfield {
fn from_mask(mask: u32, max_len: u32) -> ImageResult<Bitfield> {
if mask == 0 {
return Ok(Bitfield { shift: 0, len: 0 });
}
let mut shift = mask.trailing_zeros();
let mut len = (!(mask >> shift)).trailing_zeros();
if len != mask.count_ones() {
return Err(DecoderError::BitfieldMaskNonContiguous.into());
}
if len + shift > max_len {
return Err(DecoderError::BitfieldMaskInvalid.into());
}
if len > 8 {
shift += len - 8;
len = 8;
}
Ok(Bitfield { shift, len })
}

fn read(&self, data: u32) -> u8 {
let data = data >> self.shift;
match self.len {
1 => ((data & 0b1) * 0xff) as u8,
2 => ((data & 0b11) * 0x55) as u8,
3 => LOOKUP_TABLE_3_BIT_TO_8_BIT[(data & 0b00_0111) as usize],
4 => LOOKUP_TABLE_4_BIT_TO_8_BIT[(data & 0b00_1111) as usize],
5 => LOOKUP_TABLE_5_BIT_TO_8_BIT[(data & 0b01_1111) as usize],
6 => LOOKUP_TABLE_6_BIT_TO_8_BIT[(data & 0b11_1111) as usize],
7 => ((data & 0x7f) << 1 | (data & 0x7f) >> 6) as u8,
8 => (data & 0xff) as u8,
_ => panic!(),
}
}
}

#[derive(PartialEq, Eq)]
struct Bitfields {
r: Bitfield,
g: Bitfield,
b: Bitfield,
a: Bitfield,
}

impl Bitfields {
fn from_mask(
r_mask: u32,
g_mask: u32,
b_mask: u32,
a_mask: u32,
max_len: u32,
) -> ImageResult<Bitfields> {
let bitfields = Bitfields {
r: Bitfield::from_mask(r_mask, max_len)?,
g: Bitfield::from_mask(g_mask, max_len)?,
b: Bitfield::from_mask(b_mask, max_len)?,
a: Bitfield::from_mask(a_mask, max_len)?,
};
if bitfields.r.len == 0 || bitfields.g.len == 0 || bitfields.b.len == 0 {
return Err(DecoderError::BitfieldMaskMissing(max_len).into());
}
Ok(bitfields)
}
}

/// A bmp decoder
pub struct BmpDecoder<R> {
reader: R,
Expand Down Expand Up @@ -1308,7 +1223,7 @@ impl<R: BufRead + Seek> BmpDecoder<R> {
ImageType::RLE4 => self.read_rle_data(buf, ImageType::RLE4),
ImageType::Bitfields16 => match self.bitfields {
Some(_) => self.read_16_bit_pixel_data(buf, None),
None => Err(DecoderError::BitfieldMasksMissing(16).into()),
None => Err(BitfieldError::MasksMissing(16).into()),
},
ImageType::Bitfields32 => match self.bitfields {
Some(R8_G8_B8_COLOR_MASK) => {
Expand All @@ -1318,7 +1233,7 @@ impl<R: BufRead + Seek> BmpDecoder<R> {
self.read_full_byte_pixel_data(buf, &FormatFullBytes::RGBA32)
}
Some(_) => self.read_32_bit_pixel_data(buf),
None => Err(DecoderError::BitfieldMasksMissing(32).into()),
None => Err(BitfieldError::MasksMissing(32).into()),
},
}
}
Expand Down
97 changes: 90 additions & 7 deletions src/codecs/tga/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,29 @@ use super::header::{Header, ImageType, ALPHA_BIT_MASK, SCREEN_ORIGIN_BIT_MASK};
use crate::{
color::{ColorType, ExtendedColorType},
error::{
ImageError, ImageResult, LimitError, LimitErrorKind, UnsupportedError, UnsupportedErrorKind,
ImageError, ImageFormatHint, ImageResult, LimitError, LimitErrorKind, UnsupportedError,
UnsupportedErrorKind,
},
image::{ImageDecoder, ImageFormat},
utils::packed_color::{Bitfield, Bitfields},
};
use byteorder_lite::{LittleEndian, ReadBytesExt};
use num_traits::ToPrimitive;
use std::io::{self, Cursor, Read};

static R5_G5_B5_COLOR_MASK: Bitfields = Bitfields {
r: Bitfield { len: 5, shift: 10 },
g: Bitfield { len: 5, shift: 5 },
b: Bitfield { len: 5, shift: 0 },
a: Bitfield { len: 0, shift: 0 },
};

static R5_G5_B5_A1_COLOR_MASK: Bitfields = Bitfields {
r: Bitfield { len: 5, shift: 10 },
g: Bitfield { len: 5, shift: 5 },
b: Bitfield { len: 5, shift: 0 },
a: Bitfield { len: 1, shift: 15 },
};
use byteorder_lite::ReadBytesExt;
use std::io::{self, Read};

struct ColorMap {
/// sizes in bytes
Expand Down Expand Up @@ -57,6 +74,8 @@ pub struct TgaDecoder<R> {

header: Header,
color_map: Option<ColorMap>,

packing: Option<&'static Bitfields>,
}

impl<R: Read> TgaDecoder<R> {
Expand All @@ -76,6 +95,8 @@ impl<R: Read> TgaDecoder<R> {

header: Header::default(),
color_map: None,

packing: None,
};
decoder.read_metadata()?;
Ok(decoder)
Expand Down Expand Up @@ -152,6 +173,16 @@ impl<R: Read> TgaDecoder<R> {
self.color_type = ColorType::L8;
self.original_color_type = Some(ExtendedColorType::A8);
}
(0, 15, false) => {
self.color_type = ColorType::Rgb8;
self.original_color_type = Some(ExtendedColorType::Rgb5);
self.packing = Some(&R5_G5_B5_COLOR_MASK);
}
(1, 15, true) => {
self.color_type = ColorType::Rgba8;
self.original_color_type = Some(ExtendedColorType::Rgb5a1);
self.packing = Some(&R5_G5_B5_A1_COLOR_MASK);
}
_ => {
return Err(ImageError::Unsupported(
UnsupportedError::from_format_and_kind(
Expand Down Expand Up @@ -350,16 +381,21 @@ impl<R: Read> ImageDecoder for TgaDecoder<R> {
.unwrap_or_else(|| self.color_type().into())
}

#[allow(clippy::identity_op)]
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.
//
// also used for packed (<8 bit per channel) images
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()) {
if self.bytes_per_pixel <= usize::from(self.color_type.bytes_per_pixel())
&& self.packing.is_none()
{
buf[..pixel_data.len()].copy_from_slice(&pixel_data);
&buf[..pixel_data.len()]
} else {
Expand All @@ -368,7 +404,9 @@ impl<R: Read> ImageDecoder for TgaDecoder<R> {
}
} 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()) {
if self.bytes_per_pixel <= usize::from(self.color_type.bytes_per_pixel())
&& self.packing.is_none()
{
self.r.by_ref().read_exact(&mut buf[..num_raw_bytes])?;
&buf[..num_raw_bytes]
} else {
Expand All @@ -389,10 +427,55 @@ impl<R: Read> ImageDecoder for TgaDecoder<R> {
LimitErrorKind::DimensionError,
)));
}
buf.copy_from_slice(&pixel_data);

if self.packing.is_none() {
buf.copy_from_slice(&pixel_data);
} else {
fallback_buf.resize(buf.len(), 0);
fallback_buf.copy_from_slice(&pixel_data);
}
}

self.reverse_encoding_in_output(buf);
if let Some(bitfields) = &self.packing {
let num_pixels = self.width * self.height;
let bytes_per_unpacked_pixel = if bitfields.a.len > 0 { 4 } else { 3 };
let mut stream = Cursor::new(fallback_buf);
let bytes_per_packed_pixel = self.bytes_per_pixel;

// this check shouldn't get hit, unsupported formats should have been rejected in `read_color_information`
// but it seemed better to check this here instead of panicing below if there is an issue
if !(1..=3).contains(&bytes_per_packed_pixel) {
return Err(ImageError::Unsupported(
UnsupportedError::from_format_and_kind(
ImageFormatHint::Exact(ImageFormat::Tga),
UnsupportedErrorKind::GenericFeature(
"Unsupported packed pixel format".to_string(),
),
),
));
}

for i in 0..num_pixels {
let value = match bytes_per_packed_pixel {
1 => stream.read_u8().map(|i| -> u32 { i.to_u32().unwrap() }),
2 => stream
.read_u16::<LittleEndian>()
.map(|i| -> u32 { i.to_u32().unwrap() }),
3 => stream.read_u24::<LittleEndian>(),
_ => unimplemented!(),
}
.map_err(|e| -> ImageError { ImageError::IoError(e) })?;

buf[i * bytes_per_unpacked_pixel + 0] = bitfields.r.read(value);
buf[i * bytes_per_unpacked_pixel + 1] = bitfields.g.read(value);
buf[i * bytes_per_unpacked_pixel + 2] = bitfields.b.read(value);
if bytes_per_unpacked_pixel == 4 {
buf[i * bytes_per_unpacked_pixel + 3] = bitfields.a.read(value);
}
}
} else {
self.reverse_encoding_in_output(buf);
}

self.flip_vertically(buf);

Expand Down
8 changes: 8 additions & 0 deletions src/color.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ pub enum ExtendedColorType {
Rgb4,
/// Pixel is 4-bit RGB with an alpha channel
Rgba4,
/// Pixel is 5-bit RGB with one unused bit
Rgb5,
/// Pixel is 5-bit RGB with one bit alpha
Rgb5a1,
/// Pixel is 8-bit luminance
L8,
/// Pixel is 8-bit luminance with an alpha channel
Expand Down Expand Up @@ -179,13 +183,15 @@ impl ExtendedColorType {
ExtendedColorType::Rgb1
| ExtendedColorType::Rgb2
| ExtendedColorType::Rgb4
| ExtendedColorType::Rgb5
| ExtendedColorType::Rgb8
| ExtendedColorType::Rgb16
| ExtendedColorType::Rgb32F
| ExtendedColorType::Bgr8 => 3,
ExtendedColorType::Rgba1
| ExtendedColorType::Rgba2
| ExtendedColorType::Rgba4
| ExtendedColorType::Rgb5a1
| ExtendedColorType::Rgba8
| ExtendedColorType::Rgba16
| ExtendedColorType::Rgba32F
Expand Down Expand Up @@ -213,6 +219,8 @@ impl ExtendedColorType {
ExtendedColorType::Rgba4 => 16,
ExtendedColorType::L8 => 8,
ExtendedColorType::La8 => 16,
ExtendedColorType::Rgb5 => 16, // somewhat of a lie, it's actually 15 bits of image data and one unused bit.
ExtendedColorType::Rgb5a1 => 16,
ExtendedColorType::Rgb8 => 24,
ExtendedColorType::Rgba8 => 32,
ExtendedColorType::L16 => 16,
Expand Down
2 changes: 2 additions & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

use std::iter::repeat;

pub(crate) mod packed_color;

#[inline(always)]
pub(crate) fn expand_packed<F>(buf: &mut [u8], channels: usize, bit_depth: u8, mut func: F)
where
Expand Down
Loading

0 comments on commit e1e23f1

Please sign in to comment.