Using GtkApplication. Librsvg drawing features

Abstract of the article.

  • Using GtkApplication. Application framework. Makefile
  • Drawing library librsvg.
  • Exporting an image to GtkImage and its scaling.
  • Scaling SVG self-written functions.
  • Getting the full path in applications.
  • GtkDrawingArea vs GtkImage performance tests.

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 distinguish the application_activate and application_shutdown callback functions. 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>typedefstruct{
	GtkApplication *restrict app;
	GtkWidget *restrict win;
	GtkBuilder *restrict builder;
}appdata;
appdata data;
appdata *data_ptr;
#endif

The Makefile

is universal here, it 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, 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 interface description file window.glade

<?xml version="1.0" encoding="UTF-8"?><gresources><gresourceprefix="/com/example/YourApp"><filepreprocess="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;
}
voidapplication_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);
}
voidapplication_shutdown(GtkApplication *application, gpointer user_data){
	g_object_unref(data.builder);
}
intmain(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);
return0;
}

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, which can be created in the Glade UI editor, is also omitted.

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



will change as a result

typedefstruct{
	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.

voidapplication_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
gtk_builder_connect_signals (data.builder, NULL) is responsible for this ;

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);}
	elseprintf("Ошибка отрисовки\n");
return FALSE;
}

In some situations (for example, HMI), SVG resizing may be required. You can
change the parameters of width and height in the SVG file. Or translate into GtkPixbuf and scale there. Since GtkImage is not inherited from GtkBin, it cannot have its own ButtonClick type events (cursor-related events). For this there is an empty container - GtkEventBox. And the actual drawing itself can be hung right 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 is most often a
1x1 image with a transparent pixel. And then a picture (pixbuf) is rendered onto this surface (surface) and then it is scaled and exported to a picture (image).

And we must not forget about clearing the memory.

voidapplication_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 zamylennoy when exporting to png.

You can also programmatically change the width and height. To do 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 line-path to the file. 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>intchar_to_digit(char num){
	switch(num)
	{
		case'0': return0;
		case'1': return1;
		case'2': return2;
		case'3': return3;
		case'4': return4;
		case'5': return5;
		case'6': return6;
		case'7': return7;
		case'8': return8;
		case'9': return9;
		case'.': return-1;
		default: return-2;	
	}
}
//считывает число с позиции указателя textdoubleread_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;
}
//подситывает количество символов, которые надо удалить//intcount_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;
		elsereturn 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_freeGString* read_file_in_buffer(char *file_with_path){
	FILE *input = NULL;
    structstatbuf;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_freechar* 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);
		}
		elseprintf("Ошибка анализа: нет закрывающей скобки у тега svg");
	}
}
voidresized_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_Hvoidresized_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 part (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);}
	elseprintf("Ошибка отрисовки\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.pathvoidget_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 you can 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);}
	elseprintf("Ошибка отрисовки\n");
return FALSE;
}

Tests fast action.

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 eats off 20-30% of each Athlon 2 X3 2.5 GHz core.

Error 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 any other methods).

Thanks for attention.

Also popular now: