[pygtk] Add HSV support to gtk.gdk.Color objects



commit ee2dfca5879125346bc309f4f13c553e12fb7460
Author: Paul Pogonyshev <pogonyshev gmx net>
Date:   Sat May 16 00:12:46 2009 +0300

    Add HSV support to gtk.gdk.Color objects
    
    Add read-only float attributes for hue, saturation and value of a
    color.  Add a function to create a color objects out of HSV
    components.  Add unit tests and document new features.  Part of bug
    546019.
---
 docs/reference/pygtk-gdkcolor.xml |  100 ++++++++++++++++++++++++++++++++++++-
 gtk/gdk-base-types.defs           |    3 +
 gtk/gdkcolor.override             |   83 ++++++++++++++++++++++++++++++
 tests/test_color.py               |   21 +++++++-
 4 files changed, 204 insertions(+), 3 deletions(-)

diff --git a/docs/reference/pygtk-gdkcolor.xml b/docs/reference/pygtk-gdkcolor.xml
index 82bd82b..7a54716 100644
--- a/docs/reference/pygtk-gdkcolor.xml
+++ b/docs/reference/pygtk-gdkcolor.xml
@@ -44,7 +44,12 @@ linkend="constructor-gdkcolor">gtk.gdk.Color</link></methodname>
 <methodsynopsis language="python">
 	<methodname><link linkend="function-gdk--color-parse">gtk.gdk.color_parse</link></methodname>
 	<methodparam><parameter role="keyword">spec</parameter></methodparam>
-      </methodsynopsis></programlisting>
+      </methodsynopsis><methodsynopsis language="python">
+      <methodname><link linkend="function-gdk--color-from-hsv">gtk.gdk.color_from_hsv</link></methodname>
+      <methodparam><parameter role="keyword">hue</parameter></methodparam>
+      <methodparam><parameter role="keyword">saturation</parameter></methodparam>
+      <methodparam><parameter role="keyword">value</parameter></methodparam>
+    </methodsynopsis></programlisting>
 
   </refsect1>
 
@@ -52,7 +57,7 @@ linkend="constructor-gdkcolor">gtk.gdk.Color</link></methodname>
     <title>Attributes</title>
 
     <note>
-      <para>Floating-point attributes are available in PyGTK 2.16 and above.</para>
+      <para>Floating-point and HSV attributes are available in PyGTK 2.16 and above.</para>
     </note>
 
     <blockquote role="properties">
@@ -106,6 +111,24 @@ linkend="constructor-gdkcolor">gtk.gdk.Color</link></methodname>
 	      <entry>The value of the blue component of the color as a float in the range 0.0--1.0</entry>
 	    </row>
 
+	    <row valign="top">
+	      <entry>"hue"</entry>
+	      <entry>Read</entry>
+	      <entry>The hue (in HSV colorspace) of the color as a float in the range 0.0--1.0</entry>
+	    </row>
+
+	    <row valign="top">
+	      <entry>"saturation"</entry>
+	      <entry>Read</entry>
+	      <entry>The saturation (in HSV colorspace) of the color as a float in the range 0.0--1.0</entry>
+	    </row>
+
+	    <row valign="top">
+	      <entry>"value"</entry>
+	      <entry>Read</entry>
+	      <entry>The value (in HSV colorspace) of the color as a float in the range 0.0--1.0</entry>
+	    </row>
+
 	</tbody>
       </tgroup>
       </informaltable>
@@ -151,6 +174,17 @@ object.</para>
   color == eval(repr(color))
     </programlisting>
 
+    <para>
+      PyGTK 2.16 introduces several ways of using floating-point numbers for
+      creating <link linkend="class-gdkcolor"><classname>gtk.gdk.Color</classname></link>
+      objects or setting their individual components.  In all cases it was decided to
+      silently clamp input values to valid range, rather than being strict and e.g. raise
+      an exception.  The rationale is that floating-point arithmetics are imprecise, so
+      you could end with a computed value slightly larger than maximum or a little smaller
+      than minimum valid value.  To simplify using new features, such "slightly off"
+      values are just clamped without any warning.
+    </para>
+
   </refsect1>
 
   <refsect1 id="constructor-gdkcolor">
@@ -334,6 +368,68 @@ linkend="class-gdkcolor"><classname>gtk.gdk.Color</classname></link> is
 
     </refsect2>
 
