From 5b6645a98e83f10d13b9bcaca3f1e24420d34796 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 02:29:57 +0000 Subject: [PATCH] Optimize sRGB to Linear conversion using a precomputed lookup table Co-authored-by: himattm <6266621+himattm@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ .../kotlin/halogen/color/ColorUtils.kt | 24 +++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 2d59eae..8faea77 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,7 @@ ## 2024-07-14 - Optimize Hex Color Parsing and Formatting in KMP Hot Paths **Learning:** In Kotlin Multiplatform hot paths (such as hex color serialization and parsing), manual character array manipulation and bitwise shifts are substantially faster (up to 25x faster for serialization and 10x faster for parsing) than using standard library string methods like `toString(16).padStart()`, `uppercase()`, or `substring().toLong(16).toInt()`. This approach avoids platform-specific string allocation overhead. **Action:** When working in KMP hot paths, avoid standard library string manipulations that instantiate multiple objects per call. Favor `CharArray`, manual index iteration, bitwise shifts, and `concatToString()` to minimize allocations and latency. + +## 2024-07-15 - Precompute sRGB to Linear LUT in Color Math +**Learning:** In KMP hot paths for color manipulation, mathematical operations converting 8-bit color components (0-255) to linear space perform redundant calculations like `.pow()` and division. +**Action:** Use a pre-computed `DoubleArray(256)` lookup table for operations that strictly map a discrete 8-bit domain to improve performance. diff --git a/halogen-core/src/commonMain/kotlin/halogen/color/ColorUtils.kt b/halogen-core/src/commonMain/kotlin/halogen/color/ColorUtils.kt index c4b7893..92b6ec8 100644 --- a/halogen-core/src/commonMain/kotlin/halogen/color/ColorUtils.kt +++ b/halogen-core/src/commonMain/kotlin/halogen/color/ColorUtils.kt @@ -33,6 +33,15 @@ internal object ColorUtils { doubleArrayOf(0.05562093689691305, -0.20395524564742123, 1.0571799111220335), ) + private val LINEARIZED_TABLE = DoubleArray(256) { rgbComponent -> + val normalized = rgbComponent / 255.0 + if (normalized <= 0.040449936) { + normalized / 12.92 * 100.0 + } else { + ((normalized + 0.055) / 1.055).pow(2.4) * 100.0 + } + } + private val WHITE_POINT_D65: DoubleArray = doubleArrayOf(95.047, 100.0, 108.883) fun whitePointD65(): DoubleArray = WHITE_POINT_D65 @@ -86,11 +95,18 @@ internal object ColorUtils { } fun linearized(rgbComponent: Int): Double { - val normalized = rgbComponent / 255.0 - return if (normalized <= 0.040449936) { - normalized / 12.92 * 100.0 + // Bolt Optimization: Use a precomputed lookup table (LUT) for sRGB (0-255) to Linear conversions. + // This avoids expensive division and .pow() operations on the hot path. + // Fallback to original math logic for out-of-bounds values to preserve exact functionality. + return if (rgbComponent in 0..255) { + LINEARIZED_TABLE[rgbComponent] } else { - ((normalized + 0.055) / 1.055).pow(2.4) * 100.0 + val normalized = rgbComponent / 255.0 + if (normalized <= 0.040449936) { + normalized / 12.92 * 100.0 + } else { + ((normalized + 0.055) / 1.055).pow(2.4) * 100.0 + } } }