Cairo: Linking Cairo and Evas
This tutorial demonstrates how you can use 2 different backends of Cairo and link to Evas.
Warm-up
Become familiar with the Cairo API basics by learning about:
-
Drawing a Polygon Using a Cairo Image
Draw a polygon and rectangle using the Cairo image backend with the Evas_Object image.
-
Displaying Animated Geometrical Figures Using Cairo Evas_GL
Draw various figures using the Cairo evas-gl backend with the Evas_GL surface.
Drawing a Polygon Using a Cairo Image
This use case shows how to link Cairo to Evas and how to draw a polygon and rectangle on the screen using the Cairo API. First create a basic application that provides a basic UI application skeleton and already makes available the window object that can contain the Cairo drawing.
-
Build the environment.
Include the <cairo.h> and <math.h> headers in the sample application:
#include <cairo.h> #include <math.h>
- Create the Evas_Object image and link to Cairo.
Define the appdata structure that contains the pointers to the objects to be manipulated:
typedef struct appdata { Evas_Object *win; Evas_Object *img; cairo_surface_t *surface; cairo_t *cairo; } appdata_s;
- Create the Evas_Object window and image.
To display the Cairo drawing on the screen, create the Evas_object window and Evas_object image:
ad->win = elm_win_util_standard_add(PACKAGE, PACKAGE); evas_object_show(ad->win); ad->img = evas_object_image_filled_add(evas_object_evas_get(ad->win)); evas_object_show(ad->img);
- Create Cairo.
To create the Cairo surface, you need the window size to set the Cairo surface. You can get the window size using the evas_object_geometry_get() function, after the evas_object_show() function is called:
evas_object_geometry_get(ad->win, NULL, NULL, &ad->width, &ad->height); ad->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ad->width, ad->height); ad->cairo = cairo_create(ad->surface);
- Draw a polygon and rectangle with Cairo.
To draw the vector image itself:
- To draw Cairo with some specific vector positions, use the window size that you got before. You can compare the window width and height and get the smaller length to set the square side.
void cairo_drawing(void *data) { appdata_s *ad = data; int d = 0; if (ad->width < ad->height) d = ad->width; else d = ad->height;
- To set the background as white color, set the color as white and paint the Cairo:
// Clear background as white cairo_set_source_rgba(ad->cairo, 1, 1, 1, 1); cairo_paint(ad->cairo);
- Draw the polygon and rectangle:
cairo_translate(ad->cairo, 0.1 * d, 0.1 * d); cairo_set_line_width(ad->cairo,2); cairo_set_source_rgba(ad->cairo, 0.0, 0.0, 1.0, 1.0); cairo_move_to(ad->cairo, 0.2 * d , 0.2 * d); cairo_line_to(ad->cairo, 0.4 * d, 0.3 * d); cairo_rel_line_to(ad->cairo, 0.2 * d, -0.1 * d); cairo_arc(ad->cairo, 0.4 * d, 0.4 * d, 0.2* d * sqrt(2), -0.25 * M_PI, 0.25 * M_PI); cairo_rel_curve_to(ad->cairo,-0.2* d, -0.1 * d, -0.2* d, 0.1 * d, -0.4 * d, 0); cairo_close_path(ad->cairo); cairo_fill(ad->cairo); cairo_rectangle(ad->cairo, 0, 0, 0.8 * d, 0.8 * d); cairo_stroke(ad->cairo);
- You need to use the cairo_surface_flush() function to ensure that any pending drawing is performed for the surface:
cairo_surface_flush(ad->surface);
- Update the Evas_object image on the screen:
// Display Cairo drawing on screen unsigned char * imageData = cairo_image_surface_get_data(cairo_get_target(ad->cairo)); evas_object_image_data_set(ad->img, imageData); evas_object_image_data_update_add(ad->img, 0, 0, ad->width, ad->height); }
- The delete callback is triggered when the Window is destroyed from the main loop.
static void win_delete_request_cb(void *data, Evas_Object *obj, void *event_info) { appdata_s *ad = data; cairo_surface_destroy(ad->surface); cairo_destroy(ad->cairo); ui_app_exit(); }
- To draw Cairo with some specific vector positions, use the window size that you got before. You can compare the window width and height and get the smaller length to set the square side.
Figure: Polygon and rectangle
Displaying Animated Geometrical Figures Using Cairo Evas_GL
The following use case shows how you can draw vector graphics using the Cairo evas-gl backend. The Cairo evas-gl backend is similar to Cairo gl backend, since it can provide an implementation of possibly hardware-accelerated Cairo rendering by targeting the OpenGL® ES API.
First create a basic application that provides a basic UI application skeleton and already makes available the window object that can contain the Cairo drawing.
To display and animate vector graphics with Cairo and the Evas_GL surface:
- Build the environment:
- Include the following header files to use the Cairo evas-gl backend on the Evas_GL surface:
#include <cairo.h> #include <Evas_GL.h> #include <cairo-evas-gl.h> #include <math.h>
- Define the appdata structure that contains the pointers to objects to be manipulated:
typedef struct appdata { Evas_Object *win; Evas_Object *img; cairo_surface_t *surface; cairo_t *cairo; cairo_device_t *cairo_device; Evas_GL *evas_gl; Evas_GL_Config *evas_gl_config; Evas_GL_Surface *evas_gl_surface; Evas_GL_Context *evas_gl_context; } appdata_s;
- Include the following header files to use the Cairo evas-gl backend on the Evas_GL surface:
- Initialize Cairo and Evas_GL for Cairo evas-gl drawing.
Initialize the variables related to the Evas_GL within the appdata structure, and create the Evas_GL object using the Evas_object image created before.
- By default, Cairo evas-gl backend uses an Evas_GL context and API set. Before creating the Evas_object window for Cairo evas-gl drawing, the GL Rendering Engine is set because Evas_GL runs with Evas_GL Render Engine:
elm_config_accel_preference_set("opengl");
- The cairo_drawing callback function can be registered by the following Evas_object image function:
evas_object_image_pixels_get_callback_set(ad->img, cairo_drawing, NULL);
- For using hardware-accelerated Cairo rendering, set CAIRO_GL_COMPOSITOR to MSAA compositor:
setenv("CAIRO_GL_COMPOSITOR", "msaa", 1);
- To prevent unnecessary context switching in the Cairo evas-gl backend:
cairo_gl_device_set_thread_aware(ad->cairo_device, 0);
static void init_cairo_evasgl_drawing(appdata_s *ad) { // Create window elm_config_accel_preference_set("opengl"); ad->win = elm_win_util_standard_add(PACKAGE, PACKAGE); elm_win_autodel_set(ad->win, EINA_TRUE); if (elm_win_wm_rotation_supported_get(ad->win)) { int rots[4] = {0, 90, 180, 270}; elm_win_wm_rotation_available_rotations_set(ad->win, (const int *)(&rots), 4); } evas_object_smart_callback_add(ad->win, "delete,request", win_delete_request_cb, NULL); eext_object_event_callback_add(ad->win, EEXT_CALLBACK_BACK, win_back_cb, ad); evas_object_event_callback_add(ad->win, EVAS_CALLBACK_RESIZE, win_resize_cb, ad); evas_object_show(ad->win); // Add image object; a filled one by default evas_object_geometry_get(ad->win, NULL, NULL, &ad->width, &ad->height); ad->img = evas_object_image_filled_add(evas_object_evas_get(ad->win)); evas_object_show(ad->img); // Create evasgl and init Evas_Native_Surface ns; ad->evas_gl = evas_gl_new(evas_object_evas_get(ad->img)); ad->evas_gl_config = evas_gl_config_new(); ad->evas_gl_config->color_format = EVAS_GL_RGBA_8888; // Create a surface and context ad->evas_gl_surface = evas_gl_surface_create(ad->evas_gl, ad->evas_gl_config, ad->width, ad->height); ad->evas_gl_context = evas_gl_context_create(ad->evas_gl, NULL); evas_gl_native_surface_get(ad->evas_gl, ad->evas_gl_surface, &ns); evas_object_image_native_surface_set(ad->img, &ns); // Register Cairo drawing callback evas_object_image_pixels_get_callback_set(ad->img, (Evas_Object_Image_Pixels_Get_Cb)cairo_drawing, ad); // Create cairo and cairo device with evasgl // Use the MSAA compositor if available setenv("CAIRO_GL_COMPOSITOR", "msaa", 1); ad->cairo_device = (cairo_device_t *)cairo_evas_gl_device_create (ad->evas_gl, ad->evas_gl_context); cairo_gl_device_set_thread_aware(ad->cairo_device, 0); ad->surface = (cairo_surface_t *)cairo_gl_surface_create_for_evas_gl(ad->cairo_device, ad->evas_gl_surface, ad->evas_gl_config, ad->width, ad->height); ad->cairo = cairo_create(ad->surface); // Register animation callback to update the output according to refresh rate ecore_animator_frametime_set(0.016); ecore_animator_add(_animate_cb, (void *)ad->img); }
- By default, Cairo evas-gl backend uses an Evas_GL context and API set. Before creating the Evas_object window for Cairo evas-gl drawing, the GL Rendering Engine is set because Evas_GL runs with Evas_GL Render Engine:
- Draw geometrical figures in the cairo_drawing() callback function.
The following code snippet shows how to draw some geometrical figures such as a triangle, rectangle, and circle with various styles and random positions by using the Cairo APIs.
void cairo_drawing(void *data) { appdata_s *ad = data; int i; double r, g, b, a; int Renderloop = 50; // Clear background as white cairo_set_source_rgba(ad->cairo, 1, 1, 1, 1); cairo_paint(ad->cairo); cairo_set_operator(ad->cairo, CAIRO_OPERATOR_OVER); for (i = 0; i < Renderloop; i++) { // Random color r = drand48(); g = drand48(); b = drand48(); a = drand48(); cairo_set_source_rgba(ad->cairo, r, g, b, a); // Random position float x = drand48() * ad->width; float y = drand48() * ad->height; float side = drand48() * 300; // Random style int shape = drand48() * 3; float width = drand48() * 50 + 1; int line_cap = drand48() * 3; cairo_line_cap_t line_cap_style = CAIRO_LINE_CAP_BUTT; if (line_cap == 1) line_cap_style = CAIRO_LINE_CAP_ROUND; else if (line_cap == 2) line_cap_style = CAIRO_LINE_CAP_SQUARE; int line_join = drand48() * 3; cairo_line_join_t line_join_style = CAIRO_LINE_JOIN_MITER; if (line_join == 1) line_join_style = CAIRO_LINE_JOIN_ROUND; else if (line_join == 2) line_join_style = CAIRO_LINE_JOIN_BEVEL; double dash[] = {0.0, 0.0}; dash[0] = drand48() * 50; dash[1] = drand48() * 50; cairo_set_dash(ad->cairo, dash, 2, 0); cairo_set_line_width(ad->cairo, width); cairo_set_line_join(ad->cairo, line_join_style); cairo_set_line_cap(ad->cairo, line_cap_style); if (shape == 0) { // Draw square cairo_rectangle(ad->cairo, x, y, side, side); cairo_fill(ad->cairo); } else if (shape == 1) { // Draw circle cairo_arc(ad->cairo, x, y, side/2, 0.0, 2.0 * M_PI); cairo_stroke(ad->cairo); } else { // Draw triangle cairo_move_to(ad->cairo, x, y); cairo_line_to(ad->cairo, x + side, y); cairo_line_to(ad->cairo, x, y + side); cairo_close_path(ad->cairo); cairo_stroke(ad->cairo); } } cairo_surface_flush(ad->surface); }
- Resize the callback.
In the following code snippet, when resizing occurs, Evas GL surface is recreated and reconnected to the Evas_object image.
static void win_resize_cb(void *data, Evas *e , Evas_Object *obj , void *event_info) { appdata_s *ad = data; if (ad->evas_gl_surface) { cairo_surface_destroy(ad->surface); cairo_destroy(ad->cairo); cairo_device_destroy(ad->cairo_device); evas_gl_surface_destroy(ad->evas_gl, ad->evas_gl_surface); ad->evas_gl_surface = NULL; } evas_object_geometry_get(obj, NULL, NULL, &ad->width, &ad->height); evas_object_image_size_set(ad->img, &ad->width, &ad->height); evas_object_resize(ad->img, &ad->width, &ad->height); evas_object_show(ad->img); if (!ad->evas_gl_surface) { Evas_Native_Surface ns; ad->evas_gl_surface = evas_gl_surface_create(ad->evasgl, ad->evas_gl_config, &ad->width, &ad->height); evas_gl_native_surface_get(ad->evasgl, ad->evas_gl_surface, &ns); evas_object_image_native_surface_set(ad->img, &ns); evas_object_image_pixels_dirty_set(ad->img, EINA_TRUE); ad->cairo_device = (cairo_device_t *)cairo_evas_gl_device_create(ad->evas_gl, ad->evas_gl_context); cairo_gl_device_set_thread_aware(ad->cairo_device, 0); ad->surface = (cairo_surface_t *)cairo_gl_surface_create_for_evas_gl(ad->cairo_device, ad->evas_gl_surface, ad->evas_gl_config, ad->width, ad->height); ad->cairo = cairo_create (ad->surface); } }
- To use the animator callback for a default update refresh rate, you can add an animator.
To update the Evas_object image which is connected to Evas_GL, use the evas_object_image_pixels_dirty_set() function. This updates the Evas_object image whenever Evas renders.
static Eina_Bool _animate_cb(void *data) { Evas_Object *obj = (Evas_Object *)data; evas_object_image_pixels_dirty_set(obj, EINA_TRUE); return EINA_TRUE; }
- The delete callback is triggered when the Window is destroyed from the main loop.
static void win_delete_request_cb(void *data, Evas_Object *obj, void *event_info) { appdata_s *ad = data; cairo_surface_destroy(ad->surface); cairo_destroy(ad->cairo); cairo_device_destroy(ad->cairo_device); evas_gl_surface_destroy(ad->evas_gl, ad->evas_gl_surface); evas_gl_context_destroy(ad->evas_gl, ad->evas_gl_context); evas_gl_config_free(ad->evas_gl_config); evas_gl_free(ad->evas_gl); ui_app_exit(); }
Figure: Random geometrical figures