From 1df42e84c522335e9b9e2421cc8d718b0e89a198 Mon Sep 17 00:00:00 2001
From: Ansgar Philippsen <ansgar.philippsen@gmail.com>
Date: Mon, 2 Jul 2012 18:00:48 -0400
Subject: [PATCH] refactoring of gfx.Color to accomodate HSV better

The Color class now keeps track of RGB and HSV in preparation of better HSV use,
e.g. HSV based gradients. The interface has been extended, and parts of the old
interface have been deprecated (by means of in-source comments, not yet as
messages when using it); this applies to both C++ and Python. Instead of
gfx.Color() use the factory functions gfx.RGB(r,g,b), gfx.RGBA(r,g,b,a),
gfx.HSV(h,s,v) and gfx.HSVA(h,s,v,a). See also gfx/color.hh for in-source doc
---
 modules/gfx/pymod/__init__.py            |  49 ++-
 modules/gfx/pymod/export_color.cc        | 152 +++++---
 modules/gfx/src/color.cc                 | 450 +++++++++++++++++------
 modules/gfx/src/color.hh                 | 176 +++++++--
 modules/gfx/src/impl/cartoon_renderer.cc |   9 +-
 modules/gfx/src/impl/cpk_renderer.cc     |   2 +-
 modules/gfx/src/impl/entity_detail.cc    |   2 +-
 modules/gfx/src/impl/entity_detail.hh    |  43 ++-
 modules/gfx/src/texture.cc               |   5 +-
 modules/gfx/src/texture.hh               |  13 +-
 modules/gfx/tests/CMakeLists.txt         |   1 +
 modules/gfx/tests/test_color.cc          | 184 +++++++++
 12 files changed, 833 insertions(+), 253 deletions(-)
 create mode 100644 modules/gfx/tests/test_color.cc

diff --git a/modules/gfx/pymod/__init__.py b/modules/gfx/pymod/__init__.py
index be887c4ab..bd5750d32 100644
--- a/modules/gfx/pymod/__init__.py
+++ b/modules/gfx/pymod/__init__.py
@@ -19,34 +19,33 @@
 from _ost_gfx import *
 from py_gfx_obj import PyGfxObj
 
-WHITE=Color(1.0,1.0,1.0)
-BLACK=Color(0.0,0.0,0.0)
-GREY=Color(0.5,0.5,0.5)
-RED=Color(1.0,0.0,0.0)
-DARKRED=Color(0.5,0.0,0.0)
-LIGHTRED=Color(1.0,0.5,0.5)
-GREEN=Color(0.0,1.0,0.0)
-DARKGREEN=Color(0.0,0.5,0.0)
-LIGHTGREEN=Color(0.5,1.0,0.5)
-BLUE=Color(0.0,0.0,1.0)
-DARKBLUE=Color(0.0,0.0,0.5)
-LIGHTBLUE=Color(0.5,0.5,1.0)
-YELLOW=Color(1.0,1.0,0.0)
-DARKYELLOW=Color(0.5,0.5,0.0)
-LIGHTYELLOW=Color(1.0,1.0,0.5)
-CYAN=Color(0.0,1.0,1.0)
-DARKCYAN=Color(0.0,0.5,0.5)
-LIGHTCYAN=Color(0.5,1.0,1.0)
-MAGENTA=Color(1.0,0.0,1.0)
-DARKMAGENTA=Color(0.5,0.0,0.5)
-LIGHTMAGENTA=Color(1.0,0.5,1.0)
+WHITE=RGB(1.0,1.0,1.0)
+BLACK=RGB(0.0,0.0,0.0)
+GREY=RGB(0.5,0.5,0.5)
+RED=RGB(1.0,0.0,0.0)
+DARKRED=RGB(0.5,0.0,0.0)
+LIGHTRED=RGB(1.0,0.5,0.5)
+GREEN=RGB(0.0,1.0,0.0)
+DARKGREEN=RGB(0.0,0.5,0.0)
+LIGHTGREEN=RGB(0.5,1.0,0.5)
+BLUE=RGB(0.0,0.0,1.0)
+DARKBLUE=RGB(0.0,0.0,0.5)
+LIGHTBLUE=RGB(0.5,0.5,1.0)
+YELLOW=RGB(1.0,1.0,0.0)
+DARKYELLOW=RGB(0.5,0.5,0.0)
+LIGHTYELLOW=RGB(1.0,1.0,0.5)
+CYAN=RGB(0.0,1.0,1.0)
+DARKCYAN=RGB(0.0,0.5,0.5)
+LIGHTCYAN=RGB(0.5,1.0,1.0)
+MAGENTA=RGB(1.0,0.0,1.0)
+DARKMAGENTA=RGB(0.5,0.0,0.5)
+LIGHTMAGENTA=RGB(1.0,0.5,1.0)
 PURPLE=MAGENTA
 DARKPURPLE=DARKMAGENTA
 LIGHTPURPLE=LIGHTMAGENTA
