Creating and destroying Cairo contexts

I have a technical question concerning the creation and destruction of Cairo contexts. My simple code follows. There are multiple creations and destructions of a Cairo context, which seem redundant. I’m confused as to which is primary: is it the cairo_surface or the cairo_context ? The context is created from the surface which certainly suggests the surface is primary. I feel I should be able to create a Cairo context just once (in the configure_event_callback ?) and keep it around for the duration of this simple program.

------------------------------------------------------------------------------*/
static cairo_surface_t *cairo_surface = NULL;
/*------------------------------------------------------------------------------

---------------------------------------------------------------------------*/
static void clear_surface(void)
{
	cairo_t *cairo_context;

	cairo_context = cairo_create(cairo_surface);
	
	cairo_set_source_rgb(cairo_context, 0.7, 0.7, 0.7);
	cairo_paint(cairo_context);

	cairo_destroy(cairo_context);
}
/*------------------------------------------------------------------------------
	https://developer-old.gnome.org/gtkmm-tutorial/stable/
	sec-cairo-drawing-arcs.html.en
---------------------------------------------------------------------------*/
static inline void draw_brush(GtkWidget *widget, gdouble x, gdouble y)
{
	cairo_t *cairo_context;
	
	// Paint to the surface, where we store our state
	cairo_context = cairo_create(cairo_surface);

	//cairo_set_source_rgb(cairo_surface, 0.0, 0.2, 1.0);
	// default is black
 
	cairo_arc
	(
		cairo_context, 
		x, y, 
		2.0,			// radius 
		0.0, 2 * M_PI	// a full circle
	);	
	
	cairo_fill(cairo_context);
	cairo_destroy(cairo_context);

	// the affected region of the drawing area
	gtk_widget_queue_draw_area(widget, x - 2, y - 2, 4, 4);
}
/*------------------------------------------------------------------------------
	Handle motion events by continuing to draw if button 1 is
	still held down. The ::motion-notify signal handler receives
	a GdkEventMotion struct which contains this information.
 
	https://stackoverflow.com/questions/48557583/
	freehand-drawing-gtk-set-event-compressionfalse-does-not-work	
---------------------------------------------------------------------------*/
static gboolean motion_notify_event_callback
(
	GtkWidget      *widget,
	GdkEventMotion *event,
	gpointer        data
)
{
	// paranoia check, in case we haven't gotten a configure event
	if(cairo_surface == NULL) return FALSE;

	if(event->state & GDK_BUTTON2_MASK)
	{
#if 1
	static gdouble last_x, last_y;
		printf
		(
			"\t%6.2lf %6.2lf delta %6.2lf\n", 
			event->x, event->y,
			hypot(event->x - last_x, event->y - last_y)
		);
		last_x = event->x; last_y = event->y;
#endif
		draw_brush(widget, event->x, event->y);
	}

	return TRUE;
}
/*------------------------------------------------------------------------------
	Handle button press events by either drawing a rectangle
	or clearing the surface, depending on which button was pressed.
	The ::button-press signal handler receives a GdkEventButton
	struct which contains this information.
---------------------------------------------------------------------------*/
static gboolean button_press_event_callback 
(
	GtkWidget      *widget,
	GdkEventButton *event,
	gpointer        data
)
{
	// paranoia check, in case we haven't gotten a configure event
	if(cairo_surface == NULL) return FALSE;

	if(event->button == GDK_BUTTON_SECONDARY)
	{
		clear_surface();
		gtk_widget_queue_draw(widget);
	}


	return TRUE;
}
/*------------------------------------------------------------------------------
	Create a new surface of the appropriate size to store our scribbles
---------------------------------------------------------------------------*/
static gboolean configure_event_callback
(
	GtkWidget         *widget,
	GdkEventConfigure *event,
	gpointer           data
)
{
	cairo_t *cairo_context;
	
	if(cairo_surface) cairo_surface_destroy(cairo_surface);

	cairo_surface = gdk_window_create_similar_surface
	(
		gtk_widget_get_window(widget),
		CAIRO_CONTENT_COLOR,
		gtk_widget_get_allocated_width(widget),
		gtk_widget_get_allocated_height(widget)
	);

	cairo_context = cairo_create(cairo_surface);
	
	cairo_set_source_rgb(cairo_context, 0.7, 0.7, 0.7);
	cairo_paint(cairo_context);

	cairo_destroy(cairo_context);

	// this be the trick
	gdk_window_set_event_compression(((GdkEventButton *)event)->window, false);

	return TRUE;
}
/*------------------------------------------------------------------------------
	Redraw the screen from the surface. Note that the ::draw
	signal receives a ready-to-be-used cairo_t that is already
	clipped to only draw the exposed areas of the widget

---------------------------------------------------------------------------*/
static gboolean draw_callback
(
	GtkWidget *widget,
	cairo_t   *cairo_context,
	gpointer   data
)
{
	cairo_set_source_surface(cairo_context, cairo_surface, 0, 0);
	cairo_paint(cairo_context);
	cairo_set_source_rgb(cairo_context, 0.0, 0.2, 1.0);
 
	return FALSE;
}
/*------------------------------------------------------------------------------
	GtkWidget *event_box;
	event_box		= gtk_event_box_new();
	gtk_container_add(GTK_CONTAINER(event_box), drawing_area);
------------------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
	GtkWidget *scroll_window;
	GtkWidget *capture_button, *email_button;
	GtkWidget *frame;
	GtkWidget *drawing_area;
	
	printf("Gtk+ version %d.%d\n", gtk_major_version, gtk_minor_version);
	image_display_width = 800; image_display_height = 800;

	gtk_init(&argc, &argv);
	window			= gtk_window_new(GTK_WINDOW_TOPLEVEL);
	grid			= gtk_grid_new();
	frame			= gtk_frame_new(NULL);
	drawing_area	= gtk_drawing_area_new();
	
	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_NONE);
	gtk_window_set_title(GTK_WINDOW(window), version);
	gtk_container_set_border_width(GTK_CONTAINER(window), 5);
	
	gtk_container_add(GTK_CONTAINER(window), grid);
	gtk_grid_set_row_spacing(GTK_GRID(grid), 2);
	gtk_grid_set_column_spacing(GTK_GRID(grid), 5);

	gtk_container_add(GTK_CONTAINER(frame), drawing_area);
	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
	
	scroll_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_container_set_border_width(GTK_CONTAINER(scroll_window), 5);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window),
		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_widget_set_size_request
	(
		scroll_window, image_display_width, image_display_height
	);
	gtk_container_add(GTK_CONTAINER(scroll_window), frame);
	gtk_grid_attach(GTK_GRID(grid), scroll_window, 1, 1, 1, 10);

	capture_button	= gtk_button_new_with_label("Capture Cairo Surface");
	email_button	= gtk_button_new_with_label("Email sketch to yyy");
	gtk_grid_attach(GTK_GRID(grid), capture_button,	2, 1, 1, 1);
	gtk_grid_attach(GTK_GRID(grid), email_button,	2, 2, 1, 1);

	g_signal_connect(capture_button, "clicked", 
		G_CALLBACK(capture_surface), NULL);

	g_signal_connect(email_button,	"clicked", 
		G_CALLBACK(email_person), NULL);


	// Signals used to handle the backing surface
	g_signal_connect(drawing_area, "draw",
		G_CALLBACK(draw_callback), NULL);
					
	g_signal_connect(drawing_area,"configure-event",
		G_CALLBACK(configure_event_callback), NULL);

	g_signal_connect(drawing_area, "motion-notify-event",
		G_CALLBACK(motion_notify_event_callback), NULL);
					
	g_signal_connect(drawing_area, "button-press-event",
		G_CALLBACK(button_press_event_callback), NULL);

	gtk_widget_set_events
	(
		drawing_area, 
		gtk_widget_get_events(drawing_area) 
		| GDK_BUTTON_PRESS_MASK
		| GDK_POINTER_MOTION_MASK
	);

	g_signal_connect(window, "destroy", 
		G_CALLBACK(gtk_main_quit), NULL);

	gtk_widget_show_all(window);

	gtk_main();
	return EXIT_SUCCESS;
}
/*------------------------------------------------------------------------------

---------------------------------------------------------------------------*/

