ملخص المقال.
- باستخدام GtkApplication. تطبيق السلكي. Makefile.
- التقديم بواسطة مكتبة librsvg.
- تصدير صورة إلى GtkImage وحجمها.
- تحجيم SVG مع الميزات المخصصة.
- الحصول على المسار الكامل في التطبيقات.
- اختبارات الأداء GtkDrawingArea مقابل GtkImage.
في وقت سابق ، كانت هناك مقالات (وليست مقالات) في لوحة وصل GTK + تستخدم دالة gtk_main (void) الفارغة في الأمثلة ؛ تتيح لك فئة GtkApplication تمييز وظائف رد الاتصال application_activate و application_shutdown بشكل صريح. مع gtk_main ، تحتاج إلى ربط gtk_main_quit بشكل صريح بحيث أنه عند النقر فوق التقاطع ، ينتهي التطبيق. ينهي GtkApplication التطبيق من خلال النقر على الصليب ، وهو أكثر منطقية. يتكون إطار التطبيق نفسه من main.h و Makefile و string.gresource.xml، main.c.
الرئيسية#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عالمي هنا ، يسمح لك بترجمة جميع الملفات المصدر دون تحديد أسماء ملفات محددة ، ولكن إذا كان هناك ملفات إضافية في المجلد ، فسيقسم المحول البرمجي.
يمكنك أيضًا استخدام CC = g ++ -std = c ++ 11 ، لكن يمكنك وضع وظائف رد الاتصال
خارجي "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يعمل على تضمين الموارد في الملف القابل للتنفيذ ، وفي هذه الحالة يكون ملف وصف واجهة 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)) {
في الوسيطة الأولى للدالة gtk_application_new ، يمكنك وضع أي نص ، لكنه لم يعمل بدون نقطة. يحذف هذا المثال أيضًا ملف window.glade ، والذي يمكن إنشاؤه في محرر Glade UI.
نقسم النافذة بحاوية GtkBox إلى قسمين ، في أحدهما نضع GtkDrawingArea ، من جهة أخرى:

نتيجة لذلك ، سيتغير appdata
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;
وبالتالي التهيئة.
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); }
أضف المسار # تضمين <librsvg-2.0 / librsvg / rsvg.h>. (يجب تثبيت حزم librsvg و librsvg-dev).
يتم أخذ أسماء وظائف رد الاتصال من ملف .glade ، تكون الوظيفة مسؤولة عن ذلك
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; }
في بعض الحالات (مثل HMI) ، قد تحتاج إلى تغيير حجم SVG. يمكن
قم بتغيير معلمات العرض والارتفاع في ملف SVG. أو نقل إلى GtkPixbuf وهناك بالفعل لجعل التحجيم. نظرًا لأن GtkImage ليس موروثًا من GtkBin ، فلا يمكن أن يكون له أحداث من نوع ButtonClick (الأحداث المرتبطة بالمؤشر). هناك حاوية فارغة لهذا - GtkEventBox. ويمكن تعليق الرسم نفسه مباشرة على 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); } }
تقوم هذه الوظيفة بتحميل صورة الخلفية (2.png) ، والتي تمثل في أغلب الأحيان
رسم 1 × 1 بكسل شفاف. ثم يتم تقديم رسم (pixbuf) على هذا السطح ثم يتم التكبير والتصدير إلى الصورة (صورة).
ويجب ألا ننسى مسح الذاكرة.
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); }
والنتيجة هي:

إذا تم تعيين قيم العرض والارتفاع في معلمات SVG ، فقد تتحول الصورة إلى ضبابية عند التصدير إلى png.
يمكنك أيضًا تغيير العرض والارتفاع برمجيًا. لهذا ، قمت بإنشاء ملفات منفصلة
svg_to_pixbuf_class.c و svg_to_pixbuf_class.h. بمعنى ، يتم فتح الملف في عرض التغييرات والطول.
يتم حفظه في / dev / shm /. بعد تصدير المعلومات إلى svg_handle ، تحتاج إلى حذف الملف نفسه ومسار السطر إلى الملف. كما يتم دعم قيم العرض / الطول الكسري.
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);
الآن تغيير حجم الجانب الأيسر (وهو 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; }
كما ترون ، هناك ميزة غير سارة - المسار الكامل. بمعنى ، الأمر يستحق نقل المجلد ، حيث يتوقف الجزء الأيسر (الذي GtkDrawingArea) عن عرضه. ينطبق الشيء نفسه على جميع الموارد التي لم يتم تضمينها في الملف القابل للتنفيذ. للقيام بذلك ، كتبت وظيفة تحسب المسار الكامل للملف القابل للتنفيذ ، بغض النظر عن كيفية تشغيله.
يوجد مثالان في التعليمات البرمجية الخاصة بكيفية تشغيل الملف manager.elf. تحتاج أيضًا إلى وضع main () في بداية الوظيفة
char cwd[1024]; getcwd(cwd, sizeof(cwd)); get_real_path(argv[0]);
سوف تأخذ وظيفة الرسم النموذج التالي
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; }
اختبارات العمل السريع.لدينا 2 وظائف التعادل (GtkDrawingArea و GtkImage).
سنضع كل منهم في تصميم من نوع (دون أن ننسى الاتصال <time.h>)
clock_t tic = clock(); clock_t toc = clock(); printf("image1_draw_cb elapsed : %f seconds\n", (double)(toc - tic) / CLOCKS_PER_SEC);
وفي تطبيق htop ، يمكنك أن ترى كيف يستوعب البرنامج 20-30 ٪ من كل نواة Athlon 2 X3 2.5 GHz.
تم العثور على الخطأ بسرعة.
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);
كما اتضح فيما بعد ، فإن GtkImage لديه نظام عرض خاص به ، ويمكن تهيئة محتويات image1_draw_cb مرة واحدة فقط. وكانت الخطوط المعلقة زائدة عن الحاجة.

كما ترون ، تستغرق عملية العرض لأول مرة وقتًا أطول مع GtkImage مقارنة بـ GtkDrawingArea ، ولكن يجب أن يكون تحديث الصورة نظريًا أسرع. هناك 4 ملايين دورة للمعالجات لكل عملية إعادة رسم لصورة تبلغ 220 بكسل * 220 بكسل أكبر قليلاً ، ولا يمكنك التخزين المؤقت إلا من خلال pixbuf (على الأقل ، لا أعرف طرقًا أخرى).
شكرا لاهتمامكم