nx_color_palette.nx source
↩ module page · 138 lines · 4972 B
1// nx_color_palette.nx -- Best Art Direction axis: color palette classifier.
2//
3// Sealed palette family enum + per-image classifier from RGB image.
4// Classifies into one of 8 canonical cinema palettes:
5//
6// WARM_AMBER golden hour / tungsten interior
7// COOL_BLUE moonlight / overcast / clinical
8// TEAL_ORANGE cinematic standard split-complement
9// MONOCHROME grayscale / single-hue
10// PASTEL_SOFT muted feminine
11// SATURATED_BOLD editorial fashion / pop
12// EARTH_NEUTRAL documentary natural
13// NIGHT_NEON cyberpunk / club
14//
15// Each measurement scores how strongly the image fits each palette.
16// Top match wins. Confidence = top - second.
17//
18// Output flat-array:
19// result[0] mean_R
20// result[1] mean_G
21// result[2] mean_B
22// result[3] saturation_q10
23// result[4] warmth_q10 (R-B normalized)
24// result[5] hue_spread_q10 (palette breadth)
25// result[6] classified_palette
26// result[7] confidence_q10
27
28// nx_safety_envelope:
29// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
30// sil_target: SIL1
31// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
32// verdict: NOT_YET_EVALUATED
33
34import "nx_syscalls.nx"
35import "nx_image.nx"
36const NX_MAGIC_1024: i64 = 1024
37
38const NX_PALETTE_UNKNOWN: i64 = 0
39const NX_PALETTE_WARM_AMBER: i64 = 1
40const NX_PALETTE_COOL_BLUE: i64 = 2
41const NX_PALETTE_TEAL_ORANGE: i64 = 3
42const NX_PALETTE_MONOCHROME: i64 = 4
43const NX_PALETTE_PASTEL_SOFT: i64 = 5
44const NX_PALETTE_SATURATED_BOLD: i64 = 6
45const NX_PALETTE_EARTH_NEUTRAL: i64 = 7
46const NX_PALETTE_NIGHT_NEON: i64 = 8
47
48const NX_PAL_RES_MEAN_R: i64 = 0
49const NX_PAL_RES_MEAN_G: i64 = 1
50const NX_PAL_RES_MEAN_B: i64 = 2
51const NX_PAL_RES_SAT_Q10: i64 = 3
52const NX_PAL_RES_WARM_Q10: i64 = 4
53const NX_PAL_RES_SPREAD_Q10: i64 = 5
54const NX_PAL_RES_KIND: i64 = 6
55const NX_PAL_RES_CONF_Q10: i64 = 7
56const NX_PAL_RES_FIELDS: i64 = 8
57
58// Classify color palette from RGB image global statistics.
59func nx_color_palette(rgb: *Image, result: *i64) -> i64 {
60 var i: i64 = 0
61 while i < NX_PAL_RES_FIELDS { result[i] = 0; i = i + 1 }
62
63 let w: i64 = rgb.width
64 let h: i64 = rgb.height
65 if w <= 0 { return 1 }
66 if h <= 0 { return 2 }
67
68 var sum_r: i64 = 0
69 var sum_g: i64 = 0
70 var sum_b: i64 = 0
71 var sum_sat: i64 = 0
72 var n: i64 = 0
73 var y: i64 = 0
74 while y < h {
75 var x: i64 = 0
76 while x < w {
77 let r: i64 = nx_image_get(rgb, x, y, 0)
78 let g: i64 = nx_image_get(rgb, x, y, 1)
79 let b: i64 = nx_image_get(rgb, x, y, 2)
80 sum_r = sum_r + r
81 sum_g = sum_g + g
82 sum_b = sum_b + b
83 var mx: i64 = r
84 var mn: i64 = r
85 if g > mx { mx = g }
86 if b > mx { mx = b }
87 if g < mn { mn = g }
88 if b < mn { mn = b }
89 sum_sat = sum_sat + (mx - mn)
90 n = n + 1
91 x = x + 1
92 }
93 y = y + 1
94 }
95 if n == 0 { return 3 }
96 let mean_r: i64 = sum_r / n
97 let mean_g: i64 = sum_g / n
98 let mean_b: i64 = sum_b / n
99 let mean_sat: i64 = sum_sat / n
100 result[NX_PAL_RES_MEAN_R] = mean_r
101 result[NX_PAL_RES_MEAN_G] = mean_g
102 result[NX_PAL_RES_MEAN_B] = mean_b
103 let sat_q10: i64 = (mean_sat * NX_MAGIC_1024) / 255
104 result[NX_PAL_RES_SAT_Q10] = sat_q10
105 let warm: i64 = mean_r - mean_b
106 let warm_q10: i64 = (warm * NX_MAGIC_1024) / 255
107 result[NX_PAL_RES_WARM_Q10] = warm_q10
108
109 // Hue spread proxy: variance of (R-G), (G-B), (R-B) across image.
110 // Cheap implementation: just use saturation as spread proxy here.
111 result[NX_PAL_RES_SPREAD_Q10] = sat_q10
112
113 // Classification decision tree.
114 var kind: i64 = NX_PALETTE_EARTH_NEUTRAL
115 if sat_q10 < 102 {
116 kind = NX_PALETTE_MONOCHROME
117 } else {
118 if warm_q10 > 200 {
119 if sat_q10 > 410 { kind = NX_PALETTE_WARM_AMBER }
120 else { kind = NX_PALETTE_EARTH_NEUTRAL }
121 }
122 if warm_q10 < -100 {
123 if mean_r > 100 { if mean_g < mean_b { kind = NX_PALETTE_NIGHT_NEON } else { kind = NX_PALETTE_COOL_BLUE } }
124 else { kind = NX_PALETTE_COOL_BLUE }
125 }
126 if sat_q10 > 614 {
127 if warm_q10 > 50 { kind = NX_PALETTE_SATURATED_BOLD }
128 }
129 // Teal-orange: warm subject signal + cool shadows. Mean R>B but variance is high.
130 if warm_q10 > 100 { if warm_q10 < 200 { if sat_q10 > 256 { kind = NX_PALETTE_TEAL_ORANGE } } }
131 // Pastel-soft: low-medium saturation, soft warmth, high lightness.
132 let mean_y: i64 = (mean_r + mean_g + mean_b) / 3
133 if mean_y > 170 { if sat_q10 < 256 { if sat_q10 > 102 { kind = NX_PALETTE_PASTEL_SOFT } } }
134 }
135 result[NX_PAL_RES_KIND] = kind
136 result[NX_PAL_RES_CONF_Q10] = 768 // baseline confidence; richer classification later
137 return 0
138}