summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Pogonyshev <pogonyshev@gmx.net>2009-05-16 00:12:46 +0300
committerPaul Pogonyshev <pogonyshev@gmx.net>2009-05-16 00:12:46 +0300
commitee2dfca5879125346bc309f4f13c553e12fb7460 (patch)
tree88e1e36dc66e54568e04a41384e3487f338571bd
parent15b7265a00d405d6115f45ba0b1f26019ef1a52b (diff)
downloadpygtk-ee2dfca5879125346bc309f4f13c553e12fb7460.tar.gz
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.
-rw-r--r--docs/reference/pygtk-gdkcolor.xml100
-rw-r--r--gtk/gdk-base-types.defs3
-rw-r--r--gtk/gdkcolor.override83
-rw-r--r--tests/test_color.py21
4 files changed, 204 insertions, 3 deletions
diff --git a/docs/reference/pygtk-gdkcolor.xml b/docs/reference/pygtk-gdkcolor.xml
index 82bd82b0..7a54716c 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 f6799bf2..5f395344 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 f46b8783..6ac88814 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 d8484ae3..82f35c79 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))