Lokathor/imagine

bmp: restore

Closed this issue · 3 comments

We just need to restore bmp support from the 0.0.8 version

imagine/examples/demo.rs

Lines 374 to 881 in da17bf5

fn parse_me_a_bmp_yo(bmp: &[u8]) -> Result<(Vec<RGBA8>, u32, u32), BmpError> {
println!("== Parsing a BMP...");
let (file_header, rest) = BmpFileHeader::try_from_bytes(bmp)?;
println!("file_header: {:?}", file_header);
if file_header.total_file_size as usize != bmp.len()
|| !(COMMON_BMP_TAGS.contains(&file_header.tag))
{
println!("actual size: {}", bmp.len());
return Err(BmpError::ThisIsProbablyNotABmpFile);
}
let (info_header, mut rest) = BmpInfoHeader::try_from_bytes(rest)?;
println!("info_header: {info_header:?}", info_header = info_header);
let compression = info_header.compression();
let bits_per_pixel = info_header.bits_per_pixel() as usize;
let width: usize = info_header.width().unsigned_abs() as usize;
let height: usize = info_header.height().unsigned_abs() as usize;
let pixel_count: usize = width.saturating_mul(height);
let [r_mask, g_mask, b_mask, a_mask] = match compression {
BmpCompression::Bitfields => {
let (a, new_rest) = try_split_off_byte_array::<{ size_of::<u32>() * 3 }>(rest)
.ok_or(BmpError::InsufficientBytes)?;
rest = new_rest;
[
u32::from_le_bytes(a[0..4].try_into().unwrap()),
u32::from_le_bytes(a[4..8].try_into().unwrap()),
u32::from_le_bytes(a[8..12].try_into().unwrap()),
0,
]
}
BmpCompression::AlphaBitfields => {
let (a, new_rest) = try_split_off_byte_array::<{ size_of::<u32>() * 4 }>(rest)
.ok_or(BmpError::InsufficientBytes)?;
rest = new_rest;
[
u32::from_le_bytes(a[0..4].try_into().unwrap()),
u32::from_le_bytes(a[4..8].try_into().unwrap()),
u32::from_le_bytes(a[8..12].try_into().unwrap()),
u32::from_le_bytes(a[12..16].try_into().unwrap()),
]
}
// When bitmasks aren't specified, there's default RGB mask values based on
// the bit depth, either 555 (16-bit) or 888 (32-bit).
_ => match bits_per_pixel {
16 => [0b11111 << 10, 0b11111 << 5, 0b11111, 0],
32 => [0b11111111 << 16, 0b11111111 << 8, 0b11111111, 0],
_ => [0, 0, 0, 0],
},
};
println!(
"bitmasks: [\n r:{r_mask:032b}\n g:{g_mask:032b}\n b:{b_mask:032b}\n a:{a_mask:032b}\n]",
r_mask = r_mask,
g_mask = g_mask,
b_mask = b_mask,
a_mask = a_mask
);
// we make our final storage once we know about the masks. if there is a
// non-zero alpha mask then we assume the image is alpha aware and pixels
// default to transparent black. otherwise we assume that the image doesn't
// know about alpha and use opaque black as the default. This is significant
// because the RLE compresions can skip touching some pixels entirely and just
// leave the default color in place.
let mut final_storage: Vec<RGBA8> = Vec::new();
final_storage.try_reserve(pixel_count).map_err(|_| BmpError::AllocError)?;
final_storage.resize(
pixel_count,
if a_mask != 0 { RGBA8::default() } else { RGBA8 { r: 0, g: 0, b: 0, a: 0xFF } },
);
#[allow(unused_assignments)]
let palette: Vec<RGBA8> = match info_header.palette_len() {
0 => Vec::new(),
count => {
let mut v = Vec::new();
v.try_reserve(count).map_err(|_| BmpError::AllocError)?;
match info_header {
BmpInfoHeader::Core(_) => {
let bytes_needed = count * size_of::<[u8; 3]>();
let (pal_slice, new_rest) = if rest.len() < bytes_needed {
return Err(BmpError::InsufficientBytes);
} else {
rest.split_at(bytes_needed)
};
rest = new_rest;
let pal_slice: &[[u8; 3]] = cast_slice(pal_slice);
for [r, g, b] in pal_slice.iter().copied() {
v.push(RGBA8 { r, g, b, a: 0xFF });
}
}
_ => {
let bytes_needed = count * size_of::<[u8; 4]>();
let (pal_slice, new_rest) = if rest.len() < bytes_needed {
return Err(BmpError::InsufficientBytes);
} else {
rest.split_at(bytes_needed)
};
rest = new_rest;
let pal_slice: &[[u8; 4]] = cast_slice(pal_slice);
for [r, g, b, a] in pal_slice.iter().copied() {
v.push(RGBA8 { r, g, b, a });
}
if v.iter().copied().all(|c| c.a == 0) {
v.iter_mut().for_each(|c| c.a = 0xFF);
}
}
}
v
}
};
println!("palette: {palette:?}", palette = palette);
let pixel_data_start_index: usize = file_header.pixel_data_offset as usize;
let pixel_data_end_index: usize = pixel_data_start_index + info_header.pixel_data_len();
let pixel_data = if bmp.len() < pixel_data_end_index {
return Err(BmpError::InsufficientBytes);
} else {
&bmp[pixel_data_start_index..pixel_data_end_index]
};
match compression {
BmpCompression::RgbNoCompression
| BmpCompression::Bitfields
| BmpCompression::AlphaBitfields => {
let bits_per_line: usize =
bits_per_pixel.saturating_mul(info_header.width().unsigned_abs() as usize);
let no_padding_bytes_per_line: usize =
(bits_per_line / 8) + (((bits_per_line % 8) != 0) as usize);
let bytes_per_line: usize =
((no_padding_bytes_per_line / 4) + ((no_padding_bytes_per_line % 4) != 0) as usize) * 4;
debug_assert!(no_padding_bytes_per_line <= bytes_per_line);
debug_assert_eq!(bytes_per_line % 4, 0);
if (pixel_data.len() % bytes_per_line) != 0
|| (pixel_data.len() / bytes_per_line) != (info_header.height().unsigned_abs() as usize)
{
return Err(BmpError::PixelDataIllegalLength);
}
//
match bits_per_pixel {
1 | 2 | 4 => {
let (base_mask, base_down_shift) = match bits_per_pixel {
1 => (0b1000_0000, 7),
2 => (0b1100_0000, 6),
4 => (0b1111_0000, 4),
_ => unreachable!(),
};
let mut per_row_op = |i: &mut dyn Iterator<Item = (usize, &[u8])>| {
while let Some((y, data_row)) = i.next() {
let mut x = 0;
for byte in data_row.iter().copied() {
let mut mask: u8 = base_mask;
let mut down_shift: usize = base_down_shift;
while mask != 0 && x < width {
let pal_index = (byte & mask) >> down_shift;
let rgba8 = palette.get(pal_index as usize).copied().unwrap_or_default();
final_storage[(y * width + x) as usize] = rgba8;
//
mask >>= bits_per_pixel;
down_shift = down_shift.wrapping_sub(bits_per_pixel);
x += 1;
}
}
}
};
if info_header.height() < 0 {
per_row_op(&mut pixel_data.chunks_exact(bytes_per_line).enumerate());
} else {
per_row_op(&mut pixel_data.rchunks_exact(bytes_per_line).enumerate());
}
}
8 => {
let mut per_row_op = |i: &mut dyn Iterator<Item = (usize, &[u8])>| {
while let Some((y, data_row)) = i.next() {
for (x, pal_index) in
data_row[..no_padding_bytes_per_line].iter().copied().enumerate()
{
let rgba8 = palette.get(pal_index as usize).copied().unwrap_or_default();
final_storage[(y * width + x) as usize] = rgba8;
}
}
};
if info_header.height() < 0 {
per_row_op(&mut pixel_data.chunks_exact(bytes_per_line).enumerate());
} else {
per_row_op(&mut pixel_data.rchunks_exact(bytes_per_line).enumerate());
}
}
24 => {
let mut per_row_op = |i: &mut dyn Iterator<Item = (usize, &[u8])>| {
while let Some((y, data_row)) = i.next() {
for (x, [r, g, b]) in
cast_slice::<u8, [u8; 3]>(&data_row[..no_padding_bytes_per_line])
.iter()
.copied()
.enumerate()
{
let rgba8 = RGBA8 { r, g, b, a: 0xFF };
final_storage[y * width + x] = rgba8;
}
}
};
if info_header.height() < 0 {
per_row_op(&mut pixel_data.chunks_exact(bytes_per_line).enumerate())
} else {
per_row_op(&mut pixel_data.rchunks_exact(bytes_per_line).enumerate())
}
}
16 => {
let r_shift: u32 = if r_mask != 0 { r_mask.trailing_zeros() } else { 0 };
let g_shift: u32 = if g_mask != 0 { g_mask.trailing_zeros() } else { 0 };
let b_shift: u32 = if b_mask != 0 { b_mask.trailing_zeros() } else { 0 };
let a_shift: u32 = if a_mask != 0 { a_mask.trailing_zeros() } else { 0 };
let r_max: f32 = (r_mask >> r_shift) as f32;
let g_max: f32 = (g_mask >> g_shift) as f32;
let b_max: f32 = (b_mask >> b_shift) as f32;
let a_max: f32 = (a_mask >> a_shift) as f32;
//
#[rustfmt::skip]
let mut per_row_op = |i: &mut dyn Iterator<Item = (usize, &[u8])>| {
while let Some((y, data_row)) = i.next() {
for (x, data) in cast_slice::<u8, [u8; 2]>(&data_row[..no_padding_bytes_per_line])
.iter()
.copied()
.enumerate()
{
// TODO: look at how SIMD this could be.
let u = u16::from_le_bytes(data) as u32;
let r: u8 = if r_mask != 0 { ((((u & r_mask) >> r_shift) as f32 / r_max) * 255.0) as u8 } else { 0 };
let g: u8 = if g_mask != 0 { ((((u & g_mask) >> g_shift) as f32 / g_max) * 255.0) as u8 } else { 0 };
let b: u8 = if b_mask != 0 { ((((u & b_mask) >> b_shift) as f32 / b_max) * 255.0) as u8 } else { 0 };
let a: u8 = if a_mask != 0 { ((((u & a_mask) >> a_shift) as f32 / a_max) * 255.0) as u8 } else { 0xFF };
let rgba8 = RGBA8 { r, g, b, a };
final_storage[(y * width + x) as usize] = rgba8;
}
}
};
if info_header.height() < 0 {
per_row_op(&mut pixel_data.chunks_exact(bytes_per_line).enumerate())
} else {
per_row_op(&mut pixel_data.rchunks_exact(bytes_per_line).enumerate())
}
}
32 => {
let r_shift: u32 = if r_mask != 0 { r_mask.trailing_zeros() } else { 0 };
let g_shift: u32 = if g_mask != 0 { g_mask.trailing_zeros() } else { 0 };
let b_shift: u32 = if b_mask != 0 { b_mask.trailing_zeros() } else { 0 };
let a_shift: u32 = if a_mask != 0 { a_mask.trailing_zeros() } else { 0 };
let r_max: f32 = (r_mask >> r_shift) as f32;
let g_max: f32 = (g_mask >> g_shift) as f32;
let b_max: f32 = (b_mask >> b_shift) as f32;
let a_max: f32 = (a_mask >> a_shift) as f32;
//
#[rustfmt::skip]
let mut per_row_op = |i: &mut dyn Iterator<Item = (usize, &[u8])>| {
while let Some((y, data_row)) = i.next() {
for (x, data) in cast_slice::<u8, [u8; 4]>(&data_row[..no_padding_bytes_per_line])
.iter()
.copied()
.enumerate()
{
// TODO: look at how SIMD this could be.
let u = u32::from_le_bytes(data);
let r: u8 = if r_mask != 0 { ((((u & r_mask) >> r_shift) as f32 / r_max) * 255.0) as u8 } else { 0 };
let g: u8 = if g_mask != 0 { ((((u & g_mask) >> g_shift) as f32 / g_max) * 255.0) as u8 } else { 0 };
let b: u8 = if b_mask != 0 { ((((u & b_mask) >> b_shift) as f32 / b_max) * 255.0) as u8 } else { 0 };
let a: u8 = if a_mask != 0 { ((((u & a_mask) >> a_shift) as f32 / a_max) * 255.0) as u8 } else { 0xFF };
let rgba8 = RGBA8 { r, g, b, a };
final_storage[(y * width + x) as usize] = rgba8;
}
}
};
if info_header.height() < 0 {
per_row_op(&mut pixel_data.chunks_exact(bytes_per_line).enumerate())
} else {
per_row_op(&mut pixel_data.rchunks_exact(bytes_per_line).enumerate())
}
}
_ => return Err(BmpError::IllegalBitDepth),
}
}
BmpCompression::RgbRLE8 => {
// For the RLE encodings, there's either "encoded" pairs of bytes, or
// "absolute" runs of bytes that are also always even in length (with a
// padding byte if necessary). Thus, no matter what, the number of bytes
// in the pixel data should always be even when we're processing RLE data.
if pixel_data.len() % 2 != 0 {
return Err(BmpError::PixelDataIllegalLength);
}
let mut it = cast_slice::<u8, [u8; 2]>(pixel_data).iter().copied();
// Now the MSDN docs get kinda terrible. They talk about "encoded" and
// "absolute" mode, but whoever wrote that is bad at writing docs. What
// we're doing is we'll pull off two bytes at a time from the pixel data.
// Then we look at the first byte in a pair and see if it's zero or not.
//
// * If the first byte is **non-zero** it's the number of times that the second
// byte appears in the output. The second byte is an index into the palette,
// and you just put out that color and output it into the bitmap however many
// times.
// * If the first byte is **zero**, it signals an "escape sequence" sort of
// situation. The second byte will give us the details:
// * 0: end of line
// * 1: end of bitmap
// * 2: "Delta", the *next* two bytes after this are unsigned offsets to the
// right and up of where the output should move to (remember that this mode
// always describes the BMP with a bottom-left origin).
// * 3+: "Absolute", The second byte gives a count of how many bytes follow
// that we'll output without repetition. The absolute output sequences
// always have a padding byte on the ending if the sequence count is odd, so
// we can keep pulling `[u8;2]` at a time from our data and it all works.
let mut x = 0;
let mut y = height - 1;
'iter_pull_rle8: while let Some([count, pal_index]) = it.next() {
if count > 0 {
// data run of the `pal_index` value's color.
let rgba8 = palette.get(pal_index as usize).copied().unwrap_or_default();
let count = count as usize;
let i = y * width + x;
let target_slice_mut: &mut [RGBA8] = if i + count <= final_storage.len() {
&mut final_storage[i..(i + count)]
} else {
// this probably means the data was encoded wrong? We'll still fill
// in some of the pixels at least, and if people can find a
// counter-example we can fix this.
&mut final_storage[i..]
};
target_slice_mut.iter_mut().for_each(|c| *c = rgba8);
x += count;
} else {
match pal_index {
0 => {
// end of line.
x = 0;
y = y.saturating_sub(1);
}
1 => {
// end of bitmap
break 'iter_pull_rle8;
}
2 => {
// position delta
if let Some([d_right, d_up]) = it.next() {
x += d_right as usize;
y = y.saturating_sub(d_up as usize);
} else {
return Err(BmpError::PixelDataIllegalRLEContent);
}
}
mut raw_count => {
while raw_count > 0 {
// process two bytes at a time, which we'll call `q` and `w` for
// lack of better names.
if let Some([q, w]) = it.next() {
// q byte
let rgba8 = palette.get(q as usize).copied().unwrap_or_default();
let i = y * width + x;
// If this goes OOB then that's the fault of the encoder and
// it's better to just drop some data than to panic.
final_storage.get_mut(i).map(|c| *c = rgba8);
x += 1;
// If `raw_count` is only 1 then we don't output the `w` byte.
if raw_count >= 2 {
// w byte
let rgba8 = palette.get(w as usize).copied().unwrap_or_default();
let i = y * width + x;
final_storage.get_mut(i).map(|c| *c = rgba8);
x += 1;
}
} else {
return Err(BmpError::PixelDataIllegalRLEContent);
}
//
raw_count = raw_count.saturating_sub(2);
}
}
}
}
}
}
BmpCompression::RgbRLE4 => {
// RLE4 works *basically* how RLE8 does, except that every time we
// process a byte as a color to output then it's actually two outputs
// instead (upper bits then lower bits). The stuff about the escape
// sequences and all that is still the same sort of thing.
if pixel_data.len() % 2 != 0 {
return Err(BmpError::PixelDataIllegalLength);
}
let mut it = cast_slice::<u8, [u8; 2]>(pixel_data).iter().copied();
//
let mut x = 0;
let mut y = height - 1;
//
'iter_pull_rle4: while let Some([count, pal_index]) = it.next() {
if count > 0 {
// in this case, `count` is the number of indexes to output, and
// `pal_index` is *two* pixel indexes (high bits then low bits). We'll
// write the pair of them over and over in a loop however many times.
if (pal_index >> 4) as usize >= palette.len() {
println!("pal_index high bits oob: {} of {}", (pal_index >> 4), palette.len());
}
if (pal_index & 0b1111) as usize >= palette.len() {
println!(
"pal_index high bits oob: {} of {}",
(pal_index & 0b1111) as usize,
palette.len()
);
}
let rgba8_h = palette.get((pal_index >> 4) as usize).copied().unwrap_or_default();
let rgba8_l = palette.get((pal_index & 0b1111) as usize).copied().unwrap_or_default();
let count = count as usize;
debug_assert!(x < width, "x:{}, width:{}", x, width);
debug_assert!(y < height, "y:{}, height:{}", y, height);
let i = y * width + x;
let target_slice_mut: &mut [RGBA8] = if i + count < final_storage.len() {
&mut final_storage[i..(i + count)]
} else {
// this probably means the data was encoded wrong? We'll still fill
// in some of the pixels at least, and if people can find a
// counter-example we can fix this.
&mut final_storage[i..]
};
let mut chunks_exact_mut = target_slice_mut.chunks_exact_mut(2);
while let Some(chunk) = chunks_exact_mut.next() {
chunk[0] = rgba8_h;
chunk[1] = rgba8_l;
}
chunks_exact_mut.into_remainder().iter_mut().for_each(|c| {
*c = rgba8_h;
});
x += count;
} else {
// If the count is zero then we use the same escape sequence scheme as
// with the RLE8 format.
match pal_index {
0 => {
// end of line.
//print!("RLE4: == END OF LINE: before (x: {}, y: {})", x, y);
x = 0;
y = y.saturating_sub(1);
//println!(" after (x: {}, y: {})", x, y);
}
1 => {
// end of bitmap
//println!("RLE4: ==>> END OF THE BITMAP");
break 'iter_pull_rle4;
}
2 => {
// delta
if let Some([d_right, d_up]) = it.next() {
x += d_right as usize;
y = y.saturating_sub(d_up as usize);
} else {
return Err(BmpError::PixelDataIllegalRLEContent);
}
}
mut raw_count => {
// in this case, we'll still have raw outputs for a sequence, but
// `raw_count` is the number of indexes, and each 2 bytes in the
// sequence is **four** output indexes. The only complication is
// that we need to be sure we stop *as soon* as `raw_count` hits 0
// because otherwise we'll mess up our `x` position (ruining all
// future outputs on this scanline, and possibly ruining other
// scanlines or something).
while raw_count > 0 {
if let Some([q, w]) = it.next() {
for pal_index in [
((q >> 4) as usize),
((q & 0b1111) as usize),
((w >> 4) as usize),
((w & 0b1111) as usize),
] {
let i = y * width + x;
if pal_index >= palette.len() {
println!("oob pal_index: {} of {}", pal_index, palette.len());
}
let rgba8_h = palette.get(pal_index).copied().unwrap_or_default();
final_storage.get_mut(i).map(|c| *c = rgba8_h);
x += 1;
raw_count = raw_count.saturating_sub(1);
if raw_count == 0 {
break;
}
}
} else {
return Err(BmpError::PixelDataIllegalRLEContent);
}
}
}
}
}
}
}
// Note(Lokathor): Uh, I guess "the entire file is inside the 'pixel_array'
// data" or whatever? We need example files that use this compression before
// we can begin to check out what's going on here.
BmpCompression::Jpeg => return Err(BmpError::ParserIncomplete),
BmpCompression::Png => return Err(BmpError::ParserIncomplete),
// Note(Lokathor): probably we never need to support this until someone asks?
BmpCompression::CmykNoCompression => return Err(BmpError::ParserIncomplete),
BmpCompression::CmykRLE4 => return Err(BmpError::ParserIncomplete),
BmpCompression::CmykRLE8 => return Err(BmpError::ParserIncomplete),
}
Ok((final_storage, width as u32, height as u32))
}

added in 0.3