Radek Chalupa   konzultace a školení programování, vývoj software na zakázku

GDK - jednoduchý prohlížeč obrázků

13.10.2020

V předchozím článku o programování v GDK jsme si ukázali jak s využitím knihovny Cairo vykreslit požadovaný obsah okna. Šlo o jednoduché úsečky a výpis textu. Nyní si ukážeme jak zobrazit obrázek uložený v souboru.

Využijme k tomu GdkPixbuf. Na začátku programu si soubor načteme pomicí funkce gdk_pixbuf_new_from_file.

// globalni promenna mimo funkci main
GdkPixbuf* pixbuf_orig = NULL;

pixbuf_orig = gdk_pixbuf_new_from_file(IMAGE_FILE_PATH, NULL);
if (NULL == pixbuf_orig)
{
	printf("chyba načtení obrázku\n");
	return EXIT_FAILURE;
}

Při vykreslování budeme chtít aby se obrázek přizpůsobil aktuálnímu rozměru okna se zachováním poměru stran. Proto si vždy vytvoříme pomocný PixBuff o rozměrech odpovídajících aktuálním rozměrům kreslící oblasti. Pak použijeme trochu aritmetiky pro vypočítání poměru velikostí, který pak použijeme ve funkci gdk_pixbuf_scale, kterou přeneseme načtený PixBuff do tohoto pomocného, který pak použijeme jako zdroj cairo kontextu a vykreslíme pomocí funkce cairo_rectangle. Vše je nejlépe vidět na zdrojovém kódu funkce obsluhující událost GDK_EXPOSE.

void on_expose(GdkEventExpose* ev)
{
	if (NULL == ev->region)
		return;
	GdkDrawingContext* dc = gdk_window_begin_draw_frame(
		window, ev->region);
	cairo_t* cr = gdk_drawing_context_get_cairo_context(dc);
	if (NULL == pixbuf_orig)
	{
		cairo_set_source_rgba(cr, 1.0, 1.0, 0.8, 1.0);
		cairo_paint(cr);
		gdk_window_end_draw_frame(window, dc);
		return;
	}
	GdkPixbuf* pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
		ev->area.width, ev->area.height);
	double pomer;
	double pocX = 0;
	double pocY = 0;
	double sirkacil = (double)ev->area.width;
	double vyskacil = (double)ev->area.height;
	if ( ((double)gdk_pixbuf_get_width(pixbuf_orig) / ev->area.width) >
		 ((double)gdk_pixbuf_get_height(pixbuf_orig) / ev->area.height) )
	{
		pomer = (double)ev->area.width / gdk_pixbuf_get_width(pixbuf_orig);
		vyskacil = ((double)gdk_pixbuf_get_height(pixbuf_orig) * pomer);
		pocY = ((double)ev->area.height - vyskacil) / 2;
	}
	else
	{
		pomer = (double)ev->area.height / gdk_pixbuf_get_height(pixbuf_orig);
		sirkacil = ((double)gdk_pixbuf_get_width(pixbuf_orig)*pomer);
		pocX = ((double)ev->area.width - sirkacil)/2;
	}
	gdk_pixbuf_scale(pixbuf_orig, pb, 0, 0, ev->area.width, ev->area.height,
		0, 0, pomer, pomer,  GDK_INTERP_NEAREST);
	gdk_cairo_set_source_pixbuf(cr, pb, pocX, pocY);
	cairo_rectangle(cr, 0, 0,
		ev->area.width - pocX, ev->area.height - pocY);
	cairo_fill(cr);
	gdk_window_end_draw_frame(window, dc);
	g_object_unref(pb);
}

Pokud si program vyzkoušíte s nějakým výrazně velkým obrázkem, zjistíte že vykreslování pomocí knihovny Cairo je velice rychlé i při rychlém opakování při roztahování okna.

Na závěr opět celý výpis programu, který lze sestavit překladačem GCC následujícím příkazem (pro ladicí sestavení):

gcc gdk-okno.c -Wall -g `pkg-config --cflags --libs gdk-3.0` -ogdk-okno
#include <gdk/gdk.h>
#include <glib.h>
#include <stdlib.h>

#define IMAGE_FILE_PATH "kozel.png"

