From 387c1b8b1fcf80233d3fc73aa3be766bfea83dc8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 20 May 2011 13:18:41 -0600 Subject: Add Colors to the Python extension. --- libdimension-python/Color.c | 310 ++++++++++++++++++++++++++++++++++ libdimension-python/Color.h | 43 +++++ libdimension-python/Makefile.am | 4 +- libdimension-python/Scene.c | 1 - libdimension-python/Vector.c | 32 +++- libdimension-python/Vector.h | 6 + libdimension-python/dimension.c | 40 ++--- libdimension-python/tests/Makefile.am | 1 + libdimension-python/tests/color.py | 55 ++++++ 9 files changed, 461 insertions(+), 31 deletions(-) create mode 100644 libdimension-python/Color.c create mode 100644 libdimension-python/Color.h create mode 100755 libdimension-python/tests/color.py diff --git a/libdimension-python/Color.c b/libdimension-python/Color.c new file mode 100644 index 0000000..ae8de2f --- /dev/null +++ b/libdimension-python/Color.c @@ -0,0 +1,310 @@ +/************************************************************************* + * Copyright (C) 2009-2011 Tavian Barnes * + * * + * This file is part of The Dimension Python Module. * + * * + * The Dimension Python Module 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 of the License, or (at your option) any later version. * + * * + * The Dimension Python Module 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 program. If not, see * + * . * + *************************************************************************/ + +#include "Color.h" + +bool +dmnsn_py_Color_args(dmnsn_color *c, PyObject *args, PyObject *kwds) +{ + c->filter = 0.0; + c->trans = 0.0; + + static char *kwlist[] = { "red", "green", "blue", "filter", "trans", NULL }; + if (PyArg_ParseTupleAndKeywords(args, kwds, "ddd|dd", kwlist, + &c->R, &c->G, &c->B, &c->filter, &c->trans)) { + return true; + } else { + if (kwds) + return false; + + PyErr_Clear(); + + dmnsn_py_Color *col; + if (!PyArg_ParseTuple(args, "O!", &dmnsn_py_ColorType, &col)) + return false; + + *c = col->c; + return true; + } +} + +static int +dmnsn_py_Color_init(dmnsn_py_Color *self, PyObject *args, PyObject *kwds) +{ + return dmnsn_py_Color_args(&self->c, args, kwds) ? 0 : -1; +} + +static PyObject * +dmnsn_py_Color_repr(dmnsn_py_Color *self) +{ + PyObject *R = PyFloat_FromDouble(self->c.R); + PyObject *G = PyFloat_FromDouble(self->c.G); + PyObject *B = PyFloat_FromDouble(self->c.B); + PyObject *filter = PyFloat_FromDouble(self->c.filter); + PyObject *trans = PyFloat_FromDouble(self->c.trans); + + if (!R || !G || !B || !filter || !trans) { + Py_XDECREF(trans); + Py_XDECREF(filter); + Py_XDECREF(B); + Py_XDECREF(G); + Py_XDECREF(R); + return NULL; + } + + PyObject *repr = PyUnicode_FromFormat("dimension.Color(%R, %R, %R, %R, %R)", + R, G, B, filter, trans); + Py_XDECREF(trans); + Py_XDECREF(filter); + Py_XDECREF(B); + Py_XDECREF(G); + Py_XDECREF(R); + return repr; +} + +static PyObject * +dmnsn_py_Color_str(dmnsn_py_Color *self) +{ + PyObject *R = PyFloat_FromDouble(self->c.R); + PyObject *G = PyFloat_FromDouble(self->c.G); + PyObject *B = PyFloat_FromDouble(self->c.B); + PyObject *filter = PyFloat_FromDouble(self->c.filter); + PyObject *trans = PyFloat_FromDouble(self->c.trans); + + if (!R || !G || !B || !filter || !trans) { + Py_XDECREF(trans); + Py_XDECREF(filter); + Py_XDECREF(B); + Py_XDECREF(G); + Py_XDECREF(R); + return NULL; + } + + PyObject *str; + if (self->c.filter < dmnsn_epsilon && self->c.trans < dmnsn_epsilon) { + str = PyUnicode_FromFormat("", + R, G, B); + } else { + str = PyUnicode_FromFormat("", + R, G, B, filter, trans); + } + Py_XDECREF(trans); + Py_XDECREF(filter); + Py_XDECREF(B); + Py_XDECREF(G); + Py_XDECREF(R); + return str; +} + +static PyObject * +dmnsn_py_Color_richcompare(PyObject *lhs, PyObject *rhs, int op) +{ + if (!PyObject_TypeCheck(lhs, &dmnsn_py_ColorType) + || !PyObject_TypeCheck(rhs, &dmnsn_py_ColorType)) + { + PyErr_SetString(PyExc_TypeError, + "Colors can only be compared with Colors"); + return NULL; + } + + dmnsn_py_Color *clhs = (dmnsn_py_Color *)lhs; + dmnsn_py_Color *crhs = (dmnsn_py_Color *)rhs; + + double rdiff = (clhs->c.R - crhs->c.R)*(clhs->c.R - crhs->c.R); + double gdiff = (clhs->c.G - crhs->c.G)*(clhs->c.G - crhs->c.G); + double bdiff = (clhs->c.B - crhs->c.B)*(clhs->c.B - crhs->c.B); + double fdiff = (clhs->c.filter - crhs->c.filter) + * (clhs->c.filter - crhs->c.filter); + double tdiff = (clhs->c.trans - crhs->c.trans) + * (clhs->c.trans - crhs->c.trans); + bool equal = sqrt(rdiff + gdiff + bdiff + fdiff + tdiff) < dmnsn_epsilon; + + PyObject *result; + switch (op) { + case Py_EQ: + result = equal ? Py_True : Py_False; + break; + case Py_NE: + result = !equal ? Py_True : Py_False; + break; + default: + result = Py_NotImplemented; + break; + } + + Py_INCREF(result); + return result; +} + +static PyObject * +dmnsn_py_Color_add(PyObject *lhs, PyObject *rhs) +{ + if (!PyObject_TypeCheck(lhs, &dmnsn_py_ColorType) + || !PyObject_TypeCheck(rhs, &dmnsn_py_ColorType)) + { + PyErr_SetString(PyExc_TypeError, + "Colors can only be added to Colors"); + return NULL; + } + + dmnsn_py_Color *ret = PyObject_New(dmnsn_py_Color, &dmnsn_py_ColorType); + if (ret) { + dmnsn_py_Color *clhs = (dmnsn_py_Color *)lhs; + dmnsn_py_Color *crhs = (dmnsn_py_Color *)rhs; + ret->c = dmnsn_color_add(clhs->c, crhs->c); + } + return (PyObject *)ret; +} + +static PyObject * +dmnsn_py_Color_mul(PyObject *lhs, PyObject *rhs) +{ + dmnsn_py_Color *col; + double dbl; + + if (PyObject_TypeCheck(lhs, &dmnsn_py_ColorType)) { + col = (dmnsn_py_Color *)lhs; + dbl = PyFloat_AsDouble(rhs); + if (PyErr_Occurred()) + return NULL; + } else { + col = (dmnsn_py_Color *)rhs; + dbl = PyFloat_AsDouble(lhs); + if (PyErr_Occurred()) + return NULL; + } + + dmnsn_py_Color *ret = PyObject_New(dmnsn_py_Color, &dmnsn_py_ColorType); + if (ret) { + ret->c = dmnsn_color_mul(dbl, col->c); + } + return (PyObject *)ret; +} + +static int +dmnsn_py_Color_bool(PyObject *obj) +{ + dmnsn_py_Color *col = (dmnsn_py_Color *)obj; + return !dmnsn_color_is_black(col->c); +} + +static PyNumberMethods dmnsn_py_Color_as_number = { + .nb_add = dmnsn_py_Color_add, + .nb_multiply = dmnsn_py_Color_mul, + .nb_bool = dmnsn_py_Color_bool, +}; + +static PyMethodDef dmnsn_py_Color_methods[] = { + { NULL } +}; + +static PyObject * +dmnsn_py_Color_get_red(dmnsn_py_Color *self, void *closure) +{ + return PyFloat_FromDouble(self->c.R); +} + +static PyObject * +dmnsn_py_Color_get_green(dmnsn_py_Color *self, void *closure) +{ + return PyFloat_FromDouble(self->c.G); +} + +static PyObject * +dmnsn_py_Color_get_blue(dmnsn_py_Color *self, void *closure) +{ + return PyFloat_FromDouble(self->c.B); +} + +static PyObject * +dmnsn_py_Color_get_filter(dmnsn_py_Color *self, void *closure) +{ + return PyFloat_FromDouble(self->c.filter); +} + +static PyObject * +dmnsn_py_Color_get_trans(dmnsn_py_Color *self, void *closure) +{ + return PyFloat_FromDouble(self->c.trans); +} + +static PyGetSetDef dmnsn_py_Color_getsetters[] = { + { "red", (getter)dmnsn_py_Color_get_red, NULL, + "Red component", NULL }, + { "green", (getter)dmnsn_py_Color_get_green, NULL, + "Green componant", NULL }, + { "blue", (getter)dmnsn_py_Color_get_blue, NULL, + "Blue componant", NULL }, + { "filter", (getter)dmnsn_py_Color_get_filter, NULL, + "Filter component", NULL }, + { "trans", (getter)dmnsn_py_Color_get_trans, NULL, + "Transmittance component", NULL }, + { NULL } +}; + +PyTypeObject dmnsn_py_ColorType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "dimension.Color", + .tp_basicsize = sizeof(dmnsn_py_Color), + .tp_repr = (reprfunc)dmnsn_py_Color_repr, + .tp_str = (reprfunc)dmnsn_py_Color_str, + .tp_as_number = &dmnsn_py_Color_as_number, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "Dimension color", + .tp_richcompare = dmnsn_py_Color_richcompare, + .tp_methods = dmnsn_py_Color_methods, + .tp_getset = dmnsn_py_Color_getsetters, + .tp_init = (initproc)dmnsn_py_Color_init, +}; + +#define dmnsn_py_Color_global(name) \ + dmnsn_py_Color name = { \ + PyObject_HEAD_INIT(&dmnsn_py_ColorType) \ + }; + +dmnsn_py_Color_global(dmnsn_py_Black); +dmnsn_py_Color_global(dmnsn_py_White); +dmnsn_py_Color_global(dmnsn_py_Clear); +dmnsn_py_Color_global(dmnsn_py_Red); +dmnsn_py_Color_global(dmnsn_py_Green); +dmnsn_py_Color_global(dmnsn_py_Blue); +dmnsn_py_Color_global(dmnsn_py_Magenta); +dmnsn_py_Color_global(dmnsn_py_Orange); +dmnsn_py_Color_global(dmnsn_py_Yellow); +dmnsn_py_Color_global(dmnsn_py_Cyan); + +bool +dmnsn_py_init_ColorType(void) +{ + dmnsn_py_Black.c = dmnsn_black; + dmnsn_py_White.c = dmnsn_white; + dmnsn_py_Clear.c = dmnsn_clear; + dmnsn_py_Red.c = dmnsn_red; + dmnsn_py_Green.c = dmnsn_green; + dmnsn_py_Blue.c = dmnsn_blue; + dmnsn_py_Magenta.c = dmnsn_magenta; + dmnsn_py_Orange.c = dmnsn_orange; + dmnsn_py_Yellow.c = dmnsn_yellow; + dmnsn_py_Cyan.c = dmnsn_cyan; + + dmnsn_py_ColorType.tp_new = PyType_GenericNew; + return PyType_Ready(&dmnsn_py_ColorType) >= 0; +} diff --git a/libdimension-python/Color.h b/libdimension-python/Color.h new file mode 100644 index 0000000..1fbf5ed --- /dev/null +++ b/libdimension-python/Color.h @@ -0,0 +1,43 @@ +/************************************************************************* + * Copyright (C) 2009-2011 Tavian Barnes * + * * + * This file is part of The Dimension Python Module. * + * * + * The Dimension Python Module 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 of the License, or (at your option) any later version. * + * * + * The Dimension Python Module 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 program. If not, see * + * . * + *************************************************************************/ + +#include "dimension-python.h" + +typedef struct dmnsn_py_Color { + PyObject_HEAD + dmnsn_color c; +} dmnsn_py_Color; + +extern PyTypeObject dmnsn_py_ColorType; + +bool dmnsn_py_Color_args(dmnsn_color *v, PyObject *args, PyObject *kwds); +bool dmnsn_py_init_ColorType(void); + +/* Color constants */ +extern dmnsn_py_Color dmnsn_py_Black; +extern dmnsn_py_Color dmnsn_py_White; +extern dmnsn_py_Color dmnsn_py_Clear; +extern dmnsn_py_Color dmnsn_py_Red; +extern dmnsn_py_Color dmnsn_py_Green; +extern dmnsn_py_Color dmnsn_py_Blue; +extern dmnsn_py_Color dmnsn_py_Magenta; +extern dmnsn_py_Color dmnsn_py_Orange; +extern dmnsn_py_Color dmnsn_py_Yellow; +extern dmnsn_py_Color dmnsn_py_Cyan; diff --git a/libdimension-python/Makefile.am b/libdimension-python/Makefile.am index a9e1b8c..e75ffad 100644 --- a/libdimension-python/Makefile.am +++ b/libdimension-python/Makefile.am @@ -26,7 +26,9 @@ AM_CFLAGS = $(Python_CFLAGS) AM_LDFLAGS = $(Python_LDFLAGS) pyexec_LTLIBRARIES = dimension.la -dimension_la_SOURCES = Matrix.c \ +dimension_la_SOURCES = Color.c \ + Color.h \ + Matrix.c \ Matrix.h \ Scene.c \ Scene.h \ diff --git a/libdimension-python/Scene.c b/libdimension-python/Scene.c index bfbab63..6323402 100644 --- a/libdimension-python/Scene.c +++ b/libdimension-python/Scene.c @@ -66,6 +66,5 @@ PyTypeObject dmnsn_py_SceneType = { bool dmnsn_py_init_SceneType(void) { - Py_INCREF(&dmnsn_py_SceneType); return PyType_Ready(&dmnsn_py_SceneType) >= 0; } diff --git a/libdimension-python/Vector.c b/libdimension-python/Vector.c index 3fdc90c..6a1928f 100644 --- a/libdimension-python/Vector.c +++ b/libdimension-python/Vector.c @@ -84,11 +84,11 @@ dmnsn_py_Vector_str(dmnsn_py_Vector *self) return NULL; } - PyObject *repr = PyUnicode_FromFormat("<%S, %S, %S>", x, y, z); + PyObject *str = PyUnicode_FromFormat("<%S, %S, %S>", x, y, z); Py_DECREF(z); Py_DECREF(y); Py_DECREF(x); - return repr; + return str; } static PyObject * @@ -315,27 +315,27 @@ static PyMethodDef dmnsn_py_Vector_methods[] = { }; static PyObject * -dmnsn_py_Vector_getx(dmnsn_py_Vector *self, void *closure) +dmnsn_py_Vector_get_x(dmnsn_py_Vector *self, void *closure) { return PyFloat_FromDouble(self->v.x); } static PyObject * -dmnsn_py_Vector_gety(dmnsn_py_Vector *self, void *closure) +dmnsn_py_Vector_get_y(dmnsn_py_Vector *self, void *closure) { return PyFloat_FromDouble(self->v.y); } static PyObject * -dmnsn_py_Vector_getz(dmnsn_py_Vector *self, void *closure) +dmnsn_py_Vector_get_z(dmnsn_py_Vector *self, void *closure) { return PyFloat_FromDouble(self->v.z); } static PyGetSetDef dmnsn_py_Vector_getsetters[] = { - { "x", (getter)dmnsn_py_Vector_getx, NULL, "x coordinate", NULL }, - { "y", (getter)dmnsn_py_Vector_gety, NULL, "y coordinate", NULL }, - { "z", (getter)dmnsn_py_Vector_getz, NULL, "z coordinate", NULL }, + { "x", (getter)dmnsn_py_Vector_get_x, NULL, "x coordinate", NULL }, + { "y", (getter)dmnsn_py_Vector_get_y, NULL, "y coordinate", NULL }, + { "z", (getter)dmnsn_py_Vector_get_z, NULL, "z coordinate", NULL }, { NULL } }; @@ -354,10 +354,24 @@ PyTypeObject dmnsn_py_VectorType = { .tp_init = (initproc)dmnsn_py_Vector_init, }; +#define dmnsn_py_Vector_global(name) \ + dmnsn_py_Vector name = { \ + PyObject_HEAD_INIT(&dmnsn_py_VectorType) \ + }; + +dmnsn_py_Vector_global(dmnsn_py_Zero); +dmnsn_py_Vector_global(dmnsn_py_X); +dmnsn_py_Vector_global(dmnsn_py_Y); +dmnsn_py_Vector_global(dmnsn_py_Z); + bool dmnsn_py_init_VectorType(void) { + dmnsn_py_Zero.v = dmnsn_zero; + dmnsn_py_X.v = dmnsn_x; + dmnsn_py_Y.v = dmnsn_y; + dmnsn_py_Z.v = dmnsn_z; + dmnsn_py_VectorType.tp_new = PyType_GenericNew; - Py_INCREF(&dmnsn_py_VectorType); return PyType_Ready(&dmnsn_py_VectorType) >= 0; } diff --git a/libdimension-python/Vector.h b/libdimension-python/Vector.h index d829e3b..d7e4e05 100644 --- a/libdimension-python/Vector.h +++ b/libdimension-python/Vector.h @@ -34,3 +34,9 @@ bool dmnsn_py_init_VectorType(void); PyObject *dmnsn_py_Vector_cross(PyObject *self, PyObject *args); PyObject *dmnsn_py_Vector_dot(PyObject *self, PyObject *args); PyObject *dmnsn_py_Vector_proj(PyObject *self, PyObject *args); + +/* Vector constants */ +extern dmnsn_py_Vector dmnsn_py_Zero; +extern dmnsn_py_Vector dmnsn_py_X; +extern dmnsn_py_Vector dmnsn_py_Y; +extern dmnsn_py_Vector dmnsn_py_Z; diff --git a/libdimension-python/dimension.c b/libdimension-python/dimension.c index a238f31..e57767d 100644 --- a/libdimension-python/dimension.c +++ b/libdimension-python/dimension.c @@ -21,6 +21,7 @@ #include "dimension-python.h" #include "Vector.h" #include "Matrix.h" +#include "Color.h" #include "Scene.h" static PyObject * @@ -67,6 +68,7 @@ PyInit_dimension(void) { if (!dmnsn_py_init_VectorType() || !dmnsn_py_init_MatrixType() + || !dmnsn_py_init_ColorType() || !dmnsn_py_init_SceneType()) return NULL; @@ -77,29 +79,27 @@ PyInit_dimension(void) PyModule_AddObject(module, "Vector", (PyObject *)&dmnsn_py_VectorType); /* Vector constants */ - dmnsn_py_Vector *zero = PyObject_New(dmnsn_py_Vector, &dmnsn_py_VectorType); - dmnsn_py_Vector *x = PyObject_New(dmnsn_py_Vector, &dmnsn_py_VectorType); - dmnsn_py_Vector *y = PyObject_New(dmnsn_py_Vector, &dmnsn_py_VectorType); - dmnsn_py_Vector *z = PyObject_New(dmnsn_py_Vector, &dmnsn_py_VectorType); - if (!zero || !x || !y || !z) { - Py_XDECREF(zero); - Py_XDECREF(x); - Py_XDECREF(y); - Py_XDECREF(z); - Py_DECREF(module); - return NULL; - } - zero->v = dmnsn_zero; - x->v = dmnsn_x; - y->v = dmnsn_y; - z->v = dmnsn_z; - PyModule_AddObject(module, "Zero", (PyObject *)zero); - PyModule_AddObject(module, "X", (PyObject *)x); - PyModule_AddObject(module, "Y", (PyObject *)y); - PyModule_AddObject(module, "Z", (PyObject *)z); + PyModule_AddObject(module, "Zero", (PyObject *)&dmnsn_py_Zero); + PyModule_AddObject(module, "X", (PyObject *)&dmnsn_py_X); + PyModule_AddObject(module, "Y", (PyObject *)&dmnsn_py_Y); + PyModule_AddObject(module, "Z", (PyObject *)&dmnsn_py_Z); PyModule_AddObject(module, "Matrix", (PyObject *)&dmnsn_py_MatrixType); + PyModule_AddObject(module, "Color", (PyObject *)&dmnsn_py_ColorType); + + /* Color constants */ + PyModule_AddObject(module, "Black", (PyObject *)&dmnsn_py_Black); + PyModule_AddObject(module, "White", (PyObject *)&dmnsn_py_White); + PyModule_AddObject(module, "Clear", (PyObject *)&dmnsn_py_Clear); + PyModule_AddObject(module, "Red", (PyObject *)&dmnsn_py_Red); + PyModule_AddObject(module, "Green", (PyObject *)&dmnsn_py_Green); + PyModule_AddObject(module, "Blue", (PyObject *)&dmnsn_py_Blue); + PyModule_AddObject(module, "Magenta", (PyObject *)&dmnsn_py_Magenta); + PyModule_AddObject(module, "Orange", (PyObject *)&dmnsn_py_Orange); + PyModule_AddObject(module, "Yellow", (PyObject *)&dmnsn_py_Yellow); + PyModule_AddObject(module, "Cyan", (PyObject *)&dmnsn_py_Cyan); + PyModule_AddObject(module, "Scene", (PyObject *)&dmnsn_py_SceneType); return module; diff --git a/libdimension-python/tests/Makefile.am b/libdimension-python/tests/Makefile.am index 17589ba..48e1129 100644 --- a/libdimension-python/tests/Makefile.am +++ b/libdimension-python/tests/Makefile.am @@ -18,6 +18,7 @@ ########################################################################### TESTS = geometry.py \ + color.py \ demo.py TESTS_ENVIRONMENT = PYTHONPATH=$(top_builddir)/libdimension-python/.libs diff --git a/libdimension-python/tests/color.py b/libdimension-python/tests/color.py new file mode 100755 index 0000000..72056d1 --- /dev/null +++ b/libdimension-python/tests/color.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3 + +######################################################################### +# Copyright (C) 2010-2011 Tavian Barnes # +# # +# This file is part of The Dimension Test Suite. # +# # +# The Dimension Test Suite is free software; you can redistribute it # +# and/or modify it under the terms of the GNU General Public License as # +# published by the Free Software Foundation; either version 3 of the # +# License, or (at your option) any later version. # +# # +# The Dimension Test Suite 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 # +# General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +######################################################################### + +from dimension import * + +# Treat warnings as errors for tests +dieOnWarnings(True) + +c = Color(0, 0.5, 1, filter = 0.25, trans = 0.35) +assert repr(c) == 'dimension.Color(0.0, 0.5, 1.0, 0.25, 0.35)', repr(c) +assert str(c) == '', str(c) +assert c.red == 0, c.red +assert c.green == 0.5, c.green +assert c.blue == 1, c.blue +assert c.filter == 0.25, c.filter +assert c.trans == 0.35, c.trans + +c = Color(1, 0.5, 0) +assert str(c) == '', str(c) + +assert Black == Color(0, 0, 0), Black +assert White == Color(1, 1, 1), White +assert Clear == Color(0, 0, 0, trans = 1), Clear +assert Red == Color(1, 0, 0), Red +assert Green == Color(0, 1, 0), Green +assert Blue == Color(0, 0, 1), Blue +assert Magenta == Color(1, 0, 1), Magenta +assert Orange == Color(1, 0.5, 0), Orange +assert Yellow == Color(1, 1, 0), Yellow +assert Cyan == Color(0, 1, 1), Cyan + +assert White, bool(White) +assert not Black, not Black + +assert Red + Blue == Magenta, Red + Blue +assert 0.5*White == Color(0.5, 0.5, 0.5), 0.5*White -- cgit v1.2.3