From df4c7d7c3a87408a6a1401f6095570cb7226af45 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sat, 3 Oct 2026 02:29:19 -0400 Subject: [PATCH] Perf: chroma-only colour ops skip the OkLCh round trip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When only vibrance / saturation (and colour grading) are active, chroma is scaled on OkLab a/b directly — the same result without sin/cos (and atan2 unless vibrance needs the hue). Bench (loaded machine): finish 1920×1280 + vibrance/saturation 71.8 → 47.7 ms wall on the CPU. Co-Authored-By: Claude Opus 5.5 --- crates/pipeline/src/colorops.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/pipeline/src/colorops.rs b/crates/pipeline/src/colorops.rs index 987afa285..ff3277931 100644 --- a/crates/pipeline/src/colorops.rs +++ b/crates/pipeline/src/colorops.rs @@ -191,6 +191,27 @@ impl ColorOps { return rgb; } let lab = oklab_from_2020(rgb); + // only chroma changes (vibrance / saturation, maybe grading): scale a, b directly — the same + // result as the OkLCh round trip without its sin / cos (and atan2 unless vibrance needs the hue) + if !self.mixer && self.points.is_empty() && self.bw.is_none() && local_hue == 0.0 { + let mut lab = lab; + let c0 = (lab[1] * lab[1] + lab[2] * lab[2]).sqrt(); + let mut c = c0; + if self.vibrance != 0.0 { + let low = 1.0 - (c / 0.22).clamp(0.0, 1.0); + let skin = if self.vibrance > 0.0 { 1.0 - 0.6 * (-(wrap(lab[2].atan2(lab[1]) - self.skin) / 0.35).powi(2)).exp() } else { 1.0 }; + c *= (1.0 + self.vibrance * low * low * skin * 1.2).max(0.0); + } + if self.saturation != 0.0 || local_sat != 0.0 { + c *= (1.0 + self.saturation + local_sat).max(0.0); + } + if c0 > 0.0 { + let k = c / c0; + lab[1] *= k; + lab[2] *= k; + } + return self.grade(lab); + } let [mut l, mut c, mut h] = lab_to_lch(lab); if self.mixer { let w = band_weights(h); @@ -223,7 +244,12 @@ impl ColorOps { l = (l + mix * (c / 0.2).min(1.0) * 0.25).max(0.0); c = 0.0; } - let mut lab = lch_to_lab([l, c, h]); + self.grade(lch_to_lab([l, c, h])) + } + + /// Colour grading on OkLab, then back to linear Rec.2020. + #[inline] + fn grade(&self, mut lab: [f32; 3]) -> [f32; 3] { if let Some((wheels, blending, balance)) = &self.grading { let m = 0.5 - balance * 0.25; let width = 0.15 + blending * 0.5;