From d9d72c35691e5a61b67ec04137f41a917cc01058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Sun, 27 Aug 2017 19:51:23 +0200 Subject: [PATCH 01/12] Adding color models --- .gitignore | 2 + LICENSE | 1 + README.md | 3 +- color.hpp | 284 +++++++++++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 27 +++++ 5 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 color.hpp create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore index edf6645..e5f99fc 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ *.i*86 *.x86_64 *.hex + +__targets diff --git a/LICENSE b/LICENSE index 3ce1c6e..8ac8c03 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ The MIT License (MIT) +Copyright (c) 2017 Torbjörn Rathsman Copyright (c) 2015 Jon Ross Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index 067ae6b..6e98045 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # libcolor -C color library + +C++ library for converting between color spaces. diff --git a/color.hpp b/color.hpp new file mode 100644 index 0000000..3e6c12c --- /dev/null +++ b/color.hpp @@ -0,0 +1,284 @@ +#ifndef COLOR_HPP +#define COLOR_HPP + +#include +#include +#include + +namespace Color + { + typedef float vec4 __attribute__ ((vector_size (16))); + + class HCYpA; + class HSLA; + class HSVA; + class RGBA; + class YpCbCr; + class SRGBA; + + class RGBA + { + public: + inline explicit RGBA(SRGBA x) noexcept; + + explicit RGBA(float r,float g,float b,float a) noexcept + { + m_data[0]=r; + m_data[1]=g; + m_data[2]=b; + m_data[3]=a; + } + + RGBA()=default; + + float red() const noexcept + {return m_data[0];} + + float green() const noexcept + {return m_data[1];} + + float blue() const noexcept + {return m_data[2];} + + float alpha() const noexcept + {return m_data[3];} + + RGBA& red(float val) noexcept + { + m_data[0]=val; + return *this; + } + + RGBA& green(float val) noexcept + { + m_data[1]=val; + return *this; + } + + RGBA& blue(float val) noexcept + { + m_data[2]=val; + return *this; + } + + RGBA& alpha(float val) noexcept + { + m_data[3]=val; + return *this; + } + + private: + static inline float from_srgb(float x) noexcept; + vec4 m_data; + }; + + class SRGBA + { + public: + inline explicit SRGBA(HSLA x) noexcept; + inline explicit SRGBA(HCYpA x) noexcept; + inline explicit SRGBA(HSVA x) noexcept; + + explicit SRGBA(RGBA x) noexcept: + SRGBA(to_srgb(x.red()),to_srgb(x.green()),to_srgb(x.blue()),x.alpha()) + {} + + inline explicit SRGBA(YpCbCr x) noexcept; + + explicit SRGBA(float r,float g,float b,float a) noexcept + { + m_data[0]=r; + m_data[1]=g; + m_data[2]=b; + m_data[3]=a; + } + + SRGBA()=default; + + float red() const noexcept + {return m_data[0];} + + float green() const noexcept + {return m_data[1];} + + float blue() const noexcept + {return m_data[2];} + + float alpha() const noexcept + {return m_data[3];} + + SRGBA& red(float val) noexcept + { + m_data[0]=val; + return *this; + } + + SRGBA& green(float val) noexcept + { + m_data[1]=val; + return *this; + } + + SRGBA& blue(float val) noexcept + { + m_data[2]=val; + return *this; + } + + SRGBA& alpha(float val) noexcept + { + m_data[3]=val; + return *this; + } + + float luma() const noexcept + { + vec4 weights{0.21f,0.72f,0.07f,0.0f}; + auto temp=m_data*weights; + return temp[0] + temp[1] + temp[2] + temp[3]; + } + + private: + static float to_srgb(float x) noexcept + { + if (x < 0.00031308f) + {return 12.92f * x;} + return 1.055f*std::pow(x,(1.0f / 2.4f) ) - 0.055f; + } + + vec4 m_data; + }; + + inline RGBA::RGBA(SRGBA x) noexcept: + RGBA(from_srgb(x.red()),from_srgb(x.green()),from_srgb(x.blue()),x.alpha()) + {} + + inline float RGBA::from_srgb(float x) noexcept + { + if(x < 0.04045f) + {return x/12.92f;} + return std::pow( (x + 0.055f)/(1 + 0.055f),2.4); + } + + namespace detail + { + struct MmCH //For use with hexcone models + { + float M; + float m; + float C; + float H; + }; + + static constexpr auto pi=std::acos(-1.0f); + + static MmCH mmch(SRGBA x) + { + auto ptr=reinterpret_cast(&x); + auto offset_max=std::max_element(ptr,ptr + 3); + auto M=*offset_max; + auto m=std::min(std::min(ptr[0],ptr[1]), ptr[2]); + auto C=M - m; + float hue_lut[3]= + { + std::fmod((x.green() - x.blue())/C,6.0f) + (x.blue()>x.green()?6.0f:0.0f) + ,(x.blue() - x.red())/C + 2 + ,(x.red() - x.green())/C + 4 + }; + assert(offset_max - ptr<3); + return {M,m,C,C>0?pi*hue_lut[offset_max - ptr]/3.0f:0.0f}; + } + + static SRGBA from_hue_chroma_alpha(float hue,float chroma,float alpha) + { + assert(hue>=0 && hue<=2.0f*pi); + auto H=3.0f*hue/pi; + auto C=chroma; + auto X=chroma*(1.0f - std::abs(std::fmod(H,2) - 1.0f)); + + SRGBA rgb_lut[7]= + { + SRGBA(C,X,0,alpha) + ,SRGBA(X,C,0,alpha) + ,SRGBA(0,C,X,alpha) + ,SRGBA(0,X,C,alpha) + ,SRGBA(X,0,C,alpha) + ,SRGBA(C,0,X,alpha) + ,SRGBA(C,X,0,alpha) //So we can accept hue=2 pi without mod + }; + + return rgb_lut[static_cast(H)]; + } + } + + class HCYpA + { + public: + explicit HCYpA(SRGBA x) + { + auto temp=detail::mmch(x); + m_data[0]=temp.H; + m_data[1]=temp.C; + m_data[2]=x.luma(); + m_data[3]=x.alpha(); + } + + explicit HCYpA(float hue,float chroma,float luma,float alpha) + { + m_data[0]=hue; + m_data[1]=chroma; + m_data[2]=luma; + m_data[3]=alpha; + } + + float hue() const noexcept + {return m_data[0];} + + float chroma() const noexcept + {return m_data[1];} + + float luma() const noexcept + {return m_data[2];} + + float alpha() const noexcept + {return m_data[3];} + + HCYpA& hue(float x) noexcept + { + m_data[0]=x; + return *this; + } + + HCYpA& chroma(float x) noexcept + { + m_data[1]=x; + return *this; + } + + HCYpA& luma(float x) noexcept + { + m_data[2]=x; + return *this; + } + + HCYpA& alpha(float x) noexcept + { + m_data[3]=x; + return *this; + } + + + private: + vec4 m_data; + }; + + inline SRGBA::SRGBA(HCYpA x) noexcept + { + *this=detail::from_hue_chroma_alpha(x.hue(),x.chroma(),x.alpha()); + auto m=x.luma() - (luma()); + m_data+=vec4{m,m,m,0}; + } + } + +#endif + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..614f3c1 --- /dev/null +++ b/main.cpp @@ -0,0 +1,27 @@ +//@ {"targets":[{"name":"test","type":"application"}]} + +#include "color.hpp" + +int main() + { + + + for(int k=0;k<5;++k) + { + for(int l=0;l<5;++l) + { + for(int m=0;m<5;++m) + { + Color::SRGBA test_a(float(k)/4.0f,float(l)/4.0f,float(m)/4.0f,1.0f); + Color::HCYpA test_b(test_a); + printf("(%.7f,%.7f,%.7f,%.7f) ",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + Color::SRGBA test_c(test_b); + printf("(%.7f,%.7f,%.7f,%.7f) ",test_c.red(),test_c.green(),test_c.blue(),test_c.alpha()); + printf("(%.7f,%.7f,%.7f,%.7f)",test_b.hue(),test_b.chroma(),test_b.luma(),test_b.alpha()); + putchar('\n'); + } + } + } + + return 0; + } From 7c4dd33ae58da29c1a0e30247a5aca475062a6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Sun, 27 Aug 2017 21:12:12 +0200 Subject: [PATCH 02/12] Add HSLA --- color.hpp | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- main.cpp | 10 +++++--- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/color.hpp b/color.hpp index 3e6c12c..5502b45 100644 --- a/color.hpp +++ b/color.hpp @@ -275,7 +275,77 @@ namespace Color inline SRGBA::SRGBA(HCYpA x) noexcept { *this=detail::from_hue_chroma_alpha(x.hue(),x.chroma(),x.alpha()); - auto m=x.luma() - (luma()); + auto m=x.luma() - luma(); + m_data+=vec4{m,m,m,0}; + } + + class HSLA + { + public: + explicit HSLA(SRGBA x) + { + auto temp=detail::mmch(x); + auto L=0.5f*(temp.M + temp.m); + m_data[0]=temp.H; + m_data[1]=(temp.C>0 && L<1.0f)?temp.C/(1.0f - std::abs(2.0f*L - 1.0f)):0.0f; + m_data[2]=L; + m_data[3]=x.alpha(); + } + + explicit HSLA(float hue,float saturation,float lightness,float alpha) + { + m_data[0]=hue; + m_data[1]=saturation; + m_data[2]=lightness; + m_data[3]=alpha; + } + + float hue() const noexcept + {return m_data[0];} + + float saturation() const noexcept + {return m_data[1];} + + float lightness() const noexcept + {return m_data[2];} + + float alpha() const noexcept + {return m_data[3];} + + HSLA& hue(float x) noexcept + { + m_data[0]=x; + return *this; + } + + HSLA& saturation(float x) noexcept + { + m_data[1]=x; + return *this; + } + + HSLA& lightness(float x) noexcept + { + m_data[2]=x; + return *this; + } + + HSLA& alpha(float x) noexcept + { + m_data[3]=x; + return *this; + } + + + private: + vec4 m_data; + }; + + inline SRGBA::SRGBA(HSLA x) noexcept + { + auto chroma=(1.0f - std::abs(2*x.lightness() - 1.0f) )*x.saturation(); + *this=detail::from_hue_chroma_alpha(x.hue(),chroma,x.alpha()); + auto m=x.lightness() - 0.5f*chroma; m_data+=vec4{m,m,m,0}; } } diff --git a/main.cpp b/main.cpp index 614f3c1..3da5b27 100644 --- a/main.cpp +++ b/main.cpp @@ -15,9 +15,13 @@ int main() Color::SRGBA test_a(float(k)/4.0f,float(l)/4.0f,float(m)/4.0f,1.0f); Color::HCYpA test_b(test_a); printf("(%.7f,%.7f,%.7f,%.7f) ",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); - Color::SRGBA test_c(test_b); - printf("(%.7f,%.7f,%.7f,%.7f) ",test_c.red(),test_c.green(),test_c.blue(),test_c.alpha()); - printf("(%.7f,%.7f,%.7f,%.7f)",test_b.hue(),test_b.chroma(),test_b.luma(),test_b.alpha()); + + test_a=Color::SRGBA(Color::HCYpA(test_a)); + printf("(%.7f,%.7f,%.7f,%.7f) ",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + + test_a=Color::SRGBA(Color::HSLA(test_a)); + printf("(%.7f,%.7f,%.7f,%.7f) ",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + putchar('\n'); } } From 4dd281a6f80620da6abedfb11c2b64e50fb86d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Sun, 27 Aug 2017 21:23:37 +0200 Subject: [PATCH 03/12] Add HSV --- color.hpp | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 9 ++++--- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/color.hpp b/color.hpp index 5502b45..616a0e9 100644 --- a/color.hpp +++ b/color.hpp @@ -348,6 +348,76 @@ namespace Color auto m=x.lightness() - 0.5f*chroma; m_data+=vec4{m,m,m,0}; } + + + class HSVA + { + public: + explicit HSVA(SRGBA x) + { + auto temp=detail::mmch(x); + auto V=temp.M; + m_data[0]=temp.H; + m_data[1]=(V>0)?temp.C/V:0.0f; + m_data[2]=V; + m_data[3]=x.alpha(); + } + + explicit HSVA(float hue,float saturation,float value,float alpha) + { + m_data[0]=hue; + m_data[1]=saturation; + m_data[2]=value; + m_data[3]=alpha; + } + + float hue() const noexcept + {return m_data[0];} + + float saturation() const noexcept + {return m_data[1];} + + float value() const noexcept + {return m_data[2];} + + float alpha() const noexcept + {return m_data[3];} + + HSVA& hue(float x) noexcept + { + m_data[0]=x; + return *this; + } + + HSVA& saturation(float x) noexcept + { + m_data[1]=x; + return *this; + } + + HSVA& value(float x) noexcept + { + m_data[2]=x; + return *this; + } + + HSVA& alpha(float x) noexcept + { + m_data[3]=x; + return *this; + } + + private: + vec4 m_data; + }; + + inline SRGBA::SRGBA(HSVA x) noexcept + { + auto chroma=x.value()*x.saturation(); + *this=detail::from_hue_chroma_alpha(x.hue(),chroma,x.alpha()); + auto m=x.value() - chroma; + m_data+=vec4{m,m,m,0}; + } } #endif diff --git a/main.cpp b/main.cpp index 3da5b27..daa0190 100644 --- a/main.cpp +++ b/main.cpp @@ -14,14 +14,17 @@ int main() { Color::SRGBA test_a(float(k)/4.0f,float(l)/4.0f,float(m)/4.0f,1.0f); Color::HCYpA test_b(test_a); - printf("(%.7f,%.7f,%.7f,%.7f) ",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); test_a=Color::SRGBA(Color::HCYpA(test_a)); - printf("(%.7f,%.7f,%.7f,%.7f) ",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); test_a=Color::SRGBA(Color::HSLA(test_a)); - printf("(%.7f,%.7f,%.7f,%.7f) ",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + test_a=Color::SRGBA(Color::HSVA(test_a)); + printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + putchar('\n'); } } From ca4a0d2228babcd96baa72bb1b14115f173cd3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Mon, 28 Aug 2017 16:18:45 +0200 Subject: [PATCH 04/12] Add Y'CbCrA (according to ITU-709) --- color.hpp | 88 +++++++++++++++++++++++++++++++++++++++++++++++++------ main.cpp | 3 ++ 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/color.hpp b/color.hpp index 616a0e9..cfdc427 100644 --- a/color.hpp +++ b/color.hpp @@ -13,8 +13,8 @@ namespace Color class HSLA; class HSVA; class RGBA; - class YpCbCr; class SRGBA; + class YpCbCrA; class RGBA { @@ -83,15 +83,10 @@ namespace Color SRGBA(to_srgb(x.red()),to_srgb(x.green()),to_srgb(x.blue()),x.alpha()) {} - inline explicit SRGBA(YpCbCr x) noexcept; + inline explicit SRGBA(YpCbCrA x) noexcept; explicit SRGBA(float r,float g,float b,float a) noexcept - { - m_data[0]=r; - m_data[1]=g; - m_data[2]=b; - m_data[3]=a; - } + {m_data=vec4{r,g,b,a};} SRGBA()=default; @@ -133,11 +128,15 @@ namespace Color float luma() const noexcept { - vec4 weights{0.21f,0.72f,0.07f,0.0f}; + vec4 weights{Kr,0.7152f,0.0722f,0.0f}; auto temp=m_data*weights; return temp[0] + temp[1] + temp[2] + temp[3]; } + static constexpr auto Kr=0.2126f; + static constexpr auto Kg=0.7152f; + static constexpr auto Kb=0.0722f; + private: static float to_srgb(float x) noexcept { @@ -418,6 +417,77 @@ namespace Color auto m=x.value() - chroma; m_data+=vec4{m,m,m,0}; } + + class YpCbCrA + { + public: + explicit YpCbCrA(SRGBA x) noexcept + { + auto Yp=x.luma(); + auto Pb=0.5f*(x.blue() - Yp)/(1.0f - x.Kb); + auto Pr=0.5f*(x.red() - Yp)/(1.0f - x.Kr); + m_data=vec4{Yp,Pb,Pr,x.alpha()} + vec4{0.0f,0.5f,0.5f,0.0f}; + } + + explicit YpCbCrA(float luma,float cb,float cr,float alpha) + {m_data=vec4{luma,cb,cr,alpha};} + + float luma() const noexcept + {return m_data[0];} + + float cb() const noexcept + {return m_data[1];} + + float cr() const noexcept + {return m_data[2];} + + float alpha() const noexcept + {return m_data[3];} + + + YpCbCrA& luma(float x) noexcept + { + m_data[0]=x; + return *this; + } + + YpCbCrA& cb(float x) noexcept + { + m_data[1]=x; + return *this; + } + + YpCbCrA& cr(float x) noexcept + { + m_data[1]=x; + return *this; + } + + YpCbCrA& alpha(float x) noexcept + { + m_data[3]=x; + return *this; + } + + private: + vec4 m_data; + }; + + inline SRGBA::SRGBA(YpCbCrA x) noexcept + { + auto temp=reinterpret_cast(x); //I want to it as a vec4! + temp-=vec4{0.0f,0.5f,0.5f,0.0f}; + + auto v=vec4 + { + temp[2]*(1.0f - Kr) + ,0.0f + ,temp[1]*(1.0f - Kb) + ,0.0f + }; + m_data=2.0f*v + vec4{x.luma(),0.0f,x.luma(),x.alpha()}; + m_data[1]=(x.luma() - Kr*m_data[0] - Kb*m_data[2])/Kg; + } } #endif diff --git a/main.cpp b/main.cpp index daa0190..6356c96 100644 --- a/main.cpp +++ b/main.cpp @@ -25,6 +25,9 @@ int main() test_a=Color::SRGBA(Color::HSVA(test_a)); printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + test_a=Color::SRGBA(Color::YpCbCrA(test_a)); + printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + putchar('\n'); } } From e85694da10893759c1a7702d62d5378ba50eb9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Mon, 28 Aug 2017 16:19:23 +0200 Subject: [PATCH 05/12] Fix comment --- color.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/color.hpp b/color.hpp index cfdc427..b0a82fb 100644 --- a/color.hpp +++ b/color.hpp @@ -475,7 +475,7 @@ namespace Color inline SRGBA::SRGBA(YpCbCrA x) noexcept { - auto temp=reinterpret_cast(x); //I want to it as a vec4! + auto temp=reinterpret_cast(x); //I want to use it as a vec4! temp-=vec4{0.0f,0.5f,0.5f,0.0f}; auto v=vec4 From fdf4b18d236ccd00bc5e77ce5196a9f6e0873f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Mon, 28 Aug 2017 16:30:33 +0200 Subject: [PATCH 06/12] Indentation --- color.hpp | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/color.hpp b/color.hpp index b0a82fb..16379ef 100644 --- a/color.hpp +++ b/color.hpp @@ -9,25 +9,20 @@ namespace Color { typedef float vec4 __attribute__ ((vector_size (16))); - class HCYpA; - class HSLA; - class HSVA; - class RGBA; - class SRGBA; - class YpCbCrA; + class HCYpA; + class HSLA; + class HSVA; + class RGBA; + class SRGBA; + class YpCbCrA; class RGBA { public: - inline explicit RGBA(SRGBA x) noexcept; + inline explicit RGBA(SRGBA x) noexcept; explicit RGBA(float r,float g,float b,float a) noexcept - { - m_data[0]=r; - m_data[1]=g; - m_data[2]=b; - m_data[3]=a; - } + {m_data=vec4{r,g,b,a};} RGBA()=default; @@ -72,16 +67,16 @@ namespace Color vec4 m_data; }; - class SRGBA + class SRGBA { public: inline explicit SRGBA(HSLA x) noexcept; inline explicit SRGBA(HCYpA x) noexcept; inline explicit SRGBA(HSVA x) noexcept; - explicit SRGBA(RGBA x) noexcept: - SRGBA(to_srgb(x.red()),to_srgb(x.green()),to_srgb(x.blue()),x.alpha()) - {} + explicit SRGBA(RGBA x) noexcept: + SRGBA(to_srgb(x.red()),to_srgb(x.green()),to_srgb(x.blue()),x.alpha()) + {} inline explicit SRGBA(YpCbCrA x) noexcept; From 62d34ac573d7123f65f12c27052f02342ed98cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Mon, 28 Aug 2017 16:34:23 +0200 Subject: [PATCH 07/12] Shorter init --- color.hpp | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/color.hpp b/color.hpp index 16379ef..d102061 100644 --- a/color.hpp +++ b/color.hpp @@ -211,19 +211,11 @@ namespace Color explicit HCYpA(SRGBA x) { auto temp=detail::mmch(x); - m_data[0]=temp.H; - m_data[1]=temp.C; - m_data[2]=x.luma(); - m_data[3]=x.alpha(); + m_data=vec4{temp.H,temp.C,x.luma(),x.alpha()}; } explicit HCYpA(float hue,float chroma,float luma,float alpha) - { - m_data[0]=hue; - m_data[1]=chroma; - m_data[2]=luma; - m_data[3]=alpha; - } + {m_data=vec4{hue,chroma,luma,alpha};} float hue() const noexcept {return m_data[0];} @@ -287,12 +279,7 @@ namespace Color } explicit HSLA(float hue,float saturation,float lightness,float alpha) - { - m_data[0]=hue; - m_data[1]=saturation; - m_data[2]=lightness; - m_data[3]=alpha; - } + {m_data=vec4{hue,saturation,lightness,alpha};} float hue() const noexcept {return m_data[0];} From c2e88d9c917e473e9ed3f84284728affcd2f3d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Mon, 28 Aug 2017 17:24:03 +0200 Subject: [PATCH 08/12] Add XYZA --- color.hpp | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 6 +++++ 2 files changed, 85 insertions(+) diff --git a/color.hpp b/color.hpp index d102061..2bc629a 100644 --- a/color.hpp +++ b/color.hpp @@ -14,12 +14,14 @@ namespace Color class HSVA; class RGBA; class SRGBA; + class XYZA; class YpCbCrA; class RGBA { public: inline explicit RGBA(SRGBA x) noexcept; + inline explicit RGBA(XYZA x) noexcept; explicit RGBA(float r,float g,float b,float a) noexcept {m_data=vec4{r,g,b,a};} @@ -66,6 +68,83 @@ namespace Color static inline float from_srgb(float x) noexcept; vec4 m_data; }; + + class XYZA + { + public: + explicit XYZA(RGBA x) noexcept + { + vec4 M[4]= + { + vec4{0.4124564f,0.3575761f,0.1804375f,0.0f} + ,vec4{0.2126729f,0.7151522f,0.0721750f,0.0f} + ,vec4{0.0193339f,0.1191920f,0.9503041f,0.0f} + ,vec4{0.0,0.0f,0.0f,1.0f} + }; + auto v=reinterpret_cast(x); + for(int k=0;k<4;++k) + { + auto vals=M[k]*v; + m_data[k]=vals[0]+vals[1]+vals[2]+vals[3]; + } + } + + float x() const noexcept + {return m_data[0];} + + float y() const noexcept + {return m_data[1];} + + float z() const noexcept + {return m_data[2];} + + float alpha() const noexcept + {return m_data[3];} + + XYZA& x(float val) noexcept + { + m_data[0]=val; + return *this; + } + + XYZA& y(float val) noexcept + { + m_data[1]=val; + return *this; + } + + XYZA& z(float val) noexcept + { + m_data[2]=val; + return *this; + } + + XYZA& alpha(float val) noexcept + { + m_data[3]=val; + return *this; + } + + private: + vec4 m_data; + }; + + inline RGBA::RGBA(XYZA x) noexcept + { + vec4 M[4]= + { + vec4{3.24045483602141e+00f,-1.53713885010258e+00f,-4.98531546868481e-01f,0.0f} + ,vec4{-9.69266389875654e-01f,1.87601092884249e+00f,4.15560823466735e-02f,0.0f} + ,vec4{5.56434196042137e-02f,-2.04025854267698e-01f,1.05722516245793e+00f,0.0f} + ,vec4{0.0,0.0f,0.0f,1.0f} + }; + auto v=reinterpret_cast(x); + for(int k=0;k<4;++k) + { + auto vals=M[k]*v; + m_data[k]=static_cast( vals[0]+vals[1]+vals[2]+vals[3] ); + } + } class SRGBA { diff --git a/main.cpp b/main.cpp index 6356c96..2740aae 100644 --- a/main.cpp +++ b/main.cpp @@ -28,6 +28,12 @@ int main() test_a=Color::SRGBA(Color::YpCbCrA(test_a)); printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + test_a=Color::SRGBA(Color::RGBA( test_a)); + printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + + test_a=Color::SRGBA(Color::RGBA( Color::XYZA(Color::RGBA(test_a)))); + printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + putchar('\n'); } } From e3b4d20be296808d27f6af462cce5c6dc63756dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Mon, 28 Aug 2017 17:25:56 +0200 Subject: [PATCH 09/12] Remove noop --- color.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/color.hpp b/color.hpp index 2bc629a..7d90307 100644 --- a/color.hpp +++ b/color.hpp @@ -142,7 +142,7 @@ namespace Color for(int k=0;k<4;++k) { auto vals=M[k]*v; - m_data[k]=static_cast( vals[0]+vals[1]+vals[2]+vals[3] ); + m_data[k]=vals[0]+vals[1]+vals[2]+vals[3]; } } From c1540ea03c9c80316c078c52c4d959f3d443fbaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Mon, 28 Aug 2017 19:07:49 +0200 Subject: [PATCH 10/12] Add HCIA --- color.hpp | 104 ++++++++++++++++++++++++++++++++++--- main.cpp => color_test.cpp | 3 ++ 2 files changed, 99 insertions(+), 8 deletions(-) rename main.cpp => color_test.cpp (88%) diff --git a/color.hpp b/color.hpp index 7d90307..813d242 100644 --- a/color.hpp +++ b/color.hpp @@ -10,6 +10,7 @@ namespace Color typedef float vec4 __attribute__ ((vector_size (16))); class HCYpA; + class HCIA; class HSLA; class HSVA; class RGBA; @@ -22,6 +23,8 @@ namespace Color public: inline explicit RGBA(SRGBA x) noexcept; inline explicit RGBA(XYZA x) noexcept; + inline explicit RGBA(HCIA x) noexcept; + explicit RGBA(float r,float g,float b,float a) noexcept {m_data=vec4{r,g,b,a};} @@ -63,6 +66,9 @@ namespace Color m_data[3]=val; return *this; } + + float intensity() const noexcept + {return (m_data[0] + m_data[1] + m_data[2])/3.0f;} private: static inline float from_srgb(float x) noexcept; @@ -244,8 +250,9 @@ namespace Color }; static constexpr auto pi=std::acos(-1.0f); - - static MmCH mmch(SRGBA x) + + template + static MmCH mmch(T x) noexcept { auto ptr=reinterpret_cast(&x); auto offset_max=std::max_element(ptr,ptr + 3); @@ -262,7 +269,7 @@ namespace Color return {M,m,C,C>0?pi*hue_lut[offset_max - ptr]/3.0f:0.0f}; } - static SRGBA from_hue_chroma_alpha(float hue,float chroma,float alpha) + static SRGBA srgba_from_hue_chroma_alpha(float hue,float chroma,float alpha) noexcept { assert(hue>=0 && hue<=2.0f*pi); auto H=3.0f*hue/pi; @@ -282,18 +289,39 @@ namespace Color return rgb_lut[static_cast(H)]; } + + static RGBA rgba_from_hue_chroma_alpha(float hue,float chroma,float alpha) noexcept + { + assert(hue>=0 && hue<=2.0f*pi); + auto H=3.0f*hue/pi; + auto C=chroma; + auto X=chroma*(1.0f - std::abs(std::fmod(H,2) - 1.0f)); + + RGBA rgb_lut[7]= + { + RGBA(C,X,0,alpha) + ,RGBA(X,C,0,alpha) + ,RGBA(0,C,X,alpha) + ,RGBA(0,X,C,alpha) + ,RGBA(X,0,C,alpha) + ,RGBA(C,0,X,alpha) + ,RGBA(C,X,0,alpha) //So we can accept hue=2 pi without mod + }; + + return rgb_lut[static_cast(H)]; + } } class HCYpA { public: - explicit HCYpA(SRGBA x) + explicit HCYpA(SRGBA x) noexcept { auto temp=detail::mmch(x); m_data=vec4{temp.H,temp.C,x.luma(),x.alpha()}; } - explicit HCYpA(float hue,float chroma,float luma,float alpha) + explicit HCYpA(float hue,float chroma,float luma,float alpha) noexcept {m_data=vec4{hue,chroma,luma,alpha};} float hue() const noexcept @@ -339,11 +367,71 @@ namespace Color inline SRGBA::SRGBA(HCYpA x) noexcept { - *this=detail::from_hue_chroma_alpha(x.hue(),x.chroma(),x.alpha()); + *this=detail::srgba_from_hue_chroma_alpha(x.hue(),x.chroma(),x.alpha()); auto m=x.luma() - luma(); m_data+=vec4{m,m,m,0}; } + class HCIA + { + public: + explicit HCIA(RGBA x) + { + auto temp=detail::mmch(x); + m_data=vec4{temp.H,temp.C,x.intensity(),x.alpha()}; + } + + explicit HCIA(float hue,float chroma,float intensity,float alpha) noexcept + {m_data=vec4{hue,chroma,intensity,alpha};} + + float hue() const noexcept + {return m_data[0];} + + float chroma() const noexcept + {return m_data[1];} + + float intensity() const noexcept + {return m_data[2];} + + float alpha() const noexcept + {return m_data[3];} + + HCIA& hue(float x) noexcept + { + m_data[0]=x; + return *this; + } + + HCIA& chroma(float x) noexcept + { + m_data[1]=x; + return *this; + } + + HCIA& intensity(float x) noexcept + { + m_data[2]=x; + return *this; + } + + HCIA& alpha(float x) noexcept + { + m_data[3]=x; + return *this; + } + + + private: + vec4 m_data; + }; + + inline RGBA::RGBA(HCIA x) noexcept + { + *this=detail::rgba_from_hue_chroma_alpha(x.hue(),x.chroma(),x.alpha()); + auto m=x.intensity() - intensity(); + m_data+=vec4{m,m,m,0}; + } + class HSLA { public: @@ -404,7 +492,7 @@ namespace Color inline SRGBA::SRGBA(HSLA x) noexcept { auto chroma=(1.0f - std::abs(2*x.lightness() - 1.0f) )*x.saturation(); - *this=detail::from_hue_chroma_alpha(x.hue(),chroma,x.alpha()); + *this=detail::srgba_from_hue_chroma_alpha(x.hue(),chroma,x.alpha()); auto m=x.lightness() - 0.5f*chroma; m_data+=vec4{m,m,m,0}; } @@ -474,7 +562,7 @@ namespace Color inline SRGBA::SRGBA(HSVA x) noexcept { auto chroma=x.value()*x.saturation(); - *this=detail::from_hue_chroma_alpha(x.hue(),chroma,x.alpha()); + *this=detail::srgba_from_hue_chroma_alpha(x.hue(),chroma,x.alpha()); auto m=x.value() - chroma; m_data+=vec4{m,m,m,0}; } diff --git a/main.cpp b/color_test.cpp similarity index 88% rename from main.cpp rename to color_test.cpp index 2740aae..7709bed 100644 --- a/main.cpp +++ b/color_test.cpp @@ -34,6 +34,9 @@ int main() test_a=Color::SRGBA(Color::RGBA( Color::XYZA(Color::RGBA(test_a)))); printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + test_a=Color::SRGBA(Color::RGBA( Color::HCIA(Color::RGBA(test_a)))); + printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); + putchar('\n'); } } From 4f7a4c77d25fae990f460f3b1da1fc042a1a94ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Mon, 28 Aug 2017 19:40:24 +0200 Subject: [PATCH 11/12] Update README.md --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e98045..deaa4b6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,50 @@ # libcolor -C++ library for converting between color spaces. +This is a header-only C++ library for between different colorspaces. The type of a color value defines which colorspace the value lives in. + +## Implemented colorspaces + +The following colorspace conversions are implemented. In all cases the alpha value is untouched. Any hue component is returned in radians from 0 to 2π. Other values goes from zero to one. + + +| From\To | HCY'A | HCIA | HSLA | HSVA | RGBA | sRGBA | XYZA | Y'CbCrA | +|--------- |------- |------ |------ |------ |------ |------- |------ |--------- | +| HCY'A | | No | No | No | No | Yes | No | No | +| HCIA | No | | No | No | Yes | No | No | No | +| HSLA | No | No | | No | No | Yes | No | No | +| HSVA | No | No | No | | No | Yes | No | No | +| RGBA | No | Yes | No | No | | Yes | Yes | No | +| sRGBA | Yes | No | Yes | Yes | Yes | | No | Yes | +| XYZA | No | No | No | No | Yes | No | | No | +| Y'CbCrA | No | No | No | No | No | Yes | No | | + +To convert from XYZA to HCY'A you would do the following: + + using namespace Color; + auto xyza=static_cast( static_cast( static_cast(color_in))); + +The reason for this three-step conversion is to avoid possible ambiguities and errors. + +### HCY'A (Hue Chroma Luma Alpha) +This is a perceptual colorspace. The hue is defined identically to HSL and HSV, but the lightness uses the ITU-709 luma weights + +### HCIA (Hue Chroma Intensity Alpha) +This colorspace uses the same formulae for hue and chroma as HCY'A, but uses linear input instead of sRGB values. The intensity is the average of all color channels. + +### HSLA (Hue Saturation Lightness Alpha) +This is the colorspace used in Windows color picker dialog. + +### HSVA (Hue Saturation Value Alpha) +This is the colorspace used in the GTK+ color picker dialog. + +### RGBA (Red Green Blue Alpha) +This is the linear RGB space. + +### sRGBA (standard Red Green Blue Alpha) +This is the RGB space used for most images on the Internet. + +### XYZA (CIE) +This colorspace is sometimes used as an intermediate step between reallity and RGB + +### Y'CbCr (Luma Chroma_b Chroma_r) +This colorspace is used with photographic compression and video. This implementation uses the ITU-709 weights. From 2b3af4061fd610cbbe5ab803701834925995fba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B6rn=20Rathsman?= Date: Tue, 12 Sep 2017 14:36:08 +0200 Subject: [PATCH 12/12] Remove Y'CbCr due to ambigous definitions. Also move HCY type to linear space --- README.md | 25 +++++------ color.hpp | 118 +++++++++---------------------------------------- color_test.cpp | 6 +-- 3 files changed, 33 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index deaa4b6..ed78c52 100644 --- a/README.md +++ b/README.md @@ -7,21 +7,20 @@ This is a header-only C++ library for between different colorspaces. The type of The following colorspace conversions are implemented. In all cases the alpha value is untouched. Any hue component is returned in radians from 0 to 2π. Other values goes from zero to one. -| From\To | HCY'A | HCIA | HSLA | HSVA | RGBA | sRGBA | XYZA | Y'CbCrA | -|--------- |------- |------ |------ |------ |------ |------- |------ |--------- | -| HCY'A | | No | No | No | No | Yes | No | No | -| HCIA | No | | No | No | Yes | No | No | No | -| HSLA | No | No | | No | No | Yes | No | No | -| HSVA | No | No | No | | No | Yes | No | No | -| RGBA | No | Yes | No | No | | Yes | Yes | No | -| sRGBA | Yes | No | Yes | Yes | Yes | | No | Yes | -| XYZA | No | No | No | No | Yes | No | | No | -| Y'CbCrA | No | No | No | No | No | Yes | No | | - -To convert from XYZA to HCY'A you would do the following: +| From\To | HCYA | HCIA | HSLA | HSVA | RGBA | sRGBA | XYZA +|--------- |------ |------ |------ |------ |------ |------- |------ +| HCYA | | No | No | No | Yes | No | No +| HCIA | No | | No | No | Yes | No | No +| HSLA | No | No | | No | No | Yes | No +| HSVA | No | No | No | | No | Yes | No +| RGBA | Yes | Yes | No | No | | Yes | Yes +| sRGBA | No | No | Yes | Yes | Yes | | No +| XYZA | No | No | No | No | Yes | No | + +To convert from XYZA to HSVA you would do the following: using namespace Color; - auto xyza=static_cast( static_cast( static_cast(color_in))); + auto hsva=static_cast( static_cast( static_cast(color_in))); The reason for this three-step conversion is to avoid possible ambiguities and errors. diff --git a/color.hpp b/color.hpp index 813d242..27c9683 100644 --- a/color.hpp +++ b/color.hpp @@ -9,14 +9,15 @@ namespace Color { typedef float vec4 __attribute__ ((vector_size (16))); - class HCYpA; + class HCYA; class HCIA; class HSLA; class HSVA; class RGBA; class SRGBA; class XYZA; - class YpCbCrA; + + static constexpr vec4 s_luma_weights={0.2126729f,0.7151522f,0.0721750f,0.0f}; class RGBA { @@ -24,8 +25,8 @@ namespace Color inline explicit RGBA(SRGBA x) noexcept; inline explicit RGBA(XYZA x) noexcept; inline explicit RGBA(HCIA x) noexcept; + inline explicit RGBA(HCYA x) noexcept; - explicit RGBA(float r,float g,float b,float a) noexcept {m_data=vec4{r,g,b,a};} @@ -69,6 +70,12 @@ namespace Color float intensity() const noexcept {return (m_data[0] + m_data[1] + m_data[2])/3.0f;} + + float luma() const noexcept + { + auto temp=m_data*s_luma_weights; + return temp[0] + temp[1] + temp[2] + temp[3]; + } private: static inline float from_srgb(float x) noexcept; @@ -83,7 +90,7 @@ namespace Color vec4 M[4]= { vec4{0.4124564f,0.3575761f,0.1804375f,0.0f} - ,vec4{0.2126729f,0.7151522f,0.0721750f,0.0f} + ,s_luma_weights ,vec4{0.0193339f,0.1191920f,0.9503041f,0.0f} ,vec4{0.0,0.0f,0.0f,1.0f} }; @@ -156,14 +163,11 @@ namespace Color { public: inline explicit SRGBA(HSLA x) noexcept; - inline explicit SRGBA(HCYpA x) noexcept; inline explicit SRGBA(HSVA x) noexcept; explicit SRGBA(RGBA x) noexcept: SRGBA(to_srgb(x.red()),to_srgb(x.green()),to_srgb(x.blue()),x.alpha()) {} - - inline explicit SRGBA(YpCbCrA x) noexcept; explicit SRGBA(float r,float g,float b,float a) noexcept {m_data=vec4{r,g,b,a};} @@ -205,17 +209,6 @@ namespace Color m_data[3]=val; return *this; } - - float luma() const noexcept - { - vec4 weights{Kr,0.7152f,0.0722f,0.0f}; - auto temp=m_data*weights; - return temp[0] + temp[1] + temp[2] + temp[3]; - } - - static constexpr auto Kr=0.2126f; - static constexpr auto Kg=0.7152f; - static constexpr auto Kb=0.0722f; private: static float to_srgb(float x) noexcept @@ -312,16 +305,16 @@ namespace Color } } - class HCYpA + class HCYA { public: - explicit HCYpA(SRGBA x) noexcept + explicit HCYA(RGBA x) noexcept { auto temp=detail::mmch(x); m_data=vec4{temp.H,temp.C,x.luma(),x.alpha()}; } - explicit HCYpA(float hue,float chroma,float luma,float alpha) noexcept + explicit HCYA(float hue,float chroma,float luma,float alpha) noexcept {m_data=vec4{hue,chroma,luma,alpha};} float hue() const noexcept @@ -336,25 +329,25 @@ namespace Color float alpha() const noexcept {return m_data[3];} - HCYpA& hue(float x) noexcept + HCYA& hue(float x) noexcept { m_data[0]=x; return *this; } - HCYpA& chroma(float x) noexcept + HCYA& chroma(float x) noexcept { m_data[1]=x; return *this; } - HCYpA& luma(float x) noexcept + HCYA& luma(float x) noexcept { m_data[2]=x; return *this; } - HCYpA& alpha(float x) noexcept + HCYA& alpha(float x) noexcept { m_data[3]=x; return *this; @@ -365,9 +358,9 @@ namespace Color vec4 m_data; }; - inline SRGBA::SRGBA(HCYpA x) noexcept + inline RGBA::RGBA(HCYA x) noexcept { - *this=detail::srgba_from_hue_chroma_alpha(x.hue(),x.chroma(),x.alpha()); + *this=detail::rgba_from_hue_chroma_alpha(x.hue(),x.chroma(),x.alpha()); auto m=x.luma() - luma(); m_data+=vec4{m,m,m,0}; } @@ -566,77 +559,6 @@ namespace Color auto m=x.value() - chroma; m_data+=vec4{m,m,m,0}; } - - class YpCbCrA - { - public: - explicit YpCbCrA(SRGBA x) noexcept - { - auto Yp=x.luma(); - auto Pb=0.5f*(x.blue() - Yp)/(1.0f - x.Kb); - auto Pr=0.5f*(x.red() - Yp)/(1.0f - x.Kr); - m_data=vec4{Yp,Pb,Pr,x.alpha()} + vec4{0.0f,0.5f,0.5f,0.0f}; - } - - explicit YpCbCrA(float luma,float cb,float cr,float alpha) - {m_data=vec4{luma,cb,cr,alpha};} - - float luma() const noexcept - {return m_data[0];} - - float cb() const noexcept - {return m_data[1];} - - float cr() const noexcept - {return m_data[2];} - - float alpha() const noexcept - {return m_data[3];} - - - YpCbCrA& luma(float x) noexcept - { - m_data[0]=x; - return *this; - } - - YpCbCrA& cb(float x) noexcept - { - m_data[1]=x; - return *this; - } - - YpCbCrA& cr(float x) noexcept - { - m_data[1]=x; - return *this; - } - - YpCbCrA& alpha(float x) noexcept - { - m_data[3]=x; - return *this; - } - - private: - vec4 m_data; - }; - - inline SRGBA::SRGBA(YpCbCrA x) noexcept - { - auto temp=reinterpret_cast(x); //I want to use it as a vec4! - temp-=vec4{0.0f,0.5f,0.5f,0.0f}; - - auto v=vec4 - { - temp[2]*(1.0f - Kr) - ,0.0f - ,temp[1]*(1.0f - Kb) - ,0.0f - }; - m_data=2.0f*v + vec4{x.luma(),0.0f,x.luma(),x.alpha()}; - m_data[1]=(x.luma() - Kr*m_data[0] - Kb*m_data[2])/Kg; - } } #endif diff --git a/color_test.cpp b/color_test.cpp index 7709bed..cf01d06 100644 --- a/color_test.cpp +++ b/color_test.cpp @@ -13,10 +13,9 @@ int main() for(int m=0;m<5;++m) { Color::SRGBA test_a(float(k)/4.0f,float(l)/4.0f,float(m)/4.0f,1.0f); - Color::HCYpA test_b(test_a); printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); - test_a=Color::SRGBA(Color::HCYpA(test_a)); + test_a=Color::SRGBA(Color::RGBA(Color::HCYA(Color::RGBA(test_a)))); printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); test_a=Color::SRGBA(Color::HSLA(test_a)); @@ -25,9 +24,6 @@ int main() test_a=Color::SRGBA(Color::HSVA(test_a)); printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); - test_a=Color::SRGBA(Color::YpCbCrA(test_a)); - printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha()); - test_a=Color::SRGBA(Color::RGBA( test_a)); printf("(%.6f,%.6f,%.6f,%.6f)\n",test_a.red(),test_a.green(),test_a.blue(),test_a.alpha());