nx_lab_from_rgb.nx
buildroot/runtime/nx_lab_from_rgb.nx
about
nx_lab_from_rgb.nx -- sRGB u8 -> CIELab Q10 in pure i64 fixed-point.
CAPABILITY_COMPLETENESS: FULL
Bridges the "RGB-proxy" tier-1 chroma axes (in nx_image_grade_v0)
up to genuine colorimetric measurements:
- real ITA degrees (via nx_skin_tone_ita)
- real CIELab h_ab undertone angle (via nx_undertone_class)
Math pipeline (CIE 15:2004 / CIE 014:2018):
step 1 sRGB u8 channel value [0, 255]
-> Q14 sRGB scalar [0, 16384]
step 2 sRGB Q14 -> linear-light Q14 via sRGB OETF inverse:
val_q14 < 0.04045 * Q14 -> linear = val / 12.92
val_q14 >= 0.04045 * Q14 -> linear = ((val + 0.055)/1.055)^2.4
Composed against nx_aces_srgb_to_linear_q14 (already shipped).
step 3 Linear sRGB Q14 -> XYZ Q14 via 3x3 matrix (D65):
[0.4124564 0.3575761 0.1804375] per IEC 61966-2-1 +
[0.2126729 0.7151522 0.0721750] ITU-R BT.709 primaries
[0.0193339 0.1191920 0.9503041]
step 4 XYZ Q14 -> Lab Q10:
tn_x = X / Xn (Xn = 0.95047 for D65)
tn_y = Y / Yn (Yn = 1.00000)
tn_z = Z / Zn (Zn = 1.08883)
f(t) = t^(1/3) if t > 216/24389
(kappa*t + 16) / 116 otherwise (kappa = 24389/27)
L* = 116 * f(tn_y) - 16
a* = 500 * (f(tn_x) - f(tn_y))
b* = 200 * (f(tn_y) - f(tn_z))
Output: L*_q10, a*_q10, b*_q10 all in Q10 scaled units.
L*_q10 in [0, ~100 * 1024 = 102400]
a*_q10 / b*_q10 in approximately [-130 * 1024, +130 * 1024]
Cube root via Newton-Raphson in Q14 (5 iterations -> ~0.1%
accuracy across [0, 1]).
dependencies 4 imports · 3 importers
imports: nx_syscalls.nxnx_runtime.nxnx_tier.nxnx_aces_native.nx
imported by: nx_image_grade_v0.nxnx_image_grade_v1.nxnx_image_grade_v2.nx
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| 95 | struct NxLabColor |
consts
| 55 | const NX_MAGIC_2900: i64 = 2900 |
| 56 | const NX_MAGIC_3200: i64 = 3200 |
| 57 | const NX_MAGIC_8192: i64 = 8192 |
| 58 | const NX_MAGIC_13005: i64 = 13005 |
| 60 | const NX_LAB_Q14: nx_int = 16384 |
| 61 | const NX_LAB_Q10: nx_int = 1024 |
| 64 | const NX_LAB_XN_Q14: nx_int = 15572 // 0.95047 * 16384 |
| 65 | const NX_LAB_YN_Q14: nx_int = 16384 // 1.00000 * 16384 |
| 66 | const NX_LAB_ZN_Q14: nx_int = 17839 // 1.08883 * 16384 |
| 69 | const NX_LAB_M00: nx_int = 6759 // 0.4124564 * 16384 |
| 70 | const NX_LAB_M01: nx_int = 5859 // 0.3575761 * 16384 |
| 71 | const NX_LAB_M02: nx_int = 2957 // 0.1804375 * 16384 |
| 72 | const NX_LAB_M10: nx_int = 3484 // 0.2126729 * 16384 |
| 73 | const NX_LAB_M11: nx_int = 11718 // 0.7151522 * 16384 |
| 74 | const NX_LAB_M12: nx_int = 1183 // 0.0721750 * 16384 |
| 75 | const NX_LAB_M20: nx_int = 317 // 0.0193339 * 16384 |
| 76 | const NX_LAB_M21: nx_int = 1953 // 0.1191920 * 16384 |
| 77 | const NX_LAB_M22: nx_int = 15569 // 0.9503041 * 16384 |
| 80 | const NX_LAB_FT_THRESHOLD_Q14: nx_int = 145 // 0.008856 * 16384 |
| 101 | const NX_LAB_COLOR_BYTES: nx_size = 24 |
functions
| 114 | func _lab_cuberoot_q14(t_q14: nx_int) -> nx_int |
| 141 | func _lab_ft_q14(t_q14: nx_int) -> nx_int |
| 155 | func nx_lab_from_rgb(r_u8: nx_int, g_u8: nx_int, b_u8: nx_int) -> *NxLabColor |
| 200 | func main() -> nx_int |