Pliki graficzne w C/C++ - biblioteka FreeImage

freeimage-logo.jpgJakiś czas temu na forum linuxowo.pl pojawił się temat obsługi plików graficznych z programów w C. Ponieważ temat ten jest mi bliski postanowiłem napisać krótki tutorial jak tego dokonać posiłkując się darmową biblioteką FreeImage. Biblioteka FreeImage integruje w sobie dobrodziejstwa wielu innych bibliotek obsługujących pliki graficzne (libjpeg, libpng, …) w postaci prostego, zwięzłego i jednolitego API. Ponieważ opiera się na dobrze sprawdzonych komponentach jest niezawodna i szybka. Dla mnie odkrycie jej było lekarstwem na koszmar obsługi plików graficznych w C/C++. Zapraszam do przeczytania i komentarzy. Chętnie napiszę coś więcej na jej temat, jeśli będzie taka potrzeba.
Do tutoriala użyłem mojego Debiana Lennego, ale z powodzeniem powinno to wszystko działać także pod Ubuntu czy ogólnie dowolnym Linuksem. Pod systemem Windows także można użyć biblioteki FreeImage, lecz trzeba mieć kompilator (np. z Visual Studio C++ Express), ręcznie poustawiać kilka rzeczy i oczywiście sciągnąć bibliotekę.

Przygotowanie środowiska

Tutaj sprawa jest niezwykle prosta i sprowadza się do zainstalowania odpowiednich pakietów. Będziemy potrzebować dwóch z nich. Pierwszy to build-essential, który zawiera wszystkie potrzebne nagłówki standardowych bibliotek. Drugi to pakiet samej biblioteki o nazwie libfreeimage-dev. Instalka sprowadza się zatem do:

sudo apt-get install build-essential libfreeimage-dev

Dodam jeszcze, że w Ubuntu pakiet biblioteki FreeImage znajduje się w repozytorium Universe. Należy zatem zadbać o to, aby ów repozytorium było dodane do /etc/apt/sources.list.

Szybki przykład - konwersja formatów

convert-gif-jpg.jpg

Dla zobrazowania podstaw użycia biblioteki FreeImage rozpatrzymy prosty przykład: konwersję pliku GIF na JPEG. Weźmiemy na warsztat bardzo popularny obraz lenna.gif, który spróbujemy przekonwertować właśnie na JPEGa.

Proponuję zacząć po prostu od kodu:

// freeimage-test.c - przykładowy program używający FreeImage
#include 
#include 
#include 

int main(void)
{
	int return_value = EXIT_SUCCESS;
	FreeImage_Initialise(TRUE);
	puts("Loading...");
	FIBITMAP* bitmap = FreeImage_Load(FIF_GIF, "lenna.gif", 0);
	if (NULL != bitmap)
	{
		puts("Saving...");
		FreeImage_Save(FIF_JPEG, bitmap, "lenna.jpg", 0);
		FreeImage_Unload(bitmap);
	}
	else
	{
		puts("Image couldn't be loaded");
		return_value = EXIT_FAILURE;
	}

	FreeImage_DeInitialise();
	puts("Exit");

	return return_value;
}

No to jedziemy po koleji - co powyższy kod robi:

  • używa odpowiednich nagłówków; w przypadku biblioteki FreeImage załączamy plik nagłówkowy FreeImage.h poprzedzony dwoma standardowymi nagłówkami biblioteki standardowej języka C
  • inicjalizuje bibliotekę FreeImage; do tego celu służy wywołanie metody FreeImage_Initialise(TRUE); bez tego użycie funkcji biblioteki jest niemożliwe
  • wczytuje plik graficzny w formacie GIF; sprawa jest dość prosta, gdyż wystarczy użyć funkcji FreeImage_Load; jako parametry podajemy kolejno: typ pliku, jego nazwę oraz zero; jeśli operacja skończy się sukcesem funkcja ta zwróci wskaźnik do struktury FIBITMAP, która to zawiera wszelkie informacje o wczytanym obrazku; jeśli się to nie powiedzie to dostaniemy NULL
  • zapisuje plik graficzny w formacie JPEG; sprawa jest analogiczna do wczytywania; używamy w zamian funkcji FreeImage_Save; tutaj znowu podajemy parametry, czyli: format pliku, wskaźnik do struktury FIBITMAP, nazwę pliku wyjściowego i znowu zero
  • zwalnia miejsce zajmowane przez obrazek; właśnie po to jest wywołanie FreeImage_Unload; jeśli byśmy tego nie uczynili obrazek ciągle by okupował pamięć, co jest już niepotrzebne, bo nie będziemy go więcej używać
  • deinicjalizuje bibliotekę; gdy już nie potrzebujemy korzystać z usług biblioteki FreeImage należy ją o tym poinformować funkcją FreeImage_DeInitialise, aby mogła zwolnić pamięć przez siebie zajmowaną

