Kiva Drawing In-depth¶
Kiva is a “stateful” drawing API. What this means is that the graphics context has a collection of state which affects the results of its drawing actions. Furthermore, Kiva enables this state to be managed with a stack such that state can be “pushed” onto the stack before making some temporary changes and then “popped” off the stack to restore the state to a version which no longer includes those changes.
Here is a list of all the pieces of state tracked by a Kiva graphics context, along with the methods which operate on them:
Affine transformation (
Fill color (
Stroke color (
Line width (
Line join style (
Line cap style (
Line dashing (
Global transparency (
Miter limit (
Image interpolation (
Text drawing mode (
Kiva has two colors in its graphics state: stroke color and fill color. Stroke
color is used for the lines in paths when the drawing mode is
EOF_FILL_STROKE. Fill color is used for text and for
the enclosed sections of paths when the drawing mode is
EOF_FILL_STROKE. Additionally, the fill color can be
set by the
Even though text uses the fill color, text will not be filled with a
gradient unless the text drawing mode is
TEXT_FILL_STROKE and even that
will only work if the backend supports it.
Color values should always be passed in as 3- or 4- tuples. The order of the
color components is
(R, G, B[, A]) and values must be floating point numbers
in the range [0, 1]. Even if a graphics context is not able to draw with alpha
blending, it’s still OK to pass a 4 component color value when setting state.
State Stack Management¶
Graphics context instances have two methods for saving and restoring the state,
save_state() (“push”) and
restore_state() (“pop”). That said,
it isn’t recommended practice to call the methods directly. Instead, you can
treat the graphics context object as a
and use the
with keyword to create a block of code where the graphics state
is temporarily modified. Using the context manager approach provides safety from
“temporary” modifications becoming permanent if an uncaught exception is raised
In Enable and Chaco, it is frequently the case that a graphics context instance will be passed into a method for the purpose of some drawing. Because it is not reasonable to push the responsibility of state management “up” the call stack, the onus is on the code making state modifications to do them safely so that other changes don’t leak into other code.
Well behaved code should take care to only modify graphics state inside a
First, the whole example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
import math from kiva import CAP_ROUND, CAP_SQUARE, JOIN_ROUND from kiva.image import GraphicsContext gc = GraphicsContext((600, 600)) gc.scale_ctm(2, 2) gc.translate_ctm(150, 150) gc.set_stroke_color((0.66, 0.88, 0.66)) gc.set_line_width(7.0) gc.set_line_join(JOIN_ROUND) gc.set_line_cap(CAP_SQUARE) for i in range(0, 12): theta = i*2*math.pi / 12.0 with gc: gc.rotate_ctm(theta) gc.translate_ctm(105, 0) gc.set_stroke_color((1 - (i / 12), math.fmod(i / 6, 1), i / 12)) gc.set_line_width(10.0) gc.set_line_cap(CAP_ROUND) gc.rect(0, 0, 25, 25) gc.stroke_path() with gc: gc.rotate_ctm(theta) gc.translate_ctm(20, 0) gc.move_to(0, 0) gc.line_to(80, 0) gc.stroke_path() gc.save("state_ex.png")
The first part sets up the default graphics state. Here, that includes a scale of 2 in X and Y, a translation of (150, 150) which is affected by the preceeding scale transformation, and some line properties: stroke color, width, join, and cap:
7 8 9 10 11 12 13
gc.scale_ctm(2, 2) gc.translate_ctm(150, 150) gc.set_stroke_color((0.66, 0.88, 0.66)) gc.set_line_width(7.0) gc.set_line_join(JOIN_ROUND) gc.set_line_cap(CAP_SQUARE)
Then in a loop, we draw twice (the two
stroke_path() calls). The first
draw uses a
with block to temporarily modify the drawing state. It adds more
affine transformations: a rotate and a translate. It also changes some line
properties: stroke color, width, and cap. A rectangle is then added to the
current path and stroked.
17 18 19 20 21 22 23 24
with gc: gc.rotate_ctm(theta) gc.translate_ctm(105, 0) gc.set_stroke_color((1 - (i / 12), math.fmod(i / 6, 1), i / 12)) gc.set_line_width(10.0) gc.set_line_cap(CAP_ROUND) gc.rect(0, 0, 25, 25) gc.stroke_path()
After leaving the first
with block, the state is now restored to its
default. A new
with block is entered and the current transformation matrix
is modified with the same rotation as the first drawing block, but a
different translation is applied. The line properties are unchanged
and so use the defaults set at the top.
26 27 28 29 30 31
with gc: gc.rotate_ctm(theta) gc.translate_ctm(20, 0) gc.move_to(0, 0) gc.line_to(80, 0) gc.stroke_path()
A path is a collection of geometric objects that can be drawn in a graphics context with coloring and an affine transformation applied to it. It is the basic unit of drawing in a graphics context.
Every graphics context instance has a current path which can be manipulated by
the Path functions. However, some drawing operations are easier to
implement with an independent path instance
An independent path instance can be created in two ways. The first is via the
GraphicsContext.get_empty_path() method. The second method is to use
CompiledPath class imported from the backend being used. The
interface of a
CompiledPath instance is the same as the
Path functions (modulo
Once you have a path object, it can be drawn by adding it to the graphics
context with the
GraphicsContext.add_path() method (which adds the path
to the current path) and then calling any of the Drawing functions
which operate on the current path.
For certain backends which support it, the
GraphicsContext.draw_path_at_points() method can be used to draw a
path object at many different positions with a single function call.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
from math import pi from kiva.image import GraphicsContext gc = GraphicsContext((600, 600)) path = gc.get_empty_path() path.move_to(10, 40) path.line_to(60, 40) path.line_to(60, 90) path.close_path() gc.scale_ctm(2, 2) gc.translate_ctm(150, 150) for i in range(0, 12): gc.rotate_ctm(2*pi / 12.0) gc.set_fill_color((i / 12.0, 0.0, 1.0 - (i / 12.0))) gc.add_path(path) gc.fill_path() gc.save("compiled_path_ex.png")
Kiva Image Rendering¶
Drawing images in kiva is accomplished via
GraphicsContext.draw_image(). A unique feature of drawing images
(relative to path drawing) is that you can apply an arbitrary translation and
scaling to the image without involving the current transformation matrix.
The signature for
draw_image() is straightforward:
Render an image into a rectangle
image – An image. Can be a numpy array, a PIL
Imageinstance, or another
rect – A tuple (x, y, w, h). If not specified then the bounds of the the graphics context are used as the rectangle.
image object that is passed to
draw_image() can be a numpy
array, a PIL
GraphicsContext instance of the same backend. If
image is a
numpy array, it is typically converted to a more convenient format via
Therefore, one must be careful about the expected pixel format of the image. If
your image is rendering with incorrect colors, this might be the problem.
Passing the other allowed versions of
image should give a more consistent
image contains an alpha channel and transparent or translucent pixels,
this transparency should also be honored by the destination graphics context.
However, not all backends may support this.
rect argument to
draw_image(), if it is not
specified then the bounding rectangle of the graphics context will be used. As
rect can be used to apply an arbitrary translation and
scaling to an image. The translation is the x,y position of the rectangle and
the scaling is the ratio of the image’s width and height to those of the
rectangle. In every case,
rect will be transformed by the current
If you only want to draw a subset of an image, you should pass only that subset
draw_image(). The Kiva API does not support defining a “source”
rectangle when drawing images, only a “destination”.
If drawing images with some scaling applied, one might wish to have control
over the interpolation used when drawing the image. This can be accomplished
set_image_interpolation() is currently only implemented by the
kiva.agg backend. Other backends may have the method, but it is
effectively a no-op.
One can also save the contents of a graphics context to an image. This is done
save(filename, file_format=None, pil_options=None)
Save the graphics context to a file
Data is always saved in RGB or RGBA format, and converted to that format if not already in it.
file_formatargument is None, then the file format is inferred from the
filenameextension, and so is not usually needed.
pil_optionsargument is a dictionary of format-specific options that can be passed directly to PIL’s image file writers. For example, this can be used to control the compression level of JPEG or PNG output. Unrecognized options are silently ignored.
Kiva Text Rendering¶
Drawing text in kiva is accomplished via a few methods on
GraphicsContext. There are three basic topics: selecting a font,
measuring the size of rendered text, and drawing the text.
Font selection for use with the text rendering capabilities of
GraphicsContext can be accomplished in a few different ways depending
on the amount of control needed by your drawing code.
The simplest form of font selection is the
GraphicsContext.select_font() method. The tradeoff for this simplicity
is that you’re at the mercy of the backend’s font lookup. If your desired font
isn’t available from the system you’re using, it’s not defined what you will end
name is the name of the desired font: “Helvetica Regular”,
“Futura Medium Italic”, etc.
size is the size in points.
Supported backends: cairo, celiagg, pdf, ps, qpainter, quartz, svg.
KivaFont trait and
If you’re already doing your drawing within an application using traits, you can
KivaFont traits are initialized with a string which describes the font:
“Times Italic 18”, “Courier Bold 10”, etc. The value of the trait is a
kiva.fonttools.font.Font instance which can be passed to the
Supported backends: all backends
If you don’t want to rely on the font description parsing in
can also manually construct a
kiva.fonttools.font.Font instance. Once
you have a
Font instance, it can be passed to the
Font(face_name="", size=12, family=SWISS, weight=NORMAL, style=NORMAL)
face_name is the font’s name: “Arial”, “Webdings”, “Verdana”, etc.
size is the size in points
family is a constant from
kiva.constants. Pick from
face_name is empty, the value of
family will be used to select the
weight is a constant from
kiva.constants. Pick from
style is a constant from
kiva.constants. Pick from
Before drawing text, one often wants to know what the bounding rectangle of the
rendered text will be so that the text can be positioned correctly. To do this,
GraphicsContext.get_text_extent() method is used.
get_text_extent(text) -> (x, y, width, height)
text is the string that you want to measure. The currently selected font
will be used, so it’s important to set the font before calling this method.
The return value is a
tuple which describes a rectangle with its bottom-left
corner at (x, y) and a width and height. The rectangle is relative to the
origin and not affected by the currently set text transform. The bottom of the
rectangle won’t always be 0, depending on the font. It might be a negative
number in the situation where glyphs hang below the baseline. In any case,
y = 0 is the baseline for the rendered glyphs.
get_text_extent does not respect endline characters. It is assumed that
text describes a single line of text. To render multiple lines, one
should split the text into individual lines first and then measure and draw
each line in sequence. A blank line’s height should be the same as the
height of the selected font.
Text can be drawn in a graphics context with the
show_text_at_point(text, x, y)
show_text with a
point=(x, y) argument both
do the same thing: Draw a line of text at the given (x, y) coordinate, which
represents the horizontal position of the first glyph and the baseline position,
show_text is used without a
point argument, then the current text
position of the graphics context is used. This position can be set via the
GraphicsContext.set_text_position() method. Relatedly, the text
position can be retrieved with the
There is also a
GraphicsContext.set_text_matrix() method which
allows a text-specific affine transform to be set. Unfortunately it’s not
implemented uniformly across backends, so it’s recommended not to use it.