code wiki / _hdl_build / nx_jpeg_write.nx
nx_jpeg_write.nx source
↩ module page · 187 lines · 9068 B
1// nx_jpeg_write.nx -- SOVEREIGN baseline grayscale JFIF writer (J3). Assembles a complete, standards-
2// compliant baseline JPEG file (a real .jpg any decoder opens) from an 8-bit luma image: SOI, APP0/JFIF,
3// DQT (quant table in zigzag order), SOF0 (8-bit, 1 component), DHT (luma DC + AC standard Annex-K
4// tables), SOS, the entropy-coded scan (per-8x8-block DC-diff + AC-RLE via nx_jpeg_block_enc, final byte
5// 1-padded, 0xFF byte-stuffed), EOI. Composes the audited nx_dct8 / nx_quant_table / nx_zigzag +
6// nx_jpeg_huff_enc (J1) + nx_jpeg_block_enc (J2). W,H must be multiples of 8. JPEG/Annex K is public
7// since 1992 -> patent-clean. license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_h264_bitwriter.nx"
10import "nx_dct8.nx"
11import "nx_zigzag.nx"
12import "nx_jpeg_huff_enc.nx"
13import "nx_jpeg_block_enc.nx"
14const K_MAGIC_1024: i64 = 1024
15
16func jw_b(out: *u8, off: i64, v: i64) -> i64 { out[off]=(v & 0xff) as u8; return off+1 }
17func jw_u16(out: *u8, off: i64, v: i64) -> i64 { out[off]=((v>>8)&0xff) as u8; out[off+1]=(v&0xff) as u8; return off+2 }
18func jw_qround(v: i64, q: i64) -> i64 { if v>=0 { return (v + q/2)/q } return 0 - (((0-v) + q/2)/q) }
19
20// Write a full baseline grayscale JPEG of pixels[W*H] (row-major, 0..255) into out; return byte length.
21// qt = row-major luma quant table; dcB/dcV/acB/acV = standard Annex-K BITS/HUFFVAL; dcCO/dcSI/acCO/acSI
22// = generated canonical codes; to_zz/from_zz = zigzag maps; DM/scratch/ti/to = DCT working buffers.
23func jw_write_jpeg(out: *u8, W: i64, H: i64, qt: *i64,
24 dcB: *i64, dcV: *i64, acB: *i64, acV: *i64,
25 dcCO: *i64, dcSI: *i64, acCO: *i64, acSI: *i64,
26 to_zz: *i64, from_zz: *i64, DM: *i64, scratch: *i64, ti: *i64, to: *i64,
27 pixels: *i64) -> i64 {
28 var o: i64=0
29 // SOI
30 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xD8)
31 // APP0 / JFIF
32 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xE0); o=jw_u16(out,o,16)
33 o=jw_b(out,o,0x4A); o=jw_b(out,o,0x46); o=jw_b(out,o,0x49); o=jw_b(out,o,0x46); o=jw_b(out,o,0x00)
34 o=jw_b(out,o,1); o=jw_b(out,o,1); o=jw_b(out,o,0)
35 o=jw_u16(out,o,1); o=jw_u16(out,o,1); o=jw_b(out,o,0); o=jw_b(out,o,0)
36 // DQT (quant table in zigzag order)
37 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xDB); o=jw_u16(out,o,67); o=jw_b(out,o,0x00)
38 var k: i64=0
39 while k<64 { o=jw_b(out,o, qt[from_zz[k]]); k=k+1 }
40 // SOF0 (baseline, 8-bit, 1 component)
41 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xC0); o=jw_u16(out,o,11); o=jw_b(out,o,8)
42 o=jw_u16(out,o,H); o=jw_u16(out,o,W); o=jw_b(out,o,1)
43 o=jw_b(out,o,1); o=jw_b(out,o,0x11); o=jw_b(out,o,0)
44 // DHT - DC luma
45 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xC4); o=jw_u16(out,o, 2+1+16+12); o=jw_b(out,o,0x00)
46 var i: i64=1; while i<=16 { o=jw_b(out,o, dcB[i]); i=i+1 }
47 i=0; while i<12 { o=jw_b(out,o, dcV[i]); i=i+1 }
48 // DHT - AC luma
49 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xC4); o=jw_u16(out,o, 2+1+16+162); o=jw_b(out,o,0x10)
50 i=1; while i<=16 { o=jw_b(out,o, acB[i]); i=i+1 }
51 i=0; while i<162 { o=jw_b(out,o, acV[i]); i=i+1 }
52 // SOS
53 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xDA); o=jw_u16(out,o,8); o=jw_b(out,o,1)
54 o=jw_b(out,o,1); o=jw_b(out,o,0x00); o=jw_b(out,o,0); o=jw_b(out,o,63); o=jw_b(out,o,0)
55 // entropy scan: encode every 8x8 block into a bit buffer
56 let ebuf: *u8=sys_mmap(W*H + K_MAGIC_1024)
57 let bw: *BitWriter=sys_mmap(64) as *BitWriter; bw_init(bw, ebuf, W*H + K_MAGIC_1024)
58 let blk: *i64=sys_mmap(64*8) as *i64
59 let sh: *i64=sys_mmap(64*8) as *i64
60 let co: *i64=sys_mmap(64*8) as *i64
61 let q: *i64=sys_mmap(64*8) as *i64
62 let zz: *i64=sys_mmap(64*8) as *i64
63 var prevdc: i64=0
64 var by: i64=0
65 while by < H/8 {
66 var bx: i64=0
67 while bx < W/8 {
68 var r: i64=0
69 while r<8 { var c: i64=0; while c<8 { sh[r*8+c]=pixels[(by*8+r)*W+(bx*8+c)]-128; c=c+1 } r=r+1 }
70 nx_dct8_forward_2d(DM, sh, co, scratch, ti, to)
71 i=0; while i<64 { q[i]=jw_qround(co[i], qt[i]); i=i+1 }
72 nx_zigzag_scan(q, to_zz, zz)
73 prevdc=jbe_encode_block(bw, dcCO, dcSI, acCO, acSI, zz, prevdc)
74 bx=bx+1
75 }
76 by=by+1
77 }
78 // pad the final partial byte with 1-bits (JPEG convention)
79 while bw.bit_pos != 0 { bw_write_bit(bw, 1) }
80 let elen: i64=bw.byte_pos
81 // copy the scan into out with 0xFF byte-stuffing
82 i=0
83 while i<elen {
84 let bb: i64=ebuf[i] as i64
85 o=jw_b(out,o,bb)
86 if bb==0xFF { o=jw_b(out,o,0x00) }
87 i=i+1
88 }
89 // EOI
90 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xD9)
91 return o
92}
93
94// Flush the accumulated interval bits: 1-pad the final partial byte, copy into out
95// with 0xFF 0x00 stuffing, then re-arm the bit writer for the next interval.
96func jw_flush_interval(out: *u8, o: i64, bw: *BitWriter, ebuf: *u8, ecap: i64) -> i64 {
97 while bw.bit_pos != 0 { bw_write_bit(bw, 1) }
98 var oo: i64=o
99 var fi: i64=0
100 while fi<bw.byte_pos {
101 let bb: i64=ebuf[fi] as i64
102 oo=jw_b(out,oo,bb)
103 if bb==0xFF { oo=jw_b(out,oo,0x00) }
104 fi=fi+1
105 }
106 // BitWriter ORs set-bits into a ZEROED buffer -- re-arming over stale interval
107 // bytes would OR the next interval into the last one's data. Zero what we used.
108 var zi: i64=0
109 while zi<bw.byte_pos { ebuf[zi]=0 as u8; zi=zi+1 }
110 bw_init(bw, ebuf, ecap)
111 return oo
112}
113
114// jw_write_jpeg with restart intervals (T.81 F.2.1.3.1): ri > 0 emits a DRI segment
115// and, every ri MCUs, closes the entropy interval (1-padded, stuffed) and writes an
116// UNSTUFFED RSTn marker (n cycling 0..7), resetting the DC predictor. The marker is
117// written straight to out AFTER the interval flush precisely so the stuffing pass
118// can never mangle it. ri == 0 produces byte-identical output to jw_write_jpeg.
119func jw_write_jpeg_ri(out: *u8, W: i64, H: i64, qt: *i64,
120 dcB: *i64, dcV: *i64, acB: *i64, acV: *i64,
121 dcCO: *i64, dcSI: *i64, acCO: *i64, acSI: *i64,
122 to_zz: *i64, from_zz: *i64, DM: *i64, scratch: *i64, ti: *i64, to: *i64,
123 pixels: *i64, ri: i64) -> i64 {
124 var o: i64=0
125 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xD8)
126 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xE0); o=jw_u16(out,o,16)
127 o=jw_b(out,o,0x4A); o=jw_b(out,o,0x46); o=jw_b(out,o,0x49); o=jw_b(out,o,0x46); o=jw_b(out,o,0x00)
128 o=jw_b(out,o,1); o=jw_b(out,o,1); o=jw_b(out,o,0)
129 o=jw_u16(out,o,1); o=jw_u16(out,o,1); o=jw_b(out,o,0); o=jw_b(out,o,0)
130 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xDB); o=jw_u16(out,o,67); o=jw_b(out,o,0x00)
131 var k: i64=0
132 while k<64 { o=jw_b(out,o, qt[from_zz[k]]); k=k+1 }
133 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xC0); o=jw_u16(out,o,11); o=jw_b(out,o,8)
134 o=jw_u16(out,o,H); o=jw_u16(out,o,W); o=jw_b(out,o,1)
135 o=jw_b(out,o,1); o=jw_b(out,o,0x11); o=jw_b(out,o,0)
136 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xC4); o=jw_u16(out,o, 2+1+16+12); o=jw_b(out,o,0x00)
137 var i: i64=1; while i<=16 { o=jw_b(out,o, dcB[i]); i=i+1 }
138 i=0; while i<12 { o=jw_b(out,o, dcV[i]); i=i+1 }
139 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xC4); o=jw_u16(out,o, 2+1+16+162); o=jw_b(out,o,0x10)
140 i=1; while i<=16 { o=jw_b(out,o, acB[i]); i=i+1 }
141 i=0; while i<162 { o=jw_b(out,o, acV[i]); i=i+1 }
142 if ri>0 { o=jw_b(out,o,0xFF); o=jw_b(out,o,0xDD); o=jw_u16(out,o,4); o=jw_u16(out,o,ri) }
143 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xDA); o=jw_u16(out,o,8); o=jw_b(out,o,1)
144 o=jw_b(out,o,1); o=jw_b(out,o,0x00); o=jw_b(out,o,0); o=jw_b(out,o,63); o=jw_b(out,o,0)
145 let ecap: i64=W*H + K_MAGIC_1024
146 let ebuf: *u8=sys_mmap(ecap)
147 let bw: *BitWriter=sys_mmap(64) as *BitWriter; bw_init(bw, ebuf, ecap)
148 let sh: *i64=sys_mmap(64*8) as *i64
149 let co: *i64=sys_mmap(64*8) as *i64
150 let q: *i64=sys_mmap(64*8) as *i64
151 let zz: *i64=sys_mmap(64*8) as *i64
152 var prevdc: i64=0
153 var mcus_done: i64=0
154 var ri_left: i64=ri
155 var rst_n: i64=0
156 let mcu_total: i64=(H/8)*(W/8)
157 var by: i64=0
158 while by < H/8 {
159 var bx: i64=0
160 while bx < W/8 {
161 var r: i64=0
162 while r<8 { var c: i64=0; while c<8 { sh[r*8+c]=pixels[(by*8+r)*W+(bx*8+c)]-128; c=c+1 } r=r+1 }
163 nx_dct8_forward_2d(DM, sh, co, scratch, ti, to)
164 i=0; while i<64 { q[i]=jw_qround(co[i], qt[i]); i=i+1 }
165 nx_zigzag_scan(q, to_zz, zz)
166 prevdc=jbe_encode_block(bw, dcCO, dcSI, acCO, acSI, zz, prevdc)
167 mcus_done=mcus_done+1
168 if ri>0 {
169 ri_left=ri_left-1
170 if ri_left==0 {
171 if mcus_done < mcu_total {
172 o=jw_flush_interval(out, o, bw, ebuf, ecap)
173 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xD0+rst_n)
174 rst_n=rst_n+1; if rst_n==8 { rst_n=0 }
175 prevdc=0
176 }
177 ri_left=ri
178 }
179 }
180 bx=bx+1
181 }
182 by=by+1
183 }
184 o=jw_flush_interval(out, o, bw, ebuf, ecap)
185 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xD9)
186 return o
187}