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.