📜 ⬆️ ⬇️

Using GtkApplication. Librsvg drawing features

Abstract of the article.


Earlier there were articles (not mine) in the GTK + hub, which use the void function gtk_main (void) in the examples; The GtkApplication class allows you to explicitly allocate the callback functions application_activate and application_shutdown. C gtk_main you need to explicitly hook up gtk_main_quit so that when you click on the cross, the application is terminated. GtkApplication terminates the application by clicking on the cross, which is more logical. The application framework itself consists of the files main.h, Makefile, string.gresource.xml, main.c.

main.h
')
#ifndef MAIN_H #define MAIN_H #include <gtk/gtk.h> typedef struct{ GtkApplication *restrict app; GtkWidget *restrict win; GtkBuilder *restrict builder; }appdata; appdata data; appdata *data_ptr; #endif 

Makefile

here is universal, allows you to compile all source files without specifying specific file names, but if there are extra files in the folder, the compiler will swear.
You can also use CC = g ++ -std = c ++ 11, but in the callback functions you put
extern "C".

 CC = gcc -std=c99 PKGCONFIG = $(shell which pkg-config) CFLAGS = $(shell $(PKGCONFIG) --cflags gio-2.0 gtk+-3.0 librsvg-2.0) -rdynamic -O3 LIBS = $(shell $(PKGCONFIG) --libs gio-2.0 gtk+-3.0 gmodule-2.0 librsvg-2.0 epoxy) -lm GLIB_COMPILE_RESOURCES = $(shell $(PKGCONFIG) --variable=glib_compile_resources gio-2.0) SRC = $(wildcard *.c) GEN = gresources.c BIN = main ALL = $(GEN) $(SRC) OBJS = $(ALL:.c=.o) all: $(BIN) gresources.c: string.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=. --generate-dependencies string.gresource.xml) $(GLIB_COMPILE_RESOURCES) string.gresource.xml --target=$@ --sourcedir=. --generate-source %.o: %.c $(CC) $(CFLAGS) -c -o $(@F) $< $(BIN): $(OBJS) $(CC) -o $(@F) $(OBJS) $(LIBS) clean: @rm -f $(GEN) $(OBJS) $(BIN) 

string.gresource.xml

Serves to include resources in the executable file, in this case, the window.glade interface description file

 <?xml version="1.0" encoding="UTF-8"?> <gresources> <gresource prefix="/com/example/YourApp"> <file preprocess="xml-stripblanks" compressed="true">window.glade</file> </gresource> </gresources> 

main.c

 #include "main.h" GtkBuilder* builder_init(void) { GError *error = NULL; data.builder = gtk_builder_new(); if (!gtk_builder_add_from_resource (data.builder, "/com/example/YourApp/window.glade", &error)) { //     g_critical ("   : %s", error->message); g_error_free (error); } gtk_builder_connect_signals (data.builder,NULL); return data.builder; } void application_activate(GtkApplication *application, gpointer user_data) { GtkBuilder *builder=builder_init(); data_ptr=&data; data.win=GTK_WIDGET(gtk_builder_get_object(builder, "window1")); gtk_widget_set_size_request(data.win,360,240); gtk_application_add_window(data.app,GTK_WINDOW(data.win)); gtk_widget_show_all(data.win); } void application_shutdown(GtkApplication *application, gpointer user_data) { g_object_unref(data.builder); } int main (int argc, char *argv[]) { gtk_init (&argc, &argv); gint res; data.app = gtk_application_new("gtk.org", G_APPLICATION_FLAGS_NONE); g_signal_connect(data.app, "activate", G_CALLBACK(application_activate), NULL); g_signal_connect(data.app, "shutdown", G_CALLBACK(application_shutdown), NULL); res = g_application_run(G_APPLICATION(data.app), 0, NULL); return 0; } 

In the first argument of the gtk_application_new function, you can place any text, but I didn’t work without a dot. In this example, the window.glade file is also omitted, which can be created in the Glade UI editor.

Divide the window with a GtkBox container into 2 parts, put GtkDrawingArea into one of them, onto the other:



As a result, appdata will change.

 typedef struct{ GtkApplication *restrict app; GtkWidget *restrict win; GtkBuilder *restrict builder; GtkDrawingArea *restrict draw; GtkImage *restrict image; GtkEventBox *restrict eventbox1; RsvgHandle *restrict svg_handle_image; RsvgHandle *restrict svg_handle_svg; GdkPixbuf *pixbuf; cairo_t *restrict cr; cairo_surface_t *restrict surf; }appdata; 

