dla programistów Stereogramy Marek Sawerwain ielu Czytelników z pew- Inaczej postąpimy podczas genero- nością zetknęło się ze wania stereogramu. Gdy użytkownik stereogramami w posta- wybierze plik, który chce zamienić na Wci różnych pocztówek, stereogram, to poznamy jego pełną ścież- a być może oglądało je na ekranie kę. Wtedy wczytamy ten plik do pamięci komputera. Chociaż nie są one już tak wykorzystując bibliotekę DevIL. popularne, jak kilka lat temu, to z pew- Nasz program będzie przetwarzał nością warto wiedzieć, jak powstają tego tylko pliki z tzw. kolorem indeksowanym typu obrazy. Wbrew pozorom, technika (dlaczego stosujemy taki tryb koloru, rysowania stereogramów, np. takich jak zaraz się wyjaśni), natomiast wewnętrzne na dołączonych do artykułu rysunkach, mechanizmy GNOME bądz GTK skupiają jest zaskakująco prosta. Algorytm postę- się raczej na kolorach typu RGB. Niestety, powania można zapisać w kilkunastu ten rodzaj opisu kolorów nie nadaje się linijkach kodu. do łatwego generowania stereogramów. W tym artykule postaram się przed- Z tego powodu korzystamy z DevIL, stawić, jak napisać program do genero- gdzie ładowany plik nie jest poddawany wania prostych stereogramów. dodatkowej wewnętrznej konwersji. Po wczytaniu pliku graficznego Na początek kilka i wykonaniu stereogramu, gotowy obraz postanowień zapisujemy do pliku tymczasowego, który Nasz przyszły program napiszemy wczytujemy do widgetu GtkImage. Zapis w oparciu o środowisko GTK oraz stereogramu do ostatecznego pliku nastą- GNOME. Będziemy stosować najnowszą pi dopiero, gdy użytkownik wybierze stabilną wersje GNOME o numerze 2.6, opcję Zapisz w naszym programie. Może a bibliotekę GTK w wersji 2.4. Nie będzie- to wydać się działaniem naokoło, ale my mozolnie kodować interfejsu, gdyż w ten sposób oszczędzamy sobie sporo użyjemy w tym celu programu GLADE. pracy i kłopotów związanych z żonglo- Sam program będzie realizował tylko waniem typami kolorów oraz żmudnym podstawowe funkcje, takie jak wczy- kopiowaniem bloków pamięci. tanie obrazu, konwersję na stereogram Powyższe uwagi najlepiej jest zoba- oraz jego zapis. Dodamy jeszcze funkcję czyć w formie diagramu, znajdującego się wydruku, ponieważ dość łatwo zreali- na Rysunku 1. Jak można się domyśleć, Na płycie CD/DVD zujemy ją przy pomocy API biblioteki interfejs nie będzie trudny do zrealizowa- Na płycie CD/DVD znajduje się libgnomeprint. nia. Jedynym bardziej skomplikowanym kod zródłowy i wykonywalny programu oraz Pomyślmy, jakie najważniejsze zda- elementem wydaje się sam algorytm wszystkie listingi z artykułu. rzenia będą zachodzić w naszym pro- generowania stereogramu. gramie. Jedną z pierwszych czynności Ostatnie założenie, jakie chciałbym O autorze: jest wczytanie pliku graficznego do przedstawić, jest bodaj najważniej- Autor zajmuje się tworzeniem programu. Można to zrealizować w łatwy sze: stereogramy najlepiej generować oprogramowania dla WIN32 sposób, wykorzystując gotowy widget na podstawie czarno-białych ilustra- i Linuksa. Zainteresowania: GtkImage. Załadowanie do niego pliku cji, a dokładniej z rysunków z paletą teoria języków programowania nie będzie wymagało dużej ilości kodu. w odcieniach szarości. Stosując taki rodzaj oraz dobra literatura. Podobnie w przypadku zapisu gotowego palety można osiągnąć świetne wyniki. Kontakt z autorem: autorzy@lpmagazine.org stereogramu, znajdującego się w GtkIma- Niezbędną konwersję obrazu możemy ge wystarczą dwie linie kodu. wykonać programem GIMP, zamieniając 70 wrzesień 2004 dla programistów stereogramy kolory na odcienie szarości. Należy rów- nież wybrać tryb indeksowy, w którym podamy ilość kolorów. Standardowo powinniśmy deklarować 256 kolorów, a raczej poziomów szarości. Dodam jesz- cze, że rysunki nie powinny posiadać dużej liczby szczegółów. Algorytm generowania stereogramu Tworzenie stereogramu opiera się na dość prostej zasadzie. Jako pierw- szy tworzymy tzw. wzór bazowy. W naszym programie będzie to losowy układ pikseli, ale w istocie może to być cokolwiek, np. fragment obrazu bądz litery. Gdy mamy już gotowy wzór bazowy, możemy przystąpić do generowania stereogramu. Bierzemy pierwszy piksel obrazu i sprawdzamy jego wartość. Jeśli jest równa zeru, Oglądanie stereogramów to przenosimy w to miejsce pierwszy piksel (albo element) wzoru bazowe- Podstawowa zasada oglądania stereo- zobaczyć przestrzenny obraz. Dość go. Następnie badamy kolejny piksel gramów polega na skupianiu wzroku często łatwiej ogląda się stereogram na obrazu. Jeśli i on będzie równy zeru, poza obrazem. Powinniśmy niejako ekranie monitora. Istnieje jeszcze jeden to kopiujemy następny piksel naszego spoglądać przez kartkę i skupiać wzrok sposób, który może pomóc, a mianowicie wzorca. Jeśli nadal będą występować mniej więcej 40-50 cm za kartką. przeniesienie stereogramu na przezro- Jednym z łatwiejszych sposób oglądania czystą folię i patrzenie przez folię skupia- zera, a nasz wzór jest znacznie krót- stereogramów jest przybliżenie kartki do jąc wzrok na jakimś przedmiocie. szy niż przetwarzany obraz, to pózniej nosa i powolne jej oddalanie. Jeśli będzie- Powyższy rysunek prezentuje pewien będziemy kopiować piksele z już prze- my utrzymywać to samo skupienie i kieru- obraz do testów. Nie zdradzę w tym miej- tworzonego obrazu. Z tego powodu, nek wzroku, po paru próbach powinniśmy scu, co przedstawia. jeśli linia obrazu składa się wyłącznie z zer, to zobaczymy powtarzający się wzór bazowy po przeniesieniu całego wzoru bazowego, analizując linię, Niech wzorem bazowym będzie ciąg literę e . Po wszystkich zmianach otrzy- będziemy już przenosić nie elementy znaków: abcde. Pierwszy znak naszego mamy następujący obraz: wzorca, ale piksele zawarte w linii obrazu to zero, więc przenosimy pierw- obrazu. szy znak wzorca, czyli a . Przechodzi- abcde Gdyby piksel miał wartość jeden, to my do następnego punktu, zarówno acdee skopiujemy następny punkt ze wzorca, w obrazie, jak i we wzorcu. Ponownie acdee np. jeśli aktualnie wskazujemy na trzeci mamy zero, więc przenosimy literę b . acdee piksel wzorca, to przenosimy piksel Ponieważ pierwszy wiersz składa się abcde czwarty. Gdyby badany punkt miałby wyłącznie z zer, to nastąpi dokładne wartość dwa, to przeniesiemy piąty przepisanie wzorca. Przejdzmy teraz Ostatecznie, aby ułatwić oglądanie ste- piksel wzorca. Ogólnie, jeśli badany do drugiej linii. Wartość pierwsza - zero reogramu, dodamy wzorzec abcde na piksel ma wartość n, a pozycja we wzorcu - przeniesie nam pierwszy znak wzorca, początek i koniec każdej linii obrazu ma numer i, to przenosimy piksel z pozy- ale drugi badany element zawiera jedyn- powyżej. Nasz tekstowy stereogram cji i+n. kę. Zgodnie z naszym algorytmem, prze- przyjmie następującą postać: Wypróbujmy teraz nasz sposób niesiemy następny znak po aktualnie generowania stereogramów na pro- wskazywanym, więc nie będzie to litera abcdeabcdeabcde stym przykładzie. Mamy następujący b , ale kolejna we wzorcu litera c . abcdeacdeeabcde rysunek: W następnym badanym elemencie abcdeacdeeabcde sytuacja powtarza się. Ponownie mamy abcdeacdeeabcde 00000 jedynkę, a we wzorcu jesteśmy na pozy- abcdeabcdeabcde 01110 cji litery c , więc kopiujemy następny 01110 znak literę d . Gdyby zamiast jedynki Generowanie stereogramów jest, jak 01110 była dwójka, a we wzorze bylibyśmy widać z powyższego przykładu, łatwym 00000 na pozycji c , to należałoby przenieść zadaniem. Widać również, dlaczego 71 www.lpmagazine.org dla programistów sekcji: XResolution/sections. Liczba sekcji ma również duże znaczenie i tak naprawdę nie powinna być większa niż 8 (w ostateczności może być ich 10). Oznacza to ponadto, że wzór bazowy jest umieszczany na początku każdej linii bezpośrednio w obrazie. Z tego powodu lepiej tworzyć obrazy wyśrodkowane i nieprzylegające do krawędzi. Sam proces generowania wzoru spro- wadza się do następującej linii kodu: in_buf[x]=rand() % 256; Losujemy tu liczbę z zakresu od 0 do 255, wykorzystując funkcję rand. Ponie- waż paleta jest w odcieniach szarości (a przynajmniej taka powinna być, zgod- nie z naszymi wcześniejszymi założe- niami), to wylosowane punkty będą się układać w czarno-biały wzór. Za każdym razem wzór będzie inny, więc nawet gdy tworzymy po raz drugi stereogram z tego samego obrazu, to otrzymamy inny wzór bazowy. W efekcie nowy stereogram będzie różnił się od poprzedniego. Zadaniem drugiej pętli, jak głosi poprzedzający ją komentarz, jest utwo- rzenie stereogramu. Podobnie jak w przypadku generowania wzoru, odczy- tujemy wiersz obrazu i dokonujemy na Rysunek 1. Schemat zdarzeń w programie generującym stereogramy nim odpowiednich operacji. Stereogram powstaje z danych tuż po wzorze bazo- powinniśmy stosować paletę o 256 nego wiersza to zadanie dla funkcji get_ wym, dlatego w pętli wewnętrznej warto- poziomach szarości. Jeśli w takiej palecie raster_line. Po wykonaniu czynności ścią początkową zmiennej x jest wartość zero będzie oznaczać kolor czarny, a war- na wierszu, musimy przetworzone dane, XResolution/sections. Zwróćmy uwagę tość 256 kolor biały, to pozostałe wartości zawarte w buforze, umieścić z powrotem na to, że używamy też drugiej zmiennej i reprezentują kolory pośrednie. Jest zupeł- w obrazie i to zadanie wykonuje funkcja (jej wartością początkową jest zero). nie naturalne, że możemy traktować sza- put_raster_line. Możemy teraz przystąpić do genero- rości jako poziomy głębi. Wykorzystując Na początku każdej linii umiesz- wania stereogramu. Odczytujemy aktual- odpowiednie przesunięcia wzorca, kodu- czamy wzór bazowy, a są to po prostu nie badany piksel (jego numer zawiera jemy wrażenie głębi. losowe punkty z palety. Działamy tu zmienna x). Jeśli piksel posiada wartość Jednak, aby dostrzec stereogram, w trochę inny sposób niż w przedstawio- zero (jest czarny), to na jego miejsce należy na niego spojrzeć w odpowiedni nym wcześniej opisie algorytmu, gdyż jest przenoszony piksel o indeksie i. sposób. Kilka przydatnych uwag zawiera każda linia posiada swój własny wzór Zwróćmy uwagę, że i na początku wska- ramka Oglądanie stereogramów. Czy- bazowy. Ponieważ jest to losowy układ zuje na wzór bazowy. Jeśli jednak piksel telników zainteresowanych dokładniej- pikseli, to nie wpływa to na ostateczną jest większy od zera, to wybieramy szymi informacjami odsyłam do książki postać stereogramu. piksel, zgodnie z naszym algorytmem, Grafika PC bez tajemnic , wydanej przez Gdy spojrzymy na stereogramy dołą- przesunięty o jeden plus wartość piksela wydawnictwo Intersoftland w 1995 roku. czone do artykułu, to widać, że mają podzieloną przez szesnaście. Następnie, one powtarzające się paski. Owe paski w obydwu przypadkach, przesuwamy Implementacja algorytmu to sekcje obrazu. Ich zadaniem jest uła- się we wzorze o jeden piksel, czyli W poprzednim punkcie przedstawiłem twienie oglądania stereogramu łatwiej zwiększamy wartość i o jeden. działanie algorytmu, a teraz zajmiemy się skupić wzrok na centrum stereogramu, Wcześniej, omawiając algorytm, nie jego implementacją. gdy powtarzają się na nim pewne wzory. wykonywaliśmy żadnych dodatkowych Na początku należy utworzyć wzór Z tego powodu cały obraz został podzie- operacji, ale dzielenie jest dość ważną podstawowy. Na Listingu 1 jest to pierw- lony na sekcje i długość wzoru bazowego operacją, ponieważ zabezpiecza nas sza pętla for. W pętli tej przeglądamy jest równa długości sekcji, którą wyzna- przed zbyt dużymi przesunięciami. cały obraz linia po linii. Odczytanie jed- czamy dzieląc rozdzielczość przez liczbę Operację dzielenia traktujemy także jako 72 wrzesień 2004 dla programistów stereogramy Listing 1. Procedura tworząca stereogram Krótki przepis na stereogram void my_make_sirds_image(){ int x,y,i; Tworzenie stereogramu rozpoczy- /* tworzenie wzoru */ namy od utworzenia rysunku z tzw. for(y=0;ymapą głębokości. Zgodnie z naszymi get_raster_line(0, Xresolution, y, &in_buf[0]); założeniami, jest to obraz w odcie- for( x = 0 ; x < ( XResolution / sections ) ; x++ ){ niach szarości. Kolor czarny oznacza in_buf[x]=rand() % 255; najbliżej położony obszar, a kolor } biały najdalej. Tego rodzaju rysunek put_raster_line(0, XResolution, y, &in_buf[0]); z powodzeniem można przygotować } /* tworzenie stereogramu */ samodzielnie, czego przykładem jest for(y=0;yponiższe dzieło : get_raster_line(0, XResolution, y, &in_buf[0]); i=0; for( x = XResolution / sections; x < XResolution ; x++ ){ if(in_buf[x]==0){ in_buf[x]=in_buf[i]; i++; continue; } if(in_buf[x]>0){ in_buf[x]=in_buf[i + 1 + (in_buf[x]/16)]; i++; continue; } } Gdy dysponujemy rysunkiem, to put_raster_line(0, XResolution, y, &in_buf[0]); możemy go wczytać do naszej aplikacji } i wygenerować stereogram. Powinniśmy } otrzymać obraz podobny do poniższe- go: pewnego rodzaju skalowanie kolorów Funkcją memcpy przenosimy dane ze zamiast 256 stosujemy już tylko 16. Pole- zmiennej srids_image do zmiennej cam poeksperymentować z tą wartością, __data. Dane, jak widać, są odpo- aby przekonać się samemu, jak wpływa wiednio adresowane. Wyrażenie na ostateczny obraz stereogramu. y*XResolution przenosi nas na począ- W taki sposób przedstawia się imple- tek wiersza o numerze y. Następnie mentacja naszego algorytmu. Jak widać, przesuwamy się o from_x bajtów, generowanie stereogramów nie jest trud- aby wskazać na początek potrzeb- nym zadaniem. Algorytm można unowo- nego nam obszaru. Funkcja memcpy cześnić wprowadzając kilka innowacji, w ostatnim argumencie oczekuje Otrzymany stereogram można dalej np. dodać kolorowe piksele albo określać podania ilości bajtów do skopiowa- przetwarzać, tzn. takie operacje, jak własny wzór bazowy. nia. Będzie to różnica pomiędzy to_x skalowanie (w szczególności powięk- i from_x, czyli końcem fragmentu szanie), obracanie czy kolorowanie, Odczytywanie i zapis wiersza, który nas interesuje, a jego nie niszczą zawartości stereogramu. wiersza obrazu początkiem. Prostym sposobem na uatrakcyjnienie W implementacji algorytmu używamy W drugiej funkcji wszystkie stereogramu jest nałożenie deseniu np. dwóch sprytnych funkcji do odczytu obliczenia pozostają niezmienione. prostego gradientu na bazie koloru zie- wiersza (get_raster_line) i do jego Zamiana dotyczy dwóch pierw- lonego. W efekcie otrzymamy kolorowy stereogram: zapisu (put_raster_line). Obydwie szych argumentów. Zamieniamy je funkcje to właściwie tylko jedna linia miejscami, ponieważ tym razem kodu, zawierająca wywołanie memcpy. przenosimy dane ze zmiennej Treść pierwszej funkcji przedstawia się __data pod odpowiednie miejsce następująco: w sirds_image: S S void get_raster_line void put_raster_line S S (int from_x, int to_x, int y, (int from_x, int to_x, int y, unsigned char *__data){ unsigned char *__data){ S S memcpy((void*)__data, (void*) memcpy((void*)(sirds_image + S S (sirds_image + (y*XResolution) + (y*XResolution) + from_x), from_x), to_x from_x); (void*)__data, to_x from_x); } } 73 www.lpmagazine.org dla programistów ge, więc musimy uzyskać wskazanie okna wybrania katalogu i utworzenia Listing 2. Treść funkcji na widget o nazwie ImageWidget. Tym katalogu. Nas będzie interesować na on_SirdsGenerate_MNU_activate zajmuje się trzecia linijka kodu z funkcji razie pierwszy typ okna. Z tego powodu srand(time(0)); ilInit(); main: podaliśmy identyfikator GTK_FILE_CHO- iluInit(); OSER_ACTION_OPEN. Następne argumenty ilEnable(IL_ORIGIN_SET); S ImageWidget=glade_xml_get_widget precyzują wartości, jakie są zwracane ilOriginFunc(IL_ORIGIN_UPPER_LEFT); (xml, "ImageWidget"); w zależności od wciśniętego przycisku: ilGenImages(1, &ImgId); podajemy je parami. Wartość GTK_STOCK_ ilBindImage(ImgId); load_image(&filename[0]); Zmienną ImageWidget zadeklarowaliśmy OPEN oznacza przycisk Otwórz. Po niej my_make_sirds_image(); w sposób normalny dla GTK: GtkWidget została podana wartość, która zostanie save_sirds_image *ImageWidget;. przekazana, gdy użytkownik wciśnie ("/tmp/tmp_image.png"); Pozostały nam jeszcze dwie linie. ten przycisk. Sytuacja wygląda podobnie ilDeleteImages(1, &ImgId); Pierwszą podłączamy wszystkie zdefinio- dla następnej pary argumentów: GTK_ gtk_image_set_from_file (GTK_IMAGE(ImageWidget), wane sygnały: STOCK_CANCEL oznacza przycisk Anuluj "/tmp/tmp_image.png"); i jeśli użytkownik wciśnie ten przycisk, glade_xml_signal_autoconnect(xml); jako wynik zostanie przekazana wartość Program dla środowiska GTK_RESPONSE_CANCEL. GNOME/GTK a drugą (najważniejszą instrukcją) uru- Pozostało nam uruchomienie okna Gdy mamy już gotowy algorytm gene- chamiamy nasz program: dialogowego funkcją gtk_dialog_run. rujący stereogram, to napisanie samego Gdy użytkownik wciśnie jeden z dwóch programu nie będzie trudnym zadaniem. gtk_main() wspomnianych przycisków, funkcja Tworzenie programu rozpoczynamy gtk_dialog_run zakończy swoje działa- od zbudowania interfejsu w programie Wczytanie nie i zwróci jako wynik jedną z dwóch GLADE. Rysunek 2 przedstawia główne oraz zapis obrazu podanych wartości: GTK_RESPONSE_ACCEPT okno naszej aplikacji. Tworzenie inter- Tak naprawdę, aby załadować obraz albo GTK_RESPONSE_CANCEL. Przy pomocy fejsu jest łatwym zadaniem, więc nie z pliku do komponentu GtkImage, wystar- instrukcji warunkowej sprawdzamy, czy będę go tutaj szerzej omawiał. Zwróćmy czy wywołać jedną funkcję o nazwie jest to pierwsza z wartości. Przedstawia jednak uwagę na to, że menu oraz pasek gtk_image_set_from_file. Wcześniej użyt- się to następująco: z przyciskami są umieszczone w kompo- kownik musi jednak wybrać plik, który nentach GtkHandleBox. Ostatecznie, nasz chce wczytać. Nasz program piszemy if (gtk_dialog_run S program będzie korzystał głównie z kom- w oparciu o najnowszą wersję środowiska (GTK_DIALOG (dialog)) == GTK_RESPONSE_ ponentów GTK. Elementy środowiska GNOME, więc skorzystamy z nowego ACCEPT){ GNOME wykorzystamy do implementa- okna dialogowego do wyboru plików. ... cji wydruku oraz w oknie dialogowym Pierwszym krokiem jest zadeklarowa- } O Programie. nie zmiennej okna dialogowego: GtkWid- Utworzony plik, zawierający get *dialog;. Oprogramowanie nowego Sam proces ładowania obrazu do widgetu interfejs, np. sirds_app.glade, zosta- okna dialogowego jest znacznie mniej GtkImage wymaga poznania nazwy pliku. nie wczytany przez naszą aplikację kłopotliwe niż poprzednio stosowane W naszym programie nazwę przenosimy dzięki bibliotece libglade. Bibliote- rozwiązanie wystarczy utworzyć nowy do zmiennej filename: ka libglade posiada cenną cechę, widget wywołaniem gtk_file_chooser_ S a mianowicie potrafi samodzielnie dialog_new: strcpy(&filename[0], S podłączyć sygnały (zdarzenia) do ist- gtk_file_chooser_get_filename S niejących funkcji, jeśli tylko zgadzają dialog = gtk_file_chooser_dialog_new (GTK_FILE_CHOOSER (dialog))); się nazwy zdarzeń oraz funkcji. To ("Wybierz plik", NULL, bardzo upraszcza proces ładowania GTK_FILE_CHOOSER_ACTION_OPEN, Teraz wystarczy wywołanie gtk_image_ i podłączania zdarzeń, bowiem wszyst- GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, set_from_file, gdzie w drugim argu- kie niezbędne czynności dokonywane GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, w funkcji main sprowadzają się do NULL); pięciu linii kodu. W dwóch pierwszych dokonujemy inicjalizacji biblioteki GTK Funkcja przyjmuje wiele parametrów. oraz ładujemy plik z opisem interfejsu: W pierwszym podajemy oczywiście tytuł okna. W drugim powinniśmy wskazać gtk_init(&argc, &argv); okno, które jest właścicielem naszego S xml = glade_xml_new("glade/sirds_ okienka dialogowego, ale podanie war- app.glade", NULL, NULL); tości NULL w niczym nie przeszkadza. W trzecim parametrze podajemy, jaki W naszej aplikacji będziemy ładować typ okna dialogowego nas interesuje. obraz z pliku do widgetu GtkImage bądz Istnieją cztery podstawowe typy: okno Rysunek 2. Interfejs programu w GLADE zapisywać na dysk zawartość GtkIma- otwarcia pliku, okno zapisu pliku oraz 74 wrzesień 2004 dla programistów stereogramy mencie podajemy zmienną z nazwą jest odpowiedzialna za wzór bazowy. Potrzebujemy również informacji pliku: Następie wykonujemy czynności zwią- o wymiarach przetwarzanego rysunku: zane z korzystaniem z biblioteki DevIL. S gtk_image_set_from_file Uruchamiamy bibliotekę wywołaniem Xresolution=ilGetInteger(IL_IMAGE_WIDTH); S (GTK_IMAGE(ImageWidget), &filename[0]); funkcji ilInit oraz iluInit. Następne Yresolution=ilGetInteger(IL_IMAGE_ dwie funkcje zapobiegają ładowaniu HEIGHT); Zapis obrazu jest dość podobny. się niektórych plików graficznych do Okno dialogowe tworzymy w niemal góry nogami , co mogłoby utrudnić Zapis danych, wykonywany w save_ identyczny sposób, podając w pierw- pózniejsze oglądanie wygenerowanych sirds_image, wymaga jeszcze mniej kodu, szym argumencie tekst Zapis pliku, stereogramów. bo sprowadza się tylko do dwóch linii: natomiast w trzecim argumencie Funkcja load_image zajmuje się umieszczając wartość GTK_FILE_CHO- wczytaniem pliku. Wcześniej, jak widać, ilEnable(IL_FILE_OVERWRITE); OSER_ACTION_SAVE. musimy wygenerować odpowiedni iden- ilSaveImage(fname); Instrukcja warunkowa jest iden- tyfikator, bowiem tego wymaga bibliote- tyczna, ale sama procedura zapisu jest ka DevIL. Identyfikator usuniemy pózniej Pierwsza funkcja pozwala nadpisać plik, oczywiście inna. W pierwszej kolejności funkcją ilDeleteImages. Po załadowaniu jeśli istnieje, natomiast druga dokonu- należy uzyskać dostęp do bufora pikseli, danych z pliku (funkcja load_image), je zapisu danych do pliku o podanej czyli do struktury GdkPixBuf. Robimy to tworzymy stereogram. Zajmuje się tym nazwie. Wewnętrzne mechanizmy DevIL w następujący sposób: funkcja my_make_sirds_image. Jej pełna samodzielnie określą format pliku po treść jest zawarta na omówionym wcze- rozszerzeniu. GdkPixbuf *pixbuf; śniej Listingu 1. S pixbuf=gtk_image_get_pixbuf Po wygenerowaniu stereogramu zapi- Podsumowanie (GTK_IMAGE(ImageWidget)); sujemy obraz wywołaniem save_sirds_ Pełny kod zródłowy generatora ste- image do katalogu tymczasowego. Potem reogramów to tylko siedem kilobajtów Teraz jesteśmy gotowi, aby zapisać plik, wczytujemy stereogram do kontrolki tekstu. Jak widać, udało się nam napisać np. w formacie PNG, pod nazwą podaną ImageWidget. Oczywiście, można napisać niewielki, ale pożyteczny program. Nie- przez użytkownika. Wystarczy jedno kod, który całą tę procedurę wykonał- wiele istotnych funkcji możemy dodać wywołanie instrukcji gdk_pixbuf_save: by poprzez przepisanie odpowiednich do naszego programu, chociaż np. bloków pamięci, ale ten sposób jest nie- warto dać użytkownikowi możliwość S gdk_pixbuf_save wątpliwie znacznie łatwiejszy. podania własnego wzoru bazowego. (pixbuf, &filename[0], "png", NULL, NULL); Pomimo tego, wzór losowy, zastosowa- Funkcja load_image ny w programie, sprawdza się bardzo Pierwsze trzy parametry są oczywiste. i save_sirds_image dobrze. Ponadto, można wprowadzić Wartości NULL mają nieco inne prze- Załadowanie pliku graficznego w biblio- zmienną liczbę sekcji oraz zmienną znaczenie. Pierwsza z nich oznacza, tece DevIL sprowadza się do jednego wartość współczynnika, przez który że nie będziemy interesować się błę- polecenia: ilLoadImage(filename[0]);. dzielimy numer piksela. dami, które mogą wystąpić. ;-) Druga Wykonujemy jeszcze kilka dodatkowych Czytelnikom zainteresowanych pro- oznacza, że nie będziemy przekazy- operacji. Jeśli wczytywany obraz nie jest blemem stereogramów proponuje przej- wać żadnych dodatkowych informacji, w formacie koloru indeksowanego, to rzeć strony WWW znajdujące się w Inter- np. w przypadku formatu JPG warto wykonujemy odpowiednią konwersję: necie (najlepiej za pomocą Google'a byłoby podać jakość, z jaką ma być i hasła sirds). I jeszcze jedna rzecz na S zapisany plik. Wyglądałoby to w nastę- if(ilGetInteger(IL_IMAGE_FORMAT)!= zakończenie: obraz w ramce Oglądanie pujący sposób: IL_COLOR_INDEX){ stereogramów to fotel. S ilConvertImage(IL_COLOR_INDEX, S gdk_pixbuf_save IL_UNSIGNED_BYTE); S (pixbuf, &filename[0], "jpeg", NULL, } W Internecie: "quality", "100", NULL); Tę czynność lepiej jest wykonać za " Spis stron WWW poświęconych stereo- Generujemy stereogram pomocą dedykowanego programu do gramom: Ponieważ już wcześniej omówiłem obsługi grafiki. GIMP będzie nadawał się http://www.ccc.nottingham.ac.uk/ ~etzpc/sirds.html implementację algorytmu, to teraz zaj- do tego znakomicie. " Dokument omawiający zagadnienie miemy się tylko czynnościami pomoc- W kodzie tej funkcji wykonujemy stereogramów: niczymi. Generowanie stereogramu to dodatkowo konwersję palety, jednak waż- http://www.cg.tuwien.ac.at/~mroz/ domena funkcji on_SirdsGenerate_MNU_ niejsze czynności to odczytanie trzech sirds/index.html activate. Listing 2 zawiera jej pełną istotnych dla nas informacji. Pierwszą jest " Inna strona poświęcona stereogra- implementację. Pierwszym elementem wskazanie na bufor danych i wykorzystu- mom: jest inicjalizacja generatora pseudolo- jemy do tego funkcję ilGetData: http://www.cs.waikato.ac.nz/~singlis/ sowego: srand(time(0));. Wbrew pozo- sirds.html rom, to dość istotna czynność, gdyż sirds_image=(unsigned char*)ilGetData(); 75 www.lpmagazine.org