GMainLoop* main_loop;
GdkWindow* window;
GdkPixbuf* pixbuf_orig = NULL;

void on_expose(GdkEventExpose* ev)
{
	if (NULL == ev->region)
		return;
	GdkDrawingContext* dc = gdk_window_begin_draw_frame(
		window, ev->region);
	cairo_t* cr = gdk_drawing_context_get_cairo_context(dc);
	if (NULL == pixbuf_orig)
	{
		cairo_set_source_rgba(cr, 1.0, 1.0, 0.8, 1.0);
		cairo_paint(cr);
		gdk_window_end_draw_frame(window, dc);
		return;
	}
	GdkPixbuf* pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8,
		ev->area.width, ev->area.height);
	double pomer;
	double pocX = 0;
	double pocY = 0;
	double sirkacil = (double)ev->area.width;
	double vyskacil = (double)ev->area.height;
	if ( ((double)gdk_pixbuf_get_width(pixbuf_orig) / ev->area.width) >
		 ((double)gdk_pixbuf_get_height(pixbuf_orig) / ev->area.height) )
	{
		pomer = (double)ev->area.width / gdk_pixbuf_get_width(pixbuf_orig);
		vyskacil = ((double)gdk_pixbuf_get_height(pixbuf_orig) * pomer);
		pocY = ((double)ev->area.height - vyskacil) / 2;
	}
	else
	{
		pomer = (double)ev->area.height / gdk_pixbuf_get_height(pixbuf_orig);
		sirkacil = ((double)gdk_pixbuf_get_width(pixbuf_orig)*pomer);
		pocX = ((double)ev->area.width - sirkacil)/2;
	}
	gdk_pixbuf_scale(pixbuf_orig, pb, 0, 0, ev->area.width, ev->area.height,
		0, 0, pomer, pomer,  GDK_INTERP_NEAREST);
	gdk_cairo_set_source_pixbuf(cr, pb, pocX, pocY);
	cairo_rectangle(cr, 0, 0,
		ev->area.width - pocX, ev->area.height - pocY);
	cairo_fill(cr);
	gdk_window_end_draw_frame(window, dc);
	g_object_unref(pb);
}

void on_end_app()
{
	gdk_window_destroy(window);
	g_main_loop_quit(main_loop);
}

void on_gdk_event(GdkEvent* event, gpointer data)
{
	if (event->type == GDK_CONFIGURE)
	{
		gdk_window_invalidate_rect(window, NULL, TRUE);
	}
	else if (event->type == GDK_EXPOSE)
	{
		on_expose((GdkEventExpose*)event);
	}
	else if (event->type == GDK_DELETE)
	{
		on_end_app();
	}
	else if (event->type == GDK_KEY_PRESS)
	{
		if (((GdkEventKey*)event)->keyval == GDK_KEY_Escape)
			on_end_app();
	}
}

int main(int argc, char** argv)
{
	if (!gdk_init_check(&argc, &argv))
		return EXIT_FAILURE;
	pixbuf_orig = gdk_pixbuf_new_from_file(IMAGE_FILE_PATH, NULL);
	if (NULL == pixbuf_orig)
	{
		printf("chyba načtení obrázku\n");
		return EXIT_FAILURE;
	}
	GdkWindowAttr attributes;
	gint attr_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_TITLE | GDK_WA_WMCLASS;
	memset(&attributes, 0, sizeof(attributes));
	attributes.window_type = GDK_WINDOW_TOPLEVEL;
	attributes.x = 100;
	attributes.y = 50;
	attributes.event_mask = GDK_ALL_EVENTS_MASK;
	attributes.width = 1200;
	attributes.height = 800;
	attributes.wclass = GDK_INPUT_OUTPUT;
	attributes.title = "Image view";
	attributes.wclass = GDK_INPUT_OUTPUT;
	gdk_event_handler_set(on_gdk_event, NULL, NULL);
	window = gdk_window_new(NULL, &attributes, attr_mask);
	gdk_window_show(window);
	main_loop = g_main_loop_new(NULL, FALSE);
	g_main_loop_run(main_loop);
	g_main_loop_unref(main_loop);
	g_object_unref(pixbuf_orig);
	return EXIT_SUCCESS;
}