Saving window content as a vector graphics image

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.

Getting ready

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"

How to do it…

We will use Cinder's cairo wrappers to create images in vector formats from our rendering.

  1. To create a new circle every time the user presses the mouse we must first create a 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;
    };
  2. We should now declare std::vector of circles where we'll store the created circles. Add the following code to your class declaration:
    std::vector< Circle >mCircles;
  3. Let's create a method which will draw the circles that will take cairo::Context as their parameter:
    void renderScene( cairo::Context &context );
  4. In the method definition, iterate over 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();
        }
    }
  5. At this point we only need to add a circle whenever the user presses the mouse. To do this, we must implement the mouseDown event handler by declaring it in the class declaration.
    void mouseDown( MouseEvent event );
  6. In its implementation we add a Circle class to mCircles using the mouse position.
    void MyApp::mouseDown( MouseEvent event ){
      Circle circle( event.getPos() );
    mCircles.push_back( circle );
    }
  7. We can now draw the circles on the window by creating 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 );
    }
  8. To save the scene to an image file we must create a context bound to a surface that represents a file in a vector graphics format. Let's do this whenever the user releases a key by declaring the keyUp event handler.
    void keyUp( KeyEvent event );
  9. In the 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;
  10. We'll now compare the extension of the path with the supported formats and initialize the surface accordingly. It can be initialized as 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() );
        }
  11. Now we can create 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 );
    }
    How to do it…

How it works…

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

ci::cairo::SurfacePdf

PDF

ci::cairo::SurfaceSvg

SVG

ci::cairo::SurfaceEps

EPS

ci::cairo::SurfacePs

PostsSript

There's more...

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

ci::cairo::SurfaceImage

Anti-aliased pixel-based rasterizer

ci::cairo::SurfaceQuartz

Apple's Quartz

ci::cairo::SurfaceCgBitmapContext

Apple's CoreGraphics

ci::cairo::SurfaceGdi

Windows GDI

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.50.87