In this recipe we'll learn how to draw 2D graphics on screen and save it to an image in a vector graphics format using the cairo renderer.
Vector graphics can be extremely useful when creating visuals for printing as they can be scaled without losing quality.
Cinder has an integration for the cairo graphics library; a powerful and full-featured 2D renderer, capable of outputting to a variety of formats including popular vector graphics formats.
To learn more about the cairo library, please go to its official web page: http://www.cairographics.org
In this example we'll create an application that draws a new circle whenever the user presses the mouse. When any key is pressed, the application will open a save file dialog and save the content in a format defined by the file's extension.
To draw graphics created with the cairo renderer we must define our renderer to be Renderer2d
.
At the end of the source file of our application class there's a macro to initialize the application where the second parameter defines the renderer. If your application is called MyApp
, you must change the macro to be the following:
CINDER_APP_BASIC( MyApp, Renderer2d )
The cairo renderer allows exporting of PDF, SVG, EPS, and PostScript formats. When specifying the file to save, make sure you write one of the supported extensions: pdf
, svg
, eps
, or ps
.
Include the following files at the top of your source file:
#include "cinder/Rand.h" #include "cinder/cairo/Cairo.h"
We will use Cinder's cairo wrappers to create images in vector formats from our rendering.
Circle
class. This class will contain position, radius, and color parameters. Its constructor will take ci::Vec2f
to define its position and will generate a random radius and color.Write the following code before the application's class declaration:
class Circle{ public: Circle( const Vec2f&pos ){ this->pos = pos; radius = randFloat( 20.0f, 50.0f ); color = ColorA( randFloat( 1.0f ), randFloat( 1.0f ), randFloat( 1.0f ), 0.5f ); } Vec2f pos; float radius; ColorA color; };
std::vector
of circles where we'll store the created circles. Add the following code to your class declaration:std::vector< Circle >mCircles;
cairo::Context
as their parameter:void renderScene( cairo::Context &context );
mCircles
and draw each one in the context:void MyApp::renderScene( cairo::Context &context ){ for( std::vector< Circle >::iterator it = mCircles.begin(); it != mCircles.end(); ++it ){ context.circle( it->pos, it->radius ); context.setSource( it->color ); context.fill(); } }
mouseDown
event handler by declaring it in the class declaration.void mouseDown( MouseEvent event );
Circle
class to mCircles
using the mouse position.void MyApp::mouseDown( MouseEvent event ){ Circle circle( event.getPos() ); mCircles.push_back( circle ); }
cairo::Context
bound to the window's surface. This will let us visualize what we're drawing. Here's the draw
method implementation:void CairoSaveApp::draw() { cairo::Context context( cairo::createWindowSurface() ); renderScene( context ); }
keyUp
event handler.void keyUp( KeyEvent event );
keyUp
implementation we create ci::fs::path
and populate it by calling a save file dialog. We'll also create an empty ci::cairo::SurfaceBase
which is the base for all the surfaces that the cairo renderer can draw to.fs::path filePath = getSaveFilePath(); cairo::SurfaceBase surface;
ci::cairo::SurfacePdf
, ci::cairo::SurfaceSvg
, ci::cairo::SurfaceEps
, or as ci::cairo::SurfacePs
.if( filePath.extension() == ".pdf" ){ surface = cairo::SurfacePdf( filePath, getWindowWidth(), getWindowHeight() ); } else if( filePath.extension() == ".svg" ){ surface = cairo::SurfaceSvg( filePath, getWindowWidth(), getWindowHeight() ); } else if( filePath.extension() == ".eps" ){ surface = cairo::SurfaceEps( filePath, getWindowWidth(), getWindowHeight() ); } else if( filePath.extension() == ".ps" ){ surface = cairo::SurfacePs( filePath, getWindowWidth(), getWindowHeight() ); }
ci::cairo::Context
and render our scene to it by calling the renderScene
method and passing the context as a parameter. The circles will be rendered to the context and a file will be created in the specified format. Here's the final keyUp
method implementation:void CairoSaveApp::keyUp( KeyEvent event ){ fs::path filePath = getSaveFilePath(); cairo::SurfaceBase surface; if( filePath.extension() == ".pdf" ){ surface = cairo::SurfacePdf( filePath, getWindowWidth(), getWindowHeight() ); } else if( filePath.extension() == ".svg" ){ surface = cairo::SurfaceSvg( filePath, getWindowWidth(), getWindowHeight() ); } else if( filePath.extension() == ".eps" ){ surface = cairo::SurfaceEps( filePath, getWindowWidth(), getWindowHeight() ); } else if( filePath.extension() == ".ps" ){ surface = cairo::SurfacePs( filePath, getWindowWidth(), getWindowHeight() ); } cairo::Context context( surface ); renderScene( context ); }
Cinder wraps and integrates the cairo 2D vector renderer. It allows use of Cinder's types to draw and interact with cairo.
The complete drawing is made by calling the drawing methods of a ci::cairo::Context
object. The context in turn, must be created by passing a surface object extending ci::cairo::SurfaceBase
. All drawings will be made in the surface and rasterized according to the type of the surface.
The following surfaces allow saving images in a vector graphics format:
Surface type |
Format |
---|---|
|
|
|
SVG |
|
EPS |
|
PostsSript |
It is also possible to draw using other renderers. Though the renderers aren't able to create vector images, they can be useful in other situations.
Here are the other available surfaces:
Surface Type |
Format |
---|---|
|
Anti-aliased pixel-based rasterizer |
|
Apple's Quartz |
|
Apple's CoreGraphics |
|
Windows GDI |
18.116.50.87