And accordingly initialization.

 void application_activate(GtkApplication *application, gpointer user_data) { GtkBuilder *builder=builder_init(); data_ptr=&data; data.win=GTK_WIDGET(gtk_builder_get_object(builder, "window1")); data.draw=GTK_DRAWING_AREA(gtk_builder_get_object(builder, "drawingarea1")); data.image=GTK_IMAGE(gtk_builder_get_object(builder, "image1")); gtk_widget_set_size_request(data.win,640,480); gtk_application_add_window(data.app,GTK_WINDOW(data.win)); gtk_widget_show_all(data.win); } 

Add the path #include <librsvg-2.0 / librsvg / rsvg.h>. (The librsvg and librsvg-dev packages must be installed.)

The names of the callback functions are taken from the .glade file, the function is responsible
gtk_builder_connect_signals (data.builder, NULL);

 gboolean drawingarea1_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data) { if(!data.svg_handle_svg) {data.svg_handle_svg=rsvg_handle_new_from_file("compassmarkings.svg",NULL);} gboolean result=rsvg_handle_render_cairo(data.svg_handle_svg,cr); if(result&&cr) {cairo_stroke(cr);} else printf(" \n"); return FALSE; } 

In some situations (for example, HMI), SVG resizing may be required. Can
change parameters width and height in SVG file. Or translate into GtkPixbuf and scale there. Since GtkImage is not inherited from GtkBin, it cannot have its own events of type ButtonClick (events associated with the cursor). For this there is an empty container - GtkEventBox. And the actual drawing itself can be hung directly on the GtkImage.

 gboolean image1_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data) { if(!data.svg_handle_image) { data.svg_handle_image=rsvg_handle_new_from_file("compassmarkings.svg",NULL); data.surf=cairo_image_surface_create_from_png("2.png"); data.pixbuf=rsvg_handle_get_pixbuf(data.svg_handle_image); } if(data.pixbuf) { cairo_set_source_surface(cr,data.surf,0,0); GdkPixbuf *dest=gdk_pixbuf_scale_simple (data.pixbuf,250,250,GDK_INTERP_BILINEAR); gtk_image_set_from_pixbuf (data.image,dest); g_object_unref(dest); cairo_paint(cr); } } 

This function loads the background image (2.png), which most often represents
drawing 1x1 with transparent pixel. And then a picture (pixbuf) is rendered on this surface (surface) and then it is scaled and exported to a picture (image).

And we must not forget about clearing the memory.

 void application_shutdown(GtkApplication *application, gpointer user_data) { cairo_surface_destroy(data.surf); g_object_unref(data.svg_handle_image); g_object_unref(data.svg_handle_svg); g_object_unref(data.pixbuf); g_object_unref(data.builder); } 

The result was:


If the parameters in the SVG are set to small values ​​of width and height, then the picture may turn out to be zamylenoi when exporting to png.

You can also programmatically change the width and height. For this, I created separate files.
svg_to_pixbuf_class.c and svg_to_pixbuf_class.h. That is, the file opens in the width, height.

Saved to / dev / shm /. After exporting the information to svg_handle, you need to delete the file itself and the file path string. Fractional widths / lengths are also supported.

