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

Grafický editor využívající ImageMagick a gtkmm - 2. část

8.2.2019

V úvodním díle jsme si připravili kostru aplikace se zobrazením ukázkového obrázku. Nyní je čas zapojit knihovnu ImageMagick, resp. Magick++, jak je nazváno její rozhraní pro jazyk C++. V Linuxovém prostředí musíme mít kromě gtkmm nainstalován také balíček Magick++, který v Ubuntu najdeme pod názvem libmagick++-dev. Do hlavičkového souboru s použitými knihovnami přidáme hlavičkový soubor Magick++.h. V nastavení kompilátoru pak opět použijeme pkg-config s tím že do voleb překladače přidáme `pkg-config --cflags Magick++` a do voleb linkeru `pkg-config --libs Magick++`

Nyní si vytvoříme pomockou třídu (zde nazvanou Obrazek), které bude jako členské proměnné obsahovat Magick::Image (pro načítání a různé úpravy obrázku) a Gdk::Pixbuf (pro zobrazování načteného obrázku v okně aplikace psané v gtkmm.

Nejprve její kompletní kód (soubor Obrazek.h):

#pragma once

#include "includes.h"

class Obrazek
{
private:
	std::unique_ptr<Magick::Image> _image;
	Glib::RefPtr<Gdk::Pixbuf> _pixbuf;
	
public:
	~Obrazek() noexcept
	{
		_pixbuf.reset();
		_image.reset();
	}

public:
	void nacist(const char* soubor)
	{
		_image.reset();
		_image = std::make_unique<Magick::Image>(soubor);
		aktualizovat_data();
	}

	bool je_nacten() noexcept
	{
		return (_image && _image->isValid());
	}

	void aktualizovat_data() noexcept
	{
		if (!je_nacten())
			return;
		_pixbuf.reset();
		Magick::Blob blob;
		_image->write(&blob);
		Glib::RefPtr<Gio::MemoryInputStream> ms = Gio::MemoryInputStream::create();
		ms->add_data((const void*)blob.data(), blob.length());
		_pixbuf = Gdk::Pixbuf::create_from_stream(ms);
		ms.reset();
	}
	
	Gdk::Pixbuf* get_pixbuf() noexcept
	{
		assert(_image.get());
		return _pixbuf.get();
	}
	
	Magick::Image* get_image() noexcept
	{
		assert(_image.get());
		return _image.get();
	}
};

Klíčovou metodou je aktualizovat_data, ve které s využitím tříd Magick::Blob a Gio::MemoryInputStream vytvoříme z načteného obrázku (Magick::Image) Pixbux pro zobrazení. Později si tuto metodu rozšíříme o parametry, na základě kterých nastavíme např. kvalitu jpeg komprese nebo grafický formát.

Nyní si do okna aplikace přidáme (hlavní) nabídku, ze které budeme vybírat soubor k otevření, volit cílový soubor pro ukládání upraveného obrázku a jako ukázku funkce třídy Magick::Image rotaci obrázků o 45 stupňů.

Hlavičkový soubor třídy hlavního okna vypadá nyní takto:

#pragma once

#include "includes.h"
#include "OblastObrazku.h"
#include "Obrazek.h"

class OknoHlavni : public Gtk::Window
{
private:
	Glib::RefPtr<Gdk::Pixbuf> _pixbuf;

public:
  OknoHlavni();

private:
	Obrazek _obrazek;
	Glib::ustring _nacteny_soubor;
private:
	Gtk::Box _box;
	OblastObrazku _oblast_obrazku;
	Gtk::Menu* _submenu_soubor;
	Gtk::MenuItem* _item_soubor;
	Gtk::MenuItem* _mi_konec;
	Gtk::MenuItem* _mi_soubor_otevrit;
	Gtk::MenuItem* _mi_soubor_ulozit;
	Gtk::Menu* _submenu_upravy;
	Gtk::MenuItem* _item_upravy;
	Gtk::MenuItem* _mi_rotace_45;
private:
	void on_konec() noexcept;
	void on_rotace_45() noexcept;
	void on_soubor_otevrit() noexcept;
	void on_soubor_ulozit() noexcept;
private:
	void nacist_soubor(const char* soubor) noexcept;
	void zprava_chyba(const char* text) noexcept;
};

Implementace třídy OknoHlavní včetně pomocných funkcí pro zobrazení chybové hlášky a načtení vybraného souboru vypadá pak následovně:

#include "OknoHlavni.h"

OknoHlavni::OknoHlavni()
{
	_box.set_orientation(Gtk::ORIENTATION_VERTICAL);
	this->add(_box);
	_item_soubor = Gtk::manage(new Gtk::MenuItem("_Soubor"));
	_item_soubor->set_use_underline(true);
	_submenu_soubor = Gtk::manage(new Gtk::Menu);
	_mi_soubor_otevrit = Gtk::manage(new Gtk::MenuItem("_Otevřít"));
	_mi_soubor_otevrit->set_use_underline(true);
	_submenu_soubor->append(*_mi_soubor_otevrit);
	_mi_soubor_ulozit = Gtk::manage(new Gtk::MenuItem("_Uložit"));
	_mi_soubor_ulozit->set_use_underline(true);
	_submenu_soubor->append(*_mi_soubor_ulozit);
	_mi_konec = Gtk::manage(new Gtk::MenuItem("_Konec"));
	_mi_konec->set_use_underline(true);
	_submenu_soubor->append(*_mi_konec);
	_item_upravy = Gtk::manage(new Gtk::MenuItem("Ú_pravy"));
	_item_upravy->set_use_underline(true);
	_submenu_upravy = Gtk::manage(new Gtk::Menu);
	_mi_rotace_45 = Gtk::manage(new Gtk::MenuItem("Otočit o 45 stupňů"));
	_mi_rotace_45->set_use_underline(true);
	_submenu_upravy->append(*_mi_rotace_45);
	_item_soubor->set_submenu(*_submenu_soubor);
	_item_upravy->set_submenu(*_submenu_upravy);
	Gtk::MenuBar* menubar = Gtk::manage(new Gtk::MenuBar);
	menubar->append(*_item_soubor);
	menubar->append(*_item_upravy);
	_box.add(*menubar);
	_box.add(_oblast_obrazku);
	_oblast_obrazku.set_hexpand(true);
	_oblast_obrazku.set_vexpand(true);
	_mi_konec->signal_activate().connect(sigc::mem_fun(*this, &OknoHlavni::on_konec));
	_mi_rotace_45->signal_activate().connect(sigc::mem_fun(*this, &OknoHlavni::on_rotace_45));
	_mi_soubor_otevrit->signal_activate().connect(sigc::mem_fun(*this, &OknoHlavni::on_soubor_otevrit));
	_mi_soubor_ulozit->signal_activate().connect(sigc::mem_fun(*this, &OknoHlavni::on_soubor_ulozit));
	show_all_children();
	this->set_default_size(1024, 780);
	set_title("Editor obrázků");
	nacist_soubor("../derny-stitek.jpg");
}

void OknoHlavni::on_konec() noexcept
{
	close();
}

void OknoHlavni::on_rotace_45() noexcept
{
	_obrazek.get_image()->rotate(45.0);
	_obrazek.aktualizovat_data();
	_oblast_obrazku.set_pixbuf(_obrazek.get_pixbuf());
}

void OknoHlavni::on_soubor_otevrit() noexcept
{
	Gtk::FileChooserDialog dialog("Načíst obrázek",
          Gtk::FILE_CHOOSER_ACTION_OPEN);
	dialog.set_transient_for(*this);
	dialog.add_button("_Zrušit", Gtk::RESPONSE_CANCEL);
	dialog.add_button("Vybrat", Gtk::RESPONSE_OK);
	auto filtr = Gtk::FileFilter::create();
	filtr->set_name("Obrázky");
	filtr->add_mime_type("image/jpeg");
	filtr->add_mime_type("image/tiff");
	filtr->add_mime_type("image/gif");
	dialog.add_filter(filtr);
	int result = dialog.run();
	if (Gtk::RESPONSE_OK != result)
		return;
	std::string str_soubor = dialog.get_filename();
	nacist_soubor(str_soubor.c_str());
}

void OknoHlavni::on_soubor_ulozit() noexcept
{
	if (!_obrazek.je_nacten())
		return;
	Gtk::FileChooserDialog dialog("Uložit obrázek",
          Gtk::FILE_CHOOSER_ACTION_SAVE);
	dialog.set_transient_for(*this);
	dialog.add_button("_Zrušit", Gtk::RESPONSE_CANCEL);
	dialog.add_button("Uložit", Gtk::RESPONSE_OK);
	auto filtr = Gtk::FileFilter::create();
	filtr->set_name("Obrázky");
	filtr->add_mime_type("image/jpeg");
	filtr->add_mime_type("image/tiff");
	filtr->add_mime_type("image/gif");
	dialog.add_filter(filtr);
	int result = dialog.run();
	if (Gtk::RESPONSE_OK != result)
		return;
	std::string str_soubor = dialog.get_filename();
	_obrazek.get_image()->write(str_soubor.c_str());
}

void OknoHlavni::zprava_chyba(const char* text) noexcept
{
	std::unique_ptr<Gtk::MessageDialog> dialog;
	dialog = std::make_unique<Gtk::MessageDialog>(*this, text, false, Gtk::MESSAGE_ERROR);
	dialog->run();
	dialog.reset();
}

void OknoHlavni::nacist_soubor(const char* soubor) noexcept
{
	try
	{
		_obrazek.nacist(soubor);
		_nacteny_soubor = soubor;
		_oblast_obrazku.set_pixbuf(_obrazek.get_pixbuf());
	}
	catch (std::exception& ex)
	{
		zprava_chyba(ex.what());
	}
	
}

Projekt (v CodeLite) si můžete stáhnout v příloze níže.