Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support 5/1-bit encoded TGA images. #2303

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
119 changes: 107 additions & 12 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 @@ -103,12 +124,13 @@ impl<R: Read> TgaDecoder<R> {

/// 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.
/// To keep things simple, we won't handle bit depths that are larger than 32.
fn read_color_information(&mut self) -> ImageResult<()> {
if self.header.pixel_depth % 8 != 0 || self.header.pixel_depth > 32 {
if self.header.pixel_depth != 15
&& (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.
// to 32 OR be 15-bit
return Err(ImageError::Unsupported(
UnsupportedError::from_format_and_kind(
ImageFormat::Tga.into(),
Expand Down Expand Up @@ -137,6 +159,7 @@ impl<R: Read> TgaDecoder<R> {

self.header.pixel_depth - num_alpha_bits
};

let color = self.image_type.is_color();

match (num_alpha_bits, other_channel_bits, color) {
Expand All @@ -152,6 +175,16 @@ impl<R: Read> TgaDecoder<R> {
self.color_type = ColorType::L8;
self.original_color_type = Some(ExtendedColorType::A8);
}
(0, 15 | 16, true) => {
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 +383,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 +406,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 @@ -384,15 +424,70 @@ impl<R: Read> ImageDecoder for TgaDecoder<R> {
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() {
if self.packing.is_none() && pixel_data.len() != buf.len() {
return Err(ImageError::Limits(LimitError::from_kind(
LimitErrorKind::DimensionError,
)));
}
buf.copy_from_slice(&pixel_data);

if self.packing.is_none() {
buf.copy_from_slice(&pixel_data);
} else {
fallback_buf.resize(pixel_data.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 = if self.header.map_type == 0 {
self.bytes_per_pixel
} else {
(self.header.map_entry_size as usize + 7) / 8
};

if num_pixels * bytes_per_unpacked_pixel != buf.len() {
return Err(ImageError::Limits(LimitError::from_kind(
LimitErrorKind::DimensionError,
)));
}

// 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
Loading
Loading