Skip to content

Commit 846b570

Browse files
authored
Ignore compressed data length on RGB compression mehod (#47)
* Add image * add failing tests * Handle potentially incorrect compressed data len on compression type RGB * rustfmt * use old method for calculating uncompressed rgb size * replace unwrap to match in const fn * Simplify RawBmp::from_slice data_length calculation * bump changelog * Rearrange comments * cargo fmt
1 parent 32b5344 commit 846b570

File tree

5 files changed

+131
-8
lines changed

5 files changed

+131
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
- [#46](https://github.com/embedded-graphics/tinybmp/pull/46) `Bmp::from_slice` is now `const`, so BMPs can be put in `const`s and `static`s.
1010

11+
- [#47](https://github.com/embedded-graphics/tinybmp/pull/47) Ignore compressed data length on RGB compression mehod
1112

1213
## [0.6.0] - 2024-06-11
1314

src/header/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ impl Header {
161161
/// Returns the row length in bytes.
162162
///
163163
/// Each row in a BMP file is a multiple of 4 bytes long.
164-
pub(crate) fn bytes_per_row(&self) -> usize {
165-
let bits_per_row = self.image_size.width as usize * usize::from(self.bpp.bits());
164+
pub(crate) const fn bytes_per_row(&self) -> usize {
165+
let bits_per_row = self.image_size.width as usize * self.bpp.bits() as usize;
166166

167167
(bits_per_row + 31) / 32 * (32 / 8)
168168
}

src/raw_bmp.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,32 @@ impl<'a> RawBmp<'a> {
4444

4545
let color_type = try_const!(ColorType::from_header(&header));
4646

47-
// Believe what the bitmap tells us rather than multiplying width by
48-
// height by bits-per-pixel, because the image data might be compressed.
49-
let compressed_data_length = header.image_data_len as usize;
50-
5147
if bytes.len() < header.image_data_start {
5248
return Err(ParseError::UnexpectedEndOfFile);
5349
}
5450
let (_, image_data) = bytes.split_at(header.image_data_start);
55-
if image_data.len() < compressed_data_length {
51+
52+
let data_length = if let crate::header::CompressionMethod::Rgb = header.compression_method {
53+
// `Header::image_data_len` may be zero or bogus when compression mode is RGB
54+
// see `biSizeImage` on https://learn.microsoft.com/en-us/previous-versions/dd183376(v=vs.85)
55+
// so we should calculate width x height instead.
56+
let height = header.image_size.height as usize;
57+
58+
let Some(data_length) = header.bytes_per_row().checked_mul(height) else {
59+
return Err(ParseError::UnexpectedEndOfFile);
60+
};
61+
data_length
62+
} else {
63+
// Believe what the bitmap tells us rather than multiplying width by
64+
// height by bits-per-pixel, because the image data might be compressed.
65+
header.image_data_len as usize
66+
};
67+
68+
if image_data.len() < data_length {
5669
return Err(ParseError::UnexpectedEndOfFile);
5770
}
58-
let (image_data, _) = image_data.split_at(compressed_data_length);
71+
72+
let (image_data, _) = image_data.split_at(data_length);
5973

6074
Ok(Self {
6175
header,

tests/chessboard-16px-1bit.bmp

126 Bytes
Binary file not shown.

tests/chessboard-16px-1bit.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use embedded_graphics::{
2+
pixelcolor::{BinaryColor, Rgb888},
3+
prelude::*,
4+
};
5+
use tinybmp::{Bmp, Bpp, CompressionMethod, Header, RawBmp, RowOrder};
6+
7+
#[test]
8+
fn chessboard_16px_1bit() {
9+
let bmp =
10+
RawBmp::from_slice(include_bytes!("./chessboard-16px-1bit.bmp")).expect("Failed to parse");
11+
12+
assert_eq!(
13+
bmp.header(),
14+
&Header {
15+
file_size: 94,
16+
image_data_start: 62,
17+
bpp: Bpp::Bits1,
18+
image_size: Size::new(16, 16),
19+
image_data_len: 32,
20+
channel_masks: None,
21+
row_order: RowOrder::BottomUp,
22+
compression_method: CompressionMethod::Rgb,
23+
}
24+
);
25+
26+
let color_table = bmp.color_table().unwrap();
27+
assert_eq!(color_table.len(), 2);
28+
assert_eq!(color_table.get(0), Some(Rgb888::BLACK));
29+
assert_eq!(color_table.get(1), Some(Rgb888::WHITE));
30+
assert_eq!(color_table.get(2), None);
31+
32+
assert_eq!(bmp.image_data().len(), 64);
33+
}
34+
35+
#[test]
36+
fn chessboard_16px_1bit_iter_raw() {
37+
let bmp =
38+
RawBmp::from_slice(include_bytes!("./chessboard-16px-1bit.bmp")).expect("Failed to parse");
39+
40+
let pixels: Vec<u32> = bmp.pixels().map(|pixel| pixel.color).collect();
41+
42+
// 16px x 16px image. Check that iterator returns all pixels in it
43+
assert_eq!(pixels.len(), 16 * 16);
44+
45+
let w = 1u32;
46+
let b = 0u32;
47+
48+
let expected = vec![
49+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
50+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
51+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
52+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
53+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
54+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
55+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
56+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
57+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
58+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
59+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
60+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
61+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
62+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
63+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
64+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
65+
];
66+
67+
assert_eq!(pixels, expected);
68+
}
69+
70+
#[test]
71+
fn chessboard_16px_1bit_iter() {
72+
let bmp = Bmp::<'_, Rgb888>::from_slice(include_bytes!("./chessboard-16px-1bit.bmp"))
73+
.expect("Failed to parse");
74+
75+
let pixels: Vec<u32> = bmp
76+
.pixels()
77+
.map(|Pixel(_pos, color)| color.into_storage().into())
78+
.collect();
79+
80+
// 16px x 16px image. Check that iterator returns all pixels in it
81+
assert_eq!(pixels.len(), 16 * 16);
82+
83+
// Imagemagick inverts using a color mapping table which maps a 0 to [255, 255, 255, 0], hence
84+
// this instead of a simple `1` value.
85+
let w = Rgb888::WHITE.into_storage();
86+
let b = Rgb888::BLACK.into_storage();
87+
88+
let expected = vec![
89+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
90+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
91+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
92+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
93+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
94+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
95+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
96+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
97+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
98+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
99+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
100+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
101+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
102+
w, w, b, b, w, w, b, b, w, w, b, b, w, w, b, b, //
103+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
104+
b, b, w, w, b, b, w, w, b, b, w, w, b, b, w, w, //
105+
];
106+
107+
assert_eq!(pixels, expected);
108+
}

0 commit comments

Comments
 (0)