Po więcej informacji zapraszam do oficjalnej dokumentacji FreeImage (znajduje się tam plik PDF).

Kompilacja

Na sam koniec tego mikro-tutoriala instrukcja jak to wszystko skompilować. Użyjemy kompulatora GCC, który jest standardowo dostępny w każdym Linuksie. Skompilować program można np. tak:

gcc freeimage-test.c -lfreeimage -o freeimage-test

Zostanie utworzony plik binarny freeimage-test, a kompilator do linkowania użyje, prócz standardowych bibliotek, także bibliotekę FreeImage (przełącznik -lfreeimage). Po tej całej operacji program jest gotowy do użycia. Wystarczy umieścić plik lenna.gif w katalogu z binarką i uruchomić program. Po tej czynności powinniśmy znaleźć nowy plik lenna.jpg.

Proste? Proste!
Powodzenia w bojach z FreeImage! :D
UPDATE: FreeImage posiada także wrapper dla C++, który nieco ułatwia zabawę. Więcej informacji w dokumentacji FreeImagePlus (rzeczony wrapper).

NetBeans 6 - ciąg dalszy

Przez parę dni używałem NetBeansów do programowania projektu H-RT. Nie były to może sesje bardzo obfite w nowy kod czy długie, ale pozwoliły mi nieco bliżej przyjrzeć się funkcjonowaniu tego środowiska. Mam zatem kilka nowych przemyśleń - tym razem gorzkich. :)
Podpowiadacz
Pierwsza sprawa to wspominany już w poprzednim poście “intellisense”, czyli auto-podpowiedzi. Okazuje się, że jest on mniej użyteczny niż sądziłem. Owszem, tuż po przebudowaniu projektu i odczekaniu paru sekund działa, ale… no właśnie. Z dotąd nieznanych mi powodów mechanizm ten potrafi się w danym pliku po prostu zdezaktywować. Wyświetla wtedy w podpowiedzi smutny napis: “no suggestions”. Druga sprawa to wykorzystanie szablonów. Podpowiadacz najwyraźniej nie radzi sobie np. z szablonami dotyczącymi inteligentych wskaźników (ang. smart pointer). Owszem, potrafi podpowiedzieć to co jest w samym szablonie, ale już operator -> ukazuje nam jego bezradność (”no suggestions”). Trochę to smutne, gdyż w nowoczesnym programie w C++ dość często używa się jakiejś formy smart-pointerów. Kolejna sprawa to prędkość działania podpowiedzi. Zazwyczaj, niestety, trzeba chwilkę odczekać zanim cokolwiek się pojawi (powiedzmy koło 1 sek) po naciśnięciu Ctl+spacja. Nie doszedłem również do końca kiedy podpowiedź się pojawia automatycznie, a kiedy trzeba podpowiadacza przywołać ręcznie. Wszystkie te cechy wpływają na wydajność programowania oraz na stopień irytacji programującego. Oczywiście negatywnie.

Edytor kodu
Druga sprawa to dość dziwne czasem zachowanie samego edytora kodu. Ustawienia ilości spacji reprezentujących znak tabulacji po prostu nie działa. Co bym nie ustawił to IDE zawsze wetnie mi taby na 8 znaków. Próbowałem wszystkich możliwych dostępnych opcji w konfiguracji - bez skutku. Owszem, od czasu do czasu szanowny edytor dobrze sformatuje kod, ale takie momenty zdarzają się niezwykle rzadko. Jedynym wyjściem z tej sytuacji jest zamiana tabów na spacje.

nb6wtf1.png

Będąc jeszcze przy edytorze warto wspomnieć o jeszcze jednym błędzie. Jeśli cokolwiek zmienimy w konfiguracji kolorów podświetlania składni edytor po zamknięciu okna konfiguracyjnego powinien odświeżyć aktualny widok. No więc… czasem o tym zapomina. Mały błąd, a może wprowadzić zamęt (’Przecież to przed momentem zmieniałem!’).

