code wiki / (root) / nx_bmp_header.nx

nx_bmp_header.nx source

↩ module page · 153 lines · 5190 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 30// nx_safety_envelope: 31// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 32// sil_target: SIL1 33// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 34// verdict: NOT_YET_EVALUATED 35 36import "nx_syscalls.nx" 37 38const BMP_ERR_FORMAT: i64 = -1 39const BMP_ERR_SHORT: i64 = -2 40const BMP_ERR_DIB: i64 = -3 41 42struct BmpHeader { 43 file_size: i64, 44 pixel_offset: i64, 45 dib_size: i64, 46 width: i64, // pixels 47 height: i64, // pixels; negative means top-down 48 planes: i64, // always 1 49 bits_per_pixel: i64, // 1/4/8/16/24/32 50 compression: i64, // 0=BI_RGB, 1=BI_RLE8, ... 51 image_size: i64, // bytes; may be 0 for BI_RGB 52} 53 54func bmp_read_u16(buf: *u8, off: i64) -> i64 { 55 return buf[off] | (buf[off + 1] << 8) 56} 57 58func bmp_read_u32(buf: *u8, off: i64) -> i64 { 59 let b0: i64 = buf[off] 60 let b1: i64 = buf[off + 1] 61 let b2: i64 = buf[off + 2] 62 let b3: i64 = buf[off + 3] 63 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 64} 65 66// Sign-extend a 32-bit value into i64. 67func bmp_read_i32(buf: *u8, off: i64) -> i64 { 68 let v: i64 = bmp_read_u32(buf, off) 69 if v >= 0x80000000 { 70 return v - 0x100000000 71 } 72 return v 73} 74 75// Validate that a DIB size byte is one of the known versions. 76func bmp_dib_known(size: i64) -> i64 { 77 if size == 12 { return 1 } 78 if size == 40 { return 1 } 79 if size == 52 { return 1 } 80 if size == 56 { return 1 } 81 if size == 108 { return 1 } 82 if size == 124 { return 1 } 83 return 0 84} 85 86func bmp_parse_header(buf: *u8, n: i64, h: *BmpHeader) -> i64 { 87 if n < 14 + 40 { return BMP_ERR_SHORT } 88 if buf[0] != 0x42 { return BMP_ERR_FORMAT } // 'B' 89 if buf[1] != 0x4D { return BMP_ERR_FORMAT } // 'M' 90 91 h.file_size = bmp_read_u32(buf, 2) 92 h.pixel_offset = bmp_read_u32(buf, 10) 93 h.dib_size = bmp_read_u32(buf, 14) 94 if bmp_dib_known(h.dib_size) != 1 { return BMP_ERR_DIB } 95 96 if h.dib_size == 12 { 97 // BITMAPCOREHEADER: width/height are u16, planes/bpp follow. 98 h.width = bmp_read_u16(buf, 18) 99 h.height = bmp_read_u16(buf, 20) 100 h.planes = bmp_read_u16(buf, 22) 101 h.bits_per_pixel = bmp_read_u16(buf, 24) 102 h.compression = 0 103 h.image_size = 0 104 return 0 105 } 106 // BITMAPINFOHEADER and later extensions share the first 40 bytes. 107 h.width = bmp_read_i32(buf, 18) 108 h.height = bmp_read_i32(buf, 22) 109 h.planes = bmp_read_u16(buf, 26) 110 h.bits_per_pixel = bmp_read_u16(buf, 28) 111 h.compression = bmp_read_u32(buf, 30) 112 h.image_size = bmp_read_u32(buf, 34) 113 return 0 114} 115 116// Compile-only smoke -- 2x2 24-bit RGB BMP synth, file size 70 117// bytes (14 + 40 + 16 pixel-data). Pixel offset = 54. 118func main() -> i64 { 119 let raw: *u8 = sys_mmap(128) 120 var i: i64 = 0 121 while i < 128 { raw[i] = 0; i = i + 1 } 122 123 raw[0] = 0x42; raw[1] = 0x4D 124 raw[2] = 70 // file_size LE 125 raw[10] = 54 // pixel_offset = 0x36 126 raw[14] = 40 // dib_size = BITMAPINFOHEADER 127 128 raw[18] = 2 // width = 2 129 raw[22] = 2 // height = 2 130 raw[26] = 1 // planes = 1 131 raw[28] = 24 // bits_per_pixel = 24 132 raw[30] = 0 // BI_RGB 133 134 let h_raw: *u8 = sys_mmap(128) 135 let h: *BmpHeader = h_raw as *BmpHeader 136 if bmp_parse_header(raw, 128, h) != 0 { return 1 } 137 if h.file_size != 70 { return 2 } 138 if h.pixel_offset != 54 { return 3 } 139 if h.dib_size != 40 { return 4 } 140 if h.width != 2 { return 5 } 141 if h.height != 2 { return 6 } 142 if h.bits_per_pixel != 24 { return 7 } 143 144 // Bad magic -> BMP_ERR_FORMAT. 145 raw[0] = 0 146 if bmp_parse_header(raw, 128, h) != BMP_ERR_FORMAT { return 8 } 147 148 // Unknown DIB size -> BMP_ERR_DIB. 149 raw[0] = 0x42 150 raw[14] = 99 151 if bmp_parse_header(raw, 128, h) != BMP_ERR_DIB { return 9 } 152 return 0 153}