Source code for enable.brush

# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

""" Brush classes to make filling regions cleaner. """

from abc import abstractmethod
from operator import attrgetter

from numpy import array

from traits.api import (
    ABCHasStrictTraits, ArrayOrNone, Enum, Event, Float, HasStrictTraits,
    Instance, List, Range, Tuple, observe
)

from enable.colors import ColorTrait


[docs]class Brush(ABCHasStrictTraits): """ A brush describes how to fill the pixels of a region. To use a brush when drawing, call set_brush with the current gc as the argument. """ #: A trait which fires when the brush is updated. updated = Event()
[docs] @abstractmethod def set_brush(self, gc): """ Apply the brush settings to a graphics context. Parameters ---------- gc : graphics context The graphics context to use the brush with. """ raise NotImplementedError()
[docs] @observe("+update") def observe_update_traits(self, event): self.updated = True
[docs]class ColorBrush(Brush): """ A simple brush that paints a solid color. """ #: The color to brush the region with. color = ColorTrait("transparent", update=True)
[docs] def set_brush(self, gc): """ Apply the brush settings to a graphics context. This sets the fill color of the GC to the specified color. Parameters ---------- gc : graphics context The graphics context to use the brush with. """ gc.set_fill_color(self.color_)
[docs]class ColorStop(HasStrictTraits): """ A point on a gradient with a fixed color. """ #: The position of the color stop in the gradient, between 0.0 and 1.0. offset = Range(0.0, 1.0, update=True) #: The color at the color stop. color = ColorTrait("transparent", update=True) #: A trait which fires when the color stop is updated. updated = Event()
[docs] def to_array(self): """ Return an array which represents the color stop. This is the raw form of the color stop required by Kiva. Returns ------- stop_array : numpy array Return an array of (offset, r, b, b, a). """ return array([self.offset] + list(self.color_))
[docs] @observe("+update") def observe_update_traits(self, event): self.updated = True
[docs]class Gradient(HasStrictTraits): """ A color gradient. """ #: The sequence of color stops for the gradient. stops = List(Instance(ColorStop)) #: A trait which fires when the gradient is updated. updated = Event() #: A temporary cache for the stop array. _array_cache = ArrayOrNone()
[docs] def to_array(self): """ Return a sorted list of stop arrays. This is the raw form of the stops required by Kiva. Returns ------- stop_array_list : arrays A list of array of (offset, r, b, b, a) values corresponding to the color stops. This array should not be mutated. """ if self._array_cache is None: self._array_cache = array([ stop.to_array() for stop in sorted(self.stops, key=attrgetter("offset")) ]) return self._array_cache
[docs] @observe("stops.items.updated") def observe_stops(self, event): self._array_cache = None self.updated = True
def _stops_default(self): return [ ColorStop(offset=0.0, color="white"), ColorStop(offset=1.0, color="black"), ]
[docs]class GradientBrush(Brush): """ A brush that paints a color gradient. """ #: The sequence of color stops for the gradient. gradient = Instance(Gradient, args=(), allow_none=False, update=True) #: How the gradient extends beyond the main area of the gradient. #: #: pad #: Extend using the first and last colors. #: reflect #: Extend using a reflection of the gradient. #: repeat #: Extend by repeating the gradient. spread_method = Enum("pad", "reflect", "repeat", update=True) #: The coordinate space used by the gradient points. #: #: userSpaceOnUse #: Coordinates are specified in the current graph context's coordinate #: system. #: objectBoundingBox #: Coordinates are specified between 0.0 and 1.0 and are relative to #: the bounding box of the path being filled. units = Enum("userSpaceOnUse", "objectBoundingBox", update=True)
[docs] @observe("gradient:updated") def observe_stops(self, event): self.updated = True
[docs]class LinearGradientBrush(GradientBrush): """ A brush that paints a linear color gradient. """ #: The start point of the linear gradient. start = Tuple(Float, Float, update=True) #: The stop point of the linear gradient. end = Tuple(Float, Float, update=True)
[docs] def set_brush(self, gc): """ Apply the brush settings to a graphics context. This calls linear_gradient on the GC with the appropriate values. Parameters ---------- gc : graphics context The graphics context to use the brush with. """ gc.linear_gradient( *self.start, *self.end, self.gradient.to_array(), self.spread_method, self.units, )
[docs]class RadialGradientBrush(GradientBrush): """ A brush that paints a radial color gradient. """ #: The center point of the radial gradient. center = Tuple(Float, Float, update=True) #: The radius of the radial gradient. radius = Float(update=True) #: The focus point of the radial gradient. focus = Tuple(Float, Float, update=True)
[docs] def set_brush(self, gc): """ Apply the brush settings to a graphics context. This calls radial_gradient on the GC with the appropriate values. Parameters ---------- gc : graphics context The graphics context to use the brush with. """ gc.radial_gradient( *self.center, self.radius, *self.focus, self.gradient.to_array(), self.spread_method, self.units, )