Utilisation de GtkApplication. Fonctions de rendu Librsvg

Résumé de l'article.

  • Utilisation de GtkApplication. Application filaire. Makefile.
  • Rendu par la bibliothèque librsvg.
  • Exportez une image vers GtkImage et mettez-la à l'échelle.
  • Mise à l'échelle SVG avec des fonctionnalités personnalisées.
  • Obtenir le chemin d'accès complet dans les applications.
  • Tests de performances GtkDrawingArea vs GtkImage.

Plus tôt, il y avait des articles (pas les miens) dans le hub GTK + qui utilisaient la fonction void gtk_main (void) dans les exemples; La classe GtkApplication vous permet de mettre en évidence explicitement les fonctions de rappel application_activate et application_shutdown. Avec gtk_main, vous devez accrocher explicitement gtk_main_quit pour que lorsque vous cliquez sur la croix, l'application se termine. GtkApplication termine l'application en cliquant sur la croix, ce qui est plus logique. Le cadre d'application lui-même se compose de 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

universel ici, il vous permet de compiler tous les fichiers source sans spécifier de noms de fichiers spécifiques, mais s'il y a des fichiers supplémentaires dans le dossier, le compilateur jurera.
Vous pouvez également utiliser CC = g ++ -std = c ++ 11, mais mettez les fonctions de rappel
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

sert à inclure des ressources dans le fichier exécutable, dans ce cas, il s'agit d'un fichier de description d'interface window.glade

 <?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; } 

Dans le premier argument de la fonction gtk_application_new, vous pouvez placer n'importe quel texte, mais cela n'a pas fonctionné sans point. Cet exemple omet également le fichier window.glade, qui peut être créé dans l'éditeur d'interface utilisateur Glade.

Nous divisons la fenêtre avec le conteneur GtkBox en 2 parties, dans l'une d'entre elles, nous mettons GtkDrawingArea, de l'autre:



En conséquence, les données d'application changeront

 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; 

Et en conséquence l'initialisation.

 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); } 

Ajoutez le chemin #include <librsvg-2.0 / librsvg / rsvg.h>. (Les packages librsvg et librsvg-dev doivent être installés).

Les noms des fonctions de rappel sont extraits du fichier .glade, la fonction en est responsable
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; } 

Dans certaines situations (comme l'IHM), vous devrez peut-être redimensionner le SVG. Peut
modifiez les paramètres de largeur et de hauteur dans le fichier SVG. Ou transférez à GtkPixbuf et là déjà pour faire une mise à l'échelle. Étant donné que GtkImage n'est pas hérité de GtkBin, il ne peut pas avoir ses propres événements de type ButtonClick (événements liés au curseur). Il y a un conteneur vide pour cela - GtkEventBox. Et le dessin lui-même peut être accroché directement sur 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); } } 

Cette fonction charge l'image d'arrière-plan (2.png), qui représente le plus souvent
Dessin 1x1 avec un pixel transparent. Et puis un dessin (pixbuf) est rendu sur cette surface, puis un zoom et une exportation vers l'image ont lieu (image).

Et nous ne devons pas oublier de vider la mémoire.

 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); } 

Le résultat est:


Si les valeurs de largeur et de hauteur sont définies dans les paramètres SVG, l'image peut devenir floue lors de l'exportation au format png.

Vous pouvez également modifier par programme la largeur et la hauteur. Pour cela, j'ai créé des fichiers séparés
svg_to_pixbuf_class.c et svg_to_pixbuf_class.h. Autrement dit, le fichier s'ouvre en changements de largeur, hauteur.

Il est enregistré dans / dev / shm /. Après avoir exporté les informations vers svg_handle, vous devez supprimer le fichier lui-même et le chemin d'accès au fichier. Les valeurs de largeur / longueur fractionnaire sont également prises en charge.

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 


Redimensionnez maintenant le côté gauche (qui est 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; } 

Comme vous pouvez le voir, il y a une caractéristique désagréable ici - le chemin complet. Autrement dit, il vaut la peine de déplacer le dossier, car la partie gauche (qui GtkDrawingArea) cesse d'être affichée. La même chose s'applique à toutes les ressources qui ne sont pas incluses dans le fichier exécutable. Pour ce faire, j'ai écrit une fonction qui calcule le chemin d'accès complet au fichier exécutable, quelle que soit la façon dont il a été exécuté.

 //   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); } } 

Il y a 2 exemples dans le code de la façon d'exécuter le fichier manager.elf. Vous devez également mettre la fonction main () au début

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

La fonction de dessin prendra la forme suivante

 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; } 

Tests d'action rapide.

Nous avons 2 fonctions de dessin (GtkDrawingArea et GtkImage).

Nous allons placer chacun d'eux dans une conception d'un type (sans oublier de se connecter <time.h>)

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

Et dans l'application htop, vous pouvez voir comment le programme mange jusqu'à 20-30% de chaque cœur Athlon 2 X3 2,5 GHz.

L'erreur a été trouvée rapidement.

 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; } 

Il s'est avéré que GtkImage a son propre système de rendu et que le contenu de image1_draw_cb ne peut être initialisé qu'une seule fois. Les lignes commentées étaient redondantes.



Comme vous pouvez le voir, le premier rendu prend plus de temps avec GtkImage qu'avec GtkDrawingArea, mais théoriquement la mise à jour de l'image devrait être plus rapide. 4 millions de cycles de processeur pour chaque redessin d'image 220px * 220px, c'est un peu trop, mais vous ne pouvez mettre en cache que via pixbuf (au moins, je ne connais pas d'autres méthodes).

Merci de votre attention.

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


All Articles