Difference between revisions of "Color space conversions"
Frionil Fang (talk | contribs) m |
Frionil Fang (talk | contribs) (→HSLuv/HPLuv: added original code license) |
||
Line 166: | Line 166: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<div class="mw-collapsible mw-collapsed">The HSLuv LSL code is heavily modified from the Javascript version, MIT licensed: | |||
<div class="mw-collapsible-content"><pre> | |||
Copyright (c) 2012-2022 Alexei Boronine | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. | |||
LSL port 2023 Frionil Fang. | |||
</pre></div> | |||
</div> | |||
== Linear RGB == | == Linear RGB == |
Latest revision as of 14:54, 13 November 2023
A collection of 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: 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.
These functions are optimized for performance and don't do input verification. See clamping functions if you need to ensure the inputs are in the correct range. |
// 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: 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/HPLuv
More detail: 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 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. Using saturation over 1.0 may also produce usable colors, but the resulting RGB value may not be within spec.
- 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.
- Using S and L outside the [0, 1] range may also produce usable colors, but the resulting RGB values won't be within spec.
- There is currently no LSL implementation for RGB->HSLuv on this page.
HPLuv is a variation of HSLuv that maintains a very stable perceived luminosity, but produces only desaturated/pastel colors. Its parameters are the same as HSLuv.
- No info for what P stands for.
- Using P and L outside the [0, 1] range may also produce usable colors, but the resulting RGB value won't be within spec and the luminous uniformity degrades.
- There is currently no LSL implementation for RGB->HPLuv on this page.
These functions don't verify their inputs. See clamping functions if you need to ensure the inputs are in the correct range. |
// Takes a Hue-Saturation-Luminance vector
// H in range [0, 1[
// S, L in range [0, 1], can also be overdriven
// 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;
if(l) {
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);
} else return <0, 0, 0>;
// hCL to Luv
col = <col.z, hcos*col.y, hsin*col.y>;
// Luv to XYZ, except in YXZ order
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);
// 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;
}
// Takes a Hue-P-Luminance vector
// H in range [0, 1[
// P, L in range [0, 1], can also be overdriven
// Returns a LSL RGB vector
vector hpluv2rgb(vector col) {
// HPLuv to LCh(uv), except in hCL order
float l = (col.z+0.16); l = l*l*l/1.560896; if(l < 0.0088564516) l = col.z/9.032962962;
if(l) {
float nom; float den; list intercepts;
intercepts += col.z*0.78806252071086;
nom = col.z*((l-1)*769860.0)/(l*-120846.46173275+126452); if(nom<0) nom = -nom;
den = (l*969398.79085627/(l*-120846.46173275+126452)); den = llSqrt(den*den+1);
intercepts += nom/den;
intercepts += col.z*2.1974976111232;
nom = col.z*((l-1)*769860.0)/(l*-210946.24190439+126452); if(nom<0) nom = -nom;
den = (l*-279707.33175316/(l*-210946.24190439+126452)); den = llSqrt(den*den+1);
intercepts += nom/den;
intercepts += col.z*1.1010763526520;
nom = col.z*((l-1)*769860.0)/(l*694074.10400063+126452); if(nom<0) nom = -nom;
den = (l*-84414.418054130/(l*694074.10400063+126452)); den = llSqrt(den*den+1);
intercepts += nom/den;
col.y *= llListStatistics(LIST_STAT_MIN, intercepts);
} else return <0, 0, 0>;
// hCL to Luv
col = <col.z, llCos(TWO_PI*col.x)*col.y, llSin(TWO_PI*col.x)*col.y>;
// Luv to XYZ, except in YXZ order
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);
// 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;
}
Copyright (c) 2012-2022 Alexei Boronine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. LSL port 2023 Frionil Fang.
Linear RGB
Typically colors shown on screen are in the 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;
}
Visual Examples
These images show a visual comparison of some of the colorspaces described, rendered inworld with above functions on a 64x24 grid. Hue ranges from 0 to 1 horizontally, saturation from 0 to 1 vertically. Value/lightness/luminosity depend on the image.