Color space conversions

From Second Life Wiki
Revision as of 15:05, 1 November 2023 by Frionil Fang (talk | contribs) (some color math that's useful for color pickers and making nicer color blends)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

A collection of "Wikipedia logo"color space conversion functions. Although all colors in Second Life are RGB, other color spaces may offer more comfortable ways of specifying colors and generating more pleasing gradients than straight interpolations between RGB colors.

Hue-based color spaces are commonly used in color pickers. Second Life viewers usually provide a HSL color selector, and most other programs provide one of either HSL or HSV/HSB, with other possible color spaces available.

HSV/HSB

More detail: "Wikipedia logo"HSL and HSV.

HSV and HSB are synonyms, standing for Hue, Saturation and Value or Brightness.

  • Hue is the perceived hue and wraps around in its range, forming a color circle.
  • Saturation is the "grayness" of the color: maximum saturation gives the most intense color, minimum gives grays.
  • Value/brightness determine the "blackness" of the color: maximum value gives the brightest color, minimum gives black.
// Takes a Hue-Saturation-Value(/Brightness) vector
// H in range [0, 1[
// S, V in range [0, 1]
// Returns a LSL RGB vector
vector hsv2rgb(vector hsv) {
    if(hsv.y) {
        float ht = 6*hsv.x; integer i = (integer)ht; float f = ht-i;
        if(i == 0) return <hsv.z, hsv.z*(1-hsv.y*(1-f)), hsv.z*(1-hsv.y)>;
        else if(i == 1) return <hsv.z*(1-hsv.y*f), hsv.z, hsv.z*(1-hsv.y)>;
        else if(i == 2) return <hsv.z*(1-hsv.y), hsv.z, hsv.z*(1-hsv.y*(1-f))>;
        else if(i == 3) return <hsv.z*(1-hsv.y), hsv.z*(1-hsv.y*f), hsv.z>;
        else if(i == 4) return <hsv.z*(1-hsv.y*(1-f)), hsv.z*(1-hsv.y), hsv.z>;
        else return <hsv.z, hsv.z*(1-hsv.y), hsv.z*(1-hsv.y*f)>;
    }
    return <hsv.z, hsv.z, hsv.z>;
}

// Takes a LSL RGB vector
// Returns a HSV vector
// H in range [0, 1[
// S, V in range [0, 1]
vector rgb2hsv(vector c) {
    float min = c.x; if(c.y < min) min = c.y; if(c.z < min) min = c.z;
    float max = c.x; if(c.y >= max) max = c.y; if(c.z >= max) max = c.z;
    if(min == max) return <0, 0, max>;
    float d; float h;
    if(min == c.x) { d = c.y-c.z; h = 3; }
    else if(min == c.z) { d = c.x-c.y; h = 1; }
    else { d = c.z-c.x; h = 5; }
    return <(h-d/(max-min))*0.166666666667, (max-min)/max, max>;
}

HSL

More detail: "Wikipedia logo"HSL and HSV.

HSL stands for Hue, Saturation and Lightness or Luminance. HSL is available in the SL viewer color picker.

  • Hue is the perceived hue and wraps around in its range, forming a color circle.
  • Saturation is the "grayness" of the color: maximum saturation gives the most intense color, minimum gives grays.
  • Lightness/luminance determine the "whiteness" and "blackness" of the color: maximum value gives white, minimum gives black and the middle point gives the most intense color.

See uHSL2RGB and uRGB2HSL. Note that the linked functions may take differently styled inputs compared to ones in this page.

HSLuv

More detail: "Wikipedia logo"HSLuv and HSLuv.org.

HSLuv is a color space that attempts to combine the human-friendliness of HSV/HSL with the perceived luminous/chromatic stability of the "Wikipedia logo"CIE LCh(uv) color space. The conversion is multi-step and rather convoluted, containing many "magic numbers" that are not explained here; see HSLuv math and other implementations.

  • Hue is the perceived hue and wraps around in its range, forming a color circle.
  • Saturation is the "grayness" of the color: maximum saturation gives the most intense color, minimum gives grays.
  • Luminance determine the "whiteness" and "blackness" of the color: maximum value gives white, minimum gives black and the middle point gives the most intense color.
  • Although the parameters are like HSL, the results are different.
  • There is currently no LSL implementation for RGB->HSLuv.
// Takes a Hue-Saturation-Luminance vector
// H in range [0, 1[
// S, V in range [0, 1]
// Returns a LSL RGB vector
vector hsluv2rgb(vector col) {
    // HSLuv to LCh(uv), except in hCL order
    float hcos = llCos(TWO_PI*col.x); float hsin = llSin(TWO_PI*col.x);
    float l = (col.z+0.16); l = l*l*l/1.560896; if(l < 0.0088564516) l = col.z/9.032962962;
    float temp; list intercepts;
    temp = col.z*-6.3705630182411/(hsin+8.0217391304349*hcos);
    if(temp >= 0) intercepts += temp;
    temp = col.z*((l-1)*769860.0)/(l*-120846.46173275+126452)/
           (hsin-hcos*l*969398.79085627/(l*-120846.46173275+126452));
    if(temp >= 0) intercepts += temp;
    temp = col.z*-3.6495554177681/(hsin-1.3259649910233*hcos);
    if(temp >= 0) intercepts += temp;
    temp = col.z*((l-1)*769860.0)/(l*-210946.24190439+126452)/
           (hsin-hcos*l*-279707.33175316/(l*-210946.24190439+126452));
    if(temp >= 0) intercepts += temp;
    temp = col.z*1.1091899201577/(hsin+0.12162162162162*hcos);
    if(temp >= 0) intercepts += temp;
    temp = col.z*((l-1)*769860.0)/(l*694074.10400063+126452)/
           (hsin-hcos*l*-84414.418054130/(l*694074.10400063+126452));
    if(temp >= 0) intercepts += temp;
    col.y *= llListStatistics(LIST_STAT_MIN, intercepts);
    // hCL to Luv
    col = <col.z, hcos*col.y, hsin*col.y>;
    // Luv to XYZ, except in YXZ order
    if(col.x) {
        float u = col.y/(13*col.x)+0.19783000664283;
        float v = col.z/(13*col.x)+0.46831999493879;
        if(col.x > 0.08) { col.x = (col.x+0.16)/1.16; col.x *= col.x*col.x; } else col.x /= 9.032962962;
        col.y = -9*col.x*u/(-4*v);
        col.z = (9*col.x-15*v*col.x-v*col.y)/(3*v);
    } else col.y = col.z = 0;
    // YXZ to linear RGB
    col = <col.y*3.240969941904521+col.x*-1.537383177570093+col.z*-0.498610760293,
           col.y*-0.96924363628087+col.x*1.87596750150772+col.z*0.041555057407175,
           col.y*0.055630079696993+col.x*-0.20397695888897+col.z*1.056971514242878>;
    // linear RGB to sRGB
    if(col.x > 0.0031308) col.x = 1.055*llPow(col.x, 0.41666666666667)-0.055; else col.x = 12.92*col.x;
    if(col.y > 0.0031308) col.y = 1.055*llPow(col.y, 0.41666666666667)-0.055; else col.y = 12.92*col.y;
    if(col.z > 0.0031308) col.z = 1.055*llPow(col.z, 0.41666666666667)-0.055; else col.z = 12.92*col.z;
    return col;
}

Linear RGB

Typically colors shown on screen are in the "Wikipedia logo"sRGB color space, but some features, such as point lights expect linear RGB color instead.

See the built in functions llsRGB2Linear and llLinear2sRGB.

Clamping Functions

Hue-based colors are often specified with hue as degrees (0-360°) and saturation/value/lightness/luminance as a value from 0 to 100. Likewise, RGB colors are often given as integers in the range 0-255, as opposed to the LSL colors in the range 0-1. The conversion into the range expected on this page requires just simple division.

// Scales a hue-style color vector into [0, 1] range
vector hxx_scale(vector hxx) {
    return <hxx.x/360.0, hxx.y/100.0, hxx.z/100.0>;
}

// Scales an integer color vector into [0, 1] LSL color range
vector rgb_scale(vector rgb) {
    return rgb/255.0;
}

To ensure color vectors are in the proper range before calling a conversion, you can clamp/wrap them with the following helper functions. If necessary, scale the colors first.

// Wraps & clamps a hue-style color vector
// Hue wrapped to the range [0, 1[
// Others clamped to [0, 1]
vector hxx_clamp(vector hxx) {
    hxx.x -= (integer)hxx.x;
    if(hxx.x < 0) hxx.x = 1-hxx.x;
    if(hxx.y < 0) hxx.y = 0; else if(hxx.y > 1) hxx.y = 1;
    if(hxx.z < 0) hxx.z = 0; else if(hxx.z > 1) hxx.z = 1;
    return hxx;
}

// Clamps a RGB vector to the [0, 1] range
vector rgb_clamp(vector rgb) {
    if(rgb.x < 0) rgb.x = 0; else if(rgb.x > 1) rgb.x = 1;
    if(rgb.y < 0) rgb.y = 0; else if(rgb.y > 1) rgb.y = 1;
    if(rgb.z < 0) rgb.z = 0; else if(rgb.z > 1) rgb.z = 1;
    return rgb;
}