From ee2dfca5879125346bc309f4f13c553e12fb7460 Mon Sep 17 00:00:00 2001 From: Paul Pogonyshev Date: Sat, 16 May 2009 00:12:46 +0300 Subject: 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 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 gtk.gdk.color_parse spec - + + gtk.gdk.color_from_hsv + hue + saturation + value + @@ -52,7 +57,7 @@ linkend="constructor-gdkcolor">gtk.gdk.Color Attributes - Floating-point attributes are available in PyGTK 2.16 and above. + Floating-point and HSV attributes are available in PyGTK 2.16 and above.
@@ -106,6 +111,24 @@ linkend="constructor-gdkcolor">gtk.gdk.Color The value of the blue component of the color as a float in the range 0.0--1.0 + + "hue" + Read + The hue (in HSV colorspace) of the color as a float in the range 0.0--1.0 + + + + "saturation" + Read + The saturation (in HSV colorspace) of the color as a float in the range 0.0--1.0 + + + + "value" + Read + The value (in HSV colorspace) of the color as a float in the range 0.0--1.0 + + @@ -151,6 +174,17 @@ object. color == eval(repr(color)) + + PyGTK 2.16 introduces several ways of using floating-point numbers for + creating gtk.gdk.Color + 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. + + @@ -334,6 +368,68 @@ linkend="class-gdkcolor">gtk.gdk.Color is + + gtk.gdk.color_from_hsv + + + gtk.gdk.color_from_hsv + hue + saturation + value + + + + + hue : + Hue of the desired color as a float in range + 0.0--1.0 + + + saturation : + Saturation of the desired color as a float in range + 0.0--1.0 + + + value : + Value of the desired color as a float in range + 0.0--1.0 + + + Returns : + a gtk.gdk.Color + object + + + + + This function is available in PyGTK 2.16 and above. + + + The gtk.gdk.color_from_hsv() method returns + the gtk.gdk.Color + 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. + + 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). + + Note that internal storage is still integers, so values of + corresponding hue, saturation + and value 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 GdkColor though. + + For more details read about HSV + colorspace. + + 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 @@ -138,6 +138,44 @@ _wrap_gdk_color_new(PyGBoxed *self, return 0; } +%% +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 @@ -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)) -- cgit v1.2.1