+    <refsect2 id="function-gdk--color-from-hsv">
+      <title>gtk.gdk.color_from_hsv</title>
+
+      <programlisting><methodsynopsis language="python">
+	  <methodname>gtk.gdk.color_from_hsv</methodname>
+	  <methodparam><parameter role="keyword">hue</parameter></methodparam>
+	  <methodparam><parameter role="keyword">saturation</parameter></methodparam>
+	  <methodparam><parameter role="keyword">value</parameter></methodparam>
+	</methodsynopsis></programlisting>
+
+      <variablelist>
+	<varlistentry>
+	  <term><parameter role="keyword">hue</parameter>&nbsp;:</term>
+	  <listitem><simpara>Hue of the desired color as a float in range
+	      0.0--1.0</simpara></listitem>
+	</varlistentry>
+	<varlistentry>
+	  <term><parameter role="keyword">saturation</parameter>&nbsp;:</term>
+	  <listitem><simpara>Saturation of the desired color as a float in range
+	      0.0--1.0</simpara></listitem>
+	</varlistentry>
+	<varlistentry>
+	  <term><parameter role="keyword">value</parameter>&nbsp;:</term>
+	  <listitem><simpara>Value of the desired color as a float in range
+	      0.0--1.0</simpara></listitem>
+	</varlistentry>
+        <varlistentry>
+          <term><emphasis>Returns</emphasis>&nbsp;:</term>
+          <listitem><simpara>a <link linkend="class-gdkcolor"><classname>gtk.gdk.Color</classname></link>
+              object</simpara></listitem>
+        </varlistentry>
+      </variablelist>
+
+      <note>
+        <para>This function is available in PyGTK 2.16 and above.</para>
+      </note>
+
+      <para>The <function>gtk.gdk.color_from_hsv</function>() method returns
+        the <link linkend="class-gdkcolor"><classname>gtk.gdk.Color</classname></link>
+        specified by the HSV parameters.  All three parameters are mandatory and should be
+        floats from 0.0 to 1.0.  The range requirement, however, is not strict, see
+        below.</para>
+
+      <para>As hue goes from 0 to 1 color goes roughly as red â?? yellow â?? green â?? cyan â??
+        blue â?? magenta â?? red.  Because of the "circular" nature, this parameter wraps
+        around, so only fractional part matters.  E.g. -4.2 or 1.8 are the same as 0.8.
+        Saturation determines how intense a color is, with 0.0 meaning pure gray and 1.0
+        -- fully intense color.  Value determines how light a color is, with 0.0 standing
+        for fully black and 1.0 for completely white color.  Both saturation and value are
+        clamped to valid range (see rationale at the top of the page).</para>
+
+      <para>Note that internal storage is still integers, so values of
+        corresponding <literal>hue</literal>, <literal>saturation</literal>
+        and <literal>value</literal> attributes of the returned object will not
+        necessarily be equal to the value used as argument for this functions.  They will
+        be as close as permitted by 16-bit color component storage used
+        by <classname>GdkColor</classname> though.</para>
+
+      <para>For more details read about <ulink url="http://en.wikipedia.org/wiki/HSL_and_HSV";>HSV
+          colorspace</ulink>.</para>
+    </refsect2>
+
   </refsect1>
 
 </refentry>
diff --git a/gtk/gdk-base-types.defs b/gtk/gdk-base-types.defs
index f6799bf..5f39534 100644
--- a/gtk/gdk-base-types.defs
+++ b/gtk/gdk-base-types.defs
@@ -233,6 +233,9 @@
     '("gfloat" "red_float")
     '("gfloat" "green_float")
     '("gfloat" "blue_float")
+    '("gfloat" "hue")
+    '("gfloat" "saturation")
+    '("gfloat" "value")
   )
 )
 
diff --git a/gtk/gdkcolor.override b/gtk/gdkcolor.override
index f46b878..6ac8881 100644
--- a/gtk/gdkcolor.override
+++ b/gtk/gdkcolor.override
@@ -139,6 +139,44 @@ _wrap_gdk_color_new(PyGBoxed *self,
 }
 
 %%
+define color_from_hsv
+static PyObject *
+_wrap_color_from_hsv (PyObject *ignored, PyObject *args, PyObject*kwargs)
+{
+    static char *kwlist[] = { "hue", "saturation", "value", NULL };
+    gdouble hue, saturation, value;
+    gdouble red, green, blue;
+    GdkColor color;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ddd:gtk.gdk.color_from_hsv", kwlist,
+                                     &hue, &saturation, &value))
+        return NULL;
+
+    /* See documentation of the class for rationale. */
+
+    hue -= floor(hue);
+
+    if (saturation > 1.0)
+        saturation = 1.0;
+    else if (saturation < 0.0)
+        saturation = 0.0;
+
+    if (value > 1.0)
+        value = 1.0;
+    else if (value < 0.0)
+        value = 0.0;
+
+    gtk_hsv_to_rgb(hue, saturation, value,
+                   &red, &green, &blue);
+
+    color.red = red * 65535.0;
+    color.green = green * 65535.0;
+    color.blue = blue * 65535.0;
+
+    return pyg_boxed_new(GDK_TYPE_COLOR, &color, TRUE, TRUE);
+}
+
+%%
 override-attr GdkColor.red
 static int
 _wrap_gdk_color__set_red(PyObject *self, PyObject *value, void *closure)
