code wiki / _hdl_build / nx_jpeg_color_write.nx
nx_jpeg_color_write.nx source
↩ module page · 115 lines · 6960 B
1// nx_jpeg_color_write.nx -- SOVEREIGN baseline COLOR (YCbCr 4:4:4) JFIF writer. Extends the grayscale J3
2// writer (nx_jpeg_write) to 3 components so photo thumbnails encode as real .jpg (far smaller than lossless
3// PNG). 2 DQT (luma id0 + chroma id1, via nx_qt_luma/nx_qt_chroma), 3-component SOF0/SOS, interleaved MCUs
4// (one 8x8 block per component, no chroma subsampling). REUSES (DRY): jw_b/jw_u16/jw_qround + nx_dct8 +
5// nx_zigzag + nx_jpeg_block_enc + nx_jpeg_huff_enc, sharing the LUMA DC/AC Huffman tables across all three
6// components (valid baseline -- table choice is the encoder's; the chroma compression win is the quant table).
7// RGB->YCbCr is the standard 8-bit-integer fast transform (NO floats). W,H must be multiples of 8.
8// JPEG/Annex K public since 1992 -> patent-clean. license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_h264_bitwriter.nx"
11import "nx_dct8.nx"
12import "nx_quant_table.nx"
13import "nx_zigzag.nx"
14import "nx_jpeg_huff_enc.nx"
15import "nx_jpeg_block_enc.nx"
16import "nx_jpeg_write.nx" // reuse jw_b / jw_u16 / jw_qround
17const K_MAGIC_4096: i64 = 4096
18
19// integer RGB->YCbCr (8-bit coefficients; sums: Y 77+150+29=256, Cb/Cr center on 128). clamps to [0,255].
20func jcw_y(r: i64, g: i64, b: i64) -> i64 { var v: i64 = (77*r + 150*g + 29*b) >> 8; if v < 0 { v = 0 } if v > 255 { v = 255 } return v }
21func jcw_cb(r: i64, g: i64, b: i64) -> i64 { var v: i64 = (((0-43)*r - 85*g + 128*b) >> 8) + 128; if v < 0 { v = 0 } if v > 255 { v = 255 } return v }
22func jcw_cr(r: i64, g: i64, b: i64) -> i64 { var v: i64 = ((128*r - 107*g - 21*b) >> 8) + 128; if v < 0 { v = 0 } if v > 255 { v = 255 } return v }
23
24// encode one 8x8 block of `plane` at block (bx,by): level-shift, forward DCT, quantize with qt, zigzag,
25// entropy-encode (DC-diff vs prevdc + AC-RLE). returns the new prevdc. Uses caller-shared scratch buffers.
26func jcw_block(bw: *BitWriter, plane: *i64, W: i64, bx: i64, by: i64, qt: *i64,
27 dcCO: *i64, dcSI: *i64, acCO: *i64, acSI: *i64, to_zz: *i64,
28 DM: *i64, scratch: *i64, ti: *i64, to: *i64,
29 sh: *i64, co: *i64, q: *i64, zz: *i64, prevdc: i64) -> i64 {
30 var r: i64 = 0
31 while r < 8 { var c: i64 = 0; while c < 8 { sh[r*8+c] = plane[(by*8+r)*W + (bx*8+c)] - 128; c = c + 1 } r = r + 1 }
32 nx_dct8_forward_2d(DM, sh, co, scratch, ti, to)
33 var i: i64 = 0; while i < 64 { q[i] = jw_qround(co[i], qt[i]); i = i + 1 }
34 nx_zigzag_scan(q, to_zz, zz)
35 return jbe_encode_block(bw, dcCO, dcSI, acCO, acSI, zz, prevdc)
36}
37
38// Encode an interleaved RGB image (rgb[3*W*H], row-major, 0..255) into a baseline color JPEG in `out`.
39// Returns the JPEG byte length. dcB/dcV/acB/acV = Annex-K luma BITS/HUFFVAL (for the DHT markers);
40// dcCO/dcSI/acCO/acSI = generated canonical codes; qtL/qtC = luma/chroma quant tables; the rest = DCT/zigzag bufs.
41func jcw_write_color(out: *u8, W: i64, H: i64, rgb: *u8,
42 qtL: *i64, qtC: *i64,
43 dcB: *i64, dcV: *i64, acB: *i64, acV: *i64,
44 dcCO: *i64, dcSI: *i64, acCO: *i64, acSI: *i64,
45 to_zz: *i64, from_zz: *i64, DM: *i64, scratch: *i64, ti: *i64, to: *i64) -> i64 {
46 let npx: i64 = W * H
47 let yP: *i64 = sys_mmap(npx*8) as *i64
48 let cbP: *i64 = sys_mmap(npx*8) as *i64
49 let crP: *i64 = sys_mmap(npx*8) as *i64
50 var p: i64 = 0
51 while p < npx {
52 let r: i64 = rgb[p*3] as i64; let g: i64 = rgb[p*3+1] as i64; let b: i64 = rgb[p*3+2] as i64
53 yP[p] = jcw_y(r,g,b); cbP[p] = jcw_cb(r,g,b); crP[p] = jcw_cr(r,g,b)
54 p = p + 1
55 }
56 var o: i64 = 0
57 // SOI
58 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xD8)
59 // APP0 / JFIF
60 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xE0); o=jw_u16(out,o,16)
61 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)
62 o=jw_b(out,o,1); o=jw_b(out,o,1); o=jw_b(out,o,0); o=jw_u16(out,o,1); o=jw_u16(out,o,1); o=jw_b(out,o,0); o=jw_b(out,o,0)
63 // DQT 0 (luma) + DQT 1 (chroma), each in zigzag order
64 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xDB); o=jw_u16(out,o,67); o=jw_b(out,o,0x00)
65 var k: i64=0; while k<64 { o=jw_b(out,o, qtL[from_zz[k]]); k=k+1 }
66 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xDB); o=jw_u16(out,o,67); o=jw_b(out,o,0x01)
67 k=0; while k<64 { o=jw_b(out,o, qtC[from_zz[k]]); k=k+1 }
68 // SOF0 baseline 8-bit, 3 components (len = 8 + 3*3 = 17)
69 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xC0); o=jw_u16(out,o,17); o=jw_b(out,o,8)
70 o=jw_u16(out,o,H); o=jw_u16(out,o,W); o=jw_b(out,o,3)
71 o=jw_b(out,o,1); o=jw_b(out,o,0x11); o=jw_b(out,o,0) // Y : id1, 1x1 sampling, quant table 0
72 o=jw_b(out,o,2); o=jw_b(out,o,0x11); o=jw_b(out,o,1) // Cb : id2, quant table 1
73 o=jw_b(out,o,3); o=jw_b(out,o,0x11); o=jw_b(out,o,1) // Cr : id3, quant table 1
74 // DHT: DC luma (class0 id0) + AC luma (class1 id0) -- shared by all 3 components
75 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)
76 var i: i64=1; while i<=16 { o=jw_b(out,o, dcB[i]); i=i+1 }
77 i=0; while i<12 { o=jw_b(out,o, dcV[i]); i=i+1 }
78 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)
79 i=1; while i<=16 { o=jw_b(out,o, acB[i]); i=i+1 }
80 i=0; while i<162 { o=jw_b(out,o, acV[i]); i=i+1 }
81 // SOS: 3 components, all selecting DC table 0 + AC table 0 (len = 6 + 2*3 = 12)
82 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xDA); o=jw_u16(out,o,12); o=jw_b(out,o,3)
83 o=jw_b(out,o,1); o=jw_b(out,o,0x00) // Y -> DC0/AC0
84 o=jw_b(out,o,2); o=jw_b(out,o,0x00) // Cb -> DC0/AC0
85 o=jw_b(out,o,3); o=jw_b(out,o,0x00) // Cr -> DC0/AC0
86 o=jw_b(out,o,0); o=jw_b(out,o,63); o=jw_b(out,o,0)
87 // interleaved entropy scan (4:4:4): per MCU encode Y, Cb, Cr (3 separate DC predictors)
88 let ebuf: *u8 = sys_mmap(npx*3 + K_MAGIC_4096)
89 let bw: *BitWriter = sys_mmap(64) as *BitWriter; bw_init(bw, ebuf, npx*3 + K_MAGIC_4096)
90 let sh: *i64=sys_mmap(64*8) as *i64; let co: *i64=sys_mmap(64*8) as *i64
91 let q: *i64=sys_mmap(64*8) as *i64; let zz: *i64=sys_mmap(64*8) as *i64
92 var pdY: i64=0; var pdCb: i64=0; var pdCr: i64=0
93 var by: i64=0
94 while by < H/8 {
95 var bx: i64=0
96 while bx < W/8 {
97 pdY = jcw_block(bw, yP, W, bx, by, qtL, dcCO,dcSI,acCO,acSI, to_zz, DM, scratch, ti, to, sh, co, q, zz, pdY)
98 pdCb = jcw_block(bw, cbP, W, bx, by, qtC, dcCO,dcSI,acCO,acSI, to_zz, DM, scratch, ti, to, sh, co, q, zz, pdCb)
99 pdCr = jcw_block(bw, crP, W, bx, by, qtC, dcCO,dcSI,acCO,acSI, to_zz, DM, scratch, ti, to, sh, co, q, zz, pdCr)
100 bx = bx + 1
101 }
102 by = by + 1
103 }
104 while bw.bit_pos != 0 { bw_write_bit(bw, 1) }
105 let elen: i64 = bw.byte_pos
106 i = 0
107 while i < elen {
108 let bb: i64 = ebuf[i] as i64
109 o = jw_b(out, o, bb)
110 if bb == 0xFF { o = jw_b(out, o, 0x00) }
111 i = i + 1
112 }
113 o=jw_b(out,o,0xFF); o=jw_b(out,o,0xD9) // EOI
114 return o
115}