Аннотация статьи.

  • Использование GtkApplication. Каркас приложения. Makefile.
  • Отрисовка библиотекой librsvg.
  • Экспорт изображения в GtkImage и его масшабирование.
  • Масштабирование SVG самописными функциями.
  • Получение полного пути в приложениях.
  • Тесты быстродействия GtkDrawingArea vs GtkImage.

Ранее были статьи (не мои) в хабе GTK+, использующие в примерах функцию void gtk_main (void); класс GtkApplication позволяет явно выделить функции обратного вызова application_activate и application_shutdown. C gtk_main нужно явно подцеплять gtk_main_quit для того, чтобы при нажатии на крестик происходило завершение приложения. GtkApplication завершает приложение при нажатии на крестик, что более логично. Сам каркас приложения состоит из файлов 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

здесь универсальный, позволяет компилировать все файлы исходников без указания конкретных имён файлов, но если в папке будут лишние файлы, компилятор будет ругаться.
Можно также использовать CC = g++ -std=c++11, но в функциях обратного вызова поставить
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

cлужит для включения ресурсов в исполняемый файл, в данном случае это файл описания интерфейса 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;
}

В первом аргументе функции gtk_application_new можно разместить любой текст, но без точки у меня не работало. В этом примере также опущен файл window.glade, который можно создать в UI редакторе Glade.

Разделим окно контейнером GtkBox на 2 части, в одну из них поместим 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);
}

Добавим путь #include <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. Можно
менять параметры width и height в 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), который чаще всего представляет собой
рисунок 1x1 с прозрачным пикселем. И потом на эту поверхность(surface) рендерится рисунок(pixbuf) и далее происходит масшабирование и экспорт в картинку (image).

И нельзя забывать про очистку памяти.

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 в параметрах выставлены маленькие значения width и height, то картинка может получиться замыленной при экспорте в png.

Также можно программно изменять width и height. Для этого я создал отдельные файлы
svg_to_pixbuf_class.c и svg_to_pixbuf_class.h. То есть файл открывается в изменяется width, height.

Сохраняется в /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); //result must be freed with g_free()
#endif


Теперь изменим размер левой части (которая 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) перестанет отображаться. Это же касается всех ресурсов, которые не вошли в исполняемый файл. Для этого я написал функцию, которая вычисляет полный путь к запускаемому файлу вне зависимости от способа запуска.

//результат экспортируется в 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);
	}
}

В самом коде есть 2 примера того, как можно запустить файл 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 ГГц.

Ошибка нашлась быстро.

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

Как оказалось, GtkImage имеет свою собственную систему рендеринга, а содержимое image1_draw_cb можно только 1 раз проинициализировать. Закомментированные строки оказались лишними.



Как видно, первый раз рендеринг идёт дольше у GtkImage, чем у GtkDrawingArea, но теоретически обновление картинки должно быть более быстрым. 4 миллиона процессорных циклов на каждую перерисовку изображения размером 220px*220px как-то многовато, а закешировать можно только через pixbuf (как минимум, мне другие способы не известны).

Спасибо за внимание.

