Last active
April 6, 2025 10:14
-
-
Save MarkJeronimus/b0b3eb1f59c0f0efd1525f439c01b2e9 to your computer and use it in GitHub Desktop.
Converting sRGB to HCI and back
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Notes on this Java snippet: | |
| - I left out 'Math.' because it's boilerplate/noise | |
| - Color3f is a simple read/write container where the components are float instead of int | |
| - Implementation of 'fromSRGB' and 'toSRGB' should be straightforward | |
| - Uses tab characters for initial indentation, continues with space characters for vertical alignment purposes | |
| // HCI ///////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * This color space is a hexagonal bipyramid with the grays at the central axis | |
| * and the primary and secondary colors on a hexagonal plane at the center. | |
| * <p> | |
| * Compared to HSL, Saturation is replaced with Chroma my multiplying by Lightness, | |
| * and Lightness is replaced by perceptual Intensity. | |
| * <p> | |
| * The input is assumed to be sRGB, and the output Intensity is linear. | |
| */ | |
| public static Color3f rgb2hci(Color3f color) { | |
| float min = min(min(color.r, color.g), color.b); | |
| float max = max(max(color.r, color.g), color.b); | |
| float chroma = max - min; | |
| if (chroma < 1.0e-7f) { | |
| float intensity = fromSRGB(max); | |
| return new Color3f(0.0f, 0.0f, intensity); | |
| } | |
| float hue = rgb2hueInternal(color.r, color.g, color.b, min, max, chroma); | |
| float intensity = getPerceptualLuminosity(color.r, color.g, color.b); | |
| return new Color3f(hue, chroma, intensity); | |
| } | |
| public static Color3f hci2rgb(Color3f color) { | |
| float hue6 = (color.r - (float)floor(color.r)) * 6; | |
| // Hue & sat coding | |
| float r = max(0.0f, min(1.0f, abs(hue6 - 3.0f) - 1.0f)) * color.g; | |
| float g = max(0.0f, min(1.0f, 2.0f - abs(hue6 - 2.0f))) * color.g; | |
| float b = max(0.0f, min(1.0f, 2.0f - abs(hue6 - 4.0f))) * color.g; | |
| // Luma searching | |
| if (color.b < getPerceptualLuminosity(r, g, b)) { | |
| return findDarkerColor(r, g, b, color.b); | |
| } else { | |
| return findBrighterColor(r, g, b, color.b); | |
| } | |
| } | |
| private static float rgb2hueInternal(float r, float g, float b, float min, float max, float chroma) { | |
| assert chroma != 0.0f; | |
| if (max == r) { | |
| if (min == b) { | |
| return ((g - b) / chroma) / 6.0f; | |
| } else { | |
| return ((g - b) / chroma + 6.0f) / 6.0f; | |
| } | |
| } else { | |
| if (max == g) { | |
| return ((b - r) / chroma + 2.0f) / 6.0f; | |
| } else { | |
| return ((r - g) / chroma + 4.0f) / 6.0f; | |
| } | |
| } | |
| } | |
| public static float getPerceptualLuminosity(float r, float g, float b) { | |
| return fromSRGB(r) * 0.2126f + fromSRGB(g) * 0.7152f + fromSRGB(b) * 0.0722f; | |
| } | |
| private static Color3f findDarkerColor(float r, float g, float b, float targetIntensity) { | |
| float minMul = 0.0f; | |
| float maxMul = 1.0f; | |
| float mul = 0.0f; | |
| float intensity; | |
| while (maxMul - minMul > 1.0e-7f) { | |
| mul = (maxMul + minMul) / 2; | |
| intensity = getPerceptualLuminosity(r * mul, g * mul, b * mul); | |
| if (intensity < targetIntensity) { | |
| minMul = mul; | |
| } else { | |
| maxMul = mul; | |
| } | |
| } | |
| return new Color3f(r * mul, g * mul, b * mul); | |
| } | |
| private static Color3f findBrighterColor(float r, float g, float b, float targetIntensity) { | |
| float minAdd = 0.0f; | |
| float maxAdd = 1.0f; | |
| float add = 0.0f; | |
| float intensity; | |
| while (maxAdd - minAdd > 1.0e-7f) { | |
| add = (maxAdd + minAdd) / 2; | |
| intensity = getPerceptualLuminosity(min(r + add, 1.0f), | |
| min(g + add, 1.0f), | |
| min(b + add, 1.0f)); | |
| if (intensity < targetIntensity) { | |
| minAdd = add; | |
| } else { | |
| maxAdd = add; | |
| } | |
| } | |
| return new Color3f(min(r + add, 1.0f), | |
| min(g + add, 1.0f), | |
| min(b + add, 1.0f)); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment