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}