OOF: Finite Element Analysis of Microstructures
OOFCanvas Manual
This is the manual for OOFCanvas 1.1.1.
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 oofcanvas
Unpack 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 build
Configure OOFCanvas by running ccmake:
% ccmake ../oofcanvas-1.1.0
Type “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_TYPE
toRelease
.Change
CMAKE_INSTALL_PREFIX
to 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_PREFIX
to~/Anaconda3/envs/OOF2
Set
OOFCANVAS_PYTHON_API
toPython2
orPython3
if you want to generate the Python interface for OOFCanvas. Set it toNone
if you don’t need Python. Leave it atPython3
if you’re using OOFCanvas with OOF2.If you’re using Python3, set
OOFCANVAS_PYTHON3_VERSION
to 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_API
toPython3
, you will have to type “c” beforeOOFCANVAS_PYTHON3_VERSION
appears in the list of settings.Set
OOFCANVAS_SWIG_VERSION
to the version of SWIG that you have.Set
OOFCANVAS_USE_IMAGEMAGICK
toON
if 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_NUMPY
toON
. 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:
% make
Install OOFCanvas:
If
CMAKE_INSTALL_PREFIX
was set to a system directory, type% sudo make install
otherwise type
% make install
This will create shared libraries called
liboofcanvas*.so
orliboofcanvas*.dylib
in<prefix>/lib
, a directory calledoofcanvas
in<prefix>/lib/pythonX.Y/site-packages
(where X.Y is your python version number), a file calledoofcanvas.pc
in<prefix>/lib/pkgconfig
, and a directory calledoofcanvas
in<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 myapp
If 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/pkgconfig
where
<prefix>
is the value ofCMAKE_INSTALL_PREFIX
you 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-packages
if 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 code
or
#include <oofcanvasgui.h> // includes the gtk code as well
in C++, or
import oofcanvas
from oofcanvas import oofcanvasgui
in 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.
OffScreenCanvas
is the base class. It can be used to make drawings that will be printed or saved to a file, but not displayed.Canvas
is derived fromOOFScreenCanvas
. It creates aGtk.Layout
which can be used in Gtk3 to put theCanvas
in a GUI. It calls user-provided callback functions in response to mouse events.A slightly different
Canvas
class is available in Python. It’s derived in Python from a SWIG-wrapped C++ class calledPythonCanvas
. The main difference between the C++ and PythonCanvas
classes is that the Python class expects callback functions to be Python methods, and theGtkLayout
is 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":
= horizontalScrollBar.get_adjustment().get_value()
sx + x)
horizontalScrollBar.get_adjustment().set_value(sx ...
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
(ppu);
Canvas canvas// Get a pointer to the GtkLayout widget
*widget = canvas.gtk();
GtkWidget
// Install the canvas in the gui. For example, if it's going into
// a GtkFrame,
.add(widget);
frame
// Create a canvas layer
*layer = canvas.newLayer("layername");
CanvasLayer
// Add items to the layer
double x=1., y=2., radius=1.4;
*circle = new CanvasCircle(x, y, radius); // In user coordinates.
CanvasCircle ->setLineWidthInPixels(1.5); // In pixel units
circle(1., 0.7, 0.0, 0.5); // r, g, b, a, all in [0.0, 1.0]
Color orange.setFillColor(orange);
circle->addItem(circle);
layer
// Add more items if you want
...
// Draw the items to the canvas
.draw(); canvas
The equivalent Python is virtually identical
import oofcanvas
from oofcanvas import oofcanvasgui
False)
oofcanvas.init_OOFCanvas(
= oofcanvasgui.Canvas(width=300, height=300, ppu=1.0,
canvas =True, hexpand=True)
vexpand
frame.add(canvas.layout)
= oofcanvas.CanvasLayer("layername")
layer
= 1.
x = 2.
y = 1.4
radius = oofcanvas.CanvasCircle.create(x, y, radius)
circle 1.5)
circle.setLineWidthInPixels(= oofcanvas.Color(1., 0.7, 0.0).opacity(0.5)
orange
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:
= MyCoord(x, y)
pt = oofcanvas.CanvasCircle(pt, 1.0) circle
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:
= MyCoord( * oofcanvas.someFunctionReturningACoord() ) pt
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, withCoords
instead ofdoubles
.- From Python, only
Rectangle((x0, y0), (x1, y1))
is available.
Useful methods are
double Rectangle::xmin() const
double Rectangle::ymin() const
double Rectangle::xmax() const
double Rectangle::ymax() const
Rectangle::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)
ppu
is 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
CanvasLayer
with 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::getLayer
will 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) const
gets a particular layer from the stack. Layer 0 is the bottom layer.
Canvaslayer* OffScreenCanvas::getLayer(const std::string &name) const
gets 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() const
returns the total number of layers.
std::size_t OffScreenCanvas::nVisibleItems() const
returns the total number of visible items drawn on all layers.
void OffScreenCanvas::raiseLayer(int n, int howfar)
raises layer
n
byhowfar
places in the layer list. A higher layer may hide the contents of a lower layer. Ifn
is too large, the layer will just be moved to the top.void OffScreenCanvas::lowerLayer(int n, int howfar)
lowers layer
n
byhowfar
places in the layer list. Ifn
is too large, the layer will just be moved to the bottom.void OffScreenCanvas::raiseLayerToTop(int n)
moves layer
n
to the top of the layer list.void OffScreenCanvas::lowerLayerToBottom(int n)
moves layer
n
to 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.
maxpix
is the number of pixels to assume in the largest dimension of the image.If
bg
is 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
pt0
andpt1
to the given file.maxpix
andbg
are 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() const
returns the current scale factor.
Coord OffScreenCanvas::pixel2user(const ICoord&) const
converts 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+margin
times the width and height of the bounding box of its contents. The default value is 0.0.bool OffScreenCanvas::empty() const
has 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
CanvasItems
at the given point, if the items are in clickableCanvasLayer
.std::vector<CanvasItem*> OffScreenCanvas::allItems() const
returns a list all
CanvasItems
on the Canvas, in allCanvasLayers
.void OffScreenCanvas::datadump(const std::string&) const
writes 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(double ppu) Canvas
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() const
returns a pointer to the Canvas’s
GtkLayout
.void Canvas::destroy()
destroys the
GtkLayout
. This is called automatically by theCanvas
destructor, but it can be called manually if necessary for some reason. Don’t try to use theCanvas
after destroying it.void Canvas::show()
calls
gtk_widget_show
on the Canvas’sGtkLayout
.void Canvas::draw()
instructs the Canvas to draw all of its
CanvasItems
.int Canvas::widgetWidth() const
returns the width of the space allocated in the GUI for the
GtkLayout
.int Canvas::widgetHeight() const
returns 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
CanvasItems
are visible and as large as possible.void Canvas::center()
scrolls the canvas so that the center of bounding box of all
CanvasItems
is centered on the Canvas, without zooming.Rectangle Canvas::visibleRegion() const
returns a
Rectangle
giving the user space coordinates of the visible part of the Canvas.GtkAdjustment* Canvas::getHAdjustment() const
returns the
GtkAdjustment
that controls horizontal position of the canvas. Connecting this object to aGtkScrollBar
allows the canvas to be scrolled by the user.GtkAdjustment* Canvas::getVAdjustment() const
is 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::allowMotionEvents
function.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& eventtype
The types are “down” (button was pressed), “up” (button was released), “move” (mouse was moved), and “scroll” (scroll wheel was turned).
const Coord& position
The position of the mouse event, in user coordinates.
int button
Which mouse button was used.
bool shift
Whether or not the shift key was pressed.
bool ctrl
Whether or not the control key was pressed.
void *data
The 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
ma
in 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
RubberBand
object for displaying mouse motions. SeeRubberBand
for 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 givendata
is 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
**kwargs) Canvas(width, height, ppu,
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
position
argument is a tuple, not aCoord
. - The
data
is a Python object, not avoid*
.
Canvas.allowMotionEvents(ma)
is like the C++ version, telling the
Canvas
how 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
callback
function is a Python function anddata
is 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() const
returns 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
Canvas
is rendered. Normally this shouldn’t be needed. Adding or removing aCanvasItem
from aCanvasLayer
makes it dirty.void CanvasLayer::setOpacity(double)
sets the opacity with which the layer will be copied to the
Canvas
when it’s displayed. 0.0 is fully transparent and 1.0 is fully opaque.void CanvasLayer::raiseBy(int howfar) const
raises 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) const
is the same as
raiseBy
, but in the other direction.void CanvasLayer::raiseToTop() const
is the same as
OffScreenCanvas::raiseLayerToTop(int n)
.void CanvasLayer::lowerToBottom() const
is 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:
(Coord(0.,0.), 1.0);
CanvasCircle circ->addItem(&circ); layer
but this is correct:
*circ1 = new CanvasCircle(Coord(0.,0.), 1.0);
CanvasCircle ->addItem(circ1);
layer*circ2 = CanvasCircle::create(Coord(0.,0.), 1.);
CanvasCircle ->addItem(circ2); layer
In Python, don’t do this:
= oofcanvas.CanvasCircle((0.,0.), 1.)
circ layer.addItem(circ)
Do this instead:
= oofcanvas.CanvasCircle.create((0.,0.), 1.)
circ 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) const
returns 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() const
returns the numerical value of the line width, in pixels or user units.
bool CanvasShape::getLineWidthInPixels() const
returns
true
if 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
LineJoin
enum class:LineJoin::MITER
LineJoin::ROUND
, orLineJoin::BEVEL
,
equivalent to the members of the
Cairo::LineJoin
class.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
LineCap
enum class:LineCap::BUTT
LineCap::ROUND
, andLineCap::BSQUARE
,
equivalent to the members of the
Cairo::LineCap
class.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.
offset
indicates 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
Color
instead 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)
segment
is theCanvasSegment
that the arrowhead will be drawn on. `position
ranges 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
. Ifreversed
istrue
, then the arrow points toward the first point of the segment. (Ifposition
is 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)
width
andlength
are in user units.
or
void CanvasArrowHead::setSizeInPixels(double width, double length)
width
andlength
are 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
n
points, 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::newBlankImage( CanvasImageconst Coord& position, const ICoord& pixelsize, const Color &color)
The image is filled with a single color,
color
, so it’s not really blank.position
is the user coordinate of the lower left corner of the image.size
is the size that it will be drawn, in user units.pixelsize
is the size of the image in pixels.Read a png file:
CanvasImage* CanvasImage::newFromPNGfile( const Coord& position, const std::string& filename)
position
is the position of the lower left corner of the image in user coordinates.Read any file format that ImageMagick can handle:
* CanvasImage::newFromImageMagickFile( CanvasImageconst Coord& position, const std::string& filename)
position
is the position of the lower left corner of the image in user coordinates.Create a CanvasImage from ImageMagick data:
* CanvasImage::newFromImageMagick( CanvasImageconst Coord& position, ::Image imagedata) Magick
Create a
CanvasImage
from image data that has already been read by ImageMagick. The data is copied from the ImageMagick structure.Create a CanvasImage from NumPy data:
* CanvasImage::newFromNumpy( CanvasImageconst Coord& position, *numpyarray, PyObject bool flipy)
numpyarray
must 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. Ifflipy
is 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
setSize
orsetSizeInPixels
must 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 &) const
This returns the color of the pixel at the given point in the image. The
ICoord
is 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
ICoord
is 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
n
points, 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
pt
is some kind of point object, withpt[0]
being x andpt[1]
being y.
or
CanvasPolygon::addPoints(ptlist)
where
ptlist
is 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
n
segments, 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
pt0
topt1
.
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)
fontdesc
is 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 ifinPixels
is 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:
setRubberBand
can 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
:
LineRubberBand
is a straight line from the mouse-down position to the current position.RectangleRubberBand
is rectangle with one corner at the mouse-down position and the diagonally opposite corner at the current position.CircleRubberBand
is a circle centered on the mouse-down position and passing through the current position.EllipseRubberBand
is an ellipse that is fit into a rectangle, as inRectangleRubberBand
.SpiderRubberBand
is 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++,list
is astd::vector<Coord>*
. In Python, it’s a iterable collection of objects whereobj[0]
is the x component ofobj
andobj[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:
(const Coord&, const Coord&); // [3]
CanvasRectangle(const Coord*, const Coord*); // [4]
CanvasRectanglestatic 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]
};
CanvasRectangle
is derived fromCanvasFillableShape
, but the notes here apply just as well to items derived fromCanvasShape
or 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
CanvasShape
base class, and fill color is in theCanvasFillableShape
base class.The constructor needs to set the parameters that describe the rectangle, and to create the implementation:
::CanvasRectangle(const Coord &p0, const Coord &p1) CanvasRectangle: CanvasFillableShape( new CanvasRectangleImplementation(this, Rectangle(p0, p1))), (p0.x), ymin(p0.y), xmin(p1.x), ymax(p1.y) xmax{}
The constructor invokes the
CanvasFillableShape
constructor, 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
create
method 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
classname
method is used by the templates inpythonexportable.h
to allow a genericCanvasItem
object returned from C++ to Python to be interpreted as the correctCanvasItem
subclass. 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) { = p0.x; xmin = p0.y; ymin = p1.x; xmax = p1.x; ymax ->bbox = Rectangle(p0, p1); implementation(); modified}
Because the change has altered the rectangle’s bounding box, the implementation’s
bbox
is updated, andmodified()
is called to indicate that the rectangle will need to be re-rendered.A
CanvasItem
that isn’t used in a rubberband doesn’t need to have anupdate
method.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.h
allowsprint
to 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:
(CanvasRectangle *item, // [2]
CanvasRectangleImplementationconst 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
CanvasItem
subclass that this class implements.The constructor arguments must include the
CanvasItem
and its bounding box. The bounding box can be an uninitializedRectangle
if it’s not known yet. In this case, the bounding box is known and has been provided by the caller, theCanvasRectangle
constructor.If necessary, there can be other arguments here, since it is called only by the associated
CanvasRectangle
constructor.The
CanvasItem
and bounding box must be passed to the base class constructor.CanvasItemImplementation
andCanvasShapeImplementation
work the same way as theCanvasFillableShapeImplementation
used 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 itscanvasitem
pointer. Because of the templating,canvasitem
is a pointer to the correctCanvasItem
subclass,CanvasRectangle
.void CanvasRectangleImplementation::drawItem( ::RefPtr<Cairo::Context> ctxt) Cairoconst { double w = lineWidthInUserUnits(ctxt); double halfw = 0.5*w; = bbox; Rectangle r .expand(-halfw); // move all edges inward by half the line width r->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(); ctxt(ctxt); fillAndStroke}
lineWidthInUserUnits()
is defined inCanvasShapeImplementation
and gets the desired line width from theCanvasRectangle
, or rather itsCanvasShape
base class.fillAndStroke()
is defined inCanvasFillableShapeImplementation
and gets line, dash, and fill information fromCanvasShape
andCanvasFillableShape
.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
CanvasItem
might 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
Coord
that is known to be within the item’s bounding box,containsPoint()
returns true if theCoord
is actually within the item.containsPoint()
must be defined, although if an item will never be clicked on, defining it to simply returnfalse
is 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 || .y - bbox.ymin() <= lw || bbox.ymax() - pt.y <= lw)); pt}
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>) const
draws the current Cairo path using the line color, width, and dash settings from theCairoShape
.void CanvasFillableShapeImplementation::fillAndStroke(Cairo::RefPtr<Cairo::Context>) const
draws 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
Coord
and the canvas is starting to draw a rubberband. The function should first call the base class method, and then create theCanvasItems
that form the rubberband and add them to the givenCanvasLayer
.The function
Rubberband::doDashes(CanvasShape*)
can be used to set the dash style on anyCanvasShape
used by the rubberband. The line color and line width should be be set using the base classColor color
anddouble lineWidth
data 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
Coord
is the current user-space position of the mouse. The subclass method should call the base class method (which just setsRubberBand::currentPt
to the current coordinate) and then update itsCanvasItems
to 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.
OffScreenCanvas
andCanvas
are the user-visible C++ classes for the canvas objects.The hidden implementation class for
OffScreenCanvas
isOSCanvasImpl
.OffScreenCanvas
contains an opaque pointer,osCanvasImpl
, to anOSCanvasImpl.
Canvas
is derived fromOffScreenCanvas
, adding screen display and mouse interaction abilities.The hidden implementation class for
Canvas
isGUICanvasImpl
, which is derived fromOSCanvasImpl
. ACanvas
contains a pointer,guiCanvasImpl
, to itsGUICanvasImpl
. TheGUICanvasImpl
pointed to from aCanvas
is the same object as theOSCanvasImpl
pointed to in its base class.The user-visible Python canvas,
PythonCanvas
, is derived directly fromGUICanvasImpl
because it is effectively encapsulated by the SWIG wrapper. There is no need to derive it fromCanvas
.
CanvasLayer
is the user-visible base class for layers.- The implementation class,
CanvasLayerImpl
is derived fromCanvasLayer
.
- The implementation class,
CanvasItem
is the user-visible base class for canvas items.The hidden implementation class for a subclass
ITEM
ofCanvasItem
is a templated class,CanvasItemImplementation<ITEM>
, which is derived fromCanvasItemImplBase
.Each
CanvasItem
contains an opaque pointer,implementation
, to itsCanvasItemImplBase
, and eachCanvasItemImplementation<ITEM>
contains a pointer,canvasitem
to 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 oof_manager@list.nist.gov.
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.