Zusammenfassung des Artikels.
- Verwenden von GtkApplication. Wireframe-Anwendung. Makefile.
- Rendern durch die Bibliothek librsvg.
- Exportieren Sie ein Bild nach GtkImage und skalieren Sie es.
- SVG-Skalierung mit benutzerdefinierten Funktionen.
- Den vollständigen Pfad in Anwendungen abrufen.
- Leistungstests GtkDrawingArea vs GtkImage.
Früher gab es Artikel (nicht meine) im GTK + -Hub, die in den Beispielen die Funktion void gtk_main (void) verwenden. Mit der GtkApplication-Klasse können Sie die Rückruffunktionen application_activate und application_shutdown explizit hervorheben. Bei gtk_main müssen Sie gtk_main_quit explizit einbinden, damit die Anwendung beendet wird, wenn Sie auf das Kreuz klicken. GtkApplication beendet die Anwendung durch Klicken auf das Kreuz, was logischer ist. Das Anwendungsframework selbst besteht aus 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
MakefileHier können Sie universell alle Quelldateien kompilieren, ohne bestimmte Dateinamen anzugeben. Wenn sich jedoch zusätzliche Dateien im Ordner befinden, schwört der Compiler.
Sie können auch CC = g ++ -std = c ++ 11 verwenden, aber die Rückruffunktionen eingeben
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.xmldient dazu, Ressourcen in die ausführbare Datei aufzunehmen. In diesem Fall handelt es sich um eine Beschreibungsdatei der window.glade-Schnittstelle
<?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)) {
Im ersten Argument der Funktion gtk_application_new können Sie einen beliebigen Text platzieren, der jedoch ohne Punkt nicht funktioniert hat. In diesem Beispiel wird auch die Datei window.glade weggelassen, die im Glade-UI-Editor erstellt werden kann.
Wir teilen das Fenster mit dem GtkBox-Container in zwei Teile, in einen setzen wir GtkDrawingArea, in den anderen:

Infolgedessen ändern sich die App-Daten
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;
Und entsprechend Initialisierung.
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); }
Fügen Sie den Pfad #include <librsvg-2.0 / librsvg / rsvg.h> hinzu. (Die Pakete librsvg und librsvg-dev müssen installiert sein.)
Die Namen der Rückruffunktionen stammen aus der .glade-Datei, die Funktion ist dafür verantwortlich
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 einigen Situationen (z. B. HMI) müssen Sie möglicherweise die Größe des SVG ändern. Kann
Ändern Sie die Parameter für Breite und Höhe in der SVG-Datei. Oder auf GtkPixbuf übertragen und dort bereits skalieren. Da GtkImage nicht von GtkBin geerbt wird, kann es keine eigenen Ereignisse vom Typ ButtonClick (cursorbezogene Ereignisse) haben. Hierfür gibt es einen leeren Container - GtkEventBox. Und die Zeichnung selbst kann direkt an GtkImage aufgehängt werden.
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); } }
Diese Funktion lädt das Hintergrundbild (2.png), das am häufigsten dargestellt wird
1x1 Zeichnung mit einem transparenten Pixel. Anschließend wird eine Zeichnung (pixbuf) auf diese Oberfläche gerendert und anschließend wird gezoomt und in das Bild exportiert (Bild).
Und wir dürfen nicht vergessen, die Erinnerung zu löschen.
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); }
Das Ergebnis ist:

Wenn die Werte für Breite und Höhe in den SVG-Parametern festgelegt sind, kann das Bild beim Exportieren nach PNG unscharf werden.
Sie können auch Breite und Höhe programmgesteuert ändern. Dafür habe ich separate Dateien erstellt
svg_to_pixbuf_class.c und svg_to_pixbuf_class.h. Das heißt, die Datei wird in Änderungen Breite, Höhe geöffnet.
Es wird in / dev / shm / gespeichert. Nach dem Exportieren der Informationen nach svg_handle müssen Sie die Datei selbst und den Zeilenpfad zur Datei löschen. Bruchbreiten- / Längenwerte werden ebenfalls unterstützt.
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);
Ändern Sie nun die Größe der linken Seite (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; }
Wie Sie sehen können, gibt es hier ein unangenehmes Merkmal - den vollständigen Weg. Das heißt, es lohnt sich, den Ordner zu verschieben, da der linke Teil (der GtkDrawingArea) nicht mehr angezeigt wird. Gleiches gilt für alle Ressourcen, die nicht in der ausführbaren Datei enthalten sind. Zu diesem Zweck habe ich eine Funktion geschrieben, die den vollständigen Pfad zur ausführbaren Datei berechnet, unabhängig davon, wie sie ausgeführt wurde.
Der Code enthält zwei Beispiele für die Ausführung der Datei manager.elf. Sie müssen außerdem main () am Anfang der Funktion einfügen
char cwd[1024]; getcwd(cwd, sizeof(cwd)); get_real_path(argv[0]);
Die Zeichenfunktion hat die folgende 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; }
Schnelle Aktionstests.Wir haben 2 Zeichenfunktionen (GtkDrawingArea und GtkImage).
Wir werden jeden von ihnen in ein Design eines Typs einfügen (ohne zu vergessen, <time.h> zu verbinden).
clock_t tic = clock(); clock_t toc = clock(); printf("image1_draw_cb elapsed : %f seconds\n", (double)(toc - tic) / CLOCKS_PER_SEC);
In der htop-Anwendung können Sie sehen, wie das Programm 20 bis 30% jedes Athlon 2 X3 2,5 GHz-Kerns verbraucht.
Der Fehler wurde schnell gefunden.
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);
Wie sich herausstellte, verfügt GtkImage über ein eigenes Rendering-System, und der Inhalt von image1_draw_cb kann nur einmal initialisiert werden. Kommentierte Zeilen waren überflüssig.

Wie Sie sehen, dauert das erste Rendern mit GtkImage länger als mit GtkDrawingArea, aber theoretisch sollte die Aktualisierung des Bildes schneller sein. 4 Millionen Prozessorzyklen für jedes Neuzeichnen von 220px * 220px-Bildern sind etwas viel, aber Sie können nur über pixbuf zwischenspeichern (zumindest kenne ich keine anderen Methoden).
Vielen Dank für Ihre Aufmerksamkeit.