-ORANGE=Color(1.0,0.5,0.0)
-DARKORANGE=Color(0.5,0.25,0.0)
-LIGHTORANGE=Color(1.0,0.75,0.5)
-
+ORANGE=RGB(1.0,0.5,0.0)
+DARKORANGE=RGB(0.5,0.25,0.0)
+LIGHTORANGE=RGB(1.0,0.75,0.5)
 
 def Stereo(mode,flip=None,alg=None):
   """
diff --git a/modules/gfx/pymod/export_color.cc b/modules/gfx/pymod/export_color.cc
index 022f7669f..006331174 100644
--- a/modules/gfx/pymod/export_color.cc
+++ b/modules/gfx/pymod/export_color.cc
@@ -25,38 +25,6 @@ using namespace ost;
 using namespace ost::gfx;
 
 namespace {
-  float get_red(const Color& c) {
-    return c[0];
-  }
-
-  void set_red(Color& c, float v) {
-    c[0]=v;
-  }
-  
-  float get_green(const Color& c) {
-    return c[1];
-  }
-  
-  void set_green(Color& c, float v) {
-    c[1]=v;
-  }
-  
-  float get_blue(const Color& c) {
-    return c[2];
-  }
-  
-  void set_blue(Color& c, float v) {
-    c[2]=v;
-  }
-  
-  float get_alpha(const Color& c) {
-    return c[3];
-  }
-
-  void set_alpha(Color& c, float v) {
-    c[3]=v;
-  }
-
   float get(const Color& c, int i) {
     if(i<0 || i>3) {
       throw Error("Color: index out of bounds");
@@ -73,36 +41,124 @@ namespace {
 
   std::string repr(const Color& c) {
     std::ostringstream m;
-    m << "gfx.Color(" << c[0] << "," << c[1] << "," << c[2] << "," << c[3] << ")";
+    m << "gfx.RGBA(" << c.GetRed() << "," << c.GetGreen() << "," << c.GetBlue() << "," << c.GetAlpha() << ")";
+    m << " gfx.HSV(" << c.GetHue() << "," << c.GetSat() << "," << c.GetVal() << ")";
     return m.str();
   }
 
+  Color rgb1(float r, float g, float b) {return RGB(r,g,b);}
+  Color rgb2(uchar r, uchar g, uchar b) {return RGB(r,g,b);}
+  Color rgb3(uint rgb) {return RGB(static_cast<uchar>((rgb>>16)&0xff),
+                                   static_cast<uchar>((rgb>>8)&0xff),
+                                   static_cast<uchar>((rgb)&0xff));}
+  Color rgba1(float r, float g, float b, float a) {return RGBA(r,g,b,a);}
+  Color rgba2(uchar r, uchar g, uchar b, uchar a) {return RGBA(r,g,b,a);}
+  Color rgba3(uint rgba) {return RGBA(static_cast<uchar>((rgba>>24)&0xff),
+                                      static_cast<uchar>((rgba>>16)&0xff),
+                                      static_cast<uchar>((rgba>>8)&0xff),
+                                      static_cast<uchar>((rgba)&0xff));}
+
+  tuple get_rgb(const Color& c) {return make_tuple(c.GetRed(),c.GetGreen(),c.GetBlue());}
+  void set_rgb(Color& c, object rgb) {
+    extract<geom::Vec3&> vec3(rgb);
+    if(vec3.check()) {
+      c.SetRGB(vec3()[0],vec3()[1],vec3()[2]);
+    } else {
+      // assume sequence
+      c.SetRGB(extract<float>(rgb[0]),
+               extract<float>(rgb[1]),
+               extract<float>(rgb[2]));
+
+    }
+  }
+
+  tuple get_rgba(const Color& c) {return make_tuple(c.GetRed(),c.GetGreen(),c.GetBlue(),c.GetAlpha());}
+  void set_rgba(Color& c, object rgba) {
+    extract<geom::Vec4&> vec4(rgba);
+    if(vec4.check()) {
+      c.SetRGB(vec4()[0],vec4()[1],vec4()[2]);
+      c.SetAlpha(vec4()[3]);
+    } else {
+      // assume sequence
+      c.SetRGB(extract<float>(rgba[0]),
+               extract<float>(rgba[1]),
+               extract<float>(rgba[2]));
+      c.SetAlpha(extract<float>(rgba[3]));
+    }
+  }
+
+  tuple get_hsv(const Color& c) {return make_tuple(c.GetHue(),c.GetSat(),c.GetVal());}
+  void set_hsv(Color& c, object hsv) {
+    extract<geom::Vec3&> vec3(hsv);
+    if(vec3.check()) {
+      c.SetHSV(vec3()[0],vec3()[1],vec3()[2]);
+    } else {
+      // assume sequence
+      c.SetHSV(extract<float>(hsv[0]),
+               extract<float>(hsv[1]),
+               extract<float>(hsv[2]));
+    }
+  }
+
+  tuple get_hsva(const Color& c) {return make_tuple(c.GetHue(),c.GetSat(),c.GetVal(),c.GetAlpha());}
+  void set_hsva(Color& c, object hsva) {
+    extract<geom::Vec4&> vec4(hsva);
+    if(vec4.check()) {
+      c.SetHSV(vec4()[0],vec4()[1],vec4()[2]);
+      c.SetAlpha(vec4()[3]);
+    } else {
+      // assume sequence
+      c.SetHSV(extract<float>(hsva[0]),
+               extract<float>(hsva[1]),
+               extract<float>(hsva[2]));
+      c.SetAlpha(extract<float>(hsva[3]));
+    }
+  }
+
 }
 
 void export_color()
 {
   class_<Color>("Color",init<>())
-    .def(init<float, float, float, optional<float> >())
     .def(self_ns::str(self))
     .def("__repr__",repr)
-    .def("Red",get_red)
-    .def("Green",get_green)
-    .def("Blue",get_blue)
-    .def("Alpha",get_alpha)
+    .add_property("r",&Color::GetRed,&Color::SetRed)
+    .add_property("g",&Color::GetGreen,&Color::SetGreen)
+    .add_property("b",&Color::GetBlue,&Color::SetBlue)
+    .add_property("a",&Color::GetAlpha,&Color::SetAlpha)
+    .add_property("h",&Color::GetHue,&Color::SetHue)
+    .add_property("s",&Color::GetSat,&Color::SetSat)
+    .add_property("v",&Color::GetVal,&Color::SetVal)
+    .add_property("red",&Color::GetRed,&Color::SetRed)
+    .add_property("green",&Color::GetGreen,&Color::SetGreen)
+    .add_property("blue",&Color::GetBlue,&Color::SetBlue)
+    .add_property("alpha",&Color::GetAlpha,&Color::SetAlpha)
+    .add_property("hue",&Color::GetHue,&Color::SetHue)
+    .add_property("sat",&Color::GetSat,&Color::SetSat)
+    .add_property("val",&Color::GetVal,&Color::SetVal)
+    .add_property("rgb",get_rgb,set_rgb)
+    .add_property("rgba",get_rgba,set_rgba)
+    .add_property("hsv",get_hsv,set_hsv)
+    .add_property("hsva",get_hsva,set_hsva)
+    .def("__getitem__",get)
+    .def("__setitem__",set)
+    // DEPRECATED
+    .def(init<float, float, float, optional<float> >())
+    .def("Red",&Color::GetRed)
+    .def("Green",&Color::GetGreen)
+    .def("Blue",&Color::GetBlue)
+    .def("Alpha",&Color::GetAlpha)
     .def("ToHSV",&Color::ToHSV)
     .def("FromRGBA",&Color::FromRGB)
-    .add_property("r",get_red,set_red)
-    .add_property("g",get_green,set_green)
-    .add_property("b",get_blue,set_blue)
-    .add_property("a",get_alpha,set_alpha)
-    .add_property("red",get_red,set_red)
-    .add_property("green",get_green,set_green)
-    .add_property("blue",get_blue,set_blue)
-    .add_property("alpha",get_alpha,set_alpha)
-    .def("__getitem__",get)
-    .def("__setitem__",get)
     ;
 
+  def("RGB",rgb3);
+  def("RGB",rgb2);
+  def("RGB",rgb1);
+  def("RGBA",rgba3);
+  def("RGBA",rgba2);
+  def("RGBA",rgba1);
   def("HSV",HSV);
+  def("HSVA",HSVA);
   
 }
diff --git a/modules/gfx/src/color.cc b/modules/gfx/src/color.cc
index f40e69c53..124d7aabc 100644
--- a/modules/gfx/src/color.cc
+++ b/modules/gfx/src/color.cc
@@ -26,179 +26,385 @@
 
 namespace ost { namespace gfx {
 
-namespace {
-// maps hsv to rgb (0-1)
-geom::Vec3 HSVtoRGB(const geom::Vec3& hsv)
-{
-  geom::Vec3 rgb;
-  if (hsv[1]<1e-9){
-    rgb[0]=hsv[2];
-    rgb[1]=hsv[2];
-    rgb[2]=hsv[2];
+Color::Color()
+{
+  rgba_[0]=1.0;
+  rgba_[1]=1.0;
+  rgba_[2]=1.0;
+  rgba_[3]=1.0;
+  rgb_dirty_=false;
+  hsv_dirty_=true;
+}
+
+Color::Color(float r, float g, float b, float a) 
+{
+  rgba_[0]=r;
+  rgba_[1]=g;
+  rgba_[2]=b;
+  rgba_[3]=a;
+  rgb_dirty_=false;
+  hsv_dirty_=true;
+}
+  
+void Color::SetRGB(float r, float g, float b)
+{
+  rgba_[0]=r;
+  rgba_[1]=g;
+  rgba_[2]=b;
+  rgb_dirty_=false;
+  hsv_dirty_=true;
+}
+
+geom::Vec3 Color::GetRGB() const
+{
+  if(rgb_dirty_) to_rgb();
+  return geom::Vec3(rgba_);
+}
+
+geom::Vec4 Color::GetRGBA() const
+{
+  if(rgb_dirty_) to_rgb();
+  return geom::Vec3(rgba_);
+}
+
+float Color::GetRed() const
+{
+  if(rgb_dirty_) to_rgb();
+  return rgba_[0];
+}
+
+void Color::SetRed(float x)
+{
+  if(rgb_dirty_) to_rgb();
+  rgba_[0]=x;
+  hsv_dirty_=true;
+}
+
+float Color::GetGreen() const
+{
+  if(rgb_dirty_) to_rgb();
+  return rgba_[1];
+}
+
+void Color::SetGreen(float x)
+{
+  if(rgb_dirty_) to_rgb();
+  rgba_[1]=x;
+  hsv_dirty_=true;
+}
+
+float Color::GetBlue() const
+{
+  if(rgb_dirty_) to_rgb();
+  return rgba_[2];
+}
+
+void Color::SetBlue(float x)
+{
+  if(rgb_dirty_) to_rgb();
+  rgba_[2]=x;
+  hsv_dirty_=true;
+}
+
+void Color::SetHSV(float h, float s, float v)
+{
+  hsv_[0]=h;
+  hsv_[1]=s;
+  hsv_[2]=v;
+  hsv_dirty_=false;
+  rgb_dirty_=true;
+}
+
+geom::Vec3 Color::GetHSV() const
+{
+  if(hsv_dirty_) to_hsv();
+  return geom::Vec3(hsv_);
+}
+
+geom::Vec4 Color::GetHSVA() const
+{
+  if(hsv_dirty_) to_hsv();
+  return geom::Vec4(hsv_[0],hsv_[1],hsv_[2],rgba_[3]);
+}
+
+float Color::GetHue() const
+{
+  if(hsv_dirty_) to_hsv();
+  return hsv_[0];
+}
+
+void Color::SetHue(float x)
+{
+  if(hsv_dirty_) to_hsv();
+  hsv_[0]=x;
+  rgb_dirty_=true;
+}
+
+float Color::GetSat() const
+{
+  if(hsv_dirty_) to_hsv();
+  return hsv_[1];
+}
+
+void Color::SetSat(float x)
+{
+  if(hsv_dirty_) to_hsv();
+  hsv_[1]=x;
+  rgb_dirty_=true;
+}
+
+float Color::GetVal() const
+{
+  if(hsv_dirty_) to_hsv();
+  return hsv_[2];
+}
+
+void Color::SetVal(float x)
+{
+  if(hsv_dirty_) to_hsv();
+  hsv_[2]=x;
+  rgb_dirty_=true;
+}
+
+float Color::GetAlpha() const
+{
+  return rgba_[3];
+}
+
+void Color::SetAlpha(float x)
+{
+  rgba_[3]=x;
+}
+
+////////////////////////////////////
+// operators
+
+Color::operator const float* () const 
+{
+  if(rgb_dirty_) to_rgb();
+  return rgba_;
+}
+
+Color::operator float* () 
+{
+  if(rgb_dirty_) to_rgb();
+  hsv_dirty_=true; 
+  return rgba_;
+}
+
+Color& Color::operator*=(float rhs)
+{
+  if(rgb_dirty_) to_rgb();
+  rgba_[0]*=rhs;
+  rgba_[1]*=rhs;
+  rgba_[2]*=rhs;
+  rgba_[3]*=rhs; 
+  hsv_dirty_=true; 
+  return *this;
+}
+
+Color& Color::operator+=(float rhs)
+{
+  if(rgb_dirty_) to_rgb();
+  rgba_[0]+=rhs;
+  rgba_[1]+=rhs;
+  rgba_[2]+=rhs;
+  rgba_[3]+=rhs;
+  hsv_dirty_=true; 
+  return *this;
+}
+  
+Color& Color::operator+=(const Color& rhs)
+{
+  if(rgb_dirty_) to_rgb();
+  rgba_[0]+=rhs[0];
+  rgba_[1]+=rhs[1];
+  rgba_[2]+=rhs[2];
+  rgba_[3]+=rhs[3];  
+  hsv_dirty_=true; 
+  return *this;  
+}
+
+Color& Color::operator-=(const Color& rhs)
+{
+  if(rgb_dirty_) to_rgb();
+  rgba_[0]-=rhs[0];
+  rgba_[1]-=rhs[1];
+  rgba_[2]-=rhs[2];
+  rgba_[3]-=rhs[3];  
+  hsv_dirty_=true; 
+  return *this;
+}
+
+Color& Color::operator-=(float rhs)
+{
+  if(rgb_dirty_) to_rgb();
+  rgba_[0]-=rhs;
+  rgba_[1]-=rhs;
+  rgba_[2]-=rhs;
+  rgba_[3]-=rhs;
+  hsv_dirty_=true; 
+  return *this;
+}
+
+Color& Color::operator/=(float rhs)
+{
+  if(rgb_dirty_) to_rgb();
+  rgba_[0]/=rhs;
+  rgba_[1]/=rhs;
+  rgba_[2]/=rhs;
+  rgba_[3]/=rhs;  
+  hsv_dirty_=true; 
+  return *this;
+}
+
+//////////////////////////////////////
+// private methods
+
+void Color::to_rgb() const
+{
+  float hh=fmod(hsv_[0],1.0);
+  if(hh<0.0) hh+=1.0;
+  float ss=std::min(1.0f,std::max(0.0f,hsv_[1]));
+  float vv=std::min(1.0f,std::max(0.0f,hsv_[2]));
+  if (ss<1e-9){
+    rgba_[0]=vv;
+    rgba_[1]=vv;
+    rgba_[2]=vv;
   } else {
-    double var_h=hsv[0]*6.0<6.0?hsv[0]*6.0:0.0;
+    float var_h=hh*6.0;
     int var_i =static_cast<int>(var_h);
-    double var_1 = hsv[2]*(1-hsv[1]);
-    double var_2 = hsv[2]*(1-hsv[1]*( var_h -var_i));
-    double var_3 = hsv[2]*(1-hsv[1]*(1-(var_h-var_i)));
+    float var_1 = vv*(1-ss);
+    float var_2 = vv*(1-ss*( var_h - static_cast<float>(var_i)));
+    float var_3 = vv*(1-ss*(1-(var_h-static_cast<float>(var_i))));
     switch(var_i){
     case 0:
-      rgb[0]=hsv[2];
-      rgb[1]=var_3;
-      rgb[2]=var_1;
+      rgba_[0]=vv;
+      rgba_[1]=var_3;
+      rgba_[2]=var_1;
       break;
     case 1:
-      rgb[0] = var_2;
-      rgb[1] = hsv[2];
-      rgb[2] = var_1;
+      rgba_[0] = var_2;
+      rgba_[1] = vv;
+      rgba_[2] = var_1;
       break;
     case 2:
-      rgb[0] = var_1;
-      rgb[1] = hsv[2];
-      rgb[2] = var_3;
+      rgba_[0] = var_1;
+      rgba_[1] = vv;
+      rgba_[2] = var_3;
       break;
     case 3:
-      rgb[0] = var_1 ; 
-      rgb[1] = var_2 ; 
-      rgb[2] = hsv[2];
+      rgba_[0] = var_1 ; 
+      rgba_[1] = var_2 ; 
+      rgba_[2] = vv;
       break;
     case 4:
-      rgb[0] = var_3 ;
-      rgb[1] = var_1 ;
-      rgb[2] = hsv[2];
+      rgba_[0] = var_3 ;
+      rgba_[1] = var_1 ;
+      rgba_[2] = vv;
       break;
     case 5:
-      rgb[0] = hsv[2];
-      rgb[1] = var_1 ;
-      rgb[2] = var_2;
+      rgba_[0] = vv;
+      rgba_[1] = var_1 ;
+      rgba_[2] = var_2;
       break;
     }
   }
-  return rgb;
+  rgb_dirty_=false;
 }
 
-// maps rgb (0-1) to hsv
-geom::Vec3 RGBtoHSV(const geom::Vec3& rgb)
+void Color::to_hsv() const
 {
-  geom::Vec3 hsv;
-  double var_R = ( rgb[0] );
-  double var_G = ( rgb[1] );
-  double var_B = ( rgb[2] );
+  float rr = std::min(1.0f,std::max(0.0f,rgba_[0]));
+  float gg = std::min(1.0f,std::max(0.0f,rgba_[1]));
+  float bb = std::min(1.0f,std::max(0.0f,rgba_[2]));
 
-  double var_Min = std::min(std::min( var_R, var_G), var_B );
-  double var_Max = std::max(std::max( var_R, var_G), var_B );
-  double del_Max = var_Max - var_Min;
+  float vmin = std::min(std::min(rr, gg), bb);
+  float vmax = std::max(std::max(rr, gg), bb);
+  float vdelta = vmax - vmin;
 
-  hsv[2] = var_Max;
+  hsv_[2] = vmax;
 
-  if ( del_Max < 1.0e-9 ){
-    hsv[0] = 0.0;
-    hsv[1] = 0.0;
+  if (vdelta < 1.0e-9) {
+    hsv_[0] = 0.0;
+    hsv_[1] = 0.0;
   } else {
-    hsv[1] = del_Max / var_Max;
-    double del_R = ( ( ( var_Max - var_R ) / 6.0 ) + ( del_Max / 2.0 ) ) / del_Max;
-    double del_G = ( ( ( var_Max - var_G ) / 6.0 ) + ( del_Max / 2.0 ) ) / del_Max;
-    double del_B = ( ( ( var_Max - var_B ) / 6.0 ) + ( del_Max / 2.0 ) ) / del_Max;
+    hsv_[1] = vdelta / vmax;
+    float dr = (((vmax - rr) / 6.0) + (vdelta / 2.0)) / vdelta;
+    float dg = (((vmax - gg) / 6.0) + (vdelta / 2.0)) / vdelta;
+    float db = (((vmax - bb) / 6.0) + (vdelta / 2.0)) / vdelta;
     
-    if ( var_R == var_Max ){
-      hsv[0] = del_B - del_G;
-    } else if ( var_G == var_Max ){
-      hsv[0] = ( 1.0 / 3.0 ) + del_R - del_B;
-    } else if ( var_B == var_Max ){
-      hsv[0] = ( 2.0 / 3.0 ) + del_G - del_R;
+    if (rr == vmax) {
+      hsv_[0] = db - dg;
+    } else if (gg == vmax) {
+      hsv_[0] = (1.0 / 3.0) + dr - db;
+    } else if (bb == vmax) {
+      hsv_[0] = (2.0 / 3.0) + dg - dr;
     }
-    if ( hsv[0] < 0.0 ){
-      hsv[0] += 1.0;
+    if (hsv_[0] < 0.0) {
+      hsv_[0] += 1.0;
     }
-    if ( hsv[0] > 1.0 ){
-      hsv[0] -= 1.0;
+    if (hsv_[0] > 1.0) {
+      hsv_[0] -= 1.0;
     }
   }
-
-  hsv[0]=hsv[0]*360.0;
-  hsv[1]=hsv[1]*100.0;
-  hsv[2]=hsv[2]*100.0;
-
-  return hsv;
+  hsv_dirty_=false;
 }
-} // anon ns
 
-geom::Vec3 Color::ToHSV()
-{
-  return RGBtoHSV(geom::Vec3(rgba[0],rgba[1],rgba[2]));
-}
+////////////////////////////////////
+// stand alone functions
 
-
-Color HSV(double h, double s, double v)
+Color RGB(float r, float g, float b)
 {
-  if(h>1.0 || s>1.0 || v>1.0) {
-    h=h/360.0;
-    s=s/100.0;
-    v=v/100.0;
-  }
-  geom::Vec3 rgb=HSVtoRGB(geom::Vec3(h,s,v));
-  return Color(rgb[0],rgb[1],rgb[2]);
+  Color nrvo;
+  nrvo.SetRGB(r,g,b);
+  return nrvo;
 }
 
-std::ostream& operator<<(std::ostream& s, const Color& c)
+Color RGB(uchar r, uchar g, uchar b)
 {
-  s << "{" << c.Red() << "," << c.Green() << "," << c.Blue() << "," << c.Alpha() << "}";
-  return s;
+  static float f=1.0/255.0;
+  Color nrvo;
+  nrvo.SetRGB(static_cast<float>(r)*f,static_cast<float>(g)*f,static_cast<float>(b)*f);
+  return nrvo;
 }
 
-
-Color& Color::operator*=(float rhs)
+Color RGBA(float r, float g, float b, float a)
 {
-  rgba[0]*=rhs;
-  rgba[1]*=rhs;
-  rgba[2]*=rhs;
-  rgba[3]*=rhs; 
-  return *this;
+  Color nrvo;
+  nrvo.SetRGB(r,g,b);
+  nrvo.SetAlpha(a);
+  return nrvo;
 }
 
-Color& Color::operator+=(float rhs)
-{
-  rgba[0]+=rhs;
-  rgba[1]+=rhs;
-  rgba[2]+=rhs;
-  rgba[3]+=rhs;
-  return *this;
-}
-  
-Color& Color::operator+=(const Color& rhs)
+Color RGBA(uchar r, uchar g, uchar b, uchar a)
 {
-  rgba[0]+=rhs[0];
-  rgba[1]+=rhs[1];
-  rgba[2]+=rhs[2];
-  rgba[3]+=rhs[3];  
-  return *this;  
+  static float f=1.0/255.0;
+  Color nrvo;
+  nrvo.SetRGB(static_cast<float>(r)*f,static_cast<float>(g)*f,static_cast<float>(b)*f);
+  nrvo.SetAlpha(static_cast<float>(a)*f);
+  return nrvo;
 }
 
-Color& Color::operator-=(const Color& rhs)
+Color HSV(float h, float s, float v)
 {
-  rgba[0]-=rhs[0];
-  rgba[1]-=rhs[1];
-  rgba[2]-=rhs[2];
-  rgba[3]-=rhs[3];  
-  return *this;
+  Color nrvo;
+  nrvo.SetHSV(h,s,v);
+  return nrvo;
 }
 
-Color& Color::operator-=(float rhs)
+  Color HSVA(float h, float s, float v, float a)
 {
-  rgba[0]-=rhs;
-  rgba[1]-=rhs;
-  rgba[2]-=rhs;
-  rgba[3]-=rhs;
-  return *this;
+  Color nrvo;
+  nrvo.SetHSV(h,s,v);
+  nrvo.SetAlpha(a);
+  return nrvo;
 }
 
-Color& Color::operator/=(float rhs)
+std::ostream& operator<<(std::ostream& s, const Color& c)
 {
-  rgba[0]/=rhs;
-  rgba[1]/=rhs;
-  rgba[2]/=rhs;
-  rgba[3]/=rhs;  
-  return *this;
+  s << "Color(r=" << c.GetRed() << ",g=" <<c.GetGreen() << ",b=" << c.GetBlue() << ",h=" << c.GetHue() << ",s=" << c.GetSat() << ",v=" << c.GetVal() << ",a=" << c.GetAlpha() << ")";
+  return s;
 }
 
 }} // ns
diff --git a/modules/gfx/src/color.hh b/modules/gfx/src/color.hh
index 533381dbc..e97b085dc 100644
--- a/modules/gfx/src/color.hh
+++ b/modules/gfx/src/color.hh
@@ -32,35 +32,114 @@
 
 namespace ost { namespace gfx {
 
-/// \brief color with red, green, blue and alpha component
+/*!
+  A color is defined in both RGB as well as HSV space, with an 
+  additional A component. To initialize a color use the factory
+  functions RGB(r,g,b), RGBA(r,g,b,a), HSV(h,s,v) and HSVA(h,s,v,a)
+
+  All values are defined in the range of 0.0 to 1.0, but are not clamped
+  on reading or writing, except when updating cross-domain, i.e. changes 
+  to R,G or B cause HSV to be updated accordingly, and vice versa.
+  This may cause previously out-of-bounds values to become clamped or wrapped
+  to the defined domain. An out-of-bounds H will be wrapped to be between 0 and 1, 
+  all others will be clamped.
+
+  For interaction with OpenGL, an operator float* method exists that
+  return a pointer to a float[4], in the order RGBA
+
+  Operator overloading works on the RGB components, i.e. C1+C2, C+s, C*s
+*/
 class DLLEXPORT_OST_GFX Color :   
   private boost::additive<Color>,
   private boost::additive<Color, float>,
   private boost::multiplicative<Color, float>{
+
 public:
-  Color() {
-    rgba[0]=1.0;
-    rgba[1]=1.0;
-    rgba[2]=1.0;
-    rgba[3]=1.0;
-  }
+  //! \brief initialize to white
+  Color();
+
+  //! convenience to set RGB triplet (ranges 0-1)
+  void SetRGB(float r, float g, float b);
+  //! convenience to get RGB triplet as Vec3
+  geom::Vec3 GetRGB() const;
+  //! convenience to get RGBA as Vec4
+  geom::Vec4 GetRGBA() const;
+  //! retrieve red
+  float GetRed() const;
+  //! set red
+  void SetRed(float);
+  //! retrieve green
+  float GetGreen() const;
+  //! set green
+  void SetGreen(float);
+  //! retrieve blue
+  float GetBlue() const;
+  //! set blue
+  void SetBlue(float);
+  //! convenience to set HSV triplet (ranges 0-1)
+  void SetHSV(float h, float s, float v);
+  //! convenience to get HSV triplet as Vec3
+  geom::Vec3 GetHSV() const;
+  //! convenience to get HSVA as Vec4
+  geom::Vec4 GetHSVA() const;
+  //! retrieve hue
+  float GetHue() const;
+  //! set hue
+  void SetHue(float);
+  //! retrieve saturatuin
+  float GetSat() const;
+  //! set saturation
+  void SetSat(float);
+  //! retrieve value
+  float GetVal() const;
+  //! set value
+  void SetVal(float);
+  //! retrieve alpha
+  float GetAlpha() const;
+  //! set alpha
+  void SetAlpha(float);
+
   
-  Color(float r, float g, float b, float a=1.0) {
-    rgba[0]=r;
-    rgba[1]=g;
-    rgba[2]=b;
-    rgba[3]=a;
-  }
+  /*!
+    \brief direct access to RGBA components
+
+    In the context of a OpenGL call that requires a pointer
+    to an RGB or RGBA float triplet or quadruplet, this will
+    do the automatic casting from a Color object, i.e. 
+    glColor3fv(my_color);
+
+    This will also allow access to the RGBA values
+    via the array notation, i.e. my_color[0]
+  */
+  operator const float* () const;
+  operator float* ();
+  
+  Color& operator*=(float rhs);
+  Color& operator+=(float rhs);
+    
+  Color& operator+=(const Color& rhs);  
+  Color& operator-=(const Color& rhs);    
+  Color& operator-=(float rhs);
+  Color& operator/=(float rhs);  
 
-  float& Red() {return rgba[0];}
-  const float& Red() const {return rgba[0];}
-  float& Green() {return rgba[1];}
-  const float& Green() const {return rgba[1];}
-  float& Blue() {return rgba[2];}
-  const float& Blue() const {return rgba[2];}
-  float& Alpha() {return rgba[3];}
-  const float& Alpha() const {return rgba[3];}
+  ////////////////////////////////////////////////////////////////
 
+  //! DEPRECATED
+  geom::Vec3 ToHSV() const {return GetHSV();}
+
+  //! DEPRECATED
+  Color(float r, float g, float b, float a=1.0);
+
+  //! DEPRECATED
+  float Red() const {return GetRed();}
+  //! DEPRECATED
+  float Green() const {return GetGreen();}
+  //! DEPRECATED
+  float Blue() const {return GetBlue();}
+  //! DEPRECATED
+  float Alpha() const {return GetAlpha();}
+
+  //! DEPRECATED
   static Color FromRGB(unsigned char r, unsigned char g, 
                        unsigned char b, unsigned char a = 0xff) {
     static float f=1.0/255.0;
@@ -68,32 +147,53 @@ public:
                  f*static_cast<float>(b),f*static_cast<float>(a));
   }
 
-  geom::Vec3 ToHSV();
+  ////////////////////////////////////////////////////////////////
 
-  // these also take care of operator[](uint i) !
-  operator float* () {return rgba;}
-  operator const float* () const {return rgba;}
-  
-  Color& operator*=(float rhs);
-  Color& operator+=(float rhs);
-    
-  Color& operator+=(const Color& rhs);  
-  Color& operator-=(const Color& rhs);    
-  Color& operator-=(float rhs);
-  Color& operator/=(float rhs);  
 private:
-  float rgba[4];
+  void to_hsv() const;
+  void to_rgb() const;
+
+  mutable float rgba_[4];
+  mutable float hsv_[3];
+  mutable bool rgb_dirty_;
+  mutable bool hsv_dirty_;
 };
 
+/*
+  \brief RGB color spec from floats (0.0-1.0)
+*/
+Color DLLEXPORT_OST_GFX RGB(float r, float g, float b);
+
+/*
+  \brief RGB color spec from bytes (0-255)
+*/
+Color DLLEXPORT_OST_GFX RGB(uchar r, uchar g, uchar b);
+
+/*
+  \brief RGBA color spec from floats (0.0-1.0)
+*/
+Color DLLEXPORT_OST_GFX RGBA(float r, float g, float b, float a);
+
+/*
+  \brief RGBA color spec from bytes (0-255)
+*/
+Color DLLEXPORT_OST_GFX RGBA(uchar r, uchar g, uchar b, uchar a);
+
 /*!
   \brief HSV color spec
 
-  h: Hue from 0 to 360 (0=red, 120=green, 240=blue)
-  s: Saturation from 0 (no color) to 100 (full color)
-  v: Value from 0 (no light, black) to 100 (full light)
+  h: Hue from 0 to 1 (0=red, 2/6=green, 4/6=blue)
+  s: Saturation from 0 (no color) to 1 (full color)
+  v: Value from 0 (no light, black) to 1 (full light)
+*/
+Color DLLEXPORT_OST_GFX HSV(float h, float s, float v);
+
+/*!
+  \brief HSVA color spec
 */
-Color DLLEXPORT_OST_GFX HSV(double h, double s, double v);
+Color DLLEXPORT_OST_GFX HSVA(float h, float s, float v, float a);
 
+//! \brief string form
 DLLEXPORT_OST_GFX std::ostream& operator<<(std::ostream&, const Color& c);
 
 }}
diff --git a/modules/gfx/src/impl/cartoon_renderer.cc b/modules/gfx/src/impl/cartoon_renderer.cc
index 9af2bdf80..f9eee5106 100644
--- a/modules/gfx/src/impl/cartoon_renderer.cc
+++ b/modules/gfx/src/impl/cartoon_renderer.cc
@@ -362,8 +362,9 @@ void CartoonRenderer::rebuild_spline_obj(IndexedVertexArray& va,
     if(slist.size()==2 && slist[0].type==6) {
       // make a cylinder
       va.AddCylinder(CylinderPrim(slist[0].position,slist[1].position,
-				  options_->GetHelixWidth(),
-				  slist[0].color1,slist[1].color1),
+                                  options_->GetHelixWidth(),
+                                  slist[0].color1,
+                                  slist[1].color1),
 		     options_->GetArcDetail(),true);
       continue;
     }
@@ -458,7 +459,7 @@ TraceProfile CartoonRenderer::transform_and_add_profile(const std::vector<TraceP
       norm=Normalize(norm);
     }
     tf_prof[c]=TraceProfileEntry(vec,norm);
-    Color col = se.color1;
+    RGBAColor col = se.color1;
     if(se.type==1) {
       if(se.nflip) {
         col = c<half ? se.color1 : se.color2;
@@ -523,7 +524,7 @@ void CartoonRenderer::cap_profile(const impl::TraceProfile& p,
                                   bool flipn, IndexedVertexArray& va)
 {
   geom::Vec3 norm=flipn ? -se.direction : se.direction;
-  VertexID pi0 = va.Add(se.position,norm, se.color1,geom::Vec2(0.5,0.5));
+  VertexID pi0 = va.Add(se.position,norm, se.color1, geom::Vec2(0.5,0.5));
   std::vector<VertexID> vertices(p.size());
   float fac=2.0*M_PI/static_cast<float>(p.size()-1);
   for(unsigned int i=0;i<p.size();++i) {
diff --git a/modules/gfx/src/impl/cpk_renderer.cc b/modules/gfx/src/impl/cpk_renderer.cc
index b0aff5ec6..f780647b7 100644
--- a/modules/gfx/src/impl/cpk_renderer.cc
+++ b/modules/gfx/src/impl/cpk_renderer.cc
@@ -56,7 +56,7 @@ void CPKRenderer::PrepareRendering()
 
 void CPKRenderer::PrepareRendering(GfxView& view, IndexedVertexArray& va, bool is_sel)
 {
-  const Color& sel_clr=this->GetSelectionColor();
+  RGBAColor sel_clr=this->GetSelectionColor();
   float factor=is_sel ? 1.2 : 1.0;
   if(options_!=NULL){
     factor *= options_->GetRadiusMult();
diff --git a/modules/gfx/src/impl/entity_detail.cc b/modules/gfx/src/impl/entity_detail.cc
index 3069077f4..02e40b213 100644
--- a/modules/gfx/src/impl/entity_detail.cc
+++ b/modules/gfx/src/impl/entity_detail.cc
@@ -39,7 +39,7 @@ struct BlurQuadEntry
 {
   float zdist;
   geom::Vec3 p1,p2,p3,p4;
-  gfx::Color c1,c2,c3,c4;
+  RGBAColor c1,c2,c3,c4;
 };
 
 struct BlurQuadEntryLess
diff --git a/modules/gfx/src/impl/entity_detail.hh b/modules/gfx/src/impl/entity_detail.hh
index 5905d6b87..12412f680 100644
--- a/modules/gfx/src/impl/entity_detail.hh
+++ b/modules/gfx/src/impl/entity_detail.hh
@@ -39,13 +39,38 @@
 
 namespace ost { namespace gfx { namespace impl {
 
+struct RGBAColor {
+  RGBAColor() {
+    rgba[0]=1.0; rgba[1]=1.0; rgba[2]=1.0; rgba[3]=1.0;
+  }
+  RGBAColor(float r, float g, float b, float a=1.0) {
+    rgba[0]=r; rgba[1]=g; rgba[2]=b; rgba[3]=a;
+  }
+  RGBAColor(const Color& c) {
+    rgba[0]=c.GetRed(); rgba[1]=c.GetGreen(); rgba[2]=c.GetBlue(); rgba[3]=c.GetAlpha();
+  }
+
+  RGBAColor& operator=(const Color& c) {
+    rgba[0]=c.GetRed(); rgba[1]=c.GetGreen(); rgba[2]=c.GetBlue(); rgba[3]=c.GetAlpha();
+    return *this;
+  }
+
+  operator Color () const {return RGBA(rgba[0],rgba[1],rgba[2],rgba[3]);}
+
+  operator float* () {return rgba;}
+  operator const float* () const {return rgba;}
+  float rgba[4];
+};
+
 struct DLLEXPORT_OST_GFX AtomEntry 
 {
   AtomEntry() {}
   AtomEntry(const mol::AtomHandle& a, float r, float v, const Color& c):
-    atom(a),color(c),radius(r), vdwr(v) {}
+    atom(a), color(c), radius(r), vdwr(v) 
+  {
+  }
   mol::AtomHandle atom;
-  Color color;
+  RGBAColor color;
   float radius;
   float vdwr;
 };
@@ -88,7 +113,7 @@ typedef boost::shared_ptr<GfxView> GfxViewPtr;
 
 struct DLLEXPORT_OST_GFX NodeEntry {
   mol::AtomHandle atom;
-  Color color1, color2;
+  RGBAColor color1, color2;
   geom::Vec3 direction,normal;
   float rad;
   geom::Vec3 v0,v1,v2; // helper vectors
@@ -131,6 +156,16 @@ struct DLLEXPORT_OST_GFX SplineEntry {
     id(-1)
   {
   }
+  SplineEntry(const geom::Vec3& p, 
+              const geom::Vec3& d,
+              const geom::Vec3& n,
+              float r,
+              const RGBAColor& c1, const RGBAColor& c2,
+              unsigned int t, int i):
+    position(p),direction(d),normal(n),color1(c1),color2(c2),rad(r),type(t),
+    type1(t),type2(t),frac(0.0),running_length(0.0),v0(),v1(),v2(),nflip(false),id(i)
+  {
+  }
   SplineEntry(const geom::Vec3& p, 
               const geom::Vec3& d,
               const geom::Vec3& n,
@@ -143,7 +178,7 @@ struct DLLEXPORT_OST_GFX SplineEntry {
   }
 
   geom::Vec3 position,direction,normal;
-  Color color1, color2;
+  RGBAColor color1, color2;
   float rad;
   unsigned int type;
   unsigned int type1, type2;
diff --git a/modules/gfx/src/texture.cc b/modules/gfx/src/texture.cc
index 56ea75553..d708daa1a 100644
--- a/modules/gfx/src/texture.cc
+++ b/modules/gfx/src/texture.cc
@@ -1,4 +1,5 @@
 #include "texture.hh"
+#include "bitmap_io.hh"
 
 namespace ost { namespace gfx {
 
@@ -7,12 +8,12 @@ namespace ost { namespace gfx {
     d_()
   {
     if(!bm.data) return;
-    d_=boost::shared_array<Color>(new Color[w_*h_]);
+    d_=boost::shared_array<float>(new float[4*w_*h_]);
     static float f=1.0/255.0;
     for(GLint v=0;v<h_;++v) {
       for(GLint u=0;u<w_;++u) {
         int p=v*w_+u;
-        Color& c = d_[p];
+        float* c = &d_[p*4];
         if(bm.channels==1) {
           c[0]=f*static_cast<float>(bm.data[p]);
           c[1]=c[0];
diff --git a/modules/gfx/src/texture.hh b/modules/gfx/src/texture.hh
index b3a53dd27..dd71404fe 100644
--- a/modules/gfx/src/texture.hh
+++ b/modules/gfx/src/texture.hh
@@ -29,11 +29,11 @@
 
 #include "module_config.hh"
 #include "glext_include.hh"
-#include "color.hh"
-#include "bitmap_io.hh"
 
 namespace ost { namespace gfx {
 
+class Bitmap;
+
 class Texture
 {
 public:
@@ -46,17 +46,14 @@ public:
   Texture(GLint w, GLint h):
     w_(w),
     h_(h),
-    d_(new Color[w*h])
+    d_(new float[4*w*h])
   {}
 
   Texture(const Bitmap& b);
 
   bool IsValid() const {return d_;}
 
-  Color& operator()(uint u, uint v) {return d_[v*w_+u];}
-  const Color& operator()(uint u, uint v) const {return d_[v*w_+u];}
-
-  float* data() {return d_[0];}
+  float* data() {return &d_[0];}
   
   GLint width() const {return w_;}
   GLint height() const {return h_;}
@@ -66,7 +63,7 @@ public:
 
 private:
   GLint w_,h_;
-  boost::shared_array<Color> d_;
+  boost::shared_array<float> d_;
 };
 
 }} // ns
diff --git a/modules/gfx/tests/CMakeLists.txt b/modules/gfx/tests/CMakeLists.txt
index b5bb03b71..273ab6809 100644
--- a/modules/gfx/tests/CMakeLists.txt
+++ b/modules/gfx/tests/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(OST_GFX_UNIT_TESTS
   tests.cc
   test_gfx_node.cc
+  test_color.cc
   test_gfx.py
 )
 if (ENABLE_IMG)
diff --git a/modules/gfx/tests/test_color.cc b/modules/gfx/tests/test_color.cc
new file mode 100644
index 000000000..0f8278023
--- /dev/null
+++ b/modules/gfx/tests/test_color.cc
@@ -0,0 +1,184 @@
+//------------------------------------------------------------------------------
+// This file is part of the OpenStructure project <www.openstructure.org>
+//
+// Copyright (C) 2008-2011 by the OpenStructure authors
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation; either version 3.0 of the License, or (at your option)
+// any later version.
+// This library is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+//------------------------------------------------------------------------------
+
+/*
+  Author: Ansgar Philippsen
+*/
+
+#include <ost/gfx/color.hh>
+
+#define BOOST_TEST_DYN_LINK
+#include <boost/test/unit_test.hpp>
+#include <boost/test/floating_point_comparison.hpp>
+
+using boost::unit_test_framework::test_suite;
+
+using namespace ost;
+using namespace ost::gfx;
+
+namespace {
+  bool compare_colors(const Color& c1, const Color& c2, float tol=1e-6) {
+    return std::fabs(c1.GetRed()-c2.GetRed()<tol) &&
+      std::fabs(c1.GetGreen()-c2.GetGreen()<tol) &&
+      std::fabs(c1.GetBlue()-c2.GetBlue()<tol) &&
+      std::fabs(c1.GetAlpha()-c2.GetAlpha()<tol) &&
+      std::fabs(c1.GetHue()-c2.GetHue()<tol) &&
+      std::fabs(c1.GetSat()-c2.GetSat()<tol) &&
+      std::fabs(c1.GetVal()-c2.GetVal()<tol);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE(gfx)
+
+BOOST_AUTO_TEST_CASE(default_color)
+{
+  Color c;
+  BOOST_CHECK_CLOSE(c.GetRed(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetAlpha(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+}
+
+BOOST_AUTO_TEST_CASE(set_rgb)
+{
+  Color c;
+  c = RGB(1.0,0.0,0.0); // red
+  BOOST_CHECK_CLOSE(c.GetAlpha(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetRed(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),0.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = RGB(1.0,1.0,0.0); // yellow
+  BOOST_CHECK_CLOSE(c.GetRed(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),1.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = RGB(0.0,1.0,0.0); // green
+  BOOST_CHECK_CLOSE(c.GetRed(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),2.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = RGB(0.0,1.0,1.0); // cyan
+  BOOST_CHECK_CLOSE(c.GetRed(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),3.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = RGB(0.0,0.0,1.0); // blue
+  BOOST_CHECK_CLOSE(c.GetRed(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),4.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = RGB(1.0,0.0,1.0); // purple
+  BOOST_CHECK_CLOSE(c.GetRed(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),5.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+}
+
+BOOST_AUTO_TEST_CASE(set_hsv)
+{
+  Color c;
+  c = HSV(0.0/6.0,1.0,1.0); // red
+  BOOST_CHECK_CLOSE(c.GetAlpha(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetRed(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),0.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = HSV(0.0/6.0,0.5,1.0); // light red
+  BOOST_CHECK_CLOSE(c.GetRed(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),0.5,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),0.5,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),0.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),0.5,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = HSV(1.0/6.0,1.0,1.0); // yellow
+  BOOST_CHECK_CLOSE(c.GetRed(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),1.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = HSV(2.0/6.0,1.0,1.0); // green
+  BOOST_CHECK_CLOSE(c.GetRed(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),2.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = HSV(2.0/6.0,0.5,1.0); // light green
+  BOOST_CHECK_CLOSE(c.GetRed(),0.5,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),0.5,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),2.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),0.5,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = HSV(3.0/6.0,1.0,1.0); // cyan
+  BOOST_CHECK_CLOSE(c.GetRed(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),3.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = HSV(4.0/6.0,1.0,1.0); // blue
+  BOOST_CHECK_CLOSE(c.GetRed(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),4.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = HSV(4.0/6.0,0.5,1.0); // light blue
+  BOOST_CHECK_CLOSE(c.GetRed(),0.5,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),0.5,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),4.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),0.5,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+  c = HSV(5.0/6.0,1.0,1.0); // purple
+  BOOST_CHECK_CLOSE(c.GetRed(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetGreen(),0.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetBlue(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetHue(),5.0/6.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetSat(),1.0,1e-6);
+  BOOST_CHECK_CLOSE(c.GetVal(),1.0,1e-6);
+}
+
+BOOST_AUTO_TEST_CASE(set_char)
+{
+  BOOST_CHECK(compare_colors(RGB(255,0,0),RGB(1.0,0.0,0.0)));
+  BOOST_CHECK(compare_colors(RGBA(0,127,0,127),RGBA(0.0,0.5,0.0,0.5)));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
-- 
GitLab