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}