I wondered about that a little myself when I started with cairo, around 2007. But I think now it is too late to discuss that. I think in a different thread Mr. Bassi already told you that cairo has a mailing list, now with nearly zero traffic. I am still subscribed.

Some years ago I made the GTK and Cairo Nim bindings, and for that I had to care for such questions. Bud I never asked something, got it all working more or less. Cairo has no full gobject-introspection support, and its internal behaviour about allocating and freeing resources is different from the other GTK libs, so I had to write the cairo bindings manually.

For your actual question, the surface is obviously the heavy object. The cairo context is just a storage for some info. Note that some cairo drawing callbacks get a cairo context as parameter, which is created by the lib.

My very personal view: I can not understand why someone would start with Cairo C API in these days. C is so ugly, and Cairo is so dead. It is really no fun. I hope it is not a teacher who forces you to do that? For fun, you may play for example with Nim’s https://github.com/treeform/pixie. I was told that some people had fun with it. Or you may play with Blend2d – no docu, but at least it is a living project. And there are so many other projects which are much more fun than C and Cairo.

Thank you for your comments Stephan. I’m a grumpy OLD programmer; I wrote my very first program in Algol on a KDF9.

I continue to use C because it is the language I know and I have an art project that now has around 400,000 lines of C that I have written over the past decade. The artwork uses OpenGL and GLSL. My adventures in GTK are just side projects.

