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.
- 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
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.
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.
Go to the build directory and run make uninstall
. This deletes all
the installed files but unfortunately leaves empty directories behind.
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.
<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.
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.
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.
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.
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.
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()
.
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 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&)
.
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)
...
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.
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.
These classes are defined in the OOFCanvas namespace and are used for some arguments and return values by the main OOFCanvas methods.
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.
An ICoord
is a Coord with integer coefficients, used to identify pixels.
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
.
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
.
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.
-
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.
-
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.
-
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
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() 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.
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
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.
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
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) const
returns true if the given point is within the item on the given canvas.
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.
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.
These are the actual items that can be drawn, in alphabetical order.
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.
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.
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.
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)
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
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.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* CanvasImage::newFromImageMagickFile( const 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* CanvasImage::newFromImageMagick( const Coord& position, Magick::Image imagedata)
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* CanvasImage::newFromNumpy( const Coord& position, PyObject *numpyarray, 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.
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.
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.
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
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
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.
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.
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.
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.
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()
.
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]
};
-
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::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
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) { 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
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); }
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
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( 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 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 || 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.
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.)
-
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
.
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.
It shouldn't be necessary to understand this section in order to use OOFCanvas. It's here to help development.
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
.
-
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.
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.