code wiki / (root) / nx_color_palette.nx

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}