svg_to_pixbuf_class.c
 #include <string.h> #include <stdlib.h> #include <stdio.h> #include <gtk/gtk.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <math.h> #include <stdbool.h> int char_to_digit(char num) { switch(num) { case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case '.': return -1; default: return -2; } } //     text double read_num_in_text(char* text) { double result=0; int i=0; bool fractional_flag=FALSE; char whole_part[16]={0}; char whole_digits=0; char fractional_part[16]={0}; char fractional_digits=0; while(char_to_digit(text[i])!=-2) { if(char_to_digit(text[i])!=-1&&!fractional_flag) { whole_part[whole_digits]=char_to_digit(text[i]); printf("text_num=%d|%c\n",char_to_digit(text[i]),text[i]); ++whole_digits; ++i; } else { if(char_to_digit(text[i])==-1) { printf("fractional flag is true\n"); fractional_flag=TRUE; ++i; } else { fractional_part[fractional_digits]=char_to_digit(text[i]); ++fractional_digits; printf("frac_digit=%d|%c\n",char_to_digit(text[i]),text[i]); ++i; } } } ///    i=whole_digits; result=whole_part[whole_digits]; while(i>0) { --i; printf("whole=%d\n",whole_part[i]); result=result+pow(10,whole_digits-i-1)*whole_part[i]; } i=0; while(i<=fractional_digits) { result=result+pow(0.1,i+1)*fractional_part[i]; ++i; } printf("result_read_num=%lf\n",result); return result; } //  ,    // int count_of_digits_for_delete(char* text) { int i=0; bool fractional_flag=FALSE; char whole_part[16]={0}; int whole_digits=0; char fractional_part[16]={0}; int fractional_digits=0; while(char_to_digit(text[i])!=-2) { if(char_to_digit(text[i])!=-1&&!fractional_flag) { whole_part[whole_digits]=char_to_digit(text[i]); printf("text_num=%d|%c\n",char_to_digit(text[i]),text[i]); ++whole_digits; ++i; } else { if(char_to_digit(text[i])==-1) { printf("fractional flag is true\n"); fractional_flag=TRUE; ++i; } else { fractional_part[fractional_digits]=char_to_digit(text[i]); ++fractional_digits; printf("frac_digit=%d|%c\n",char_to_digit(text[i]),text[i]); ++i; } } } if(fractional_flag) return whole_digits+1+fractional_digits; else return whole_digits; } //      /dev/shm //      char* create_dump_file(char *file_with_path) { char *file=NULL; int i=0; while(file_with_path[i]!='\0') {++i;} while(file_with_path[i]!='/'&&i>0) {--i;} file=file_with_path+i; GString *string=g_string_new("test -f /dev/shm"); g_string_append(string,file); g_string_append(string,"|| touch /dev/shm/"); g_string_append(string,file); system(string->str); ///  -  GString *full_path=g_string_new("/dev/shm"); g_string_append(full_path,file); char *result=g_string_free(full_path,FALSE); return result; } //result must be freed with g_string_free GString* read_file_in_buffer(char *file_with_path) { FILE *input = NULL; struct stat buf; int fh, result; char *body=NULL; // GString *resultat=g_string_new(""); fh=open(file_with_path, O_RDONLY); result=fstat(fh, &buf); if (result !=0) printf("  \n"); else { printf("%s",file_with_path); printf(" : %ld\n", buf.st_size); printf(" : %lu\n", buf.st_dev); printf(" : %s", ctime(&buf.st_atime)); input = fopen(file_with_path, "r"); if (input == NULL) { printf("Error opening file"); } body=(char*)calloc(buf.st_size+64,sizeof(char)); //    //    if(body==NULL) { printf("      body\n"); } int size_count=fread(body,sizeof(char),buf.st_size, input); if(size_count!=buf.st_size) printf("   "); resultat=g_string_append(resultat,body); free(body); } fclose(input); return resultat; } void* write_string_to_file(char* writed_file, char* str_for_write, int lenght) { FILE * ptrFile = fopen (writed_file ,"wb"); size_t writed_byte_count=fwrite(str_for_write,1,lenght,ptrFile); //if(writed_byte_count>4) return TRUE; //else return FALSE; fclose(ptrFile); } //      g_free char* get_resized_svg(char *file_with_path, int width, int height) { char *writed_file=create_dump_file(file_with_path); //       GString *body=read_file_in_buffer(file_with_path); char *start_search=NULL; char *end_search=NULL; char *width_start=NULL; char *width_end=NULL; char *height_start=NULL; char *height_end=NULL; start_search=strstr(body->str,"<svg"); int j=0; //   if(start_search) { end_search=strstr(start_search,">"); if(end_search) { ///  width width_start=strstr(start_search,"width"); width_end=width_start+strlen("width"); ///   width    while(width_end[j]==0x0A||width_end[j]==0x20) ++j; if(width_end[j]=='=') ++j; while(width_end[j]==0x0A||width_end[j]==0x20) ++j; if(width_end[j]!='"') printf("   svg.     width=%c\n",width_end[j]); else ++j; ///  ///  ,   gssize size=count_of_digits_for_delete(width_end+j); ///   (1  - 1 ) gssize pos=width_end+j-body->str; ///       g_string_erase(body,pos,size); char width_new[8]; g_snprintf(width_new,8,"%d",width); g_string_insert(body, pos, width_new); ///  height height_start=strstr(start_search,"height"); height_end=height_start+strlen("height"); ///   height    j=0; while(height_end[j]==0x0A||height_end[j]==0x20) ++j; if(height_end[j]=='=') ++j; while(height_end[j]==0x0A||height_end[j]==0x20) ++j; if(height_end[j]!='"') printf("   svg. \    height=%c%c%c\n",height_end[j-1],height_end[j],height_end[j+1]); else ++j; ///  ///  ,   size=count_of_digits_for_delete(height_end+j); ///   (1  - 1 ) pos=height_end+j-body->str; ///       g_string_erase(body,pos,size); char height_new[8]; g_snprintf(height_new,8,"%d",height); g_string_insert(body, pos, height_new); ///      dev/shm/ ///   write_string_to_file(writed_file,body->str,strlen(body->str)); return writed_file; //g_free(writed_file); g_string_free(body,TRUE); } else printf(" :      svg"); } } void resized_svg_free(char *path) { if (remove (path)==-1 ) { printf("    %s\n",path); } } 


