nx_jpeg_upsample.nx source
↩ module page · 87 lines · 3575 B
1// nx_jpeg_upsample.nx -- chroma upsampling for JPEG decode.
2//
3// JPEG with sub-sampled chroma (4:2:2, 4:2:0) stores Cb/Cr at a
4// lower resolution than Y. Before YCbCr->RGB conversion the
5// chroma planes must be upsampled to match Y dimensions.
6//
7// This primitive implements nearest-neighbor (sample-replicate)
8// upsampling per T.81 Annex A.1.1:
9//
10// Each source pixel at (sx, sy) is written into a (max_h/Hi) x
11// (max_v/Vi) rectangle of the destination starting at
12// (sx * max_h/Hi, sy * max_v/Vi).
13//
14// Sampling combinations:
15// 4:4:4 (Hi=Vi=max_h=max_v=1) -> identity copy
16// 4:2:2 (Y H=2 V=1; Cb/Cr H=V=1, max_h=2 max_v=1) -> 2x1 replicate for chroma
17// 4:2:0 (Y H=V=2; Cb/Cr H=V=1, max_h=max_v=2) -> 2x2 replicate for chroma
18//
19// Substrate-honest MVP: nearest-neighbor only. Bilinear
20// upsampling per JFIF Annex F.1.1.3 is a sibling stone for
21// quality-critical decode paths; it adds smoother chroma edges
22// but costs ~3x more arithmetic per pixel.
23//
24// nx_safety_envelope:
25// intended_use: "Chroma-plane upsampling for JPEG decode."
26// sil_target: SIL1
27// evidence: [t81_annex_a_1_1_canonical_basis,
28// sample_replicate_no_arithmetic,
29// bounded_iteration]
30// hazard_register: [bug-tape-upsample-stride-overflow,
31// bug-tape-edge-clipping-wrong-on-non-multiple-dims]
32// residual_risk: "Nearest-neighbor produces blocky chroma
33// boundaries vs bilinear; acceptable for
34// substrate's first pass."
35// verdict: NOT_YET_EVALUATED
36
37import "nx_syscalls.nx"
38
39const NX_JPEG_UPSAMPLE_OK: i64 = 0
40const NX_JPEG_UPSAMPLE_BAD_FACTOR: i64 = 1 // Hi > max_h or Vi > max_v
41const NX_JPEG_UPSAMPLE_RESULT_N: i64 = 2
42
43// Replicate a sub-sampled plane to full image dimensions.
44//
45// src -- source plane (u8)
46// src_w, src_h -- source dimensions (Hi*8 * mcu_cols, Vi*8 * mcu_rows)
47// src_stride -- bytes per source row
48// hi, vi -- this component's H + V sampling factors
49// max_h, max_v -- frame's max H + V (from nx_jpeg_sof_parse)
50// dst -- destination plane (u8); must be sized for
51// max_h*8*mcu_cols x max_v*8*mcu_rows
52// dst_stride -- bytes per destination row
53//
54// The "replication factor" per axis is max_h/hi (h) and max_v/vi (v).
55// For 4:2:0 Cb: hi=vi=1, max_h=max_v=2, so each src pixel becomes a
56// 2x2 block in dst.
57func nx_jpeg_upsample_nearest(src: *u8, src_w: i64, src_h: i64,
58 src_stride: i64,
59 hi: i64, vi: i64,
60 max_h: i64, max_v: i64,
61 dst: *u8, dst_stride: i64) -> i64 {
62 if hi > max_h { return NX_JPEG_UPSAMPLE_BAD_FACTOR }
63 if vi > max_v { return NX_JPEG_UPSAMPLE_BAD_FACTOR }
64 let rep_h: i64 = max_h / hi
65 let rep_v: i64 = max_v / vi
66 var sy: i64 = 0
67 while sy < src_h {
68 var sx: i64 = 0
69 while sx < src_w {
70 let v: u8 = src[sy * src_stride + sx]
71 var dy: i64 = 0
72 while dy < rep_v {
73 var dx: i64 = 0
74 while dx < rep_h {
75 let dst_x: i64 = sx * rep_h + dx
76 let dst_y: i64 = sy * rep_v + dy
77 dst[dst_y * dst_stride + dst_x] = v
78 dx = dx + 1
79 }
80 dy = dy + 1
81 }
82 sx = sx + 1
83 }
84 sy = sy + 1
85 }
86 return NX_JPEG_UPSAMPLE_OK
87}