From 8ab84dfc696066741a205c8eb142a972efdb4978 Mon Sep 17 00:00:00 2001 From: Christophe Riccio Date: Sun, 30 Sep 2018 10:45:17 +0200 Subject: [PATCH] Initial experiments for color extension --- glm/ext/vector_color.hpp | 116 ++++++++ glm/ext/vector_color.inl | 518 ++++++++++++++++++++++++++++++++++ test/ext/CMakeLists.txt | 1 + test/ext/ext_vector_color.cpp | 13 + 4 files changed, 648 insertions(+) create mode 100644 glm/ext/vector_color.hpp create mode 100644 glm/ext/vector_color.inl create mode 100644 test/ext/ext_vector_color.cpp diff --git a/glm/ext/vector_color.hpp b/glm/ext/vector_color.hpp new file mode 100644 index 00000000..d11af297 --- /dev/null +++ b/glm/ext/vector_color.hpp @@ -0,0 +1,116 @@ +/// @ref ext_vector_color +/// @file glm/ext/vector_color.hpp +/// +/// @defgroup ext_vector_color GLM_EXT_vector_color +/// @ingroup ext +/// +/// Exposes comparison functions for vector types that take a user defined epsilon values. +/// +/// Include to use the features of this extension. +/// +/// @see ext_vector_float4 +/// @see ext_vector_relational + +#pragma once + +// Dependencies +#include "../detail/qualifier.hpp" + +#if GLM_MESSAGES == GLM_ENABLE && !defined(GLM_EXT_INCLUDED) +# pragma message("GLM: GLM_EXT_vector_color extension included") +#endif + +namespace glm +{ + /// @addtogroup ext_vector_color + /// @{ + + /// Convert a linear color to sRGB color using a standard gamma correction. + /// IEC 61966-2-1:1999 / Rec. 709 specification https://www.w3.org/Graphics/Color/srgb + template + GLM_FUNC_DECL vec convertLinearToSRGB(vec const& ColorLinear); + + /// Convert a linear color to sRGB color using a custom gamma correction. + /// IEC 61966-2-1:1999 / Rec. 709 specification https://www.w3.org/Graphics/Color/srgb + template + GLM_FUNC_DECL vec convertLinearToSRGB(vec const& ColorLinear, T Gamma); + + /// Convert a sRGB color to linear color using a standard gamma correction. + /// IEC 61966-2-1:1999 / Rec. 709 specification https://www.w3.org/Graphics/Color/srgb + template + GLM_FUNC_DECL vec convertSRGBToLinear(vec const& ColorSRGB); + + /// Convert a sRGB color to linear color using a custom gamma correction. + // IEC 61966-2-1:1999 / Rec. 709 specification https://www.w3.org/Graphics/Color/srgb + template + GLM_FUNC_DECL vec convertSRGBToLinear(vec const& ColorSRGB, T Gamma); + + /// Converts a color from HSV color space to its color in RGB color space. + /// @see gtx_color_space + template + GLM_FUNC_DECL vec<3, T, Q> rgbColor( + vec<3, T, Q> const& hsvValue); + + /// Converts a color from RGB color space to its color in HSV color space. + /// @see gtx_color_space + template + GLM_FUNC_DECL vec<3, T, Q> hsvColor( + vec<3, T, Q> const& rgbValue); + + /// Build a saturation matrix. + /// @see gtx_color_space + template + GLM_FUNC_DECL mat<4, 4, T, defaultp> saturation( + T const s); + + /// Modify the saturation of a color. + /// @see gtx_color_space + template + GLM_FUNC_DECL vec<3, T, Q> saturation( + T const s, + vec<3, T, Q> const& color); + + /// Modify the saturation of a color. + /// @see gtx_color_space + template + GLM_FUNC_DECL vec<4, T, Q> saturation( + T const s, + vec<4, T, Q> const& color); + + /// Compute color luminosity associating ratios (0.33, 0.59, 0.11) to RGB canals. + /// @see gtx_color_space + template + GLM_FUNC_DECL T luminosity( + vec<3, T, Q> const& color); + + + /// Convert a color from RGB color space to YCoCg color space. + /// @see gtx_color_space_YCoCg + template + GLM_FUNC_DECL vec<3, T, Q> rgb2YCoCg( + vec<3, T, Q> const& rgbColor); + + /// Convert a color from YCoCg color space to RGB color space. + /// @see gtx_color_space_YCoCg + template + GLM_FUNC_DECL vec<3, T, Q> YCoCg2rgb( + vec<3, T, Q> const& YCoCgColor); + + /// Convert a color from RGB color space to YCoCgR color space. + /// @see "YCoCg-R: A Color Space with RGB Reversibility and Low Dynamic Range" + /// @see gtx_color_space_YCoCg + template + GLM_FUNC_DECL vec<3, T, Q> rgb2YCoCgR( + vec<3, T, Q> const& rgbColor); + + /// Convert a color from YCoCgR color space to RGB color space. + /// @see "YCoCg-R: A Color Space with RGB Reversibility and Low Dynamic Range" + /// @see gtx_color_space_YCoCg + template + GLM_FUNC_DECL vec<3, T, Q> YCoCgR2rgb( + vec<3, T, Q> const& YCoCgColor); + + /// @} +}//namespace glm + +#include "vector_color.inl" diff --git a/glm/ext/vector_color.inl b/glm/ext/vector_color.inl new file mode 100644 index 00000000..c758b507 --- /dev/null +++ b/glm/ext/vector_color.inl @@ -0,0 +1,518 @@ +namespace glm{ +namespace detail +{ + template + struct compute_rgbToSrgb + { + GLM_FUNC_QUALIFIER static vec call(vec const& ColorRGB, T GammaCorrection) + { + vec const ClampedColor(clamp(ColorRGB, static_cast(0), static_cast(1))); + + return mix( + pow(ClampedColor, vec(GammaCorrection)) * static_cast(1.055) - static_cast(0.055), + ClampedColor * static_cast(12.92), + lessThan(ClampedColor, vec(static_cast(0.0031308)))); + } + }; + + template + struct compute_rgbToSrgb<4, T, Q> + { + GLM_FUNC_QUALIFIER static vec<4, T, Q> call(vec<4, T, Q> const& ColorRGB, T GammaCorrection) + { + return vec<4, T, Q>(compute_rgbToSrgb<3, T, Q>::call(vec<3, T, Q>(ColorRGB), GammaCorrection), ColorRGB.w); + } + }; + + template + struct compute_srgbToRgb + { + GLM_FUNC_QUALIFIER static vec call(vec const& ColorSRGB, T Gamma) + { + return mix( + pow((ColorSRGB + static_cast(0.055)) * static_cast(0.94786729857819905213270142180095), vec(Gamma)), + ColorSRGB * static_cast(0.07739938080495356037151702786378), + lessThanEqual(ColorSRGB, vec(static_cast(0.04045)))); + } + }; + + template + struct compute_srgbToRgb<4, T, Q> + { + GLM_FUNC_QUALIFIER static vec<4, T, Q> call(vec<4, T, Q> const& ColorSRGB, T Gamma) + { + return vec<4, T, Q>(compute_srgbToRgb<3, T, Q>::call(vec<3, T, Q>(ColorSRGB), Gamma), ColorSRGB.w); + } + }; + + template + class compute_YCoCgR { + public: + static GLM_FUNC_QUALIFIER vec<3, T, Q> rgb2YCoCgR + ( + vec<3, T, Q> const& rgbColor + ) + { + vec<3, T, Q> result; + result.x/*Y */ = rgbColor.g * static_cast(0.5) + (rgbColor.r + rgbColor.b) * static_cast(0.25); + result.y/*Co*/ = rgbColor.r - rgbColor.b; + result.z/*Cg*/ = rgbColor.g - (rgbColor.r + rgbColor.b) * static_cast(0.5); + return result; + } + + static GLM_FUNC_QUALIFIER vec<3, T, Q> YCoCgR2rgb + ( + vec<3, T, Q> const& YCoCgRColor + ) + { + vec<3, T, Q> result; + T tmp = YCoCgRColor.x - (YCoCgRColor.z * static_cast(0.5)); + result.g = YCoCgRColor.z + tmp; + result.b = tmp - (YCoCgRColor.y * static_cast(0.5)); + result.r = result.b + YCoCgRColor.y; + return result; + } + }; + + template + class compute_YCoCgR { + public: + static GLM_FUNC_QUALIFIER vec<3, T, Q> rgb2YCoCgR + ( + vec<3, T, Q> const& rgbColor + ) + { + vec<3, T, Q> result; + result.y/*Co*/ = rgbColor.r - rgbColor.b; + T tmp = rgbColor.b + (result.y >> 1); + result.z/*Cg*/ = rgbColor.g - tmp; + result.x/*Y */ = tmp + (result.z >> 1); + return result; + } + + static GLM_FUNC_QUALIFIER vec<3, T, Q> YCoCgR2rgb + ( + vec<3, T, Q> const& YCoCgRColor + ) + { + vec<3, T, Q> result; + T tmp = YCoCgRColor.x - (YCoCgRColor.z >> 1); + result.g = YCoCgRColor.z + tmp; + result.b = tmp - (YCoCgRColor.y >> 1); + result.r = result.b + YCoCgRColor.y; + return result; + } + }; +}//namespace detail + + template + GLM_FUNC_QUALIFIER vec convertLinearToSRGB(vec const& ColorLinear) + { + return detail::compute_rgbToSrgb::call(ColorLinear, static_cast(0.41666)); + } + + // Based on Ian Taylor http://chilliant.blogspot.fr/2012/08/srgb-approximations-for-hlsl.html + template<> + GLM_FUNC_QUALIFIER vec<3, float, lowp> convertLinearToSRGB(vec<3, float, lowp> const& ColorLinear) + { + vec<3, float, lowp> S1 = sqrt(ColorLinear); + vec<3, float, lowp> S2 = sqrt(S1); + vec<3, float, lowp> S3 = sqrt(S2); + return 0.662002687f * S1 + 0.684122060f * S2 - 0.323583601f * S3 - 0.0225411470f * ColorLinear; + } + + template + GLM_FUNC_QUALIFIER vec convertLinearToSRGB(vec const& ColorLinear, T Gamma) + { + return detail::compute_rgbToSrgb::call(ColorLinear, static_cast(1) / Gamma); + } + + template + GLM_FUNC_QUALIFIER vec convertSRGBToLinear(vec const& ColorSRGB) + { + return detail::compute_srgbToRgb::call(ColorSRGB, static_cast(2.4)); + } + + template + GLM_FUNC_QUALIFIER vec convertSRGBToLinear(vec const& ColorSRGB, T Gamma) + { + return detail::compute_srgbToRgb::call(ColorSRGB, Gamma); + } + + template + GLM_FUNC_QUALIFIER vec<3, T, Q> rgbColor(const vec<3, T, Q>& hsvColor) + { + vec<3, T, Q> hsv = hsvColor; + vec<3, T, Q> rgbColor; + + if(hsv.y == static_cast(0)) + // achromatic (grey) + rgbColor = vec<3, T, Q>(hsv.z); + else + { + T sector = floor(hsv.x * (T(1) / T(60))); + T frac = (hsv.x * (T(1) / T(60))) - sector; + // factorial part of h + T o = hsv.z * (T(1) - hsv.y); + T p = hsv.z * (T(1) - hsv.y * frac); + T q = hsv.z * (T(1) - hsv.y * (T(1) - frac)); + + switch(int(sector)) + { + default: + case 0: + rgbColor.r = hsv.z; + rgbColor.g = q; + rgbColor.b = o; + break; + case 1: + rgbColor.r = p; + rgbColor.g = hsv.z; + rgbColor.b = o; + break; + case 2: + rgbColor.r = o; + rgbColor.g = hsv.z; + rgbColor.b = q; + break; + case 3: + rgbColor.r = o; + rgbColor.g = p; + rgbColor.b = hsv.z; + break; + case 4: + rgbColor.r = q; + rgbColor.g = o; + rgbColor.b = hsv.z; + break; + case 5: + rgbColor.r = hsv.z; + rgbColor.g = o; + rgbColor.b = p; + break; + } + } + + return rgbColor; + } + + template + GLM_FUNC_QUALIFIER vec<3, T, Q> hsvColor(const vec<3, T, Q>& rgbColor) + { + vec<3, T, Q> hsv = rgbColor; + float Min = min(min(rgbColor.r, rgbColor.g), rgbColor.b); + float Max = max(max(rgbColor.r, rgbColor.g), rgbColor.b); + float Delta = Max - Min; + + hsv.z = Max; + + if(Max != static_cast(0)) + { + hsv.y = Delta / hsv.z; + T h = static_cast(0); + + if(rgbColor.r == Max) + // between yellow & magenta + h = static_cast(0) + T(60) * (rgbColor.g - rgbColor.b) / Delta; + else if(rgbColor.g == Max) + // between cyan & yellow + h = static_cast(120) + T(60) * (rgbColor.b - rgbColor.r) / Delta; + else + // between magenta & cyan + h = static_cast(240) + T(60) * (rgbColor.r - rgbColor.g) / Delta; + + if(h < T(0)) + hsv.x = h + T(360); + else + hsv.x = h; + } + else + { + // If r = g = b = 0 then s = 0, h is undefined + hsv.y = static_cast(0); + hsv.x = static_cast(0); + } + + return hsv; + } + + template + GLM_FUNC_QUALIFIER mat<4, 4, T, defaultp> saturation(T const s) + { + vec<3, T, defaultp> const rgbw = vec<3, T, defaultp>(T(0.2126), T(0.7152), T(0.0722)); + + vec<3, T, defaultp> const col((T(1) - s) * rgbw); + + mat<4, 4, T, defaultp> result(T(1)); + result[0][0] = col.x + s; + result[0][1] = col.x; + result[0][2] = col.x; + result[1][0] = col.y; + result[1][1] = col.y + s; + result[1][2] = col.y; + result[2][0] = col.z; + result[2][1] = col.z; + result[2][2] = col.z + s; + + return result; + } + + template + GLM_FUNC_QUALIFIER vec<3, T, Q> saturation(const T s, const vec<3, T, Q>& color) + { + return vec<3, T, Q>(saturation(s) * vec<4, T, Q>(color, T(0))); + } + + template + GLM_FUNC_QUALIFIER vec<4, T, Q> saturation(const T s, const vec<4, T, Q>& color) + { + return saturation(s) * color; + } + + template + GLM_FUNC_QUALIFIER T luminosity(const vec<3, T, Q>& color) + { + vec<3, T, Q> const tmp(0.33, 0.59, 0.11); + return dot(color, tmp); + } + + + template + GLM_FUNC_QUALIFIER vec<3, T, Q> rgb2YCoCg(vec<3, T, Q> const& rgbColor) + { + vec<3, T, Q> result; + result.x/*Y */ = rgbColor.r / T(4) + rgbColor.g / T(2) + rgbColor.b / T(4); + result.y/*Co*/ = rgbColor.r / T(2) + rgbColor.g * T(0) - rgbColor.b / T(2); + result.z/*Cg*/ = - rgbColor.r / T(4) + rgbColor.g / T(2) - rgbColor.b / T(4); + return result; + } + + template + GLM_FUNC_QUALIFIER vec<3, T, Q> YCoCg2rgb(vec<3, T, Q> const& YCoCgColor) + { + vec<3, T, Q> result; + result.r = YCoCgColor.x + YCoCgColor.y - YCoCgColor.z; + result.g = YCoCgColor.x + YCoCgColor.z; + result.b = YCoCgColor.x - YCoCgColor.y - YCoCgColor.z; + return result; + } + + template + GLM_FUNC_QUALIFIER vec<3, T, Q> rgb2YCoCgR(vec<3, T, Q> const& rgbColor) + { + return detail::compute_YCoCgR::is_integer>::rgb2YCoCgR(rgbColor); + } + + template + GLM_FUNC_QUALIFIER vec<3, T, Q> YCoCgR2rgb(vec<3, T, Q> const& YCoCgRColor) + { + return detail::compute_YCoCgR::is_integer>::YCoCgR2rgb(YCoCgRColor); + } + + // Converting pure hue to RGB + template + vec<3, T, defaultp> HUEtoRGB(float H) + { + T const R = abs(H * 6 - 3) - 1; + T const G = 2 - abs(H * 6 - 2); + T const B = 2 - abs(H * 6 - 4); + return saturate(float3(R,G,B)); + } + + // Converting RGB to hue/chroma/value + template + GLM_FUNC_QUALIFIER vec<3, T, Q> RGBtoHCV(vec<3, T, Q> const& RGB) + { + float const Epsilon = 1e-10; + // Based on work by Sam Hocevar and Emil Persson + float4 P = (RGB.g < RGB.b) ? float4(RGB.bg, -1.0, 2.0/3.0) : float4(RGB.gb, 0.0, -1.0/3.0); + float4 Q = (RGB.r < P.x) ? float4(P.xyw, RGB.r) : float4(RGB.r, P.yzx); + float C = Q.x - min(Q.w, Q.y); + float H = abs((Q.w - Q.y) / (6 * C + Epsilon) + Q.z); + return float3(H, C, Q.x); + } + + // Converting HSV to RGB + template + GLM_FUNC_QUALIFIER vec<3, T, Q> HSVtoRGB(vec<3, T, Q> const& HSV) + { + vec<3, T, Q> const RGB = HUEtoRGB(HSV.x); + return ((RGB - 1) * HSV.y + 1) * HSV.z; + } + + // Converting HSL to RGB + template + GLM_FUNC_QUALIFIER vec<3, T, Q> HSLtoRGB(vec<3, T, Q> const& HSL) + { + vec<3, T, Q> const RGB = HUEtoRGB(HSL.x); + float C = (1 - abs(2 * HSL.z - 1)) * HSL.y; + return (RGB - 0.5) * C + HSL.z; + } + + // Converting HCY to RGB + // The weights of RGB contributions to luminance. + // Should sum to unity. + template + GLM_FUNC_QUALIFIER vec<3, T, Q> HCYtoRGB(vec<3, T, Q> const& HCY) + { + vec<3, T, Q> const HCYwts(0.299, 0.587, 0.114); + vec<3, T, Q> const RGB = HUEtoRGB(HCY.x); + T const Z = dot(RGB, HCYwts); + + if(HCY.z < Z) + { + HCY.y *= HCY.z / Z; + } + else if(Z < 1) + { + HCY.y *= (1 - HCY.z) / (1 - Z); + } + + return (RGB - Z) * HCY.y + HCY.z; + } + + //Converting HCL to RGB + template + GLM_FUNC_QUALIFIER vec<3, T, Q> HCLtoRGB(vec<3, T, Q> const& HCL) + { + float HCLgamma = 3; + float HCLy0 = 100; + float HCLmaxL = 0.530454533953517; // == exp(HCLgamma / HCLy0) - 0.5 + float PI = 3.1415926536; + + vec<3, T, Q> RGB(0, 0, 0); + if(HCL.z != 0) + { + float H = HCL.x; + float C = HCL.y; + float L = HCL.z * HCLmaxL; + float Q = exp((1 - C / (2 * L)) * (HCLgamma / HCLy0)); + float U = (2 * L - C) / (2 * Q - 1); + float V = C / Q; + float T = tan((H + min(frac(2 * H) / 4, frac(-2 * H) / 8)) * PI * 2); + H *= 6; + if (H <= 1) + { + RGB.r = 1; + RGB.g = T / (1 + T); + } + else if (H <= 2) + { + RGB.r = (1 + T) / T; + RGB.g = 1; + } + else if (H <= 3) + { + RGB.g = 1; + RGB.b = 1 + T; + } + else if (H <= 4) + { + RGB.g = 1 / (1 + T); + RGB.b = 1; + } + else if (H <= 5) + { + RGB.r = -1 / T; + RGB.b = 1; + } + else + { + RGB.r = 1; + RGB.b = -T; + } + RGB = RGB * V + U; + } + return RGB; + } + + // Converting RGB to HSV + template + GLM_FUNC_QUALIFIER vec<3, T, Q> RGBtoHSV(vec<3, T, Q> const& RGB) + { + vec<3, T, Q> HCV = RGBtoHCV(RGB); + float S = HCV.y / (HCV.z + Epsilon); + return vec<3, T, Q>(HCV.x, S, HCV.z); + } +/* + vec3 rgb2hsv(vec3 c) + { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); + } + + Update: ​Emil Persson suggests using the ternary operator explicitly to force compilers into using a fast conditional move instruction: + + vec4 p = c.g < c.b ? vec4(c.bg, K.wz) : vec4(c.gb, K.xy); + vec4 q = c.r < p.x ? vec4(p.xyw, c.r) : vec4(c.r, p.yzx); + + And because a lot of people get it wrong, too, here is the reverse operation in GLSL. It is the algorithm almost everyone uses (or should use): + + vec3 hsv2rgb(vec3 c) + { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); + } +*/ + + // Converting RGB to HSL + template + GLM_FUNC_QUALIFIER vec<3, T, Q> RGBtoHSL(vec<3, T, Q> const& RGB) + { + vec<3, T, Q> HCV = RGBtoHCV(RGB); + float L = HCV.z - HCV.y * 0.5; + float S = HCV.y / (1 - abs(L * 2 - 1) + Epsilon); + return float3(HCV.x, S, L); + } + + // Converting RGB to HCY + template + GLM_FUNC_QUALIFIER vec<3, T, Q> RGBtoHCY(vec<3, T, Q> const& RGB) + { + // Corrected by David Schaeffer + float3 HCV = RGBtoHCV(RGB); + float Y = dot(RGB, HCYwts); + float Z = dot(HUEtoRGB(HCV.x), HCYwts); + + if (Y < Z) + { + HCV.y *= Z / (Epsilon + Y); + } + else + { + HCV.y *= (1 - Z) / (Epsilon + 1 - Y); + } + + return float3(HCV.x, HCV.y, Y); + } + + // Converting RGB to HCL + template + GLM_FUNC_QUALIFIER vec<3, T, Q> RGBtoHCL(vec<3, T, Q> const& RGB) + { + vec<3, T, Q> HCL; + T H = 0; + T U = min(RGB.r, min(RGB.g, RGB.b)); + T V = max(RGB.r, max(RGB.g, RGB.b)); + T Q = HCLgamma / HCLy0; + HCL.y = V - U; + + if (HCL.y != 0) + { + H = atan2(RGB.g - RGB.b, RGB.r - RGB.g) / PI; + Q *= U / V; + } + Q = exp(Q); + HCL.x = frac(H / 2 - min(frac(H), frac(-H)) / 6); + HCL.y *= Q; + HCL.z = lerp(-U, V, Q) / (HCLmaxL * 2); + + return HCL; + } +}//namespace glm diff --git a/test/ext/CMakeLists.txt b/test/ext/CMakeLists.txt index f4152e70..17670b94 100644 --- a/test/ext/CMakeLists.txt +++ b/test/ext/CMakeLists.txt @@ -16,6 +16,7 @@ glmCreateTestGTC(ext_scalar_relational) glmCreateTestGTC(ext_vec1) glmCreateTestGTC(ext_vector_bool1) glmCreateTestGTC(ext_vector_common) +glmCreateTestGTC(ext_vector_color) glmCreateTestGTC(ext_vector_iec559) glmCreateTestGTC(ext_vector_integer) glmCreateTestGTC(ext_vector_relational) diff --git a/test/ext/ext_vector_color.cpp b/test/ext/ext_vector_color.cpp new file mode 100644 index 00000000..ed238d8a --- /dev/null +++ b/test/ext/ext_vector_color.cpp @@ -0,0 +1,13 @@ +#include +#include +#include +#include +#include + +int main() +{ + int Error = 0; + + + return Error; +}