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