// ═══════════════════════════════════════════════════════════════ // ODIX Zone Mapper v1.0 // Zone-system tonal control for digital cinema // // Based on Ansel Adams' zone system — divides the tonal range // into 10 discrete zones (0–9), each with an explicit exposure // adjustment in stops. Unlike curves, each zone has a defined // tonal center, giving you precise control over where tones land. // // ZONES: // Zone 0 — Pure black. No texture. // Zone I — Near black. First trace of texture. // Zone II — Deep shadows. Texture barely visible. // Zone III— Dark shadows. Full shadow texture. // Zone IV — Dark midtones. Skin in shadow. // Zone V — Middle gray. Average caucasian skin in sun. // Zone VI — Light midtones. Average Asian/Latino skin. // Zone VII— Light grays. Highlights with texture. // Zone VIII— Whites. Detail barely held. // Zone IX — Pure white. Specular highlights. // // USAGE: // 1. Read your image's tonal values on the waveform. // 2. Identify which zone a key element falls in. // 3. Adjust that zone's slider (in stops) to place it // where you want it in the final grade. // 4. Global Gain applies uniform exposure across all zones. // 5. Luma Only keeps color unchanged while adjusting tone. // // COLOR SPACE: // Scene-linear footage (post CST). For log, offset zone // thresholds using the Zone Offset parameter. // // by Odbat Batsaikhan — odixbat.com — @odixbat // ═══════════════════════════════════════════════════════════════ DEFINE_UI_PARAMS(p_Z0, Zone 0 — Black, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_Z1, Zone I — Deep Shd, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_Z2, Zone II — Shd Tex, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_Z3, Zone III— Dark Mid, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_Z4, Zone IV — Dk Skin, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_Z5, Zone V — Mid Gray, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_Z6, Zone VI — Lt Skin, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_Z7, Zone VII— Lt Gray, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_Z8, Zone VIII—Whites, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_Z9, Zone IX — Specular, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_GlobalGain, Global Gain, DCTLUI_SLIDER_FLOAT, 0.0, -3.0, 3.0, 0.01) DEFINE_UI_PARAMS(p_LumaOnly, Luma Only, DCTLUI_CHECK_BOX, 0) DEFINE_UI_PARAMS(p_ZoneOffset, Zone Offset, DCTLUI_SLIDER_FLOAT, 0.0, -0.5, 0.5, 0.01) __DEVICE__ float getZoneAdjust(float normalizedLuma, float z0, float z1, float z2, float z3, float z4, float z5, float z6, float z7, float z8, float z9) { float zones[10]; zones[0] = z0; zones[1] = z1; zones[2] = z2; zones[3] = z3; zones[4] = z4; zones[5] = z5; zones[6] = z6; zones[7] = z7; zones[8] = z8; zones[9] = z9; float zf = _clampf(normalizedLuma * 9.0f, 0.0f, 9.0f); int zLo = (int)zf; int zHi = (zLo < 9) ? zLo + 1 : 9; float t = zf - (float)zLo; // Smooth interpolation between zones float adjLo = zones[zLo]; float adjHi = zones[zHi]; float t_s = t * t * (3.0f - 2.0f * t); // smoothstep return adjLo + (adjHi - adjLo) * t_s; } __DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y, float p_R, float p_G, float p_B) { float luma = 0.2126f * p_R + 0.7152f * p_G + 0.0722f * p_B; float lumaNorm = _clampf(luma + p_ZoneOffset, 0.0f, 1.0f); float adj = getZoneAdjust(lumaNorm, p_Z0, p_Z1, p_Z2, p_Z3, p_Z4, p_Z5, p_Z6, p_Z7, p_Z8, p_Z9) + p_GlobalGain; float gain = _powf(2.0f, adj); float r_out, g_out, b_out; if (p_LumaOnly) { // Apply gain to luma only, preserve chroma ratio float newLuma = luma * gain; float lumaScale = (luma > 0.00001f) ? newLuma / luma : 1.0f; r_out = p_R * lumaScale; g_out = p_G * lumaScale; b_out = p_B * lumaScale; } else { r_out = p_R * gain; g_out = p_G * gain; b_out = p_B * gain; } return make_float3(r_out, g_out, b_out); }