1//! Device Independent Bitmap (DIB) header.
2
3use embedded_graphics::prelude::*;
4
5use crate::{
6 header::CompressionMethod,
7 parser::{le_i32, le_u16, le_u32, take_slice},
8 Bpp, ChannelMasks, ParseError, RowOrder,
9};
10
11const DIB_INFO_HEADER_SIZE: u32 = 40;
12const DIB_V3_HEADER_SIZE: u32 = 56;
13const DIB_V4_HEADER_SIZE: u32 = 108;
14const DIB_V5_HEADER_SIZE: u32 = 124;
15
16/// Device Independent Bitmap (DIB) header.
17#[derive(Debug)]
18pub 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
29impl 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)]
122pub enum HeaderType {
123 Info,
124 V3,
125 V4,
126 V5,
127}
128
129impl HeaderType {
130 fn is_at_least(self, header_type: HeaderType) -> bool {
131 self >= header_type
132 }
133}
134