From 293f1a4a0a6151fe7eb01d77ab21d5c3fd29d401 Mon Sep 17 00:00:00 2001
From: Ansgar Philippsen <ansgar.philippsen@gmail.com>
Date: Fri, 6 Jul 2012 18:34:26 -0400
Subject: [PATCH] added hsv mode to gradient

---
 modules/gfx/pymod/export_gradient.cc | 24 ++++++++++-
 modules/gfx/pymod/export_primlist.cc |  4 +-
 modules/gfx/pymod/gradients.xml      |  9 +++++
 modules/gfx/src/gradient.cc          | 60 +++++++++++++++++++++-------
 modules/gfx/src/gradient.hh          | 54 ++++++++++++++++++-------
 5 files changed, 119 insertions(+), 32 deletions(-)

diff --git a/modules/gfx/pymod/export_gradient.cc b/modules/gfx/pymod/export_gradient.cc
index 6cb26cee0..483249538 100644
--- a/modules/gfx/pymod/export_gradient.cc
+++ b/modules/gfx/pymod/export_gradient.cc
@@ -16,6 +16,11 @@
 // along with this library; if not, write to the Free Software Foundation, Inc.,
 // 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 //------------------------------------------------------------------------------
+
+/*
+  Authors: Marco Biasini, Ansgar Philippsen
+*/
+
 #include <boost/python.hpp>
 #include <boost/python/suite/indexing/vector_indexing_suite.hpp>
 using namespace boost::python;
@@ -76,15 +81,27 @@ namespace {
           throw std::runtime_error("expected values of gfx.Color or float triplets");
         }
         try {
-          col=gfx::Color(extract<float>(val[0]),extract<float>(val[1]),extract<float>(val[2]));
+          col=gfx::RGB(extract<float>(val[0]),extract<float>(val[1]),extract<float>(val[2]));
         } catch (...) {
-          throw std::runtime_error("expected values of gfx.Color or float triplets");
+          throw std::runtime_error("expected values of gfx.Color float triplets");
         }
       }
       grad->SetColorAt(mark,col);
     }
     return grad.release();
   }
+
+  std::string sl_repr(const Gradient::StopList& sl) {
+    std::ostringstream m;
+    m << "[";
+    for(size_t i=0;i<sl.size();++i) {
+      Color c = sl[i].color;
+      m << "(" << sl[i].t << "," << "gfx.RGB(" << c[0] << "," << c[1] << "," << c[2] << "))";
+      if(i<sl.size()-1) m << ",";
+    }
+    m << "]";
+    return m.str();
+  }
 }
 
 void export_gradient()
@@ -96,13 +113,16 @@ void export_gradient()
     .def("SetColorAt", &Gradient::SetColorAt)
     .def("GetColorAt", &Gradient::GetColorAt)
     .def("GetStops", &Gradient::GetStops)
+    .add_property("stops", &Gradient::GetStops)
     .def("GradientToInfo", &Gradient::GradientToInfo)
     .def("GradientFromInfo", &Gradient::GradientFromInfo).staticmethod("GradientFromInfo")
+    .add_property("hsv_mode",&Gradient::GetHSVMode,&Gradient::SetHSVMode)
   ;
   implicitly_convertible<String, Gradient>();
 
   class_<Gradient::StopList>("GradientStopList", init<>())
     .def(vector_indexing_suite<Gradient::StopList>())
+    .def("__repr__",sl_repr)
   ;
 
   class_<Gradient::Stop>("GradientStop", init<>())
