From 176054363582b08aaf93bb4fcc0f26521c0f884c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 4 Dec 2011 18:43:52 -0500 Subject: Use a separate class for sRGB colors. --- dimension/client.py.in | 3 +- dimension/tests/complex.dmnsn | 14 ++-- dimension/tests/demo.dmnsn | 6 +- libdimension-python/tests/color.py | 7 +- libdimension-python/tests/demo.py | 3 +- libdimension-python/wrapper.pyx | 162 ++++++++++++++++++++++--------------- 6 files changed, 114 insertions(+), 81 deletions(-) diff --git a/dimension/client.py.in b/dimension/client.py.in index 2eaf3c3..9f68198 100644 --- a/dimension/client.py.in +++ b/dimension/client.py.in @@ -107,7 +107,8 @@ def main(): "objects" : [], "lights" : [], "camera" : PerspectiveCamera(), - "default_texture" : Texture(finish = Ambient(0.1) + Diffuse(0.7)), + "default_texture" : Texture(finish = Ambient(sRGB(0.1)) + + Diffuse(sRGB(0.7))), "default_interior" : Interior(), "background" : Black, "recursion_limit" : None, diff --git a/dimension/tests/complex.dmnsn b/dimension/tests/complex.dmnsn index b081d14..a5a3ec2 100644 --- a/dimension/tests/complex.dmnsn +++ b/dimension/tests/complex.dmnsn @@ -20,10 +20,10 @@ camera = PerspectiveCamera(location = (3, 6, -11), look_at = 0) -background = 0.5*Color(0.73, 0.90, 0.97) +background = 0.5*sRGB(0.73, 0.90, 0.97) def make_light(x, y, z): - return PointLight(location = (x, y, z), color = 1/4*White) + return PointLight(location = (x, y, z), color = White/4) for x in [-3, -1, 1, 3]: for y in [0, 5]: @@ -34,8 +34,8 @@ objects.append( normal = Y, distance = -4, texture = Texture( - pigment = Color(0.73, 0.90, 0.97), - finish = Ambient(0.5), + pigment = sRGB(0.73, 0.90, 0.97), + finish = Ambient(sRGB(0.5)), ) ) ) @@ -53,8 +53,10 @@ def make_sphere(x, y, z, size): radius = 2/size, texture = Texture( - pigment = Color(x/size, y/size, z/size), - finish = Ambient(0.25) + Diffuse(0.8) + Reflection(0.0, 0.25) + pigment = sRGB(x/size, y/size, z/size), + finish = Ambient(sRGB(0.25)) + + Diffuse(sRGB(0.8)) + + Reflection(0, sRGB(0.5)) ) ) diff --git a/dimension/tests/demo.dmnsn b/dimension/tests/demo.dmnsn index 3a04abc..75a97a7 100644 --- a/dimension/tests/demo.dmnsn +++ b/dimension/tests/demo.dmnsn @@ -33,7 +33,7 @@ background = PigmentMap( pattern = Gradient(Y), map = { 0: image_map, - 0.35: Color(0, 0.1, 0.2, trans = 0.1, filter = 0.0), + 0.35: sRGB(0, 0.1, 0.2, trans = 0.1, filter = 0.0), }, ) @@ -50,8 +50,8 @@ hollow_cube = Difference( (-1, -1, -1), (1, 1, 1), texture = Texture( - pigment = Color(0, 0, 1, trans = 0.75, filter = 1/3), - finish = Reflection(min = 0, max = 0.5), + pigment = sRGB(0, 0, 1, trans = 0.75, filter = 1/3), + finish = Reflection(min = 0, max = sRGB(0.5)), ), interior = Interior( ior = 1.1, diff --git a/libdimension-python/tests/color.py b/libdimension-python/tests/color.py index 1299a88..9482545 100755 --- a/libdimension-python/tests/color.py +++ b/libdimension-python/tests/color.py @@ -26,8 +26,7 @@ die_on_warnings(True) c = Color(0, 0.5, 1, trans = 0.5, filter = 0.35) assert repr(c) == "dimension.Color(0.0, 0.5, 1.0, 0.5, 0.35)", repr(c) -assert str(c) == "", str(c) +assert str(c) == "Color<0.0, 0.5, 1.0, trans = 0.5, filter = 0.35>", str(c) assert c.red == 0, c.red assert c.green == 0.5, c.green assert c.blue == 1, c.blue @@ -35,7 +34,7 @@ assert c.trans == 0.5, c.filter assert c.filter == 0.35, c.trans c = Color(1, 0.5, 0) -assert str(c) == "", str(c) +assert str(c) == "Color<1.0, 0.5, 0.0>", str(c) assert Black == Color(0, 0, 0), Black assert White == Color(1, 1, 1), White @@ -44,7 +43,7 @@ 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 Orange == sRGB(1, 0.5, 0), Orange assert Yellow == Color(1, 1, 0), Yellow assert Cyan == Color(0, 1, 1), Cyan diff --git a/libdimension-python/tests/demo.py b/libdimension-python/tests/demo.py index 09ae184..d290a7f 100755 --- a/libdimension-python/tests/demo.py +++ b/libdimension-python/tests/demo.py @@ -49,7 +49,8 @@ scene = Scene(canvas = canvas, objects = objects, lights = lights, camera = camera) -scene.default_texture = Texture(finish = Ambient(0.1) + Diffuse(0.7)) +scene.default_texture = Texture(finish = Ambient(sRGB(0.1)) + + Diffuse(sRGB(0.7))) scene.background = background scene.adc_bailout = 1/255 scene.recursion_limit = 5 diff --git a/libdimension-python/wrapper.pyx b/libdimension-python/wrapper.pyx index 6ec7c15..8e48a5c 100644 --- a/libdimension-python/wrapper.pyx +++ b/libdimension-python/wrapper.pyx @@ -396,20 +396,13 @@ cdef class _Transformable: # Colors # ########## -cdef class Color: - """ - An sRGB color. - - Note that 0.5*White == Color(0.5, 0.5, 0.5), which is not technically a half- - intensity white, due to sRGB gamma. Dimension handles the gamma correctly - when rendering, though. - """ +cdef class _BaseColor: cdef dmnsn_color _c - cdef dmnsn_color _sRGB + cdef dmnsn_color _clin def __init__(self, *args, **kwargs): """ - Create a Color. + Create a color. Keyword arguments: red -- The red component @@ -422,56 +415,72 @@ cdef class Color: tuple or other sequence (red, green, blue[, trans[, filter]]). """ if len(args) == 1: - if isinstance(args[0], Color): - self._sRGB = (args[0])._sRGB + if isinstance(args[0], _BaseColor): + self._clin = (<_BaseColor>args[0])._clin + self._unlinearize() + return elif hasattr(args[0], "__iter__"): self._real_init(*args[0]) else: - self._sRGB = dmnsn_color_mul(args[0], dmnsn_white) + self._c = dmnsn_color_mul(args[0], dmnsn_white) else: self._real_init(*args, **kwargs) - self._c = dmnsn_color_from_sRGB(self._sRGB) + self._linearize() def _real_init(self, double red, double green, double blue, double trans = 0.0, double filter = 0.0): - self._sRGB = dmnsn_new_color5(red, green, blue, trans, filter) + self._c = dmnsn_new_color5(red, green, blue, trans, filter) property red: """The red component.""" def __get__(self): - return self._sRGB.R + return self._c.R property green: """The green component.""" def __get__(self): - return self._sRGB.G + return self._c.G property blue: """The blue component.""" def __get__(self): - return self._sRGB.B + return self._c.B property trans: """The transparency of the color.""" def __get__(self): - return self._sRGB.trans + return self._c.trans property filter: """How filtered the transparency is.""" def __get__(self): - return self._sRGB.filter + return self._c.filter def intensity(self): return dmnsn_color_intensity(self._c) def gray(self): - return _Color(dmnsn_color_mul(self.intensity(), dmnsn_white)) + return _Color(dmnsn_color_mul(self.intensity(), dmnsn_white), type(self)) def __add__(lhs, rhs): - return _sRGBColor(dmnsn_color_add(Color(lhs)._sRGB, Color(rhs)._sRGB)) + if isinstance(lhs, _BaseColor): + if isinstance(rhs, _BaseColor): + if type(lhs) is not type(rhs): + return NotImplemented + else: + rhs = type(lhs)(rhs) + elif isinstance(rhs, _BaseColor): + lhs = type(rhs)(lhs) + else: + return NotImplemented + + return _Color(dmnsn_color_add((<_BaseColor>lhs)._c, (<_BaseColor>rhs)._c), + type(lhs)) + def __mul__(lhs, rhs): - if isinstance(lhs, Color): - return _sRGBColor(dmnsn_color_mul(rhs, (lhs)._sRGB)) + if isinstance(lhs, _BaseColor): + return _Color(dmnsn_color_mul(rhs, (<_BaseColor>lhs)._c), type(lhs)) else: - return _sRGBColor(dmnsn_color_mul(lhs, (rhs)._sRGB)) - def __truediv__(Color lhs not None, double rhs): - return _sRGBColor(dmnsn_color_mul(1/rhs, lhs._sRGB)) + return _Color(dmnsn_color_mul(lhs, (<_BaseColor?>rhs)._c), type(rhs)) + + def __truediv__(_BaseColor lhs not None, double rhs): + return _Color(dmnsn_color_mul(1/rhs, lhs._c), type(lhs)) def __richcmp__(lhs, rhs, int op): cdef clhs = Color(lhs) @@ -493,41 +502,63 @@ cdef class Color: return NotImplemented def __repr__(self): - return "dimension.Color(%r, %r, %r, %r, %r)" % \ - (self.red, self.green, self.blue, self.trans, self.filter) - + return "dimension.%s(%r, %r, %r, %r, %r)" % \ + (type(self).__name__, self.red, self.green, self.blue, self.trans, + self.filter) def __str__(self): if self.trans >= dmnsn_epsilon: - return "" % \ - (self.red, self.green, self.blue, self.trans, self.filter) + return "%s<%s, %s, %s, trans = %s, filter = %s>" % \ + (type(self).__name__, + self.red, self.green, self.blue, self.trans, self.filter) else: - return "" % \ - (self.red, self.green, self.blue) - -cdef Color _sRGBColor(dmnsn_color sRGB): - """Wrap a color object around a dmnsn_color already in sRGB.""" - cdef Color self = Color.__new__(Color) - self._sRGB = sRGB - self._c = dmnsn_color_from_sRGB(sRGB) - return self + return "%s<%s, %s, %s>" % \ + (type(self).__name__, self.red, self.green, self.blue) + +cdef class Color(_BaseColor): + """ + An object or light color. -cdef Color _Color(dmnsn_color c): - """Wrap a Color object around a dmnsn_color.""" - cdef Color self = Color.__new__(Color) - self._c = c - self._sRGB = dmnsn_color_to_sRGB(c) + These colors are in a linear RGB space. For colors in the sRGB space, which + is used by the web and computer displays, see the sRGB class. + """ + + def _linearize(self): + self._clin = self._c + def _unlinearize(self): + self._c = self._clin + +cdef class sRGB(_BaseColor): + """ + An sRGB color. + + Color operations with these colors occur in sRGB space, which is used by the + web and computer displays. However, it is not linear, so (for example) two + lights with intensity sRGB(0.5) is not the same as one light with intensity + sRGB(1). + """ + + def _linearize(self): + self._clin = dmnsn_color_from_sRGB(self._c) + def _unlinearize(self): + self._c = dmnsn_color_to_sRGB(self._clin) + +cdef _BaseColor _Color(dmnsn_color c, type t): + """Wrap a _BaseColor subclass around a dmnsn_color.""" + cdef _BaseColor self = t.__new__(t) + self._c = c + self._linearize() return self -Black = _Color(dmnsn_black) -White = _Color(dmnsn_white) -Clear = _Color(dmnsn_clear) -Red = _Color(dmnsn_red) -Green = _Color(dmnsn_green) -Blue = _Color(dmnsn_blue) -Magenta = _Color(dmnsn_magenta) -Orange = _Color(dmnsn_orange) -Yellow = _Color(dmnsn_yellow) -Cyan = _Color(dmnsn_cyan) +Black = _Color(dmnsn_black, Color) +White = _Color(dmnsn_white, Color) +Clear = _Color(dmnsn_clear, Color) +Red = _Color(dmnsn_red, Color) +Green = _Color(dmnsn_green, Color) +Blue = _Color(dmnsn_blue, Color) +Magenta = _Color(dmnsn_magenta, Color) +Orange = _Color(dmnsn_orange, Color) +Yellow = _Color(dmnsn_yellow, Color) +Cyan = _Color(dmnsn_cyan, Color) ############ # Canvases # @@ -629,7 +660,7 @@ cdef class _CanvasProxy: return self._canvas.height def __getitem__(self, int y): self._bounds_check(y) - return _Color(dmnsn_canvas_get_pixel(self._canvas, self._x, y)) + return _Color(dmnsn_canvas_get_pixel(self._canvas, self._x, y), Color) def __setitem__(self, int y, color): self._bounds_check(y) dmnsn_canvas_set_pixel(self._canvas, self._x, y, Color(color)._c) @@ -831,27 +862,26 @@ cdef class Ambient(Finish): cdef class Diffuse(Finish): """Lambertian diffuse reflection.""" - def __init__(self, double diffuse): + def __init__(self, diffuse): """ Create a Diffuse finish. Keyword arguments: diffuse -- the intensity of the diffuse reflection """ - self._finish.diffuse = dmnsn_new_lambertian( - dmnsn_sRGB_inverse_gamma(diffuse) - ) + self._finish.diffuse = dmnsn_new_lambertian(Color(diffuse).intensity()) cdef class Phong(Finish): """Phong specular highlight.""" - def __init__(self, double strength, double size = 40.0): + def __init__(self, strength, double size = 40.0): """ Create a Phong highlight. + + Keyword arguments: + strength -- the strength of the Phong highlight + size -- the "shininess" of the material """ - self._finish.specular = dmnsn_new_phong( - dmnsn_sRGB_inverse_gamma(strength), - size - ) + self._finish.specular = dmnsn_new_phong(Color(strength).intensity(), size) cdef class Reflection(Finish): """Reflective finish.""" -- cgit v1.2.3