Usando GtkApplication. Funciones de representación de Librsvg

Resumen del artículo.

  • Usando GtkApplication. Aplicación de estructura metálica. Makefile
  • Renderizado por la biblioteca librsvg.
  • Exportar una imagen a GtkImage y escalarla.
  • Escalado SVG con características personalizadas.
  • Obteniendo la ruta completa en las aplicaciones.
  • Pruebas de rendimiento GtkDrawingArea vs GtkImage.

Anteriormente había artículos (no míos) en el centro GTK + que usan la función void gtk_main (void) en los ejemplos; La clase GtkApplication le permite resaltar explícitamente las funciones de devolución de llamada application_activate y application_shutdown. Con gtk_main, necesita enganchar explícitamente gtk_main_quit para que cuando haga clic en la cruz, la aplicación finalice. GtkApplication finaliza la aplicación haciendo clic en la cruz, lo cual es más lógico. El marco de la aplicación en sí consiste en 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

universal aquí, le permite compilar todos los archivos de origen sin especificar nombres de archivo específicos, pero si hay archivos adicionales en la carpeta, el compilador jurará.
También puede usar CC = g ++ -std = c ++ 11, pero poner en las funciones de devolución de llamada
Externa "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

sirve para incluir recursos en el archivo ejecutable, en este caso es un archivo de descripción de interfaz 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; } 

En el primer argumento de la función gtk_application_new, puede colocar cualquier texto, pero no funcionó sin un punto. Este ejemplo también omite el archivo window.glade, que se puede crear en el editor Glade UI.

Dividimos la ventana con el contenedor GtkBox en 2 partes, en una de ellas colocamos GtkDrawingArea, en la otra:



Como resultado, la aplicación cambiará

 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; 

Y en consecuencia inicialización.

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

Agregue la ruta #include <librsvg-2.0 / librsvg / rsvg.h>. (Los paquetes librsvg y librsvg-dev deben estar instalados).

Los nombres de las funciones de devolución de llamada se toman del archivo .glade, la función es responsable de esto
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; } 

En algunas situaciones (como HMI), es posible que deba cambiar el tamaño del SVG. Puede
cambiar los parámetros de ancho y alto en el archivo SVG. O transfiéralo a GtkPixbuf y allí para hacer escala. Dado que GtkImage no se hereda de GtkBin, no puede tener sus propios eventos de tipo ButtonClick (eventos relacionados con el cursor). Hay un contenedor vacío para esto: GtkEventBox. Y el dibujo en sí se puede colgar directamente en 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); } } 

Esta función carga la imagen de fondo (2.png), que a menudo representa
Dibujo 1x1 con un píxel transparente. Y luego se dibuja un dibujo (pixbuf) en esta superficie y luego se realiza el zoom y la exportación a la imagen (imagen).

Y no debemos olvidarnos de limpiar la memoria.

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

El resultado es:


Si los valores de ancho y alto se establecen en los parámetros SVG, la imagen puede aparecer borrosa al exportar a png.

También puede cambiar mediante programación el ancho y el alto. Para esto, creé archivos separados
svg_to_pixbuf_class.c y svg_to_pixbuf_class.h. Es decir, el archivo se abre en cambios de ancho, alto.

Se guarda en / dev / shm /. Después de exportar la información a svg_handle, debe eliminar el archivo en sí y la ruta de la línea al archivo. También se admiten valores fraccionales de ancho / largo.

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 


Ahora cambie el tamaño del lado izquierdo (que es 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; } 

Como puede ver, aquí hay una característica desagradable: la ruta completa. Es decir, vale la pena mover la carpeta, ya que la parte izquierda (que GtkDrawingArea) deja de mostrarse. Lo mismo se aplica a todos los recursos que no están incluidos en el archivo ejecutable. Para hacer esto, escribí una función que calcula la ruta completa al archivo ejecutable, independientemente de cómo se ejecutó.

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

Hay 2 ejemplos en el código de cómo ejecutar el archivo manager.elf. También necesita poner la función main () al principio

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

La función de dibujo tomará la siguiente forma

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

Pruebas de acción rápida.

Tenemos 2 funciones de dibujo (GtkDrawingArea y GtkImage).

Colocaremos cada uno de ellos en un diseño de un tipo (sin olvidar conectar <time.h>)

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

Y en la aplicación htop, puede ver cómo el programa consume del 20 al 30% de cada núcleo Athlon 2 X3 2.5 GHz.

El error fue encontrado rápidamente.

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

Al final resultó que, GtkImage tiene su propio sistema de representación, y el contenido de image1_draw_cb solo se puede inicializar una vez. Las líneas comentadas eran redundantes.



Como puede ver, la representación por primera vez lleva más tiempo con GtkImage que con GtkDrawingArea, pero en teoría actualizar la imagen debería ser más rápido. 4 millones de ciclos de procesador por cada redibujo de imagen de 220px * 220px es un poco demasiado, y puede almacenar en caché solo a través de pixbuf (al menos, no conozco otros métodos).

Gracias por su atencion

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


All Articles