code wiki / (root) / nx_png_header.nx

nx_png_header.nx source

↩ module page · 171 lines · 5916 B

1// png_header.nx -- parse PNG signature + IHDR + chunk stream. 2// 3// PNG (W3C PNG 2nd ed / ISO 15948): 4// - 8-byte signature: 0x89 'P' 'N' 'G' 0x0D 0x0A 0x1A 0x0A 5// - Series of chunks, each: 6// 4-byte big-endian length 7// 4-byte type tag (ASCII, case encodes flags) 8// <length> bytes data 9// 4-byte CRC-32 over (type || data) 10// - First chunk must be IHDR (13-byte data payload). 11// - Must end with IEND (0-byte data). 12// 13// IHDR layout: 14// width u32 BE 15// height u32 BE 16// bit_depth u8 (1/2/4/8/16) 17// color_type u8 (0 gray, 2 rgb, 3 indexed, 4 gray+alpha, 6 rgba) 18// compression u8 must be 0 19// filter u8 must be 0 20// interlace u8 0=none, 1=Adam7 21// 22// Module does: sig-check, IHDR parse, chunk-walker (type tag + 23// offsets) without CRC validation. Callers handle CRC verification 24// once deflate.nx lands and full decode is viable. 25// 26// Invariants: 27// P1 Signature bytes are exact. 28// P2 IHDR must be first; length must equal 13. 29// P3 Color-type + bit-depth combinations per W3C PNG table 5.12 30// are NOT validated here; caller decides. 31 32// nx_safety_envelope: 33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 34// sil_target: SIL1 35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 36// verdict: NOT_YET_EVALUATED 37 38import "nx_syscalls.nx" 39 40const PNG_ERR_FORMAT: i64 = -1 41const PNG_ERR_SHORT: i64 = -2 42const PNG_ERR_IHDR: i64 = -3 43 44struct PngIhdr { 45 width: i64, 46 height: i64, 47 bit_depth: i64, 48 color_type: i64, 49 compression: i64, 50 filter: i64, 51 interlace: i64, 52} 53 54// Read a big-endian u32. 55func png_read_u32_be(buf: *u8, off: i64) -> i64 { 56 let b0: i64 = buf[off] 57 let b1: i64 = buf[off + 1] 58 let b2: i64 = buf[off + 2] 59 let b3: i64 = buf[off + 3] 60 return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3 61} 62 63// Check PNG signature at buf[0..8]. Returns 0 on match. 64func png_check_sig(buf: *u8, n: i64) -> i64 { 65 if n < 8 { return PNG_ERR_SHORT } 66 if buf[0] != 0x89 { return PNG_ERR_FORMAT } 67 if buf[1] != 0x50 { return PNG_ERR_FORMAT } // 'P' 68 if buf[2] != 0x4E { return PNG_ERR_FORMAT } // 'N' 69 if buf[3] != 0x47 { return PNG_ERR_FORMAT } // 'G' 70 if buf[4] != 0x0D { return PNG_ERR_FORMAT } 71 if buf[5] != 0x0A { return PNG_ERR_FORMAT } 72 if buf[6] != 0x1A { return PNG_ERR_FORMAT } 73 if buf[7] != 0x0A { return PNG_ERR_FORMAT } 74 return 0 75} 76 77// Parse the IHDR chunk that must follow the signature. Returns 78// 0 on success, negative on error. 79func png_parse_ihdr(buf: *u8, n: i64, out: *PngIhdr) -> i64 { 80 // Chunk starts right after signature at offset 8. 81 if n < 8 + 8 + 13 + 4 { return PNG_ERR_SHORT } 82 let len: i64 = png_read_u32_be(buf, 8) 83 if len != 13 { return PNG_ERR_IHDR } 84 // Type tag must be "IHDR" = 49 48 44 52. 85 if buf[12] != 0x49 { return PNG_ERR_IHDR } 86 if buf[13] != 0x48 { return PNG_ERR_IHDR } 87 if buf[14] != 0x44 { return PNG_ERR_IHDR } 88 if buf[15] != 0x52 { return PNG_ERR_IHDR } 89 90 out.width = png_read_u32_be(buf, 16) 91 out.height = png_read_u32_be(buf, 20) 92 out.bit_depth = buf[24] 93 out.color_type = buf[25] 94 out.compression = buf[26] 95 out.filter = buf[27] 96 out.interlace = buf[28] 97 return 0 98} 99 100// Locate the next chunk after offset. Returns offset of chunk 101// start (length field) or -1 if no more chunks / malformed. 102// `cur_off` is the current chunk's length field; we advance past 103// it + type + data + crc = 4 + 4 + length + 4. 104func png_next_chunk(buf: *u8, n: i64, cur_off: i64) -> i64 { 105 if n < cur_off + 8 { return -1 } 106 let len: i64 = png_read_u32_be(buf, cur_off) 107 let next: i64 = cur_off + 4 + 4 + len + 4 108 if next + 8 > n { return -1 } 109 return next 110} 111 112// Copy a chunk's 4-byte type tag to out[0..4]. Returns 0 on ok. 113func png_chunk_type(buf: *u8, n: i64, off: i64, out: *u8) -> i64 { 114 if n < off + 8 { return PNG_ERR_SHORT } 115 out[0] = buf[off + 4] 116 out[1] = buf[off + 5] 117 out[2] = buf[off + 6] 118 out[3] = buf[off + 7] 119 return 0 120} 121 122// Test if a chunk at `off` has the four-letter type `t0..t3`. 123// Cheaper than copying the tag when caller just wants to check. 124func png_chunk_is(buf: *u8, n: i64, off: i64, 125 t0: i64, t1: i64, t2: i64, t3: i64) -> i64 { 126 if n < off + 8 { return 0 } 127 if buf[off + 4] != t0 { return 0 } 128 if buf[off + 5] != t1 { return 0 } 129 if buf[off + 6] != t2 { return 0 } 130 if buf[off + 7] != t3 { return 0 } 131 return 1 132} 133 134// Compile-only smoke. 135func main() -> i64 { 136 let raw: *u8 = sys_mmap(128) 137 var i: i64 = 0 138 while i < 128 { raw[i] = 0; i = i + 1 } 139 140 // Signature. 141 raw[0] = 0x89; raw[1] = 0x50; raw[2] = 0x4E; raw[3] = 0x47 142 raw[4] = 0x0D; raw[5] = 0x0A; raw[6] = 0x1A; raw[7] = 0x0A 143 144 // IHDR chunk length = 13 (big-endian). 145 raw[11] = 13 146 // Type "IHDR" 147 raw[12] = 0x49; raw[13] = 0x48; raw[14] = 0x44; raw[15] = 0x52 148 // Width = 320, Height = 200, bit_depth=8, color_type=2 (RGB) 149 // 320 = 0x00000140, 200 = 0x000000C8 150 raw[16] = 0x00; raw[17] = 0x00; raw[18] = 0x01; raw[19] = 0x40 151 raw[20] = 0x00; raw[21] = 0x00; raw[22] = 0x00; raw[23] = 0xC8 152 raw[24] = 8 // bit_depth 153 raw[25] = 2 // color_type RGB 154 raw[26] = 0; raw[27] = 0; raw[28] = 0 155 // CRC (4 bytes) -- leave zero, we don't verify. 156 157 if png_check_sig(raw, 128) != 0 { return 1 } 158 159 let ih_raw: *u8 = sys_mmap(64) 160 let ih: *PngIhdr = ih_raw as *PngIhdr 161 if png_parse_ihdr(raw, 128, ih) != 0 { return 2 } 162 if ih.width != 320 { return 3 } 163 if ih.height != 200 { return 4 } 164 if ih.bit_depth != 8 { return 5 } 165 if ih.color_type != 2 { return 6 } 166 167 // Bad signature -> PNG_ERR_FORMAT. 168 raw[0] = 0 169 if png_check_sig(raw, 128) != PNG_ERR_FORMAT { return 7 } 170 return 0 171}