1 | //! Device Independent Bitmap (DIB) header. |
2 | |
3 | use embedded_graphics::prelude::*; |
4 | |
5 | use crate::{ |
6 | header::CompressionMethod, |
7 | parser::{le_i32, le_u16, le_u32, take_slice}, |
8 | Bpp, ChannelMasks, ParseError, RowOrder, |
9 | }; |
10 | |
11 | const DIB_INFO_HEADER_SIZE: u32 = 40; |
12 | const DIB_V3_HEADER_SIZE: u32 = 56; |
13 | const DIB_V4_HEADER_SIZE: u32 = 108; |
14 | const DIB_V5_HEADER_SIZE: u32 = 124; |
15 | |
16 | /// Device Independent Bitmap (DIB) header. |
17 | #[derive (Debug)] |
18 | pub struct DibHeader { |
19 | pub image_size: Size, |
20 | pub bpp: Bpp, |
21 | pub compression: CompressionMethod, |
22 | pub image_data_len: u32, |
23 | pub channel_masks: Option<ChannelMasks>, |
24 | pub header_type: HeaderType, |
25 | pub row_order: RowOrder, |
26 | pub color_table_num_entries: u32, |
27 | } |
28 | |
29 | impl DibHeader { |
30 | pub fn parse(input: &[u8]) -> Result<(&[u8], Self), ParseError> { |
31 | let (input, dib_header_length) = le_u32(input)?; |
32 | |
33 | // The header size in the BMP includes its own u32, so we strip it out by subtracting 4 |
34 | // bytes to get the right final offset to the end of the header. |
35 | let data_length = dib_header_length |
36 | .checked_sub(4) |
37 | .ok_or(ParseError::UnsupportedHeaderLength(dib_header_length))?; |
38 | let (input, dib_header_data) = take_slice(input, data_length as usize)?; |
39 | |
40 | // Add 4 back on so the constants remain the correct size relative to the BMP |
41 | // documentation/specs. |
42 | let header_type = match dib_header_length { |
43 | DIB_V3_HEADER_SIZE => HeaderType::V3, |
44 | DIB_V4_HEADER_SIZE => HeaderType::V4, |
45 | DIB_V5_HEADER_SIZE => HeaderType::V5, |
46 | DIB_INFO_HEADER_SIZE => HeaderType::Info, |
47 | _ => return Err(ParseError::UnsupportedHeaderLength(dib_header_length)), |
48 | }; |
49 | |
50 | // Fields common to all DIB variants |
51 | let (dib_header_data, image_width) = le_i32(dib_header_data)?; |
52 | let (dib_header_data, image_height) = le_i32(dib_header_data)?; |
53 | let (dib_header_data, _color_planes) = le_u16(dib_header_data)?; |
54 | let (dib_header_data, bpp) = Bpp::parse(dib_header_data)?; |
55 | |
56 | // Extra fields defined by DIB variants |
57 | // Variants are described in |
58 | // <https://www.liquisearch.com/bmp_file_format/file_structure/dib_header_bitmap_information_header> |
59 | // and <https://docs.microsoft.com/en-us/windows/win32/gdi/bitmap-header-types> |
60 | let (dib_header_data, compression_method) = CompressionMethod::parse(dib_header_data)?; |
61 | let (dib_header_data, image_data_len) = le_u32(dib_header_data)?; |
62 | let (dib_header_data, _pels_per_meter_x) = le_u32(dib_header_data)?; |
63 | let (dib_header_data, _pels_per_meter_y) = le_u32(dib_header_data)?; |
64 | let (dib_header_data, colors_used) = le_u32(dib_header_data)?; |
65 | let (dib_header_data, _colors_important) = le_u32(dib_header_data)?; |
66 | |
67 | let (_dib_header_data, channel_masks) = if header_type.is_at_least(HeaderType::V3) |
68 | && compression_method == CompressionMethod::Bitfields |
69 | { |
70 | let (dib_header_data, mask_red) = le_u32(dib_header_data)?; |
71 | let (dib_header_data, mask_green) = le_u32(dib_header_data)?; |
72 | let (dib_header_data, mask_blue) = le_u32(dib_header_data)?; |
73 | let (dib_header_data, mask_alpha) = le_u32(dib_header_data)?; |
74 | |
75 | ( |
76 | dib_header_data, |
77 | Some(ChannelMasks { |
78 | red: mask_red, |
79 | green: mask_green, |
80 | blue: mask_blue, |
81 | alpha: mask_alpha, |
82 | }), |
83 | ) |
84 | } else { |
85 | (dib_header_data, None) |
86 | }; |
87 | |
88 | let color_table_num_entries = if colors_used == 0 && bpp.bits() < 16 { |
89 | 1 << bpp.bits() |
90 | } else { |
91 | colors_used |
92 | }; |
93 | |
94 | if image_width <= 0 || image_height == 0 { |
95 | return Err(ParseError::InvalidImageDimensions); |
96 | } |
97 | |
98 | let row_order = if image_height < 0 { |
99 | RowOrder::TopDown |
100 | } else { |
101 | RowOrder::BottomUp |
102 | }; |
103 | |
104 | Ok(( |
105 | input, |
106 | Self { |
107 | header_type, |
108 | image_size: Size::new(image_width.unsigned_abs(), image_height.unsigned_abs()), |
109 | image_data_len, |
110 | bpp, |
111 | channel_masks, |
112 | compression: compression_method, |
113 | row_order, |
114 | color_table_num_entries, |
115 | }, |
116 | )) |
117 | } |
118 | } |
119 | |
120 | // Note: Do not change the order of the enum variants! |
121 | #[derive (Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)] |
122 | pub enum HeaderType { |
123 | Info, |
124 | V3, |
125 | V4, |
126 | V5, |
127 | } |
128 | |
129 | impl HeaderType { |
130 | fn is_at_least(self, header_type: HeaderType) -> bool { |
131 | self >= header_type |
132 | } |
133 | } |
134 | |