code wiki / (root) / jpeg_header.nx

jpeg_header.nx source

↩ module page · 156 lines · 5570 B

1// jpeg_header.nx -- parse JPEG JFIF markers enough to extract 2// width + height + component count without decoding the scan. 3// 4// JPEG streams are a sequence of markers, each: 5// - 0xFF prefix byte (occasionally padded with more 0xFFs) 6// - 1 marker byte 7// - For most markers: 2-byte big-endian length (includes its own 8// 2 bytes) + (length-2) bytes of data. 9// - SOI (0xD8) and EOI (0xD9) and RSTn have no length. 10// 11// The markers we care about: 12// 0xD8 SOI start of image (must be first) 13// 0xE0 APP0 JFIF identifier, density info 14// 0xE1 APP1 often EXIF 15// 0xC0..0xCF SOF start of frame -- contains width + height + 16// component count. 0xC4 is DHT, 0xC8 is 17// reserved, 0xCC is DAC -- these are NOT SOF 18// even though they're in the range; we skip. 19// 0xDA SOS start of scan -- data follows; we stop here. 20// 0xD9 EOI end of image. 21// 22// SOF data layout: 23// 1 byte precision (bits per sample, typically 8) 24// 2 bytes height (BE) 25// 2 bytes width (BE) 26// 1 byte num_components (1 grayscale, 3 YCbCr, 4 CMYK) 27// 28// Invariants: 29// J1 First two bytes must be 0xFF 0xD8 (SOI). 30// J2 Parser advances until it finds a SOF marker; returns its 31// decoded dims. If SOS is hit first we return ERR_NOSOF 32// (malformed file / truncated header). 33// J3 We ignore restart markers (0xD0..0xD7) which have no 34// length field. 35 36import "syscalls.nx" 37 38const JPEG_ERR_FORMAT: i64 = -1 39const JPEG_ERR_SHORT: i64 = -2 40const JPEG_ERR_NOSOF: i64 = -3 41 42struct JpegInfo { 43 precision: i64, // bits per sample 44 width: i64, // pixels 45 height: i64, // pixels 46 num_components: i64, // 1/3/4 47} 48 49func jpeg_read_u16_be(buf: *u8, off: i64) -> i64 { 50 return (buf[off] << 8) | buf[off + 1] 51} 52 53// Test whether a marker byte in range 0xC0..0xCF is actually a SOF. 54// DHT (0xC4), reserved (0xC8), DAC (0xCC) are exceptions. 55func jpeg_is_sof(m: i64) -> i64 { 56 if m < 0xC0 { return 0 } 57 if m > 0xCF { return 0 } 58 if m == 0xC4 { return 0 } 59 if m == 0xC8 { return 0 } 60 if m == 0xCC { return 0 } 61 return 1 62} 63 64// Test whether a marker is a length-less one (SOI, EOI, RSTn). 65func jpeg_no_length(m: i64) -> i64 { 66 if m == 0xD8 { return 1 } 67 if m == 0xD9 { return 1 } 68 if m >= 0xD0 { 69 if m <= 0xD7 { return 1 } 70 } 71 return 0 72} 73 74// Parse JPEG headers until we find the SOF marker. Returns 0 on 75// success with info populated; negative on error. 76func jpeg_parse_header(buf: *u8, n: i64, info: *JpegInfo) -> i64 { 77 if n < 4 { return JPEG_ERR_SHORT } 78 // SOI must be 0xFF 0xD8. 79 if buf[0] != 0xFF { return JPEG_ERR_FORMAT } 80 if buf[1] != 0xD8 { return JPEG_ERR_FORMAT } 81 82 var off: i64 = 2 83 while off + 4 <= n { 84 // Consume any 0xFF fill bytes. 85 while off < n { 86 if buf[off] != 0xFF { return JPEG_ERR_FORMAT } 87 if off + 1 < n { 88 if buf[off + 1] != 0xFF { break } 89 } 90 off = off + 1 91 } 92 if off + 2 > n { return JPEG_ERR_SHORT } 93 let m: i64 = buf[off + 1] 94 off = off + 2 95 96 // SOS means we've passed the headers without finding SOF. 97 if m == 0xDA { return JPEG_ERR_NOSOF } 98 // EOI end of file. 99 if m == 0xD9 { return JPEG_ERR_NOSOF } 100 // Length-less markers (SOI, RSTn); ignore. 101 if jpeg_no_length(m) == 1 { continue } 102 103 if off + 2 > n { return JPEG_ERR_SHORT } 104 let seg_len: i64 = jpeg_read_u16_be(buf, off) 105 if seg_len < 2 { return JPEG_ERR_FORMAT } 106 if off + seg_len > n { return JPEG_ERR_SHORT } 107 108 if jpeg_is_sof(m) == 1 { 109 // SOF payload: precision(1) + height(2) + width(2) + ncomp(1). 110 if seg_len < 8 { return JPEG_ERR_FORMAT } 111 info.precision = buf[off + 2] 112 info.height = jpeg_read_u16_be(buf, off + 3) 113 info.width = jpeg_read_u16_be(buf, off + 5) 114 info.num_components = buf[off + 7] 115 return 0 116 } 117 118 off = off + seg_len 119 } 120 return JPEG_ERR_NOSOF 121} 122 123// Compile-only smoke -- forge a minimal JPEG with SOI, APP0, 124// SOF0, SOS. Only SOI + SOF0 matter for this test. 125func main() -> i64 { 126 let raw: *u8 = sys_mmap(64) 127 var i: i64 = 0 128 while i < 64 { raw[i] = 0; i = i + 1 } 129 130 // SOI 131 raw[0] = 0xFF; raw[1] = 0xD8 132 // SOF0 (0xFF 0xC0) with length 17 (big-endian), precision=8, 133 // height=200 (0x00C8), width=320 (0x0140), num_components=3 134 raw[2] = 0xFF; raw[3] = 0xC0 135 raw[4] = 0; raw[5] = 17 136 raw[6] = 8 // precision 137 raw[7] = 0x00; raw[8] = 0xC8 // height = 200 138 raw[9] = 0x01; raw[10] = 0x40 // width = 320 139 raw[11] = 3 // num_components 140 // pad out seg_len-6 = 11 bytes of dummy component specs 141 i = 12 142 while i < 19 { raw[i] = 0; i = i + 1 } 143 144 let info_raw: *u8 = sys_mmap(64) 145 let info: *JpegInfo = info_raw as *JpegInfo 146 if jpeg_parse_header(raw, 64, info) != 0 { return 1 } 147 if info.width != 320 { return 2 } 148 if info.height != 200 { return 3 } 149 if info.num_components != 3 { return 4 } 150 if info.precision != 8 { return 5 } 151 152 // Bad SOI 153 raw[1] = 0 154 if jpeg_parse_header(raw, 64, info) != JPEG_ERR_FORMAT { return 6 } 155 return 0 156}