@@ -251,6 +289,51 @@ _wrap_gdk_color__set_blue_float(PyObject *self, PyObject *value, void *closure)
     }
 }
 %%
+override-attr GdkColor.hue
+
+static PyObject *
+_wrap_gdk_color__get_hue(PyObject *self, void *closure)
+{
+    GdkColor *color = pyg_boxed_get(self, GdkColor);
+    gdouble red = color->red / 65535.0;
+    gdouble green = color->green / 65535.0;
+    gdouble blue = color->blue / 65535.0;
+    gdouble hue;
+
+    gtk_rgb_to_hsv(red, green, blue, &hue, NULL, NULL);
+    return PyFloat_FromDouble(hue);
+}
+%%
+override-attr GdkColor.saturation
+
+static PyObject *
+_wrap_gdk_color__get_saturation(PyObject *self, void *closure)
+{
+    GdkColor *color = pyg_boxed_get(self, GdkColor);
+    gdouble red = color->red / 65535.0;
+    gdouble green = color->green / 65535.0;
+    gdouble blue = color->blue / 65535.0;
+    gdouble saturation;
+
+    gtk_rgb_to_hsv(red, green, blue, NULL, &saturation, NULL);
+    return PyFloat_FromDouble(saturation);
+}
+%%
+override-attr GdkColor.value
+
+static PyObject *
+_wrap_gdk_color__get_value(PyObject *self, void *closure)
+{
+    GdkColor *color = pyg_boxed_get(self, GdkColor);
+    gdouble red = color->red / 65535.0;
+    gdouble green = color->green / 65535.0;
+    gdouble blue = color->blue / 65535.0;
+    gdouble value;
+
+    gtk_rgb_to_hsv(red, green, blue, NULL, NULL, &value);
+    return PyFloat_FromDouble(value);
+}
+%%
 override gdk_color_parse kwargs
 static PyObject *
 _wrap_gdk_color_parse(PyObject *self, PyObject *args, PyObject *kwargs)
diff --git a/tests/test_color.py b/tests/test_color.py
index d8484ae..82f35c7 100644
--- a/tests/test_color.py
+++ b/tests/test_color.py
@@ -44,7 +44,10 @@ class Tests(unittest.TestCase):
 
         self.assertRaises(TypeError, lambda: gtk.gdk.Color([]))
 
-    def test_float_attribute(self):
+    def test_color_from_hsv(self):
+        self.assertEqual(gtk.gdk.Color('red'), gtk.gdk.color_from_hsv(0.0, 1.0, 1.0))
+
+    def test_float_attributes(self):
         c = gtk.gdk.Color(0, 10000, 65535)
         self.assertAlmostEqual(c.red_float, 0.0)
         self.assertAlmostEqual(c.green_float, 10000.0 / 65535.0)
@@ -57,6 +60,22 @@ class Tests(unittest.TestCase):
         c.green = 12345
         self.assertAlmostEqual(c.green_float, 12345.0 / 65535.0)
 
+    def test_hue(self):
+        self.assertAlmostEqual(gtk.gdk.Color('red').hue, 0 * 1.0 / 6)
+        self.assertAlmostEqual(gtk.gdk.Color('yellow').hue, 1 * 1.0 / 6)
+        self.assertAlmostEqual(gtk.gdk.Color('green').hue, 2 * 1.0 / 6)
+        self.assertAlmostEqual(gtk.gdk.Color('cyan').hue, 3 * 1.0 / 6)
+        self.assertAlmostEqual(gtk.gdk.Color('blue').hue, 4 * 1.0 / 6)
+        self.assertAlmostEqual(gtk.gdk.Color('magenta').hue, 5 * 1.0 / 6)
+
+    def test_saturation(self):
+        self.assertAlmostEqual(gtk.gdk.Color('red').saturation, 1.0)
+        self.assertAlmostEqual(gtk.gdk.Color('gray').saturation, 0.0)
+
+    def test_value(self):
+        self.assertAlmostEqual(gtk.gdk.Color('black').value, 0.0)
+        self.assertAlmostEqual(gtk.gdk.Color('white').value, 1.0)
+
     def test_equal(self):
         self.assertEqual(gtk.gdk.Color(0, 0, 0), gtk.gdk.Color(0, 0, 0))
         self.assertEqual(gtk.gdk.Color(100, 200, 300), gtk.gdk.Color(100, 200, 300))



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]