Комментарии (9)


  1. humbug
    09.01.2019 19:28
    +3

    Статья хорошая. Блеванул от форматирования кода.


    if(result&&cr)
    {cairo_stroke(cr);}
    else
    printf("Ошибка отрисовки\n");
    return FALSE;
    
    typedef struct{
    GtkApplication *restrict app;
    GtkWidget *restrict win;
    GtkBuilder *restrict builder;
    }appdata;

    Так нельзя. Это обучающая статья по работе с библиотекой, важный компонентом является не только текст статьи, картинки, но и сам код.


    В main.c затесался <b>main.c</b>.


    1. AntonSazonov
      09.01.2019 21:21
      +1

      Я с вами полностью согласен; Не пойму почему минусуют.
      Плюс ко всему ключевое слово restrict понапихано абсолютно везде где нужно и не нужно, при чем там где не нужно намного превышает там где нужно.


  1. c0f04
    10.01.2019 21:04

    Краткое ревью кода

    Сюда должен передаваться указатель на data:

    g_signal_connect(data.app, «activate», G_CALLBACK(application_activate), NULL &data);


    Далее в обработчике эти данные можно получить:
    void application_activate(GtkApplication *application, gpointer user_data)
    {
    appdata *data_ptr = user_data;
    GtkBuilder *builder=builder_init();
    data_ptr=&data;


    Получение данные рекомендую делать однотипно и всегда первой строкой. Однако можете даже сразу в функции объявить требуемый тип данный:
    void application_activate(GtkApplication *application, appdata *data_ptr)


    Среди этих двух вариантов всё зависит от того, как принято в рамках проекта, т. к. они эквивалентны с точки зрения Си.

    Аналогично с обработчиком завершения.

    Глобальные переменные в заголовочном файле — это бессмысленная и крайне плохая практика. Такой файл никуда больше не подключишь, т. к. будет повторное создание переменных. Крайний случай — extern, но лучше писать полностью реентерабельные решения без использования глобальных переменных.

    Форматирование тоже выбрано не очень удачным. Рекомендую везде и всегда ставить фигурные скобки, это повышает единообразие кода, соответственно, и читабельность.

    Работа со строками тоже не является качественной. Как минимум в таких случаях делают PATH_MAX:
    char* result=(char*)calloc(1024,sizeof(char));
    Но в таком случае обходятся обычно стековым массивом, тут вообще нет смысла выделять память, т. к. алгоритм не предполагает выполнения realloc() в случае нехватки выделенной памяти.

    Просмотрел бегло, если интересно, могут отрецензировать более подробно.


    1. SanyaZ7 Автор
      11.01.2019 00:18

      Спасибо за ревью. В следующей статье учту рекомендации. Да и архив с исходниками надо будет прикрепить.


      1. c0f04
        11.01.2019 19:28

        Пожалуйста. Наконец, добрался до этой статьи ещё раз, для загрузки SVG нативная поддержка есть в GdkPixbufLoader.В итоге получается GdkPixbuf. Явно задать размер можно по обработчику «size-prepared», для этого в нём требуется вызвать функцию gdk_pixbuf_loader_set_size(). Любые данные туда можно опять же передать через user_data. При этом в обработчик попадают натуральные размеры, которые заданы в самом svg-файле. При создании своего виджета на основе картинки, к примеру, можно использовать эти размеры как натуральные для виджета (у виджетов есть натуральные и минимальные).

        Единственное «но» — если требуется изменить размер, Pixbuf требуется пересоздавать, но оно и понятно, — по-другому никак. Для этого можно закешировать файл в GBytes, и писать его через gdk_pixbuf_loader_write_bytes(). Для чтения файла рекомендую g_mapped_file_get_bytes().

        Если будут вопросы, спрашивайте, что могу, подскажу.


      1. c0f04
        11.01.2019 19:50

        И ещё, если пишете под GLib, то лучше и используйте функции GLib. Например, вместо calloc()/free() лучше использовать g_malloc0()/g_free(). Результат такого выделения памяти не требуется проверять на NULL, т. к. в него встроен g_assert(), что называется «ленивым программированием».

        У Вас же используется calloc()/free(), но не проверяется результат работы этой функции calloc(). Она имеет полное право вернуть NULL, если памяти не хватило. В общедоступных примерах такие проверки обязательно должны быть всегда и везде, иначе новички могут приучиться к плохому.

        А почему не используете вместо get_real_path() стандартную функцию realpath()?


      1. c0f04
        11.01.2019 23:11

        И ещё одну вещь пропустил, всегда sizeof(char) == 1, это гарантируется компилятором. Что, как и почему — хорошо описано в Википедии. Поэтому проще и понятнее не использовать sizeof в этом случае. Сам некоторое время страдал такой паранойей, пока в стандарт не полез:
        www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf

        Уж извиняюсь, что много написал, посто делать ревью кода и показывать все недостатки уже в привычке по работе, а код на Си тут редко проплывает, не могу пройти мимо. :)


        1. SanyaZ7 Автор
          11.01.2019 23:55

          Ещё раз спасибо за содержательное ревью.


  1. SanyaZ7 Автор
    11.01.2019 23:46

    Да, использование g_malloc0()/g_free() является более правильным с точки зрения ведения логов. Действительно, поддержка svg есть

    GSList *list_header=gdk_pixbuf_get_formats();
    	GSList *list=list_header;
    	while(list->next!=NULL)
    	{
    		printf("%s\n",gdk_pixbuf_format_get_name(list->data));
    		list=list->next;
    	}
    	printf("%s\n",gdk_pixbuf_format_get_name(list->data));
    	g_slist_free(list_header);
    

    Выдало форматы: GdkPixdata, tiff, jpeg, tga, bmp, wmf, png, svg, ico, qtif, gif, ani, xpm, xbm, pnm, icns