Prędkość działania
W pierwszym poście na temat NB6 pochwaliłem je za większą prędkość działania niż jego konkurent - Eclipse CDT. Prawdę mówiąc nie jestem teraz o tym tak do końca przekonany. Czasem środowisko ma chwile słabości i potrafi się zablokować na 2-3 sekundy. W ogóle czasem mam wrażenie, że wszystko działa ociężale. A nie mam słabego procesora, bo trudno słabym nazwać Core 2 Duo 2.4GHz.

Rozwój
Ostatnią sprawą, która mnie martwi jest fakt, że rozwój wsparcia dla C++ w projekcie NetBeans chyba zamarł. I to dobre 3 miesiące temu. Przynajmniej tak wygląda to z punktu widzenia CVS’a. Nie wiem co o tym wszystkim myśleć. Czy warto nadal pracować w tym środowisku, skoro może ono pozostać w aktualnym punkcie rozwoju (mówię oczywiście o C++). Czas pewnie pokaże. Mam jednak nadzieję, że rozwój części C++ NetBeansów pójdzie do przodu, bo środowisko jest całkiem sympatyczne.

NetBeans 6 dla C++

nb-logo-single.jpg

Jako że wreszcie zeszła mi grypowa temperatura postanowiłem nieco czasu poświęcić na poszukiwanie darmowej, wieloplatformowej alternatywy dla Visual C++ 2005. Miałem już zabierać się za Eclipse CDT, gdy przypomniałem sobie o tym, że parę tygodni temu zainstalowałem nową wersję NetBeansów (czyli wersja 6) specjalnie przeznaczoną dla C++. Był to krótki epizod. Nie testowałem tego środowiska pod kątem czegoś więcej niż standardowe HelloWorld. Tak czy inaczej same NetBeansy wywarły na mnie pozytywne wrażenie. Dlatego też postanowiłem do nich wrócić.

Środowisko wydaje mi się nieco bardziej spójne od Eclipsa. Działa również nieco od swojego konkurenta szybciej. Zaskakujące jest to, że IDE pierwotnie przeznaczone dla języka Java okazało się dobrze przystosowane do C++. Podświetlanie składni działa poprawnie, choć jest nieco uboższe od tego z tandemu VC++2005/VisualAssist. Idzie się jednak do niego przyzwyczaić. Dość pozytywnie zaskoczył mnie mechanizm podpowiedzi, czyli odpowiednik microsoftowego Intellisensa. Działa on dość sprawnie. Znowu, nie jest to aż tak zaawansowane narzędzie jak podpowiedzi VisualAssista, ale jest wystarczające do większości zastosowań. Szkoda tylko, że podpowiedzi nie działają jeszcze zbyt dobrze dla szablonów. Kolejną rzeczą jest integracja w pakiecie obsługi CVS oraz, co dla mnie ważniejsze, Subversion. Tutaj należy się wielki plus dla developerów NetBeansów. Wreszcie jakieś IDE ma w podstawowej wersji zainstalowaną wtyczkę do Subversion, która działa out-of-the-box. Jeśli chodzi o obsługę projektów C++ to programiści NetBeans postawili na kompatybilność z narzędziem make. Innymi słowy, nasz projekt możemy zbudować poza środowiskiem używając po prostu starego dobrego make’a.
Do testów NetBeansów posłużył mi projekt H-RayTracer. H-RT jest raytracerem napisanym w C++, który w chwili obecnej służy mi do badań przy mojej pracy magisterskiej. Do tej pory rozwijałem go w Visual Studio 2005 z wtyczką VisualAssist. Ponieważ chciałem, aby sam projekt był wieloplatformowy postanowiłem go przeportować na Linuksa. I udało się, właśnie dzięki NetBeansom. Po paru godzinach zmagań i nauki nowego środowiska udało mi się zbudować w pełni działający raytracer pod Debianem.
Tak więc nie pozostaje mi nic innego jak zachęcić wszystkich programistów C++ do spróbowania NetBeansów 6 dla C++. Wydaje mi się, że warto.

NetBeans 6.0 for C/C++

Koszt CRITICAL_SECTION

