Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -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.
24 changes: 20 additions & 4 deletions halogen-core/src/commonMain/kotlin/halogen/color/ColorUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
}

Expand Down
Loading