code wiki / (root) / bmp_header.nx

bmp_header.nx source

↩ module page · 147 lines · 5083 B

1// bmp_header.nx -- parse Windows BMP/DIB image headers. 2// 3// BMP is two concatenated structures: 4// 1. BITMAPFILEHEADER (14 bytes, fixed): 5// 0 2 bfType "BM" = 0x42 0x4D 6// 2 4 bfSize total file size LE 7// 6 2 bfReserved1 8// 8 2 bfReserved2 9// 10 4 bfOffBits offset of pixel data from file start 10// 11// 2. DIB header; the first u32 LE is its size, which discriminates: 12// 12 = BITMAPCOREHEADER (OS/2 1.x) 13// 40 = BITMAPINFOHEADER (Windows 3.0+, the common case) 14// 52 = BITMAPV2INFOHEADER 15// 56 = BITMAPV3INFOHEADER 16// 108 = BITMAPV4HEADER 17// 124 = BITMAPV5HEADER 18// 19// We parse the BMP file header and the first two u32s of the DIB 20// (its size + width), plus the canonical BITMAPINFOHEADER fields 21// when size >= 40. Later DIB-version extensions are surfaced 22// only by their presence (size value). 23// 24// Invariants: 25// B1 Magic "BM" (0x4D42 LE) required. 26// B2 dib_size must be one of the canonical sizes. 27// B3 Height may be negative in BITMAPINFOHEADER -- means top-down. 28// We surface raw signed value; caller picks orientation. 29 30import "syscalls.nx" 31 32const BMP_ERR_FORMAT: i64 = -1 33const BMP_ERR_SHORT: i64 = -2 34const BMP_ERR_DIB: i64 = -3 35 36struct BmpHeader { 37 file_size: i64, 38 pixel_offset: i64, 39 dib_size: i64, 40 width: i64, // pixels 41 height: i64, // pixels; negative means top-down 42 planes: i64, // always 1 43 bits_per_pixel: i64, // 1/4/8/16/24/32 44 compression: i64, // 0=BI_RGB, 1=BI_RLE8, ... 45 image_size: i64, // bytes; may be 0 for BI_RGB 46} 47 48func bmp_read_u16(buf: *u8, off: i64) -> i64 { 49 return buf[off] | (buf[off + 1] << 8) 50} 51 52func bmp_read_u32(buf: *u8, off: i64) -> i64 { 53 let b0: i64 = buf[off] 54 let b1: i64 = buf[off + 1] 55 let b2: i64 = buf[off + 2] 56 let b3: i64 = buf[off + 3] 57 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 58} 59 60// Sign-extend a 32-bit value into i64. 61func bmp_read_i32(buf: *u8, off: i64) -> i64 { 62 let v: i64 = bmp_read_u32(buf, off) 63 if v >= 0x80000000 { 64 return v - 0x100000000 65 } 66 return v 67} 68 69// Validate that a DIB size byte is one of the known versions. 70func bmp_dib_known(size: i64) -> i64 { 71 if size == 12 { return 1 } 72 if size == 40 { return 1 } 73 if size == 52 { return 1 } 74 if size == 56 { return 1 } 75 if size == 108 { return 1 } 76 if size == 124 { return 1 } 77 return 0 78} 79 80func bmp_parse_header(buf: *u8, n: i64, h: *BmpHeader) -> i64 { 81 if n < 14 + 40 { return BMP_ERR_SHORT } 82 if buf[0] != 0x42 { return BMP_ERR_FORMAT } // 'B' 83 if buf[1] != 0x4D { return BMP_ERR_FORMAT } // 'M' 84 85 h.file_size = bmp_read_u32(buf, 2) 86 h.pixel_offset = bmp_read_u32(buf, 10) 87 h.dib_size = bmp_read_u32(buf, 14) 88 if bmp_dib_known(h.dib_size) != 1 { return BMP_ERR_DIB } 89 90 if h.dib_size == 12 { 91 // BITMAPCOREHEADER: width/height are u16, planes/bpp follow. 92 h.width = bmp_read_u16(buf, 18) 93 h.height = bmp_read_u16(buf, 20) 94 h.planes = bmp_read_u16(buf, 22) 95 h.bits_per_pixel = bmp_read_u16(buf, 24) 96 h.compression = 0 97 h.image_size = 0 98 return 0 99 } 100 // BITMAPINFOHEADER and later extensions share the first 40 bytes. 101 h.width = bmp_read_i32(buf, 18) 102 h.height = bmp_read_i32(buf, 22) 103 h.planes = bmp_read_u16(buf, 26) 104 h.bits_per_pixel = bmp_read_u16(buf, 28) 105 h.compression = bmp_read_u32(buf, 30) 106 h.image_size = bmp_read_u32(buf, 34) 107 return 0 108} 109 110// Compile-only smoke -- 2x2 24-bit RGB BMP synth, file size 70 111// bytes (14 + 40 + 16 pixel-data). Pixel offset = 54. 112func main() -> i64 { 113 let raw: *u8 = sys_mmap(128) 114 var i: i64 = 0 115 while i < 128 { raw[i] = 0; i = i + 1 } 116 117 raw[0] = 0x42; raw[1] = 0x4D 118 raw[2] = 70 // file_size LE 119 raw[10] = 54 // pixel_offset = 0x36 120 raw[14] = 40 // dib_size = BITMAPINFOHEADER 121 122 raw[18] = 2 // width = 2 123 raw[22] = 2 // height = 2 124 raw[26] = 1 // planes = 1 125 raw[28] = 24 // bits_per_pixel = 24 126 raw[30] = 0 // BI_RGB 127 128 let h_raw: *u8 = sys_mmap(128) 129 let h: *BmpHeader = h_raw as *BmpHeader 130 if bmp_parse_header(raw, 128, h) != 0 { return 1 } 131 if h.file_size != 70 { return 2 } 132 if h.pixel_offset != 54 { return 3 } 133 if h.dib_size != 40 { return 4 } 134 if h.width != 2 { return 5 } 135 if h.height != 2 { return 6 } 136 if h.bits_per_pixel != 24 { return 7 } 137 138 // Bad magic -> BMP_ERR_FORMAT. 139 raw[0] = 0 140 if bmp_parse_header(raw, 128, h) != BMP_ERR_FORMAT { return 8 } 141 142 // Unknown DIB size -> BMP_ERR_DIB. 143 raw[0] = 0x42 144 raw[14] = 99 145 if bmp_parse_header(raw, 128, h) != BMP_ERR_DIB { return 9 } 146 return 0 147}