Pisanie aplikacji, które są i wydajne i przyjazne użytkownikowi wymaga użycia wielowątkowości. Wiadomo, jeśli będziemy coś liczyć na wątku odpowiedzialnym za rysowanie interfejsu to tego rysowania w tym czasie nie będzie i otrzymamy aplikację, która nie odpowiada. Tak jest np. w przypadku paska menu Start. Wystarczy kliknąć na na “Moje bieżące dokumenty” czy spróbować wyświetlić menu “Połącz z”. Musimy wtedy swoje odczekać. Gdy do tego jeszcze któryś z plików (z bieżących dokumentów) jest poza naszym komputerem to gwarantowane czekanie kilkadziesiąt sekund. W tym czasie pasek startu jest zawieszony w obowiązkach. ;) Tak czy inaczej podczas jednej z takich sytuacji zacząłem się zastanawiać jaki to wielki koszt czasowy jest związany z blokowaniem za pomocą CRITICAL_SECTION. Oto malutki program, który mi posłużył do testów:

	CRITICAL_SECTION cs;
	InitializeCriticalSection(&cs);
	int testValue = 0;
	LARGE_INTEGER lintStart, lintEnd;
	LARGE_INTEGER lintFreq;
	QueryPerformanceFrequency(&lintFreq);
	QueryPerformanceCounter(&lintStart);
	int i;
	for(i=0; i<1000000; i++)
	{
		EnterCriticalSection(&cs);
		testValue = i;
		LeaveCriticalSection(&cs);
	}
	QueryPerformanceCounter(&lintEnd);
	std::cout << boost::format("CPU Freq: %1% Hz")
		% lintFreq.QuadPart << std::endl;
	std::cout << boost::format("Cycles per enter/leave: %1%")
		% (double(lintEnd.QuadPart-lintStart.QuadPart)
		/ double(i)) << std::endl;

No i cóż się okazało? Na moim Core 2 Duo koszt zablokowania sekcji krytycznej i jej zwolnienia to całe 90-100 cykli zegara CPU. Jeśli metodyka pomiaru była prawidłowa to jest to bardzo mały narzut. Wydaje się zatem, że zysk dla użytkownika programu, który nie musi czekać aż łaskawie program skończy jakąś operację, jest o wiele większy niż koszt. Czy zatem programiści od Windows Explorera nie mogliby tego zastosować, aby żyło się nieco łatwiej z ich systemem?

Projekt: H-RayTracer

H-RTByć może ktoś przeglądając mojego bloga zauważył w odsyłaczach link do strony domowej projektu H-RayTracer. Chciałbym w dzisiejszym wpisie przedstawić H-RT, gdyż jestem jego autorem. Wszystko zaczęło się w zeszłym roku, gdy musiałem sobie wybrać temat pracy magisterskiej. Jako promotora swojej pracy wybrałem dra Dariusza Sawickiego, który jest entuzjastą grafiki komputerowej (pozdrawiam!). Odkąd pamiętam grafika zawsze była mi bliska i już jakiś czas temu podjąłem decyzję, że chciałbym napisać magisterkę na ten temat. I tak oto zdobyłem temat pracy. Nie wdając się zbytnio w szczegóły praca polega na przeanalizowaniu pod kątem użyteczności i wydajności kilku modeli lokalnego odbicia światła (w tym jeden szczególny oparty na fizyce). Ponieważ do moich badań potrzebuję narzędzi powstał niejako produkt poboczny (nie związany bezpośrednio z samą pracą) czyli właśnie H-RayTracer.

H-RayTracer jest narzędziem do generowania grafiki 3D algorytmem ray tracingu. Póki co jest we wczesnej fazie rozwojowej, ale z dnia na dzień staje się coraz bardziej funkcjonalny. Jedyna jak dotąd wydana oficjalnie wersja (0.1) potrafi używać tylko bardzo prostego algorytmu śledzenia promieni (opisanego przez Whitteda ponad 20 lat temu). Obecnie pracuję nad ray tracingiem stochastycznym, który bierze pod uwagę także odbicia światła od obiektów, a nie tylko światło pochodzące bezpośrednio ze źródeł.
Program jest napisany w języku C++ w pełni obiektowo. Wydaje mi się, że jest na tyle elastyczny, że można będzie w nim zawrzeć większość algorytmów renderingu opartych na śledzeniu promieni. Kwestia jedynie ich poznania i zaimplementowania, co nie jest zazwyczaj proste, ale jest za to bardzo ciekawe. Zdecydowałem się także na pełną otwartość kodu mojego rozwiązania - H-RayTracer jest oprogramowaniem open source.
Tyle informacji na temat H-RayTracer w dniu dzisiejszym. Jednakże od czasu do czasu będę zamieszczał na blogu wpis tyczący się H-RT.

Do obejrzenia:
przykładowy rendering z wersji 0.1