I began my little scribble email drawing project with Cairo because it was the only simple code I could find on-line. There are of course massive GTK projects like Gimp and Xournal that draw, but have huge Glade generated UIs. They undoubtedly use the event history to interpolate the freehand drawing too.

From this discussion, it seems I should be using OpenGL within GTK for this kind of task and not Cairo following this: OpenGL in GTK written by Emmanuele. I may investigate this by trying glxgears within GTK.

And then, perhaps, to figure out how to load a shader from GTK :crazy_face:

My scribble email project is planned to do nothing more than capture an image of a stylus written note, on a tablet, and email it to someone at the touch of a button. The Curl library is very easy to use. It is all rather retro, I’m afraid – hand writing short notes to friends. Each friend will have their own button. Virtual keyboards on a tablets are horrible.

The idea is great! What devices do you consider as a target platform? You mean with tablets “real” tablets and not transformers with Intel/AMD inside? I ask this because real tablets and GTK have a hard relationship. Glad to be corrected!

Sorry to disappoint you Stephan, but I’m a Linux devotee so I won’t write for Android or iOS. The hardware platform is a very cheap x86 Celeron N4120 thing . It came with Windows that I immediately discarded and installed Linux Mint. It does come with a stylus. The touch screen and stylus orientations are messed up but I fixed that. The battery life is good.

I see my wife using her iPad and I’m jealous. I hate Apple so what can I do…

I keep thinking about what small, tablet-like thing would I like. I dislike touch screens but like using a stylus. I have gone back to see if there is a workable Graffiti (from Palm Pilot days) library around but people have given up on that type of interface. I found it easy to learn and excellent for quick notes.

A Graffiti module for GTK would be wonderful, but far beyond my skill to write.

What is your impression from touch-capabilities of LM? Do you have the Cinnamon edition? Typical desktops used by LM are mouse-driven. However, I guess that the use with stylus is not much different, but with finger(s)… GTK provides a good-looking gesture API, so I wanted to try it for long time. In general, this should be possible even with an appropriate touchpad.

The touch and stylus interfaces with Linux Mint work well enough. I presume Cinnamon is just as good. The laptop I am writing on, a Dell Inspiron of some kind, comes with a stylus. It worked ‘out of the box’ as soon as Mint was installed. The trackpad’s gestures work too.

I work at several computers all of which have Linux Mint installed in a near identical manner. I value consistency more than a fancy UI.

I guess we are off topic… :smile:

I’m not at all familiar with the past Gnome ‘wars’ but I am of the opinion that too much time is spent by people fussing with desktop designs, that is why I opted for Mint – something rudimentary but quite workable.

Your original question is well explained in this cairo tutorial:
https://martimm.github.io/gnome-gtk3/content-docs/tutorial/Cairo/drawing-model.html

Tutorial does not use C but the important thing it’s how well it explains Cairo concepts.

No, this is not how Cairo works.

The context is the current pen state. You create a context, set up the drawing, and then commit it to the surface, which will store the end state.

Contexts are cheap for that reason.

You should read the Cairo documentation.

That page is a shorter version of the Cairo tutorial, which uses C.

I have my scribble email code working well enough and will put it up on github after I clean it up, if anyone is interest. It still uses three deprecated Gtk functions but fixing them is beyond me…

It’s now up on github and is called scribble_email.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.