code wiki / (root) / png_header.nx

png_header.nx source

↩ module page · 165 lines · 5827 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 32import "syscalls.nx" 33 34const PNG_ERR_FORMAT: i64 = -1 35const PNG_ERR_SHORT: i64 = -2 36const PNG_ERR_IHDR: i64 = -3 37 38struct PngIhdr { 39 width: i64, 40 height: i64, 41 bit_depth: i64, 42 color_type: i64, 43 compression: i64, 44 filter: i64, 45 interlace: i64, 46} 47 48// Read a big-endian u32. 49func png_read_u32_be(buf: *u8, off: i64) -> i64 { 50 let b0: i64 = buf[off] 51 let b1: i64 = buf[off + 1] 52 let b2: i64 = buf[off + 2] 53 let b3: i64 = buf[off + 3] 54 return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3 55} 56 57// Check PNG signature at buf[0..8]. Returns 0 on match. 58func png_check_sig(buf: *u8, n: i64) -> i64 { 59 if n < 8 { return PNG_ERR_SHORT } 60 if buf[0] != 0x89 { return PNG_ERR_FORMAT } 61 if buf[1] != 0x50 { return PNG_ERR_FORMAT } // 'P' 62 if buf[2] != 0x4E { return PNG_ERR_FORMAT } // 'N' 63 if buf[3] != 0x47 { return PNG_ERR_FORMAT } // 'G' 64 if buf[4] != 0x0D { return PNG_ERR_FORMAT } 65 if buf[5] != 0x0A { return PNG_ERR_FORMAT } 66 if buf[6] != 0x1A { return PNG_ERR_FORMAT } 67 if buf[7] != 0x0A { return PNG_ERR_FORMAT } 68 return 0 69} 70 71// Parse the IHDR chunk that must follow the signature. Returns 72// 0 on success, negative on error. 73func png_parse_ihdr(buf: *u8, n: i64, out: *PngIhdr) -> i64 { 74 // Chunk starts right after signature at offset 8. 75 if n < 8 + 8 + 13 + 4 { return PNG_ERR_SHORT } 76 let len: i64 = png_read_u32_be(buf, 8) 77 if len != 13 { return PNG_ERR_IHDR } 78 // Type tag must be "IHDR" = 49 48 44 52. 79 if buf[12] != 0x49 { return PNG_ERR_IHDR } 80 if buf[13] != 0x48 { return PNG_ERR_IHDR } 81 if buf[14] != 0x44 { return PNG_ERR_IHDR } 82 if buf[15] != 0x52 { return PNG_ERR_IHDR } 83 84 out.width = png_read_u32_be(buf, 16) 85 out.height = png_read_u32_be(buf, 20) 86 out.bit_depth = buf[24] 87 out.color_type = buf[25] 88 out.compression = buf[26] 89 out.filter = buf[27] 90 out.interlace = buf[28] 91 return 0 92} 93 94// Locate the next chunk after offset. Returns offset of chunk 95// start (length field) or -1 if no more chunks / malformed. 96// `cur_off` is the current chunk's length field; we advance past 97// it + type + data + crc = 4 + 4 + length + 4. 98func png_next_chunk(buf: *u8, n: i64, cur_off: i64) -> i64 { 99 if n < cur_off + 8 { return -1 } 100 let len: i64 = png_read_u32_be(buf, cur_off) 101 let next: i64 = cur_off + 4 + 4 + len + 4 102 if next + 8 > n { return -1 } 103 return next 104} 105 106// Copy a chunk's 4-byte type tag to out[0..4]. Returns 0 on ok. 107func png_chunk_type(buf: *u8, n: i64, off: i64, out: *u8) -> i64 { 108 if n < off + 8 { return PNG_ERR_SHORT } 109 out[0] = buf[off + 4] 110 out[1] = buf[off + 5] 111 out[2] = buf[off + 6] 112 out[3] = buf[off + 7] 113 return 0 114} 115 116// Test if a chunk at `off` has the four-letter type `t0..t3`. 117// Cheaper than copying the tag when caller just wants to check. 118func png_chunk_is(buf: *u8, n: i64, off: i64, 119 t0: i64, t1: i64, t2: i64, t3: i64) -> i64 { 120 if n < off + 8 { return 0 } 121 if buf[off + 4] != t0 { return 0 } 122 if buf[off + 5] != t1 { return 0 } 123 if buf[off + 6] != t2 { return 0 } 124 if buf[off + 7] != t3 { return 0 } 125 return 1 126} 127 128// Compile-only smoke. 129func main() -> i64 { 130 let raw: *u8 = sys_mmap(128) 131 var i: i64 = 0 132 while i < 128 { raw[i] = 0; i = i + 1 } 133 134 // Signature. 135 raw[0] = 0x89; raw[1] = 0x50; raw[2] = 0x4E; raw[3] = 0x47 136 raw[4] = 0x0D; raw[5] = 0x0A; raw[6] = 0x1A; raw[7] = 0x0A 137 138 // IHDR chunk length = 13 (big-endian). 139 raw[11] = 13 140 // Type "IHDR" 141 raw[12] = 0x49; raw[13] = 0x48; raw[14] = 0x44; raw[15] = 0x52 142 // Width = 320, Height = 200, bit_depth=8, color_type=2 (RGB) 143 // 320 = 0x00000140, 200 = 0x000000C8 144 raw[16] = 0x00; raw[17] = 0x00; raw[18] = 0x01; raw[19] = 0x40 145 raw[20] = 0x00; raw[21] = 0x00; raw[22] = 0x00; raw[23] = 0xC8 146 raw[24] = 8 // bit_depth 147 raw[25] = 2 // color_type RGB 148 raw[26] = 0; raw[27] = 0; raw[28] = 0 149 // CRC (4 bytes) -- leave zero, we don't verify. 150 151 if png_check_sig(raw, 128) != 0 { return 1 } 152 153 let ih_raw: *u8 = sys_mmap(64) 154 let ih: *PngIhdr = ih_raw as *PngIhdr 155 if png_parse_ihdr(raw, 128, ih) != 0 { return 2 } 156 if ih.width != 320 { return 3 } 157 if ih.height != 200 { return 4 } 158 if ih.bit_depth != 8 { return 5 } 159 if ih.color_type != 2 { return 6 } 160 161 // Bad signature -> PNG_ERR_FORMAT. 162 raw[0] = 0 163 if png_check_sig(raw, 128) != PNG_ERR_FORMAT { return 7 } 164 return 0 165}