diff --git a/modules/gfx/pymod/export_primlist.cc b/modules/gfx/pymod/export_primlist.cc
index bb986d7e1..0d6d2bb16 100644
--- a/modules/gfx/pymod/export_primlist.cc
+++ b/modules/gfx/pymod/export_primlist.cc
@@ -62,7 +62,7 @@ namespace {
       if(!PyArray_TYPE(na)==NPY_FLOAT) {
         throw std::runtime_error("expected normal array to be of dtype=float32");
       }
-      if(PyArray_SIZE(na)!=v_size) {
+      if((size_t)PyArray_SIZE(na)!=v_size) {
         throw std::runtime_error("expected normal array size to match vertex array size");
       }
       np=reinterpret_cast<float*>(PyArray_DATA(na));
@@ -78,7 +78,7 @@ namespace {
       if(!PyArray_TYPE(ca)==NPY_FLOAT) {
         throw std::runtime_error("expected color array to be of dtype=float32");
       }
-      if(PyArray_SIZE(ca)!=v_count*4) {
+      if((size_t)PyArray_SIZE(ca)!=v_count*4) {
         throw std::runtime_error("expected color array size to equal vertex-count x 4");
       }
       cp=reinterpret_cast<float*>(PyArray_DATA(ca));
diff --git a/modules/gfx/pymod/gradients.xml b/modules/gfx/pymod/gradients.xml
index dc3e03fd2..e3e1fbd0c 100644
--- a/modules/gfx/pymod/gradients.xml
+++ b/modules/gfx/pymod/gradients.xml
@@ -17,6 +17,11 @@
 0.0615451	0.0313726	0.286275	0.501961	1
 0.502146	1	1	1	1
 0.978541	0.890105	0.075639	0.0546578	1
+</Gradient>
+  <Gradient hsv_color="1" hsv_mode="1" Name="HEAT_MAP_HSV" >3
+0.0615451	0.576389 0.9375 0.501961	1
+0.502146	0 0 1 1
+0.978541	0.00418562 0.938594 0.890105	1
 </Gradient>
   <Gradient Name="AUTUMN" >3
 0.0615451	1	0.972015	0.0132753	1
@@ -61,6 +66,10 @@
 0.748835  0 1 1 1
 0.857779  0 0.623529  1 1
 0.97541 0 0.133333  1 1
+</Gradient>
+<Gradient hsv_color="1" hsv_mode="1" Name="RED_TO_BLUE_HSV">2
+0.0 1 1 1 1
+1.0 0.66666667 1 1 1
 </Gradient>
  </Gradients>
 </EMDataInfo>
diff --git a/modules/gfx/src/gradient.cc b/modules/gfx/src/gradient.cc
index 3414135af..c65261b16 100644
--- a/modules/gfx/src/gradient.cc
+++ b/modules/gfx/src/gradient.cc
@@ -19,6 +19,9 @@
 #include "gradient.hh"
 #include <iostream>
 #include <vector>
+#include <algorithm>
+
+#include <ost/log.hh>
 
 #include <ost/info/info.hh>
 #include <ost/info/info_fw.hh>
@@ -28,13 +31,18 @@
 namespace ost { namespace gfx {
 
 
-Gradient::Gradient() 
-{
-}
+Gradient::Gradient():
+  stops_(),
+  hsv_mode_(false)
+{}
 
 Gradient::Gradient(const String& name)
 {
   Gradient gradient = GradientManager::Instance().GetGradient(name);
+  // why doesn't this work:
+  //  stops_=gradient.stops_
+  // or even better
+  //  *this = gradient
   StopList stops = gradient.GetStops();
   for(unsigned int i = 0; i < stops.size(); i++){
     Stop stop = stops[i];
@@ -55,8 +63,11 @@ void Gradient::SetColorAt(float t, const Color& c)
 
 Color Gradient::GetColorAt(float t) const
 {
-  if (stops_.empty())
-    throw Error("Can not calculate color with 0 Stops set!");
+  if (stops_.empty()) {
+    return Color();
+  } else if(stops_.size()==1) {
+    return stops_[0].color;
+  }
   
   uint c=0;
   while (t>=stops_[c].t && c<stops_.size()) {
@@ -64,16 +75,17 @@ Color Gradient::GetColorAt(float t) const
   }
   if (c==0) {
     return stops_.front().color;
-  }
-  if (c==stops_.size()) {
+  } else if (c==stops_.size()) {
     return stops_.back().color;
   }
-  float d=stops_[c].t-stops_[c-1].t;
-  float tt=0.0;
-  if (d>0.0001) {
-    tt=(t-stops_[c-1].t)/d;
-  } 
-  return stops_[c-1].color*(1.0-tt)+stops_[c].color*tt;
+  float d = stops_[c].t-stops_[c-1].t;
+  float tt = d>0.001  ? (t-stops_[c-1].t)/d : 0.0;
+  if(hsv_mode_) {
+    geom::Vec4 v=stops_[c-1].color.GetHSVA()*(1.0-tt)+stops_[c].color.GetHSVA()*tt;
+    return HSVA(v[0],v[1],v[2],v[3]);
+  } else {
+    return stops_[c-1].color*(1.0-tt)+stops_[c].color*tt;
+  }
 }
 
 void Gradient::GradientToInfo(info::InfoGroup& group) const
@@ -86,6 +98,7 @@ void Gradient::GradientToInfo(info::InfoGroup& group) const
     ss << stops_[i].t << "\t" << stops_[i].color.Red() << "\t" << stops_[i].color.Green() << "\t" << stops_[i].color.Blue() << "\t" << stops_[i].color.Alpha() << "\n";
   }
   group.SetTextData(ss.str());
+  group.SetAttribute("hsv_mode",hsv_mode_? "1" : "0");
 }
 
 Gradient::StopList Gradient::GetStops() const{
@@ -95,6 +108,16 @@ Gradient::StopList Gradient::GetStops() const{
 Gradient Gradient::GradientFromInfo(const info::InfoGroup& group)
 {
   std::istringstream ss(group.GetTextData());
+  bool hsv_color=false;
+  if(group.HasAttribute("hsv_color")) {
+    String hsv_color_s = group.GetAttribute("hsv_color");
+    std::transform(hsv_color_s.begin(), hsv_color_s.end(), hsv_color_s.begin(), ::tolower);
+    if(hsv_color_s=="0" || hsv_color_s=="false" || hsv_color_s=="no" || hsv_color_s=="off") {
+      hsv_color=false;
+    } else {
+      hsv_color=true;
+    }
+  }
   Gradient grad;
 
   StopList::size_type size = 0;
@@ -102,10 +125,19 @@ Gradient Gradient::GradientFromInfo(const info::InfoGroup& group)
   float t, r, g, b, a;
   for( StopList::size_type i = 0; i < size; i++ ) {
     ss >> t >> r >> g >> b >> a;
-    Color c = Color(r,g,b,a);
+    Color c = hsv_color ? HSVA(r,g,b,a) : RGBA(r,g,b,a);
     grad.SetColorAt(t, c);
   }
 
+  if(group.HasAttribute("hsv_mode")) {
+    String hsv_mode_s = group.GetAttribute("hsv_mode");
+    std::transform(hsv_mode_s.begin(), hsv_mode_s.end(), hsv_mode_s.begin(), ::tolower);
+    if(hsv_mode_s=="0" || hsv_mode_s=="false" || hsv_mode_s=="no" || hsv_mode_s=="off") {
+      grad.SetHSVMode(false);
+    } else {
+      grad.SetHSVMode(true);
+    }
+  }
   return grad;
 }
 
diff --git a/modules/gfx/src/gradient.hh b/modules/gfx/src/gradient.hh
index 0b220faf0..e08a24eda 100644
--- a/modules/gfx/src/gradient.hh
+++ b/modules/gfx/src/gradient.hh
@@ -20,8 +20,8 @@
 #define OST_GFX_GRADIENT_HH
 
 /*
-  Author: Marco Biasini
- */
+  Authors: Marco Biasini, Ansgar Philippsen
+*/
 
 #include <vector>
 
@@ -30,16 +30,32 @@
 #include <ost/info/info.hh>
 #include <ost/info/info_fw.hh>
 
-
 namespace ost { namespace gfx {
 
-/// \brief color gradient
-/// 
-/// Gradients map a scalar value in the range of 0 to 1 to a color. The 
-/// gradient consists of zero or more color stops. These stop points define the 
-/// output of GetColorAt(). New stops can be added with SetColorAt().
-/// 
-/// \sa \ref gradient.py "Gradient Example"
+/*!
+ \brief color gradient
+ 
+ Gradients map a scalar value in the range of 0 to 1 to a color. The 
+ gradient consists of zero or more color stops. These stop points define the 
+ output of GetColorAt(). New stops can be added with SetColorAt().
+
+ Usually, the gradient operates in RGB colorspace, which means a linear
+ interpolation of each color component between stops. As an alternative,
+ interpolation can also be done in HSV colorspace, by setting HSV mode
+ to true with SetHSVMode(). However, carefully using HSV colorspace will
+ yield better results, if some potential caveats are taken into consideration.
+ The best example is a simple gradient from red to blue. The half-point in RGB
+ colorspace is (0.5,0.0,0.5), so a darkish purple. Converting to HSV one can see
+ that the saturation is at 50%, which is unfortunate. Using hsv mode can circument
+ this issue, but in the above example, naively using hsv mode will give green
+ at the half-point and not purple. Why? Because red translates to HSV(0,1,1) and blue to
+ HSV(2/3,1,1), which at the half-point is HSV(1/3,1,1), or green. To interpolate
+ on the hue in the other direction, use HSV and unwrapped hue values, e.g. 
+ [HSV(0.0,1,1),HSV(-1/3,1,1)] or [HSV(1,1,1),HSV(2/3,1,1)] - both of these will
+ give a half-point or a nice purple with full saturation
+ 
+ \sa \ref gradient.py "Gradient Example"
+*/
 class DLLEXPORT_OST_GFX Gradient {
 public:
   struct Stop {
@@ -49,14 +65,14 @@ public:
     float t;
     Color color;
     
-    Color GetColor(){
+    Color GetColor() const {
       return color;
     }
     
-    float GetRel(){
+    float GetRel() const {
       return t;
     }
-    
+
     bool operator==(const Stop& ref) const
     {
       return t==ref.t && color==ref.color;
@@ -85,16 +101,26 @@ public:
   void SetColorAt(float t, const Color& color);  
   
   /// \brief convert gradient to info
+  // TODO: rename to ToInfo
+  // TODO:: add stand-alone function named GradientToInfo(Gradient, InfoGroup)
   void GradientToInfo(info::InfoGroup& group) const;
 
   /// \brief get list of color stops
   StopList GetStops() const;
   
   /// \brief create gradient from info
+  // TODO: move outside
+  // TODO: add FromInfo member function that overwrites this
   static gfx::Gradient GradientFromInfo(const info::InfoGroup& group);
+
+  /// \brief turn HSV interpolation mode on or off
+  void SetHSVMode(bool m) {hsv_mode_=m;}
+  /// \brief return the HSV interpolation mode flag
+  bool GetHSVMode() const {return hsv_mode_;}
+    
 private:
   std::vector<Stop>  stops_;
-
+  bool hsv_mode_;
 };
 
 
-- 
GitLab