OOF: Finite Element Analysis of Microstructures
OOFCanvas Manual
This is the manual for OOFCanvas 1.1.2.
Please see the Disclaimer and Copyright notice at the bottom of this document.
OOFCanvas is a replacement for libgnomecanvas, designed for use in OOF2, but hopefully useful elsewhere. OOFCanvas is based on Cairo and is compatible with gtk3. It might eventually also be compatible with gtk+2 or gtk4. It can be called from C++, Python2, or Python3.
OOF2 used libgnomecanvas to display and interact with images and meshes. But libgnomecanvas requires gtk+2, and gtk+2 works only with Python3, not Python2, and Python2 is being phased out. In order to upgrade OOF2 to Python3, we need to first upgrade it from gtk+2 to gtk+3, and to do that we need to first replace libgnomecanvas.
The canvas is a drawing area that can display a variety of shapes, including text. It can be scrolled, zoomed, and printed. It can report which shapes are drawn at a mouse click location.
OOFCanvas is not a drop-in replacement for libgnomecanvas. It’s also not a full-fledged gtk widget. It’s a set of classes that does some of what libgnomecanvas did and uses gtk.
All of the code is in C++. Wrappers for Python 2 or 3 are generated by SWIG.
Contents
- Installation
- Programming
- A Simple Example
- Details of the Classes
- Appendix: Debugging Tools
- Appendix: Adding New CanvasItem Subclasses
- Appendix: Adding New RubberBand classes
- Appendix: Internal details
- Disclaimer
Installation
Prequisites
Before installing OOFCanvas, install the following programs and libraries. If you’re using a package manager that packages header files in separate developer packages, install the developer packages. Headers aren’t necessary for cmake, SWIG, or pkg-config.
- A C++ compiler
- cmake, version 3.18 or later
- SWIG, version 4.0 or later, and its python module if provided separately
- pkg-config
- Python, version 2.7, or 3.8 or later
- Gtk3, version 3.22.0 or later, but not 4.0 or later
- PyGObject, version 3.22 or later
- CairoMM, version 1.12 or later
- Pango, version 1.40 or later
- PangoCairo, version 1.40 or later
If you want OOFCanvas to display images loaded by the ImageMagick library, optionally install
- ImageMagick, version 6.0 or later. (OOFCanvas has not been tested with ImageMagick 7.) It must be ImageMagick, not GraphicsMagick. Make sure to install the C++ header files for Magick++.
If you want to try OOFCanvas’s experimental support for numpy and scikit-image images, you will need to also install
- NumPy version 1.21 or later
- scikit-image version 0.20 or later
but note that OOF2 does not work well if numpy support is enabled in OOFCanvas, at least in OOF2 version 2.3.0
We don’t really know the minimum acceptable version numbers for the prerequisites. The listed versions are the ones that we’ve been able to use and test. It’s quite possible that earlier versions will work as well.
For detailed instructions on installing the prerequisites using package managers on various systems are on the OOFCanvas prerequisites page. Those instructions aren’t included in this file because they may change and aren’t under our control. This file is included in the OOFCanvas distribution and we don’t want to create a new version everytime installation instructions need to be updated.
Installing OOFCanvas
After installing the prerequisites, build and install OOFCanvas by following these steps. Lines beginning with ‘%’ should be typed in a terminal window. Type everything after the initial ‘%’.
Download the latest OOFCanvas tar (
.tar.gz) file fromhttps://www.ctcms.nist.gov/oof/oofcanvas/Create a working directory. In your home directory or some other convenient location, enter
% mkdir oofcanvas % cd oofcanvasUnpack the tar file. If it’s in your Downloads directory, type
% tar -xf ~/Downloads/oofcanvas-1.1.0.tar.gz(changing the version number if necessary). This will create a directory called
oofcanvas-1.1.0, or something like that.Create a build directory.
% mkdir build % cd buildConfigure OOFCanvas by running ccmake:
% ccmake ../oofcanvas-1.1.0Type “c” to start the configuration.
Use the up and down arrow keys to move between fields. To change a text field, press return and type a value. You can use the left and right arrow keys to move around within the existing txt. To accept your changes, press return. To discard your changes, press escape. To toggle a boolean (ON/OFF) field, press return. In a field that takes a preset list of values, the left and right arrow keys cycle through the possibilities.
Set
CMAKE_BUILD_TYPEtoRelease.Change
CMAKE_INSTALL_PREFIXto the location where you want OOFCanvas to be installed. The default value is probably a system directory which you don’t have permission to modify. Setting the prefix to your home directory (~) is a good choice. If you’re installing into an Anaconda enviroment namedOOF2, setCMAKE_INSTALL_PREFIXto~/Anaconda3/envs/OOF2Set
OOFCANVAS_PYTHON_APItoPython2orPython3if you want to generate the Python interface for OOFCanvas. Set it toNoneif you don’t need Python. Leave it atPython3if you’re using OOFCanvas with OOF2.If you’re using Python3, set
OOFCANVAS_PYTHON3_VERSIONto the Python3 version number by using the right and left arrows to flip through the versions. The default value,Latest, tells cmake to use the latest version it finds. Make sure to choose the same version number that you used when installing the OOFCanvas prerequisites.If you have just switched
OOFCANVAS_PYTHON_APItoPython3, you will have to type “c” beforeOOFCANVAS_PYTHON3_VERSIONappears in the list of settings.Set
OOFCANVAS_SWIG_VERSIONto the version of SWIG that you have.Set
OOFCANVAS_USE_IMAGEMAGICKtoONif you want to be able to use the ImageMagick library to load image files into the canvas.If you want to be able to display images contained in NumPy arrays, set
OOFCANVAS_USE_NUMPYtoON. You will first need to enable advanced settings by typing “t”. This feature is not currently recommended if you’re using OOFCanvas with OOF2.Type “c” again to re-configure.
Type “g” to generate the build scripts.
See the CMake manual for full instructions on how to use ccmake.
Build OOFCanvas:
% makeInstall OOFCanvas:
If
CMAKE_INSTALL_PREFIXwas set to a system directory, type% sudo make installotherwise type
% make installThis will create shared libraries called
liboofcanvas*.soorliboofcanvas*.dylibin<prefix>/lib, a directory calledoofcanvasin<prefix>/lib/pythonX.Y/site-packages(where X.Y is your python version number), a file calledoofcanvas.pcin<prefix>/lib/pkgconfig, and a directory calledoofcanvasin<prefix>/include.When building a program that uses OOFCanvas, use the compiler and linker options provide by
pkg-config oofcanvas:% c++ `pkg-config --cflags oofcanvas` -c myfile.cpp ... % c++ `pkg-config --libs oofcanvas` myfile.o ... -o myappIf you’ve installed OOFCanvas in a nonstandard location (such as an Anaconda environment or your home directory), you may have to tell pkg-config where to find it by setting the environment variable
PKG_CONFIG_PATH, e.g.% export PKG_CONFIG_PATH=<prefix>/lib/pkgconfigwhere
<prefix>is the value ofCMAKE_INSTALL_PREFIXyou used when configuring OOFCanvas.When running a program that uses the OOFCanvas python interface, you may need to tell python where to find the OOFCanvas modules, e.g
% export PYTHONPATH=<prefix>/lib/python3.10/site-packagesif you’re using python 3.10.
Uninstalling OOFCanvas
Go to the build directory and run make uninstall. This
deletes all the installed files but unfortunately leaves empty
directories behind.
Programming with OOFCanvas
All the classes and functions described here are defined in the C++
OOFCanvas namespace. For simplicity we haven’t included it
explicitly in the discussion below.
Header Files
<prefix>/include/oofcanvas/oofcanvas.h declares
all OOFCanvas classes, functions, and constants. If your program uses
pkg-config oofcanvas to build, then you can use
#include <oofcanvas.h> // does not include any gtk codeor
#include <oofcanvasgui.h> // includes the gtk code as wellin C++, or
import oofcanvas
from oofcanvas import oofcanvasguiin Python. If you don’t include/import the oofcanvasgui components,
you can use still use the OffScreenCanvas and save its
contents, but can’t display it on the screen.
Initialization
When using OOFCanvas in Python, it must be initialized by calling
init_OOFCanvas(threaded)before any other calls. Calling init_OOFCanvas more than
once is harmless. The threaded argument is a boolean value
indicating whether or not OOFCanvas will be used in a multithreaded
environment.
Class Overview
In general, you create a Canvas object and add it to your Gtk3 user interface (or an OffScreenCanvas if you don’t have a GUI). The Canvas contains CanvasLayers, and CanvasLayers contain CanvasItems, such as lines, circles, and text. Items have positions, sizes, and colors, among other attributes. The Canvas can be zoomed and scrolled.
Mouse clicks and motions on the canvas can invoke a callback function.
Coordinate Systems
There are two important coordinates systems: user coordinates and pixel coordinates.
Pixel coordinates measure distance in pixels, with x increasing from left to right and y increasing from top to bottom. The origin is at the upper left corner of the Canvas, which may or may not be visible on the screen. In pixel coordinates, a screen pixel is 1.0 x 1.0 units.
Items drawn on the canvas are specified in user coordinates, which may be anything convenient to the user. x goes from left to right on the screen, and y goes from bottom to top. This is not the convention in many graphics libraries, but is standard in math, physics, and other parts of the real world.
The conversion from user to pixel coordinates depends on the size of
the canvas and the current zoom factor, and determines the
ppu (pixels per unit). Almost all objects in OOFCanvas are
specified in user coordinates, so the user does not need to worry about
the pixel coordinate system at all. The one exception is that the
sizes of some objects can be specified in pixels.
The Canvas Classes
Three kinds of Canvas objects are defined.
OffScreenCanvasis the base class. It can be used to make drawings that will be printed or saved to a file, but not displayed.Canvasis derived fromOOFScreenCanvas. It creates aGtk.Layoutwhich can be used in Gtk3 to put theCanvasin a GUI. It calls user-provided callback functions in response to mouse events.A slightly different
Canvasclass is available in Python. It’s derived in Python from a SWIG-wrapped C++ class calledPythonCanvas. The main difference between the C++ and PythonCanvasclasses is that the Python class expects callback functions to be Python methods, and theGtkLayoutis created in Python.
The pixel size of a Canvas or PythonCanvas
is determined by the Gtk window that it’s part of. The pixel size of an
OffScreenCanvas is only computed when it’s saved as an
image and the size of the image is given.
The CanvasLayer Class
Drawing is done by creating one or more CanvasLayers and
adding CanvasItems to them. Opaque items in higher layers
obscure the items in lower layers. A newly created layer is always
topmost. CanvasLayers can be shown, hidden, and reordered,
making it easy to change what’s visible on the canvas.
CanvasLayers are created by calling
OffScreenCanvas::newLayer() and destroyed by calling either
CanvasLayer::destroy() or
OffScreenCanvas::deleteLayer().
The CanvasItem Classes
Everything drawn on a Canvas is an instance of a
CanvasItem subclass. Pointers to CanvasItems
are passed to CanvasLayer::addItem. The
CanvasLayer will destroy its CanvasItems when
appropriate – the user should never destroy them explicitly.
Each CanvasItem has a bunch of parameters that determine
its position, shape, color, and transparency. Position parameters are
always given in user coordinates. Some parameters, such as line widths,
can be given in either user or pixel units.
In C++, CanvasItems can be created either by calling
their constructors, or calling the subclass’s static create
method. In Python, only the create method is available,
which ensures that ownership of the object remains in C++, and that
Python’s garbage collector will not delete an object. The arguments to
the create method are always the same as the arguments to
the constructor.
Details of each CanvasItem subclass are given somewhere below.
The Mouse
The Canvas’s setMouseCallback method installs a mouse
event handler, which will be called whenever a mouse button is pressed
or released, the mouse is moved, or the scroll wheel is turned.
Call
Canvas::setMouseCallback(MouseCallback callback, void *data)
to install a mouse event handler. callback will be called
whenever a mouse button is pressed, the mouse is moved, or the window is
scrolled.
To install a rubberband that will be displayed when the mouse is
moving, call Canvas::setRubberBand(RubberBand*) from the
callback for the mouse-down event. The various types of
RubberBand and details of how to use them are described in
the section on the RubberBand class, below. To stop
displaying the RubberBand, pass a null pointer (in C++) or
None (in Python) to setRubberBand().
OOFCanvas does not handle selection of objects with the mouse, but it
does provide the position of a mouse click as part of the data passed to
the callback function. Additionally, it is possible to get a list of all
CanvasItems at a point with
OffScreenCanvas::clickedItems(const Coord&).
Scrolling
A canvas can be scrolled in one of two ways. It can be connected to
GtkScrollBars or other widgets elsewhere in the GUI, and it
can respond to scroll events generated within the
GtkLayout.
To connect to scroll bars, call
scrollbar.set_adjustment(adj) (in Python) or
gtk_range_set_adjustment(scrollbar, adj) (in C++), where
adj is the GtkAdjustment returned by
Canvas::getHAdjustment() or
Canvas::getVAdjustment().
If the GtkLayout receives a scroll event, the
mousehandler is called with event set to
scroll. The x and y values are
the changes in position, and can be used to modify the adjustments of
the scroll bars:
def mouseCB(eventtype, x, y, button, shift, ctrl, data):
if eventtype == "scroll":
sx = horizontalScrollBar.get_adjustment().get_value()
horizontalScrollBar.get_adjustment().set_value(sx + x)
...A Simple Example
In C++
#include "oofcanvas/guicanvas.h" // gui-dependent classes (Canvas, Rubberband)
#include "oofcanvas/oofcanvas.h" // everything else
double ppu; // pixels per unit -- initialize to something sensible
// Create a Canvas
Canvas canvas(ppu);
// Get a pointer to the GtkLayout widget
GtkWidget *widget = canvas.gtk();
// Install the canvas in the gui. For example, if it's going into
// a GtkFrame,
frame.add(widget);
// Create a canvas layer
CanvasLayer *layer = canvas.newLayer("layername");
// Add items to the layer
double x=1., y=2., radius=1.4;
CanvasCircle *circle = new CanvasCircle(x, y, radius); // In user coordinates.
circle->setLineWidthInPixels(1.5); // In pixel units
Color orange(1., 0.7, 0.0, 0.5); // r, g, b, a, all in [0.0, 1.0]
circle.setFillColor(orange);
layer->addItem(circle);
// Add more items if you want
...
// Draw the items to the canvas
canvas.draw();The equivalent Python is virtually identical
import oofcanvas
from oofcanvas import oofcanvasgui
oofcanvas.init_OOFCanvas(False)
canvas = oofcanvasgui.Canvas(width=300, height=300, ppu=1.0,
vexpand=True, hexpand=True)
frame.add(canvas.layout)
layer = oofcanvas.CanvasLayer("layername")
x = 1.
y = 2.
radius = 1.4
circle = oofcanvas.CanvasCircle.create(x, y, radius)
circle.setLineWidthInPixels(1.5)
orange = oofcanvas.Color(1., 0.7, 0.0).opacity(0.5)
circle.setFillColor(orange)
layer.addItem(circle)
canvas.draw()Calling Canvas::draw doesn’t actually draw anything.
Instead, it generates a Gtk event that causes the real drawing method to
be called from the Gtk main loop.
Details of the Classes
This section contains detailed information about all of the externally visible classes in OOFCanvas, starting with the utility classes that are used by the rest of the code.
Utility Types
These classes are defined in the OOFCanvas namespace and are used for some arguments and return values by the main OOFCanvas methods.
Coord
Coord is a position in user coordinates, the coordinate
system in which CanvasItems are defined.
The Coord class is defined in C++, but not in Python.
Methods that return a position to Python simply return a tuple, (x,y).
When an OOFCanvas function in Python requires a position argument, any
type that can be indexed can be used. That is, if you have a coordinate
class called MyCoord, you can do this:
pt = MyCoord(x, y)
circle = oofcanvas.CanvasCircle(pt, 1.0)as long as pt[0] is x and pt[1] is y. When
an OOFCanvas function returns a Coord, it’s really
returning a tuple, so you can do this:
pt = MyCoord( * oofcanvas.someFunctionReturningACoord() )Whenever a C++ function described below returns a Coord,
assume that the Python version works as described above.
The Coord constructors are
Coord()creates a point at the origin.
Coord(double x, double y)creates a point at (x,y).
The components can be accessed via the x and y data members or via
indexing. coord.x == coord[0].
Basic arithmetic, assignment, and equality operations are supported.
Coord::norm2() returns the square of the L2 norm.
Coord::operator*(const Coord&) is the dot product, and
cross(const Coord&, const Coord&) is the cross
product.
ICoord
An ICoord is a Coord with integer coefficients, used to
identify pixels.
Rectangle
The Rectangle class is not the same as the
CanvasRectangle, described below.
CanvasRectangle is a CanvasItem that can be
displayed. Rectangle is just a region of space.
A Rectangle can be constructed in several ways:
Rectangle()creates an empty uninitialized rectangle at an undefined position.Rectangle(double x0, double y0, double x1, double y1)creates a rectangle with diagonally opposite corners at (x0, y0) and (x1, y1). It doesn’t matter which pair of diagonally opposite corners are given.Rectangle(const Coord &pt0, const Coord &pt1)does the same, withCoordsinstead ofdoubles.- From Python, only
Rectangle((x0, y0), (x1, y1))is available.
Useful methods are
double Rectangle::xmin() constdouble Rectangle::ymin() constdouble Rectangle::xmax() constdouble Rectangle::ymax() constRectangle::swallow(const Coord&)expands the rectangle to include the given point.If the rectangle was uninitialized, this initializes it to an rectangle of size 0 at the given point. That is,
Rectangle r; r.swallow(pt);is the same asRectangle r(pt, pt);for someCoord pt.Rectangle::swallow(const Rectangle&)expands the rectangle include the givenRectangle.
Color
Colors are stored as RGBA values, which are doubles between 0 and 1.
C++ Constructors:
Color()Initializes to black.
Color(double r, double g, double b)Alpha is 1 (opaque).
Color(double r, double g, double b, double a)
The only Python constructor is
Color(r, g, b)
To change the opacity of a Python color, use
Color Color::opacity(alpha)
which returns a new Color with the given opacity and the
same RGB values.
Predefined constants are defined for black,
white, red, green,
blue, gray, yellow,
magenta, and cyan.
Canvas Classes
OffScreenCanvas
OffScreenCanvas is the base class for the other Canvas
classes. As the name implies, it can’t be displayed on the screen, but
it can be drawn to and the resulting image can be saved to a file.
OffScreenCanvas exists in both C++ and Python. The
discussion below uses C++ syntax, but the translation to Python is
trivial, except that (a) Coords are handled as discussed above, and (b)
the methods that return a std::vector in C++ return a list
in Python.
The constructor is
OffScreenCanvas(double ppu)ppuis the pixels per unit that determines the conversion between user and pixel coordinates. This is just an initial value. It can be changed later by zooming, but some nonzero initial value is required.
Layer
manipulation methods in OffScreenCanvas
CanvasLayer* OffScreenCanvas::newLayer(const std::string& name)creates a new
CanvasLayerwith the given name. All layers should be created with this method. The name is just for convenience and debugging. It need not be unique unlessOffScreenCanvas::getLayerwill be used to retrieve layers by name. If OOFCanvas is built in debug mode, a warning will be printed when a non-unique name is used.void OffScreenCanvas::deleteLayer(CanvasLayer *layer)deletes a canvas layer from the Canvas and destroys it. Do not simply delete a layer with
delete layer;void OffScreenCanvas::clear()deletes all layers.
CanvasLayer* OffScreenCanvas::getLayer(int) constgets a particular layer from the stack. Layer 0 is the bottom layer.
Canvaslayer* OffScreenCanvas::getLayer(const std::string &name) constgets a layer by name. The Python equivalent is
OffScreenCanvas.getLayerByName(name). If you’re going to use this, make sure that your layers have unique names.std::size_t OffScreenCanvas::nLayers() constreturns the total number of layers.
std::size_t OffScreenCanvas::nVisibleItems() constreturns the total number of visible items drawn on all layers.
void OffScreenCanvas::raiseLayer(int n, int howfar)raises layer
nbyhowfarplaces in the layer list. A higher layer may hide the contents of a lower layer. Ifnis too large, the layer will just be moved to the top.void OffScreenCanvas::lowerLayer(int n, int howfar)lowers layer
nbyhowfarplaces in the layer list. Ifnis too large, the layer will just be moved to the bottom.void OffScreenCanvas::raiseLayerToTop(int n)moves layer
nto the top of the layer list.void OffScreenCanvas::lowerLayerToBottom(int n)moves layer
nto the bottom of the layer list.void OffScreenCanvas::reorderLayers(const std::vector<CanvasLayer*>* layerlist)puts the layers in the order given in
layerlist. The list must contain all of the layers currently in the Canvas and no more. In Python the argument is a list[]of layers.
Output methods in
OffScreenCanvas
bool OffScreenCanvas::saveAsPDF(const std::string& filename, int maxpix, bool bg)saves the entire contents of the Canvas to a pdf file with the given name.
Although the output should be independent of the pixel resolution, it’s still necessary to pretend that there is a pixel size so that OOFCanvas can compute line thicknesses and other quantities that might be specified in pixel units.
maxpixis the number of pixels to assume in the largest dimension of the image.If
bgis true the background will be drawn. Otherwise it will be left blank.The return value is true if something was drawn successfully.
bool OffScreenCanvas::saveAsPNG(...)is the same as
saveAsPDF(...)but writes a PNG file.bool OffScreenCanvas::saveRegionAsPDF(const std::string& filename, int maxpix, bool bg, const Coord& pt0, const Coord& pt1)saves the rectangle defined by
pt0andpt1to the given file.maxpixandbgare the same as insaveAsPDF. In the Python version,pt0[0]is the x coordinate of a corner, andpt0[1]is the y coordinate.bool OffScreenCanvas::saveRegionAsPNG(...)is the same as
saveRegionAsPDF(...)but writes a PNG file.
Miscellaneous methods
in OffScreenCanvas
double OffScreenCanvas::getPixelsPerUnit() constreturns the current scale factor.
Coord OffScreenCanvas::pixel2user(const ICoord&) constconverts a pixel coordinate to a user coordinate. The Python equivalent is
OffScreenCanvas.pixel2user(x,y), which returns a 2-tuple.void OffScreenCanvas::setAntialias(bool)turns anti-aliasing on and off. The default value depends on your device.
void OffScreenCanvas::setMargin(double)sets the size of the margin around the items on the canvas. The size of the canvas is
1+margintimes the width and height of the bounding box of its contents. The default value is 0.0.bool OffScreenCanvas::empty() consthas anything been drawn?
OffScreenCanvas::setBackgroundColor(const Color&)sets the color of the parts of the canvas where nothing has been drawn.
std::vector<CanvasItem*> OffScreenCanvas::clickedItems(const Coord&)returns a list of the
CanvasItemsat the given point, if the items are in clickableCanvasLayer.std::vector<CanvasItem*> OffScreenCanvas::allItems() constreturns a list all
CanvasItemson the Canvas, in allCanvasLayers.void OffScreenCanvas::datadump(const std::string&) constwrites a text representation of the contents of each canvas layer to a file with the given name. This can be useful for debugging.
Canvas (C++)
Canvas is the C++ class that actually draws to the
screen. It is derived from OffScreenCanvas, and it creates a
GtkLayout when it is constructed. The
GtkLayout should be inserted into the application’s
GUI.
The Canvas constructor is
Canvas::Canvas(double ppu)ppu is the initial value to use for the pixels per unit scale factor
when the canvas is empty. A new value will be computed if you call
Canvas::zoomToFill() after adding some
CanvasItems, so the initial ppu is nearly
irrelevant.
All of the methods defined in OffScreenCanvas are
available in Canvas. In addition, Canvas
defines:
GtkWidget *Canvas::gtk() constreturns a pointer to the Canvas’s
GtkLayout.void Canvas::destroy()destroys the
GtkLayout. This is called automatically by theCanvasdestructor, but it can be called manually if necessary for some reason. Don’t try to use theCanvasafter destroying it.void Canvas::show()calls
gtk_widget_showon the Canvas’sGtkLayout.void Canvas::draw()instructs the Canvas to draw all of its
CanvasItems.int Canvas::widgetWidth() constreturns the width of the space allocated in the GUI for the
GtkLayout.int Canvas::widgetHeight() constreturns the height of the space allocated in the GUI for the
GtkLayout.void Canvas::zoom(double factor)zooms the canvas by the specified factor, keeping the center point fixed.
void Canvas::zoomAbout(const Coord& fixedpt, double factor)zooms the canvas by the specified factor, keeping the given point fixed. The point is specified in user coordinates.
void Canvas::zoomToFill()zooms the canvas so that all
CanvasItemsare visible and as large as possible.void Canvas::center()scrolls the canvas so that the center of bounding box of all
CanvasItemsis centered on the Canvas, without zooming.Rectangle Canvas::visibleRegion() constreturns a
Rectanglegiving the user space coordinates of the visible part of the Canvas.GtkAdjustment* Canvas::getHAdjustment() constreturns the
GtkAdjustmentthat controls horizontal position of the canvas. Connecting this object to aGtkScrollBarallows the canvas to be scrolled by the user.GtkAdjustment* Canvas::getVAdjustment() constis the same, for the vertical position of the canvas.
void Canvas::setMouseCallback(MouseCallback, void *data)assigns a mouse click callback function, which will be called when the mouse button is pressed or released, the mouse is moved, or the scroll wheel is scrolled. To limit the proliferation of motion events, see the
Canvas::allowMotionEventsfunction.The signature of the callback function is
typedef void (*MouseCallback)(const std::string &event, const Coord &position, int button, bool shift, bool ctrl, void *data);The following arguments are passed to the callback:
const std:string& eventtypeThe types are “down” (button was pressed), “up” (button was released), “move” (mouse was moved), and “scroll” (scroll wheel was turned).
const Coord& positionThe position of the mouse event, in user coordinates.
int buttonWhich mouse button was used.
bool shiftWhether or not the shift key was pressed.
bool ctrlWhether or not the control key was pressed.
void *dataThe data pointer that was passed to
setMouseCallback.
void Canvas::removeMouseCallback()removes the mouse callback function.
MotionAllowed Canvas::allowMotionEvents(MotionAllowed ma)tells the canvas how to respond when the mouse moves, if a mouse callback function is installed. The values of
main C++ areMotionAllowed::NEVER: don’t call the callback when the mouse moves.MotionAllowed::ALWAYS: call the callback whenever the mouse moves.MotionAllowed::MOUSEDOWN: call the callback when the mouse moves only if a mouse button is pressed.
The default value is
MotionAllowed::NEVER, so you must explicitly allow motion events if you want them.allowMotionEvents()returns the previous state of the motion handler, in case you want to restore it afterwards.void Canvas::setRubberBand(RubberBand*)tells the canvas to start using the
RubberBandobject for displaying mouse motions. SeeRubberBandfor details.void Canvas::removeRubberBand()tells the canvas to stop using the rubberband.
void Canvas::setResizeCallback(ResizeCallback, void *data)specifies a function to call when the canvas size changes. The function must take a single
void*argument, and returnvoid. When called, the givendatais passed.
Canvas (Python)
This is the Canvas class that available in Python. It is
derived from a SWIG generated wrapper around a C++ class called
PythonCanvas, which is derived from OffScreenCanvas.
The Python Canvas creates a GtkLayout using
Gtk’s Python interface. The Gtk widget can be accessed directly via
Canvas.layout.
The constructor is
Canvas(width, height, ppu, **kwargs)where width and height are the desired size
of the GtkLayout, in pixels. ppu is the
initial pixels per unit value. Any additional keyword arguments in
kwargs are passed to the GtkLayout
constructor.
All of the methods available in OffScreenCanvas and in the C++
Canvas are also available in the
Python Canvas, so refer to those sections for the
details.
In Python, the Canvas methods that set callback
functions expect the callbacks to be Python functions, but are otherwise
just like the C++ functions:
Canvas.setMouseCallback(callback, data)installs a mouse callback function. This is identical to the callback function in the C++ version, except
- It’s a Python function, not a C++ function.
- The
positionargument is a tuple, not aCoord. - The
datais a Python object, not avoid*.
Canvas.allowMotionEvents(ma)is like the C++ version, telling the
Canvashow to respond when the mouse moves, except that in C++ the argument is one of the constantsmotionAlways,motionNever, ormotionMouseDown.Canvas.setResizeCallback(callback, data)Again, this is just like the C++ version, except the
callbackfunction is a Python function anddatais a Python object.
CanvasLayer
CanvasLayers hold sets of CanvasItems, which are the things
that are drawn on the Canvas. Layers may be raised, lowered, shown, and
hidden.
Layers should only be created by a Canvas or
OffScreenCanvas, using its newLayer()
method.
CanvasLayer methods include:
void CanvasLayer::clear()removes all objects from the layer and makes it transparent.
void CanvasLayer::clear(const Color&)removes all objects from the layer and fills it with the given
Color.void CanvasLayer::addItem(CanvasItem*)adds the given item to the layer. The layer owns the item. After it’s been added to the layer it should not be deleted except by clearing or destroying the layer.
void CanvasLayer::removeAllItems()bool CanvasLayer::empty() constreturns true if the layer contains no
CanvasItems.void CanvasLayer::destroy()destroys the layer and removes it from the Canvas.
void CanvasLayer::show()makes the layer visible if it was previously hidden. New layers are initially visible.void CanvasLayer::hide()make the layer invisible.
void CanvasLayer::setClickable(bool)If the argument is true, objects in the layer can be listed by
OffScreenCanvas::clickedItems().void CanvasLayer::markDirty()Force the layer to be redrawn the next time the
Canvasis rendered. Normally this shouldn’t be needed. Adding or removing aCanvasItemfrom aCanvasLayermakes it dirty.void CanvasLayer::setOpacity(double)sets the opacity with which the layer will be copied to the
Canvaswhen it’s displayed. 0.0 is fully transparent and 1.0 is fully opaque.void CanvasLayer::raiseBy(int howfar) constraises the layer in the Canvas by the given amount. This is the same as
OffScreenCanvas::raiseLayer(int n, int howfar)except that you don’t need to know the layer numbern.void CanvasLayer::lowerBy(int howfar) constis the same as
raiseBy, but in the other direction.void CanvasLayer::raiseToTop() constis the same as
OffScreenCanvas::raiseLayerToTop(int n).void CanvasLayer::lowerToBottom() constis the same as
OffScreenCanvas::lowerLayerToBottom(int n).void CanvasLayer::writeToPNG(const std::string& filename)saves the contents of the layer to a PNG file.
CanvasItem
CanvasItem is the abstract base class for everything
that can be drawn on the canvas. Generally you get a pointer to a new
CanvasItem, call its methods to set its properties, and
pass the pointer to CanvasLayer::addItem().
In C++, always allocate new CanvasItems with
new or use the class’s static create method.
In Python, always use the create method. The arguments to
create are always the same as the arguments to the
constructor.
This is incorrect:
CanvasCircle circ(Coord(0.,0.), 1.0);
layer->addItem(&circ);but this is correct:
CanvasCircle *circ1 = new CanvasCircle(Coord(0.,0.), 1.0);
layer->addItem(circ1);
CanvasCircle *circ2 = CanvasCircle::create(Coord(0.,0.), 1.);
layer->addItem(circ2);In Python, don’t do this:
circ = oofcanvas.CanvasCircle((0.,0.), 1.)
layer.addItem(circ)Do this instead:
circ = oofcanvas.CanvasCircle.create((0.,0.), 1.)
layer.addItem(circ)After an item has been added to a layer, the layer owns it. The item will be deleted when it is removed from the layer or when the layer is deleted.
CanvasItem defines the following method:
bool CanvasItem::containsPoint(const OffScreenCanvas *canvas, const Coord &point) constreturns true if the given point is within the item on the given canvas.
Abstract CanvasItem Subclasses
CanvasShape
This is an abstract base class for most other CanvasItem
classes. It describes an object that can be drawn with a line, but not
necessarily filled. The default line color is black. There is no default
line width. If the line width is not set, nothing will be drawn.
CanvasShape defines the following methods:
void CanvasShape::setLineWidth(double)Set the line width in user units.
void CanvasShape::setLineWidthInPixels(double)Set the line width in pixel units.
double CanvasShape::getLineWidth() constreturns the numerical value of the line width, in pixels or user units.
bool CanvasShape::getLineWidthInPixels() constreturns
trueif the line width should be interpreted in pixel units.void CanvasShape::setLineJoin(Cairo::LineJoin)This determines how line segments are joined. In C++, the argument is a member of the
LineJoinenum class:LineJoin::MITERLineJoin::ROUND, orLineJoin::BEVEL,
equivalent to the members of the
Cairo::LineJoinclass.In Python, the choices are
lineJoinMiter,1ineJoinRound, orlineJoinBevel, which are defined in the OOFCanvas namespace.void CanvasShape::setLineCap(LineCap)This determines how the ends of line segments are drawn. In C++, the argument is a member of the
LineCapenum class:LineCap::BUTTLineCap::ROUND, andLineCap::BSQUARE,
equivalent to the members of the
Cairo::LineCapclass.In Python, the choices are
lineCapButt,lineCapRound, orlineCapSquare, which are defined in the OOFCanvas namespace.void CanvasShape::setLineColor(const Color&)Sets the line color. See
Color. The default color is black.
By default, lines are solid. They can be made dashed by calling one of the following methods:
void CanvasShape::setDash(const std::vector<double>&, int offset)The vector contains a pattern of dash lengths, which are in user units. The pattern repeats as necessary.
offsetindicates where the pattern starts. In Python, pass a list of doubles for the dash lengths.void CanvasShape::setDashInPixels(const std::vector<double>&, int offset)The same as the above
setDash, but the dash lengths are interpreted in pixel units.void CanvasShape::setDash(double)Use a single dash length, which is in user units.
void CanvasShape::setDashInPixels(double)The same as
setDash(double), but the dash lengths are in pixel units.void CanvasShape::setDashColor(const Color&)Fill the spaces between dashes with the given
Colorinstead of leaving them blank.void CanvasShape::unsetDashes()Turn off dashes. Draw solid lines.
CanvasFillableShape
This abstract class is derived from CanvasShape and is used for closed
shapes that can be filled with a color. It provides one method:
void CanvasFillableShape::setFillColor(const Color&)Fill the shape with the given color.
Concrete CanvasItem Subclasses
These are the actual items that can be drawn, in alphabetical order.
CanvasArrowhead
An arrowhead can be placed on a CanvasSegment. The
CanvasArrowhead class is not derived from CanvasShape. Its constructor is
CanvasArrowHead(const CanvasSegment *segment, double position, bool reversed)segmentis theCanvasSegmentthat the arrowhead will be drawn on. `positionranges from 0.0 to 1.0, and determines where the tip of the arrow will appear on the segment. A value of 0.0 puts the tip at the first point of the segment, and a value of 1.0 puts it at the second point. The color of the arrowhead is the same as the line color of theCanvasSegment. Ifreversedistrue, then the arrow points toward the first point of the segment. (Ifpositionis 0.0, you probably wantreversed==true, but that is not enforced.)
The size of the arrowhead is set by either
void CanvasArrowHead::setSize(double width, double length)widthandlengthare in user units.
or
void CanvasArrowHead::setSizeInPixels(double width, double length)widthandlengthare in pixels.
Either setSize() or setSizeInPixels()
must be called before an arrowhead can be drawn.
CanvasCircle
Derived from CanvasFillableShape. Its
constructor is
CanvasCircle(const Coord ¢er, double radius)
The coordinates of the center and the radius are in user units. To
specify the radius in pixels, use CanvasDot instead.
CanvasCurve
A CanvasCurve is a set of line segments connected end to
end. It is derived from CanvasShape. It is specified by
listing the sequence of Coords joined
by the segments. Its constructors are
CanvasCurve()Create an empty curve, containing no points. This form of the constructor is the only one available in Python.
CanvasCurve(int n)Create a curve with room reserved for
npoints, but don’t actually create the points.CanvasCurve(const std::vector<Coord> &points)Create a curve with the given points.
Points can be added to a CanvasCurve via
void CanvasCurve::addPoint(const Coord&)
or
void CanvasCurve::addPoints(const std::vector<Coord>*)
In Python, the argument to addPoints is a list of Coord-like (ie, indexable) objects.
int CanvasCurve::size() returns the number of points in
the curve.
CanvasDot
Derived from CanvasFillableShape, a
CanvasDot is a circle with a fixed size in pixels. Its line
width is also always measured in pixels. The constructor is
CanvasDot(const Coord ¢er, double radius)
CanvasEllipse
Derived from CanvasFillableShape. The
constructor is
CanvasEllipse(const Coord &c, const Coord &r, double angle)
where c is the center in user coordinates and the
components of r are the radii in user units.
r[0] is the radius in the x direction before rotation. The
rotation angle in degrees is measured counterclockwise.
CanvasImage
CanvasImage can display a PNG file, or if compiled with
the ImageMagick library, any file
format that ImageMagick can read. It can also use an image already
loaded by ImageMagick. To enable ImageMagick, define
OOFCANVAS_USE_IMAGEMAGICK when building OOFCanvas.
If OOFCanvas is built with the OOFCANVAS_USE_NUMPY and
PYTHON_API options, then it can display image data stored
in a NumPy array, such as one created
by scikit-image.
The constructor creates an empty image:
CanvasImage(const Coord &position, const ICooord &npixels)
where position is the position of the lower left corner
of the image in user coordinates.
Confusion Opportunity! There are two
kinds of pixels. There are the pixels on your computer screen, and there
are the pixels in the CanvasImage. They don’t have to be
the same size. A CanvasImage may be displayed at a
different scale from its natural size, in which case one
CanvasImage pixel will be larger or smaller than one screen
pixel.
Since an empty image isn’t very useful, CanvasImage
includes some static factory methods for creating
CanvasImage objects.
Create a blank image:
CanvasImage* CanvasImage::newBlankImage( const Coord& position, const ICoord& pixelsize, const Color &color)The image is filled with a single color,
color, so it’s not really blank.positionis the user coordinate of the lower left corner of the image.sizeis the size that it will be drawn, in user units.pixelsizeis the size of the image in pixels.Read a png file:
CanvasImage* CanvasImage::newFromPNGfile( const Coord& position, const std::string& filename)positionis the position of the lower left corner of the image in user coordinates.Read any file format that ImageMagick can handle:
CanvasImage* CanvasImage::newFromImageMagickFile( const Coord& position, const std::string& filename)positionis the position of the lower left corner of the image in user coordinates.Create a CanvasImage from ImageMagick data:
CanvasImage* CanvasImage::newFromImageMagick( const Coord& position, Magick::Image imagedata)Create a
CanvasImagefrom image data that has already been read by ImageMagick. The data is copied from the ImageMagick structure.Create a CanvasImage from NumPy data:
CanvasImage* CanvasImage::newFromNumpy( const Coord& position, PyObject *numpyarray, bool flipy)numpyarraymust contain RGB or RGBA data. The values be unsigned bytes (chars) in the range 0-255, or doubles in the range 0.0-1.0. Ifflipyis true, the order of the rows in the image will be reversed.
CanvasImage provides the following useful methods:
Set the displayed size of the image, in user coordinates:
void CanvasImage::setSize(const Coord&)or in pixel (screen) coordinates:
void CanvasImage::setSizeInPixels(const Coord&)Either
setSizeorsetSizeInPixelsmust be called before an image can be displayed. See the note above about pixels: this size refers to screen pixels, not image pixels.Set the style for drawing individual pixels
CanvasImage::setDrawIndividualPixels(flag)Cairo draws pixels as small fuzzy blobs, which may or may not be what you want, especially if you need to zoom in. When examining data on the image pixel level (not the screen pixel level) it can be convenient to draw each pixel as a rectangle. Call
setDrawIndividualPixels(true)to switch to this mode, orsetDrawIndividualPixels(false)to turn it off.Examine individual pixels
Color CanvasImage::get(const ICoord &) constThis returns the color of the pixel at the given point in the image. The
ICoordis the location of the pixel in the image, not the canvas. As such, it uses standard image coordinates, with x increasing from left to right and y increasing from top to bottom.Modify individual pixels
void CanvasImage::set(const ICoord&, const Color&)The
ICoordis the location of the pixel in the image, not the canvas. As such, it uses standard image coordinates, with x increasing from left to right and y increasing from top to bottom.If you need to make extensive modifications to an image, it’s probably better to use some other tools first and then load the modified image into the
CanvasImage.Set overall opacity
void CanvasImage::setOpacity(double alpha)This sets the opacity for the entire image, used when it is copied to the
Canvas. It doesn’t actually change any image data.
CanvasPolygon
A CanvasPolygon is a closed CanvasCurve, derived from CanvasFillableShape. It is
specified by listing the user coordinates of the corners of the polygon,
counterclockwise. Its constructors are
CanvasPolygon()Create an empty polygon, containing no points. Only this constructor is available in Python.
CanvasPolygon(int n)Create a polygon with room for
npoints, but don’t actually create the points. Points must be added withaddPoints.CanvasPolygon(const std::vector<Coord>& points)Create a polygon from the given vector of
Coords.
Points must be added to a polygon in order, clockwise. When drawn, the last point will be connected to the first. There is currently no mechanism for inserting points in the middle of the sequence, or for deleting them.
To add points to a polygon, in C++ use either
CanvasPolygon::addPoint(const Coord&)CanvasPolygon::addPoint(const Coord*)
or
CanvasPolygon::addPoints(const std::vector<Coord>*)
In Python, use
CanvasPolygon.addCoord(pt)where
ptis some kind of point object, withpt[0]being x andpt[1]being y.
or
CanvasPolygon::addPoints(ptlist)where
ptlistis a list of point objectspt, wherept[0]is x andpt[1]is y.
CanvasRectangle
Derived from CanvasFillableShape. The
constructor is
CanvasRectangle(const Coord&, const Coord&)
where the Coords are the user coordinates of any two
opposite corners of the rectangle.
CanvasSegment
A single line segment, derived from CanvasShape. The constructor is
CanvasSegment(const Coord &point0, const Coord &point1)
The positions are given in user coordinates.
CanvasSegments
CanvasSegments is derived from CanvasShape and draws a set of
unconnected line segments all with the same color and width.
The constructors are
CanvasSegments()creates an empty object.
CanvasSegments(int n)allocates space for
nsegments, but doesn’t create them. This form is only available in C++.
To add segments to the object, use
CanvasSegments::addSegment(const Coord &pt0, const Coord &p1)The segment goes from
pt0topt1.
CanvasText
CanvasText displays text at an arbitrary position and
orientation. It is derived from CanvasItem. The text is drawn by the
Pango
library.
The constructor is
CanvasText(const Coord &location, const std::string &text)
where location is the position of the lower left corner
of the text, in user coordinates.
CanvasText methods include
CanvasText::setFillColor(const Color& color)sets the color of the text.
CanvasText::setFont(const std::string &fontdesc, bool inPixels)fontdescis a string that will be passed topango_font_description_from_string()to determine the font. It includes a font family or families, style options, and size (for example,"Times Bold 0.2"). The size is interpreted in pixels ifinPixelsis true and in user units otherwise. The names of the installed font families are returned by thelist_fonts()function, defined globally in the OOFCanvas namespace.CanvasText::rotate(angle)rotates the text by the given angle, in degrees, about the left end of the text’s baseline. Positive angles are counterclockwise.
RubberBand
Rubberbands are lines drawn on top of the rest of the Canvas to
indicate mouse movements while a mouse button is pressed. To use a
rubberband, create a RubberBand object and pass it to
GUICanvasImpl::setRubberBand(RubberBand*). The rubberband
will be redrawn every time the mouse moves until
GUICanvasImpl::removeRubberBand() is called.
Notes:
setRubberBandcan be called from the mouse-down callback, but it doesn’t need to be.OOFCanvas does not take ownership of the rubberband object. The calling code must delete it when done with it in C++ (if necessary) and make sure to retain a reference to it in Python (when necessary).
Various subclasses of RubberBand are defined in
rubberband.h:
LineRubberBandis a straight line from the mouse-down position to the current position.RectangleRubberBandis rectangle with one corner at the mouse-down position and the diagonally opposite corner at the current position.CircleRubberBandis a circle centered on the mouse-down position and passing through the current position.EllipseRubberBandis an ellipse that is fit into a rectangle, as inRectangleRubberBand.SpiderRubberBandis a set of line segments, starting at given points and ending at the current mouse position. The start points are specified by callingSpiderRubberBand::addPoints(list), where in C++,listis astd::vector<Coord>*. In Python, it’s a iterable collection of objects whereobj[0]is the x component ofobjandobj[1]is its y component.
The appearance of the rubberband is controlled by these functions in
the RubberBand base class:
void RubberBand::setLineWidth(double width)width is the line width in pixel units.
void RubberBand::setColor(const Color& color)sets the color of the dashed line.
void RubberBand::setDashLength(double length)sets the length of the dashes in pixels.
void RubberBand::setDashColor(const Color &color)sets the color of the line between the dashes. If this function is not called, the spaces between the dashes are not filled.
void RubberBand::setDashed(bool)turns the dashes on and off. Undashed rubberbands drawn with solid lines may be hard to see on some backgrounds. The default is to draw dashes.
Adding new rubberband classes is described in the appendix, below.
Appendix: Debugging Tools
Building OOFCanvas with CMAKE_BUILD_TYPE
set to “Debug” enables some features that can help with debugging.
CanvasItem::drawBoundingBox(double, const Color&)sets a flag indicating that the item’s bounding box should be drawn when the item is drawn. The arguments are a line width and a
Color.OffScreenCanvas::newLayer()will print a message if the new layer’s name is not unique.
Appendix: Adding New CanvasItem Subclasses
New CanvasItem subclasses can be derived in C++ from
CanvasItem, CanvasShape, or
CanvasFillableShape. A CanvasShape is a
CanvasItem with predefined methods for setting line drawing
parameters. A CanvasFillableShape is a
CanvasShape with predefined methods for setting a fill
color.
Actually, two classes must be written for each new canvas item. One,
derived from CanvasItem, contains the parameters describing
the item and is visible to the calling program. The other, derived from
the CanvasItemImplementation template, contains the Cairo
code for actually drawing the item, and is hidden from the calling
program.
The template argument for CanvasItemImplementation is
the CanvasItem subclass that the template implements. The
template is derived from the non-templated
CanvasItemImplBase class, which contains all of the methods
that don’t explicitly depend on the template parameter. There are also
two templated classes derived from
CanvasItemImplementation,
CanvasShapeImplementation and
CanvasFillableShapeImplementation, which are used to
implement common items derived from CanvasShape and
CanvasFillableShape.
This will be easier to explain with an example, so what follows is an
annotation of the CanvasRectangle class and its
implementation.
Bounding Boxes
First, though, comes a discussion of bounding boxes. Every item needs to be able to compute its bounding box, which is the smallest rectangle, aligned with the x and y axes, that completely encloses the item in user space. The rectangle is used to make some computations more efficient, to determine how large the bitmap needs to be, and to define what “zoom to fill” means.
If an item contains components with sizes specified in pixels, it
will not be possible to compute the bounding box in user coordinates
without knowing the current ppu. As a simple example,
consider a circle of radius 2 in user space, with a perimeter that is
drawn two pixels wide outside of that radius. Each side of the
bounding box is 4 user units plus 4 pixels.
This is potentially a problem, since the bounding box is one of the
things that determines the ppu in some situations. Instead,
items provide their “bare” bounding box, which is what the bounding box
would be if the ppu were infinite and the pixel size were
zero. In the example above, the bare bounding box is a square of side 4
centered on the circle.
It is possible that an item’s size is given entirely in pixels, which
means that its bare bounding box has size zero in both directions. This
is fine. The bounding box will be Rectangle(pt,pt) where
pt is a Coord at the
position of the item.
An item’s bounding box is stored in its implementation, in a public
data member Rectangle CanvasItemImplBase::bbox. It’s
public, because an implementation is only visible to its particular
CanvasItem subclass. If a change to the
CanvasItem changes its bounding box, it can simply reset
bbox and call
CanvasItemImplBase::modified().
The CanvasItem
Subclass
canvasrectangle.h contains the declaration
class CanvasRectangle : public CanvasFillableShape // [1]
{
protected:
double xmin, ymin, xmax, ymax; // [2]
public:
CanvasRectangle(const Coord&, const Coord&); // [3]
CanvasRectangle(const Coord*, const Coord*); // [4]
static CanvasRectangle *create(const Coord *p0, const Coord *p1); [5]
virtual const std::string &classname() const; // [6]
void update(const Coord&, const Coord&); // [7]
double getXmin() const { return xmin; } // [8]
double getXmax() const { return xmax; }
double getYmin() const { return ymin; }
double getYmax() const { return ymax; }
friend std::ostream &operator<<(std::ostream &, const CanvasRectangle&);
virtual std::string print() const; // [9]
};CanvasRectangleis derived fromCanvasFillableShape, but the notes here apply just as well to items derived fromCanvasShapeor directly fromCanvasItem.These are all of the parameters that define the rectangle. Line thickness, color, and dashes are not included because they’re set in the
CanvasShapebase class, and fill color is in theCanvasFillableShapebase class.The constructor needs to set the parameters that describe the rectangle, and to create the implementation:
CanvasRectangle::CanvasRectangle(const Coord &p0, const Coord &p1) : CanvasFillableShape( new CanvasRectangleImplementation(this, Rectangle(p0, p1))), xmin(p0.x), ymin(p0.y), xmax(p1.x), ymax(p1.y) {}The constructor invokes the
CanvasFillableShapeconstructor, whose argument is a pointer to a new implementation. The item owns the implementation and will delete it when it’s done with it. The implementation class will be discussed below.This form of the constructor, using pointers instead of references for its arguments, is for use by SWIG when generating the Python interface.
The
createmethod just calls one of the constructors, and returns a pointer to the newCanvasRectangle. It will be the only form of the constructor available to Python.The
classnamemethod is used by the templates inpythonexportable.hto allow a genericCanvasItemobject returned from C++ to Python to be interpreted as the correctCanvasItemsubclass. The method just returns the name of the class:const std::string &CanvasRectangle::classname() const { static const std::string name("CanvasRectangle"); return name; }update()is used to change the parameters of the rectangle, and is used when the rectangle is a rubberband, meaning that it will be reconfigured and redrawn repeatedly:void CanvasRectangle::update(const Coord &p0, const Coord &p1) { xmin = p0.x; ymin = p0.y; xmax = p1.x; ymax = p1.x; implementation->bbox = Rectangle(p0, p1); modified(); }Because the change has altered the rectangle’s bounding box, the implementation’s
bboxis updated, andmodified()is called to indicate that the rectangle will need to be re-rendered.A
CanvasItemthat isn’t used in a rubberband doesn’t need to have anupdatemethod.getXmin(), etc, are convenience functions that might be useful to a user but aren’t actually required by OOFCanvas.The
print()method is required, but it’s really only there for debugging. Theto_string()function template inutility.hallowsprintto be defined in terms ofoperator<<:std::string CanvasRectangle::print() const { return to_string(*this); }
The
CanvasItemImplementation Subclass
Just as a CanvasItem subclass can be derived from
CanvasItem, CanvasShape, or
CanvasFillableShape, its implementation can be derived from
CanvasItemImplementation,
CanvasShapeImplementation, or
CanvasFillableShapeImplementation. These templates
are defined in canvasitemimpl.h and
canvasshapeimpl.h. The template parameter is the
CanvasItem class that the implementation implements. The
templates share a non-templated base class,
CanvasItemImplBase, which contains all the code that
doesn’t depend on the template parameter.
CanvasRectangleImplementation is be declared and defined
entirely within the same C++ file that defines
CanvasRectangle, because it is accessed only via virtual
functions and the pointer that’s stored in the
CanvasRectangle. Because CanvasRectangle is
derived from CanvasFillableShape,
CanvasRectangleImplementation must be derived from
CanvasFillableShapeImplementation. So
canvasrectangle.C contains this declaration:
class CanvasRectangleImplementation
: public CanvasFillableShapeImplementation<CanvasRectangle> // [1]
{
public:
CanvasRectangleImplementation(CanvasRectangle *item, // [2]
const Rectangle &bb) // [2]
: CanvasFillableShapeImplementation<CanvasRectangle>(item, bb) // [3]
{}
virtual void drawItem(Cairo::RefPtr<Cairo::Context>) const; // [4]
virtual bool containsPoint(const OSCanvasImpl*, const Coord&) const; // [5]
};The template argument is the
CanvasItemsubclass that this class implements.The constructor arguments must include the
CanvasItemand its bounding box. The bounding box can be an uninitializedRectangleif it’s not known yet. In this case, the bounding box is known and has been provided by the caller, theCanvasRectangleconstructor.If necessary, there can be other arguments here, since it is called only by the associated
CanvasRectangleconstructor.The
CanvasItemand bounding box must be passed to the base class constructor.CanvasItemImplementationandCanvasShapeImplementationwork the same way as theCanvasFillableShapeImplementationused here.drawItem()must be defined. Given aCairo::Context, it creates a path, and strokes or fills it, using information in theCanvasRectangle, which it can access using itscanvasitempointer. Because of the templating,canvasitemis a pointer to the correctCanvasItemsubclass,CanvasRectangle.void CanvasRectangleImplementation::drawItem( Cairo::RefPtr<Cairo::Context> ctxt) const { double w = lineWidthInUserUnits(ctxt); double halfw = 0.5*w; Rectangle r = bbox; r.expand(-halfw); // move all edges inward by half the line width ctxt->move_to(r.xmin(), r.ymin()); ctxt->line_to(r.xmax(), r.ymin()); ctxt->line_to(r.xmax(), r.ymax()); ctxt->line_to(r.xmin(), r.ymax()); ctxt->close_path(); fillAndStroke(ctxt); }lineWidthInUserUnits()is defined inCanvasShapeImplementationand gets the desired line width from theCanvasRectangle, or rather itsCanvasShapebase class.fillAndStroke()is defined inCanvasFillableShapeImplementationand gets line, dash, and fill information fromCanvasShapeandCanvasFillableShape.Note that the perimeter is drawn so that the outer edges of the lines are at the nominal bounds of the rectangle. A different kind of
CanvasItemmight choose to center the lines on the nominal bounds, but in that case it would have to increase the size of the bounding box.Given a user-space
Coordthat is known to be within the item’s bounding box,containsPoint()returns true if theCoordis actually within the item.containsPoint()must be defined, although if an item will never be clicked on, defining it to simply returnfalseis legal.Here is the definition from
CanvasRectangleImplementation:bool CanvasRectangleImplementation::containsPoint( const OSCanvasImpl *canvas, const Coord &pt) const { double lw = lineWidthInUserUnits(canvas); return canvasitem->filled() || (canvasitem->lined() && (pt.x - bbox.xmin() <= lw || bbox.xmax() - pt.x <= lw || pt.y - bbox.ymin() <= lw || bbox.ymax() - pt.y <= lw)); }Because the given point is known to be within the bounding box, and the rectangle fills the bounding box, there’s nothing to compute if the rectangle is filled. If it’s not filled, it’s necessary to compute whether or not the point is on a perimeter segment.
The first argument is an
OSCanvasImpl*, a pointer to the implementation class forOffScreenCanvas, which is needed for conversion between coordinate systems, if the line width was specified in pixels.
pixelExtents
One more function needs to be defined in any
CanvasItemImplementation that includes graphical elements
whose size is specified in pixels.
void CanvasItemImplBase::pixelExtents(double &left, double &right, double &up, double &down) const;sets the distance, in pixel units, that the item extends
past its bare bounding box, in each of the
given directions. (left == -x, right == +x, up == +y, down == -y) The
default version sets all four values to zero. Since
CanvasRectangleImplementation draws its lines inside the
bounding box, it uses the default version. If its perimeters were drawn
with their centerlines on the bounding box edges,
pixelExtents would set each of the four arguments to half
the line width, assuming that the line width was specified in
pixels.
When an item contains elements defined in pixel units as well as
elements defined is user units, it’s possible that the bounding box
constant for large ppu and ppu-dependent for
small ppu, with a crossover at some finite non-zero
ppu. Such a canvas item should probably be represented as
two or more better-behaved canvas items.
The default version of pixelExtents defined in
CanvasItemImplBase sets all four extents to zero. The
version in CanvasShapeImplementation will work for any item
derived from CanvasShape or
CanvasFillableShape whose perimeter line segments are given
in pixel units, but has no other pixel unit dimensions. (Actually, it
only works approximately, but is good enough if the line segments aren’t
too thick.)
Other Useful
CanvasItem Methods
void CanvasShapeImplementation::stroke(Cairo::RefPtr<Cairo::Context>) constdraws the current Cairo path using the line color, width, and dash settings from theCairoShape.void CanvasFillableShapeImplementation::fillAndStroke(Cairo::RefPtr<Cairo::Context>) constdraws the current Cairo path using the line color, width, and dash settings from theCairoShape, and fills it using the fill color fromCanvasFillableShape.
Appendix: Adding New RubberBand Classes
Rubberbands are derived from the RubberBand class
declared in oofcanvas/oofcanvasgui/rubberband.h. For simple
examples, see that file and
oofcanvas/oofcanvasgui/rubberband.C. Each class needs to
redefine three virtual functions:
RubberBand::start(CanvasLayer*, const Coord&)is called when the mouse is clicked at the given
Coordand the canvas is starting to draw a rubberband. The function should first call the base class method, and then create theCanvasItemsthat form the rubberband and add them to the givenCanvasLayer.The function
Rubberband::doDashes(CanvasShape*)can be used to set the dash style on anyCanvasShapeused by the rubberband. The line color and line width should be be set using the base classColor coloranddouble lineWidthdata members.RubberBand::stop()is called when the canvas stops drawing the rubberband. The base class method should be called, and in many cases will be sufficient, but if the subclass needs to do any cleaning up, it can do it here.
RubberBand::update(const Coord &pt)is called whenever the mouse moves. The given
Coordis the current user-space position of the mouse. The subclass method should call the base class method (which just setsRubberBand::currentPtto the current coordinate) and then update itsCanvasItemsto reflect the new configuration of the rubberband.
Appendix: Internal Details
It shouldn’t be necessary to understand this section in order to use OOFCanvas. It’s here to help development.
Class Hierarchies and Encapsulation
Encapsulation is used to separate the implementation details from the user-visible header files. Encapsulation is handled differently for different classes, depending on the complexity of their inheritance structure.
OffScreenCanvasandCanvasare the user-visible C++ classes for the canvas objects.The hidden implementation class for
OffScreenCanvasisOSCanvasImpl.OffScreenCanvascontains an opaque pointer,osCanvasImpl, to anOSCanvasImpl.Canvasis derived fromOffScreenCanvas, adding screen display and mouse interaction abilities.The hidden implementation class for
CanvasisGUICanvasImpl, which is derived fromOSCanvasImpl. ACanvascontains a pointer,guiCanvasImpl, to itsGUICanvasImpl. TheGUICanvasImplpointed to from aCanvasis the same object as theOSCanvasImplpointed to in its base class.The user-visible Python canvas,
PythonCanvas, is derived directly fromGUICanvasImplbecause it is effectively encapsulated by the SWIG wrapper. There is no need to derive it fromCanvas.
CanvasLayeris the user-visible base class for layers.- The implementation class,
CanvasLayerImplis derived fromCanvasLayer.
- The implementation class,
CanvasItemis the user-visible base class for canvas items.The hidden implementation class for a subclass
ITEMofCanvasItemis a templated class,CanvasItemImplementation<ITEM>, which is derived fromCanvasItemImplBase.Each
CanvasItemcontains an opaque pointer,implementation, to itsCanvasItemImplBase, and eachCanvasItemImplementation<ITEM>contains a pointer,canvasitemto itsITEM.
The Rendering Call Sequence
Each CanvasLayer contains a
Cairo::ImageSurface which contains a bitmap of what’s been
drawn in the layer, a Cairo::Context which controls drawing
to surface, and a Rectangle which is the bounding box (in
user coordinates) of all of the layer’s CanvasItems.
When a CanvasItem is added to a
CanvasLayer, the layer is marked “dirty” and the item is
stored in the layer. No drawing is done at this point.
When all items have been added to the layers, calling
GUICanvasImpl::draw() generates a draw event on the
GtkLayout. This causes
GUICanvasImpl::drawHandler() to be called. The argument to
drawHandler is the Cairo::Context for drawing to the
GtkLayout’s Cairo::Surface.
GUICanvasImpl::drawHandler() begins by computing the
horizontal and vertical offsets that will be used to keep the image
centered in the gtk window (if the image is smaller than the window) or
at the position determined by the scroll bars (if the image is larger
than the window).
Next, drawHandler calls Canvas::setTransform(), which
computes the matrix that converts from user coordinates to bitmap
coordinates within the layer, given the ppu. The GtkLayout
is resized if necessary so that it is large enough to accomodate the
bounding boxes of all of the layers, plus an optional margin (set by
OffScreenCanvas::setMargin()). Note that a layer’s bounding
box, in user units, can depend on the ppu if the layer contains items
with sizes given in pixels.
What happens next depends on whether or not a rubberband is being
drawn. If there is no rubberband,
GUICanvasImpl::drawHandler draws the background color and
then, for each layer from bottom to top, tells the layer to draw all of
its CanvasItems to its own Cairo::ImageSurface
(CanvasLayer::render()), and copies the layer’s surface to
the GtkLayout’s surface
(CanvasLayer::copyToCanvas()) at the position given by the
scroll bars. (CanvasLayer::render() only redraws its items
if any have changed since the last time they were drawn.)
If there is an active rubberband, on the first call to
drawHandler after the mouse button was pressed all of the
CanvasLayers other than the rubberband’s layer are rendered
to a separate Cairo::ImageSurface called the
nonRubberBandBuffer. Then this buffer is copied to the
GtkLayout and the rubberband is drawn on top of it. On
subsequent calls to drawHandler, the
nonRubberBandBuffer is copied and the rubberband is drawn,
but the nonRubberBandBuffer is not rebuilt unless the
layers have changed.
Disclaimer and Copyright
NIST-developed software is provided by NIST as a public service. You may use, copy and distribute copies of the software in any medium, provided that you keep intact this entire notice. You may improve, modify and create derivative works of the software or any portion of the software, and you may copy and distribute such modifications or works. Modified works should carry a notice stating that you changed the software and should note the date and nature of any such change. Please explicitly acknowledge the National Institute of Standards and Technology as the source of the software. To facilitate maintenance we ask that before distributing modified versions of this software, you first contact the authors at [email protected].
NIST-developed software is expressly provided “AS IS.” NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT OR ARISING BY OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE SOFTWARE OR THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE.
You are solely responsible for determining the appropriateness of using and distributing the software and you assume all risks associated with its use, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and the unavailability or interruption of operation. This software is not intended to be used in any situation where a failure could cause risk of injury or damage to property. The software developed by NIST employees is not subject to copyright protection within the United States.