svg_to_pixbuf_class.h
 #ifndef SVG_TO_PIXBUF_CLASS_H #define SVG_TO_PIXBUF_CLASS_H void resized_svg_free(char *path); char* get_resized_svg(char *file_with_path, int width, int height); //result must be freed with g_free() #endif 


Now change the size of the left side (which is GtkDrawingArea)
 gboolean drawingarea1_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data) { if(!data.svg_handle_svg) { char* path=get_resized_svg("/home/alex/svg_habr/compassmarkings.svg", 220, 220); data.svg_handle_svg=rsvg_handle_new_from_file(path,NULL); resized_svg_free(path); g_free(path); } gboolean result=rsvg_handle_render_cairo(data.svg_handle_svg,cr); if(result&&cr) {cairo_stroke(cr);} else printf(" \n"); return FALSE; } 

As you can see, there is an unpleasant feature - the full path. That is, it is worth moving the folder as the left side (which is GtkDrawingArea) will no longer be displayed. The same applies to all resources that are not included in the executable file. To do this, I wrote a function that calculates the full path to the file being launched, regardless of the launch method.

 //   data.path void get_real_path(char *argv0) { char* result=(char*)calloc(1024,sizeof(char)); char* cwd=(char*)calloc(1024,sizeof(char)); getcwd(cwd, 1024); int i=0; while(argv0[i]!='\0'&&i<1024) ++i; while(argv0[i]!='/'&&i>0) --i; result[i]='\0'; while(i>0) { --i; result[i]=argv0[i]; } /*alex@alex-System-Product-Name:~/project_manager$ ./manager.elf argv[0]=./manager.elf path=/home/alex/project_manager*/ if(strlen(result)<=strlen(cwd)) //   { free(result); strcpy(data.path,cwd); strcat(data.path,"/"); //printf("path_cwd=%s\n",cwd); free(cwd);} else { /*alex@alex-System-Product-Name:/home$ '/home/alex/project_manager/manager.elf' argv[0]=/home/alex/project_manager/manager.elf path=/home*/ free(cwd); strcpy(data.path,result); strcat(data.path,"/"); //printf("path_result=%s\n",result); free(result); } } 

In the code itself there are 2 examples of how to run the file manager.elf. You also need to put the beginning of the function main ()

 char cwd[1024]; getcwd(cwd, sizeof(cwd)); get_real_path(argv[0]); 

The rendering function will take the following form

 gboolean drawingarea1_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data) { if(!data.svg_handle_svg) { char image_path[1024]; strcat(image_path,data.path); strcat(image_path,"compassmarkings.svg"); printf("image_path=%s\n",image_path); char* path=get_resized_svg(image_path, 220, 220); data.svg_handle_svg=rsvg_handle_new_from_file(path,NULL); resized_svg_free(path); g_free(path); } gboolean result=rsvg_handle_render_cairo(data.svg_handle_svg,cr); if(result&&cr) {cairo_stroke(cr);} else printf(" \n"); return FALSE; } 

Quick Tests.

We have 2 rendering functions (GtkDrawingArea and GtkImage).

Each of them is placed in the view construction (remembering to connect <time.h>)

 clock_t tic = clock(); clock_t toc = clock(); printf("image1_draw_cb elapsed : %f seconds\n", (double)(toc - tic) / CLOCKS_PER_SEC); 

And in the htop application, you can see how the program devours 20-30% of each Athlon 2 X3 2.5 GHz core.

The error was found quickly.

 gboolean image1_draw_cb (GtkWidget *widget, cairo_t *cr, gpointer user_data) { clock_t tic = clock(); if(!data.svg_handle_image) { data.svg_handle_image=rsvg_handle_new_from_file("compassmarkings.svg",NULL); data.surf=cairo_image_surface_create_from_png("2.png"); data.pixbuf=rsvg_handle_get_pixbuf(data.svg_handle_image); //} //if(data.pixbuf) // { cairo_set_source_surface(cr,data.surf,0,0); GdkPixbuf *dest=gdk_pixbuf_scale_simple (data.pixbuf,250,250,GDK_INTERP_BILINEAR); gtk_image_set_from_pixbuf (data.image,dest); g_object_unref(dest); //cairo_paint(cr); } clock_t toc = clock(); printf("image1_draw_cb elapsed : %f seconds\n", (double)(toc - tic) / CLOCKS_PER_SEC); return FALSE; } 

As it turned out, GtkImage has its own rendering system, and the contents of image1_draw_cb can only be initialized once. Commented lines were redundant.



As you can see, the first time rendering takes longer for GtkImage than for GtkDrawingArea, but theoretically the update of the image should be faster. 4 million processor cycles for each redrawing of a 220px * 220px image is a bit too much, and you can cache it only through pixbuf (at least, I don’t know other methods).

Thanks for attention.

Source: https://habr.com/ru/post/435564/


All Articles