dla programistów Konsolowa przeglądarka plików graficznych Marek Sawerwain Linuksie, gdy chcemy zakładamy, że zawsze będzie on korzy- pracować w trybie gra- stał z trybu o 32-bitowym kolorze. Posłu- ficznym, zazwyczaj sto- giwanie się tymi funkcjami jest bardzo Wsujemy system X Win- proste. dow. Jądro Linuksa pozwala nam jednak Uzyskanie dostępu do bufora to zada- w bardzo łatwy sposób uruchomić stan- nie funkcji open_fb, więc jedną z pierw- dardową konsolę tekstową w dowol- szych czynności, którą trzeba wykonać, nie wybranym przez nas trybie graficz- to wywołanie tej funkcji: nym. Bardzo często nazywa się to trybem bufora ramki z tego powodu, że mamy int h; bezpośredni dostęp do pamięci karty h=open_fb(); graficznej. Mówiąc wprost, korzysta- my z trybu graficznego, ale bez pośred- W zmiennej h znajdzie się uchwyt repre- nictwa systemu X Window. W obecnej zentujący bufor ramki. Dzięki wartości, chwili najpopularniejszym zastosowa- którą on zawiera, możemy łatwo spraw- niem graficznej konsoli jest wyświetlanie dzić, czy udało się nam uzyskać dostęp graficznego logo podczas startu systemu, do karty graficznej. Obsługa sytuacji, ale niektórzy z czytelników z pewno- gdy nie udało się otworzyć bufora ścią stosowali przeglądarkę Links właśnie ramki, sprowadza się do jednej instruk- w trybie graficznym konsoli (wystarczy cji if: dodać podczas wywołania parametr -g links -g). Programów korzystających if(!h) { S z takiego trybu graficznego jest naturalnie printf("Błąd otwarcia bufora CD/DVD więcej. Istnieją również gotowe biblioteki ramki!!!\n"); Po uruchomieniu dystrybucji widgetów, jak choćby QT czy GTK+, które return -1; Linux+ Live CD/DVD i przełą- potrafią wykorzystać bufor ramki. } czeniu się na konsolę tekstową, W tym artykule chciałbym pokazać, będzie można przetestować dzia- jak napisać prosty, ale przydatny pro- Listing 1. Fragment programu Hello łanie omawianego programu. gram, pracujący na poziomie graficznej World! konsoli. Będzie to przeglądarka obraz- Na płycie CD/DVD ków w wielu formatach, takich jak png, for(i=0;i<9999999;i++){ Na płycie CD/DVD znajdują się jpeg, tiff itd. Wbrew pozorom, programo- x=rand() % vinfo.xres; pliki zródłowe i binarne bibliotek, wanie na poziomie bufora ramki nie jest y=rand() % vinfo.yres; kompletny kod omawianego pro- trudne. Biblioteka podstawowych funkcji r=rand() % 255; gramu oraz listingi z artykułu. jest niewielka to tylko 5 kB kodu zró- g=rand() % 255; O autorze dłowego. b=rand() % 255; Autor zajmuje się tworzeniem setpixel32(x,y, r,b,g, 0); oprogramowania dla WIN32 i Małe co nieco if( (i % 1000000)==0) Linuksa. Zainteresowania: teoria S na rozgrzewkę draw_msg(vinfo.xres + 50, języków programowania oraz dobra literatura. Kontakt z auto- Do podstawowej obsługi bufora ramki (vinfo.yres / 2)-25 ); rem: autorzy@linux.com.pl. wystarczy osiem funkcji. Ich spis znajdu- } je się w Tabeli 1. W naszym programie 62 lipiec 2004 przeglądarka plików dla programistów Tabela 1. Spis funkcji omawianych w artykule Listing 2. Funkcja rysująca komunikat Obsługa bufora ramki (fb_lib.c) Nazwa funk- Krótki opis void draw_msg(int xx, int yy){ cji int x,y,i=0; line,size_line=sizeof(msg)/5; open_fb otwarcie bufora ramki x=xx; get_basic_ pobranie podstawowych informacji y=yy; info for(i=1;i<=sizeof(msg);i++){ map_fb utworzenie odwzorowania pamięci graficznej if(msg[i-1]==1) unmap_fb usunięcie odwzorowania pamięci graficznej whiteblock(x,y,block_size); setpixel32 postawienie piksela pod podanymi współrzędnymi x=x+block_size; whiteblock narysowanie białego kwadratu pod wskazanymi współrzędnymi if((i % size_line)==0) { x=xx; y=y+block_size; } clear32 kasowanie ekranu } close_fb zamknięcie bufora ramki } get_buffer_ odczytanie aktualnego wskaznika na obszar pamięci graficznej ptr wyświetlenia. Fragment tej tablicy znaj- Obsługa plików graficznych duje się poniżej: ilInit inicjalizacja podstawowych funkcji biblioteki DevIL iluInit inicjalizacja dodatkowych funkcji biblioteki DevIL char msg[]={1001... ilEnable włączanie dodatkowych funkcji 1001... 1111... ilOriginFunc ustalenie początku obrazu 1001... iluImagePa- ustalanie dodatkowych parametrów 1001... rameter np. filtru używanego podczas skalowania }; ilGenImages generowanie identyfikatorów ilBindImage ustalanie bieżącego identyfikatora Jedynka oznacza, że ma zostać wyświe- ilDeleteIma- usuwanie identyfikatora tlony biały kwadrat, a zero odwrotnie, ges ilGetInteger pobieranie parametrów o obrazie Instalacja biblioteki ilConvertPal konwersja palety DevIL iluScale skalowanie obrazu Instalacja tej biblioteki w systemie jest ilGetData pobieranie wskaznika na dane obrazu zadaniem trywialnym, a to ze względu na ilGetPalette pobieranie wskaznika na paletę kolorów obecność skryptu configure. Wobec tego wydajemy tylko trzy polecenia: Jeśli nie mieliśmy kłopotów, to możemy wyświetla losowo na ekranie punkty S ./configure prefix=/katalog/do/ korzystać z funkcji setpixel32. Postawie- o dowolnych kolorach oraz co pewien instalacji nie białego piksela pod współrzędnymi czas wyświetla na ekranie komunikat tek- make 100,100 wygląda tak: stowy, ale zbudowany z białych kwadra- make install tów (to dlatego w naszej mikro bibliote- Jeśli zastosowana została ostatnio naj- setpixel32(100, 100, 255, 255, 255, 0); ce znajduje się funkcja whiteblock). nowsza wersja 1.6.6, to podczas kompi- Listing 1 przedstawia najważniej- lacji naszego programu picview, a dokład- Po zakończeniu pracy z trybem graficz- szą pętlę w naszym pierwszym progra- niej w momencie łączenia, możemy otrzy- nym możemy go zamknąć. Czynność tę mie. Pętla ta rysuje dziesięć milionów mać błąd braku funkcji _vsnprintf. Roz- wykonujemy funkcją close_fb podając pikseli, ale po każdym milionie punk- wiązanie tego problemu wymaga niewiel- za argument zmienną uchwytu. tów rysowany jest także napis Hello kiej modyfikacji kodu biblioteki DevIL, W naszej docelowej aplikacji nie World! . To zadanie wykonuje funkcja a dokładnej pliku in_tiff.c. Odszukujemy potrzebujemy dodatkowych funkcji draw_msg. w nim wystąpienia funkcji _vsnprintf wystarczy nam tylko stawianie punktów, i zamieniamy je na poprawną nazwę bez ale mamy jeszcze funkcję clear32, kasu- Jak działa draw_msg? podkreślenia, czyli vsnprintf. Po ponow- jącą zawartość ekranu, oraz whiteblock, Następnym istotnym fragmentem nasze- nej kompilacji całej biblioteki wystarczy zainstalować bibliotekę raz jeszcze i pro- która wie , jak narysować biały kwadrat go pierwszego programu jest funkcja blem zniknie. Poprzednie wersje DevIL o podanej długości boku. draw_msg. Jej pełny kod zawiera Listing nie posiadają tego błędu, za to nie obsłu- Uzbrojeni w taką wiedzę możemy 2. Jak widać, funkcja jest dość krótka gują formatu GIF. Z tego powodu, jeśli napisać niewielki program wyświetlają- i nie zawiera żadnego ciągu znaków, chcemy, aby nasza przeglądarka wczy- cy Hello World! dla trybu bufora ramki. który stanowiłby treść naszego komuni- tywała ten rodzaj pliku, musimy korzystać Pełny kod zródłowy zawiera plik hw.c. katu. Korzystamy tylko z tablicy msg to z wersji 1.6.6 DevIL. Program działa w następujący sposób: w niej został zakodowany tekst do 63 www.lpmagazine.org dla programistów Jak uruchomić obsługę framebuffera w jądrze systemu? Gdy do jądra została dodana obsługa trybu VESA, to wystarczy podczas startu systemu podać numer trybu graficznego, jaki chcemy uruchomić. Z poziomu lilo piszemy np. linux vga=789. System uru- chomi się w trybie 800x600-32bit. Tabela 2 zawiera spis najważniejszych trybów i ich oznaczeń. Gdyby okazało się, że jądro które- go używamy, nie posiada obsługi stan- dardu VESA, to możemy jeszcze spró- bować załadować moduł sterownika dla karty graficznej. Trzeba jednak pamię- tać, że obsługiwanych jest zaledwie kilka typów kart graficznych. Dla popularnych kart NVIDIA wystarczy wydać polece- nie modprobe rivafb i jeśli mamy star- szy typ karty tego producenta, to powinni- śmy już cieszyć się trybem bufora ramki. Rysunek 1. Konfiguracja obsługi bufora ramki VESA dla jądra 2.4.x Szczęśliwi posiadacze najnowszych kart, np. GeForceFX 5900, obejdą się sma- kiem, ponieważ moduł rivafb nie rozpo- W pętli odczytujemy kolejne znaki z tabli- przybliżyć sposób implementacji funkcji znaje tego typu kart. Z tego powodu naj- cy msg, więc aby poznać, czy już prze- związanych z obsługą trybu graficznego. lepiej korzystać z trybów VESA. kroczyliśmy linię, musimy znać liczbę W Linuksie, a ogólnie w systemach unik- W przypadku braku obsługi trybów znaków w linii. W tablicy msg sam nie sowych, urządzenia są reprezentowane VESA, czeka nas kompilacja jądra. wiem, ile wpisałem znaków, ale każdy jako pliki. Nie inaczej jest w przypadku W pierwszej kolejności należy odzna- przeglądając kod zródłowy łatwo spraw- bufora ramki. Zanim zaczniemy z niego czyć opcję Prompt for development dzi, że mamy wpisane pięć linii, toteż obli- korzystać, należy otworzyć plik, który go and/or incomplete code/drivers w sekcji czenie, ile znaków jest w linii jest bardzo reprezentuje: Code maturity level options. Następnie, proste: size_line=sizeof(msg)/5. Gdy w zależności od typu jądra, trzeba przejść mamy tę informację, narysowanie zawar- fb_handle=open("/dev/fb0", O_RDWR); do Console Drivers/Frame-buffer-options tości tablicy msg jest już bardzo proste. dla jądra w wersji 2.4.x bądz Device Odczytując zawartość msg sprawdza- Następnie możemy uzyskać podsta- drivers/Graphics support w przypadku nowych jąder 2.6.x (należy zaznaczyć my, czy aktualny i-ty element tablicy to wowe informacje (np. rozdzielczość, obsługę bufora ramki dla trybu VESA, ale jedynka, a jeśli tak, to wyświetlamy biały sposób kodowania kolorów itd.). Tego nie w trybie modułu, bowiem nie będzie- kwadrat. W każdej iteracji pętli zwiększa- typu dane zawierają struktury o typie: my mogli podać numeru trybu podczas my wartość współrzędnej x o wielkość fb_var_screeninfo vinfo oraz fb_fix_ uruchamiania systemu). W artykule znaj- bloku: x=x+block_size;. Oczywiście, screeninfo. dują się dwa rysunki pokazujące, jakie w przypadku, gdy osiągniemy koniec Aby wypełnić pierwszą strukturę opcje trzeba zaznaczyć w przypadku linii komunikatu, wykonujemy dwie potrzebnymi informacjami, stosujemy tych dwóch serii jądra Linuksa. czynności: powracamy na początek miej- funkcję ioctl w następującej postaci: sca, od którego zaczęliśmy rysować nasz S czyli nic nie zostanie wyświetlone. Trzeba komunikat (x=xx;), oraz przechodzimy ioctl(fb_handle, FBIOGET_VSCREENINFO, pamiętać o jednym fakcie: cały komunikat, linię niżej (y=y+block_size;). &vinfo) choć został w kodzie zródłowym odpo- wiednio sformatowany, nadal jest tabli- Programowanie bufora Dość trudno będzie operować na bufo- cą jednowymiarową. Podczas wyświetla- ramki rze ramki z poziomu pliku. Dlatego, aby nia komunikatu po wyświetleniu pierw- Zanim na dobre rozpoczniemy pisać normalnie operować pamięcią graficz- szej linii musimy przejść linię niżej. naszą przeglądarkę, chciałbym jeszcze ną, musimy ją jeszcze odwzorować za pomocą zwykłego wskaznika. W naszym Tabela 2. Spis najważniejszych trybów VESA przypadku będzie to typ char. Pomoże to Bitów na kolor 640x480 800x600 1024x768 1280x1024 nam w implementacji funkcji setpixel32. Odwzorowanie pliku w pamięci wyko- 8 769 771 773 775 nujemy za pomocą funkcji mmap: 15 784 787 790 793 16 785 788 791 794 S char *fb_map=(char*)mmap(0,sc,PROT_READ 24/32 786 789 792 795 | PROT_WRITE, MAP_SHARED, fb_handle, 0); 64 lipiec 2004 przeglądarka plików dla programistów blue_idx32 = vinfo.blue.offset / 8; Listing 3. Funkcja setpixel32 transp_idx32 = vinfo.transp.offset / 8; void setpixel32(int x, int y, int r, Stawiamy piksele int g, int b, int a){ Podczas wyświetlania obrazu na ekra- int pos; nie będziemy używać tylko jednej S pos = (x+vinfo.xoffset) * pomocniczej funkcji, a mianowicie set- S (vinfo.bits_per_pixel/8) + pixel32. Kod tej funkcji przedstawia (y+vinfo.yoffset) * finfo.line_length; Listing 3. Jedynym bardziej skompli- *(fb_map + pos + red_idx32 ) = r; kowanym elementem, jaki tam napo- *(fb_map + pos + green_idx32 ) = g; tykamy, jest wyznaczenie miejsca *(fb_map + pos + blue_idx32 ) = b; w pamięci na podstawie współrzęd- *(fb_map + pos + transp_idx32 ) = a; nych docelowych x i y. Współrzędna y } to numer linii, w jakiej chcemy umieścić punkt. Wyznaczenie, o którą linię nam W wywołaniu mmap znajduje się szereg parametrów. W pierwszym można podać adres początkowy, od którego pamięć zostanie odwzorowana. W przykła- dzie podaliśmy zero, więc system sam wygeneruje odpowiedni adres. Następ- nie podaje się wielkość obszaru jest to zmienna sc. Dalej określany tryb dostę- pu interesuje nas odczyt i zapis. Kolej- ny parametr to tryb dzielenia pamięci. Wartość MAP_SHARED oznacza, że pamięć będzie dzielona z innymi procesami. Następnym parametrem jest uchwyt pliku, co oznacza, że za pomocą mmap możemy odwzorować w pamięci także inne zwykłe pliki. Ostatni parametr to tzw. przesunięcie: zero oznacza, że nie będziemy pomijać żadnych danych z początku odwzorowywanego pliku. Jak widać, czynności przygoto- wawcze do korzystania z bufora ramki nie są zbyt skomplikowane, ale trzeba zwrócić uwagę na kolejność odwzo- rowania kolorów w buforze ramki. Nie zawsze kolory są ułożone w trójki RGB bądz, tak jak w przypadku nasze- go trybu 32-bitowego, w czwórkę RGBA (A oznacza kanał alfa, nazywany kana- łem przezroczystości). Bardzo często spotyka się tryb BGRA. Rada na różne ułożenia kolorów jest bardzo prosta. Wspominana powyżej struktura vinfo zawiera numer bitu, pod którym roz- poczyna się odpowiednia składo- wa koloru. Nam potrzebne są bajty. Wystarczy wartości przesunięcia podzielić przez osiem. Możemy tak zrobić, ponieważ działamy w trybie 32- bitowym, gdzie na każdy kolor przypa- da osiem bitów: red_idx32 = vinfo.red.offset / 8; Rysunek 2. Schemat działania przeglądarki plików graficznych green_idx32 = vinfo.green.offset / 8; 65 www.lpmagazine.org dla programistów podano argument (nazwę pliku) do wyświetlenia: picview obrazek.jpg Następnie uzyskujemy dostęp do bufora ramki, wczytujemy plik i, to bardzo ważny moment, staramy się dokonać takich konwersji za pomocą API biblio- teki DevIL, aby format obrazu był iden- tyczny z formatem trzydziestodwubi- towego koloru, co ułatwi nam proces wyświetlania obrazu. Dodatkowo, jeśli rysunek, który wczytaliśmy, jest większy niż dostępna rozdzielczość, dokonujemy jego przeskalowania. Gdy obraz znajdzie się na ekranie, oczekujemy na wciśnięcie klawisza [Enter] (wystarczy zastosować funkcję getchar). Test, czy podano dodatkowy argu- ment w momencie wywołania naszego Rysunek 3. Konfiguracja obsługi bufora ramki VESA dla jądra 2.6.x programu, sprowadza się tylko do spraw- dzenia, czy parametr argc funkcji main chodzi, wykonuje ten fragment kodu: numerów składowych kolorów, aby jest mniejszy bądz większy od dwóch. Po (y+vinfo.yoffset) * finfo.line_length. poprawnie postawić punkt w potrzeb- tym teście dokonujemy inicjalizacji biblio- W pierwszej części wyznaczamy odpo- nym kolorze. Przykładowo, dla zielo- teki DevIL: wiednie przesunięcie w pamięci y+vin- nej składowej koloru przedstawia się to fo.yoffset, a następnie mnożymy tę następująco: ilInit(); wielkość przez całkowitą długość linii iluInit(); w bajtach finfo.line_length. W ten *(fb_map + pos + green_idx32) = g; sposób wiemy, w którym miejscu zaczy- Pierwsza linia odpowiada za podstawo- na się linia, w której chcemy umieścić Zadanie główne wy zbiór instrukcji, a druga uruchamia nasz punkt. Wystarczy do tej wielkości przeglądarka plików dodatkowy zestaw funkcji, w którym dodać wyrażenie (x+vinfo.xoffset) * graficznych znajduje się bardzo wygodna funkcja do (vinfo.bits_per_pixel/8), aby popraw- Nasz program ma obsługiwać wiele róż- przeskalowania obrazu. Nie martwimy nie odszukać odpowiedni adres w nych formatów graficznych, a jak już się o deinicjalizację, ponieważ obydwie pamięci bufora ramki. Jak widać, w tej wspomniałem, sam kod zródłowy jest funkcje podłączają się do łańcucha funk- części obliczeń ponownie wyznaczamy niewielki. Oznacza to, że korzysta- cji wyjściowych za pomocą atexit. wartość przesunięcia x+vinfo.xoffset, my z dodatkowej biblioteki do obsłu- Następnym elementem jest ustale- ale mnożymy to przez liczbę bajtów gi plików graficznych o nazwie DevIL. nie początku obrazu tak, aby znajdował vinfo.bits_per_pixel/8 (dzielimy całko- Zanim przystąpimy do tworzenia kodu, się on w lewym górnym rogu. Musimy witą liczbę bitów na jeden piksel warto zastanowić się, jakie czynno- tak postąpić, ponieważ niektóre forma- przez osiem) jaka przypada na jeden ści będzie wykonywać nasz program. ty, np. bmp i tga, zapamiętują obraz do piksel. Ogólny schemat działania przeglądar- góry nogami . Z tego powodu za pomocą Pozostaje nam tylko wykorzy- ki prezentuje Rysunek 2. Na samym ilEnable i parametru IL_ORIGIN_SET oraz stać wcześniej wyznaczoną wartość początku programu sprawdzamy, czy funkcji ilOriginFunc nakazujemy biblio- tece DevIL, aby obraz miał swój początek w lewym górnym rogu (co jest zgodne Listing 4. Konwersja formatu palety kolorów z działaniem funkcji setpixel32; gdy if(ilGetInteger(IL_PALETTE_TYPE)==IL_PAL_BGR24){ podamy współrzędne 0,0, to postawimy ilConvertPal(IL_PAL_RGB24); punkt w lewym górnym rogu). Ważną } operacją jest także ustalenie typu filtro- if(ilGetInteger(IL_PALETTE_TYPE)==IL_PAL_BGRA32 || wania obrazu podczas zmiany jego roz- ilGetInteger(IL_PALETTE_TYPE)==IL_PAL_RGB32 || miarów. Filtrem, który daje obraz wyso- ilGetInteger(IL_PALETTE_TYPE)==IL_PAL_BGR32 || kiej jakości, jest LANCZOS. Z tego ilGetInteger(IL_PALETTE_TYPE)==IL_PAL_BGRA32){ powodu w naszej aplikacji stosujemy ten ilConvertPal(IL_PAL_RGBA32); typ filtru. Pamiętajmy jednak, że choć } daje on bardzo dobry i ostry obraz, to działa dość wolno, więc przy skalowaniu 66 lipiec 2004 przeglądarka plików dla programistów Listing 5. Przenoszenie danych obrazu do bufora ramki RGB24. W drugim przypadku, jeśli mamy do czynienia z kolorami ułożonymi data=ilGetData(); w trójkę BGR, ale zapisanymi na peł- if(ilGetInteger(IL_PALETTE_TYPE)==IL_PAL_NONE){ nych 32 bitach, nakazujemy ich ułożenie for(y=0;y for(x=0;x pos=(y * bytes_per_line) + ( x * bytes_per_pixel ); przenosi dane obrazu do bufora ramki. r=*(data + pos + off_r); Po wykonaniu konwersji sprawdza- g=*(data + pos + off_g); my, czy wczytany obraz nie jest przy- b=*(data + pos + off_b); padkiem większy niż rozdzielczość setpixel32(fromx + x, fromy +y, r, g, b, 0); dostępna dla bufora ramki. Korzy- } stamy z funkcji ilGetInteger, która } w zależności od podanych parametrów } zwraca różne ważne informacje o obra- zie. Jeśli podamy w argumencie war- większych obrazów proces ten może i odczytujemy podstawowe informacje. tość IL_IMAGE_WIDTH, to otrzymamy sze- zabrać kilka sekund: Istotą naszej przeglądarki jest jednak rokość obrazu. Dwa pierwsze argumenty funkcja DevILLoad to ona wykonuje naj- iluScale z pewnością nikogo nie dziwią ilEnable(IL_ORIGIN_SET); ważniejsze zadanie, czyli wczytuje obraz, są to wymiary bufora ramki, ale trzeci, ilOriginFunc(IL_ORIGIN_UPPER_LEFT); poddaje go niezbędnej konwersji oraz w którym podano zmienną depth, z pew- S iluImageParameter(ILU_FILTER, wyświetla na ekranie. nością jest zaskakujący. Nie jest to liczba ILU_SCALE_LANCZOS3); bitów przeznaczonych na opis koloru, ale Aadowanie obrazu oraz wymiar obrazu zazwyczaj równy jeden. Po ustaleniu tych parametrów możemy konwersja obrazu i palety DevIL potrafi obsługiwać obrazy 3D, tzw. wygenerować identyfikator obrazu. Pierwszą czynnością funkcji DevILLo- volume images wówczas ta wartość Każdy, kto napisał choćby najmniej- ad jest wczytanie pliku. Nie musimy obrazu może się zmieniać: szy program w OpenGL, zobaczy, że ta nawet podawać jakiego typu jest to S część interfejsu DevIL jest bardzo podob- plik, bowiem biblioteka samodzielnie if(ilGetInteger(IL_IMAGE_WIDTH) > S na do API OpenGL. W pierwszej kolejno- go wykryje. Wczytanie pliku wykonu- vinfo.xres && ilGetInteger S ści generujemy identyfikator: ilGenIma- jemy za pomocą funkcji ilLoadImage, (IL_IMAGE_HEIGHT) > S ges(1, &ImgId). Oczywiście, gdy chcemy np. w taki sposób: vinfo.yres) iluScale(vinfo.xres, wygenerować większą liczbę identyfika- vinfo.yres, depth); torów, wystarczy, aby drugi argument był if (!ilLoadImage(fname)){ tablicą o typie ILuint. Gdy identyfikator printf("Nie mozna otworzyc pliku !!!\n"); Po wykonaniu skalowania koniecz- został wygenerowany, musimy go uczy- return -1; nie trzeba uaktualnić podstawowe dane nić aktualnym. Wykonuje się to w nastę- } o obrazie, takie jak wymiary, liczba pujący sposób: ilBindImage(ImgId). Jeśli bajtów na jeden piksel oraz całkowita w programie przetwarzamy więcej roż- Jeśli nie wystąpiły problemy, to dokonuje- liczba bajtów w jednej linii obrazu: nych obrazów, to należy pamiętać o tym, my konwersji kolorów zapisanych w pale- aby za pomocą ilBindImage wybrać wła- cie, o ile oczywiście dany plik posiada w=ilGetInteger(IL_IMAGE_WIDTH); ściwy obraz. paletę. Listing 4 zawiera kod, który wyko- h=ilGetInteger(IL_IMAGE_HEIGHT); S Po tych wstępnych czynnościach nuje tę czynność. W pierwszej instrukcji, bytes_per_pixel=ilGetInteger w naszej aplikacji za pomocą open_fb jeśli typ palety to BGR24 (bez kanału (IL_IMAGE_BYTES_PER_PIXEL); uzyskujemy dostęp do bufora ramki alfa), to konwertujemy kolory do formatu bytes_per_line=w * bytes_per_pixel; Jeśli nasz program wczyta obraz mniejszy niż dostępna rozdzielczość, to umieszcza obraz na środku ekranu. Aby przesunąć obraz na środek do zmiennych fromx i fromy, należy wstawić odpowiednie war- tości. Dla wartości x (oraz analogicznie dla y) należy podzielić przez dwa szero- kość bufora ramki (wartość vinfo.xres) i odjąć połowę szerokości obrazu. W ten sposób otrzymamy wartość, o jaką trzeba w poziomie przesunąć obraz, aby wyświetlać go na środku. Kod wyznaczający te wartości jest Rysunek 2. Logo DevIL (inaczej OpenIL) wyświetlane w trybie konsoli następujący: 67 www.lpmagazine.org dla programistów cokolwiek przenosić, należy odczytać I to stanowi o podstawowej różnicy Listing 6. Przenoszenie danych obrazu wskaznik na dane o obrazie. Zwraca go pomiędzy obrazem z paletą i bez palety. do bufora ramki wersja obrazu z paletą funkcja o nazwie ilGetData: W pierwszym przypadku każdy piksel kolorów miał własny opis kolorów. W przypad- if(ilGetInteger(IL_PALETTE_TYPE)== data=ilGetData(); ku palety, piksel w obrazie to tylko IL_PAL_RGBA32){ numer koloru w palecie. Aby odczytać, pal=ilGetPalette(); Postawmy sobie teraz pytanie: co to jakie są wartość trójki RGB, musimy się- for(y=0;y for(x=0;xS pos=*(data + ((y * sany bezpośrednio za pomocą trójki (inaczej mówiąc numer koloru) mno- S bytes_per_line) + RGB (bądz czwórki z kanałem alfa) żymy przez cztery, bowiem tyle bajtów ( x * bytes_per_pixel ))); w obrazie (przypadek z paletą omówię opisuje piksel RGBA32. Po tej opera- r=*(pal + (pos * 4) + 0 ); w następnym punkcie). Oznacza to cji, wykorzystując arytmetykę wskazni- g=*(pal + (pos * 4) + 1 ); również, że nasza wiedza zdobyta pod- kową, przesuwamy się pod potrzebne b=*(pal + (pos * 4) + 2 ); czas implementacji funkcji setpixel32 miejsce w palecie. Na koniec tej opera- S setpixel32(fromx + x, może zostać ponownie wykorzystana, cji dodajemy odpowiednio zero, jeden fromy + y, r, g, b, 0); gdyż wiemy, w jaki sposób należy obli- i dwa, aby odczytać wartości dla koloru } czyć pozycję w obrazie na podstawie czerwonego, zielonego i niebieskiego. } dwóch współrzędnych. W tym konkret- W kodzie ta operacja przedstawia się } nym przypadku pozycję wyznaczamy następująco: w następujący sposób: r=*(pal + (pos * 4) + 0 ); S fromx=0; fromy=0; pos=(y * bytes_per_line) + ( x * g=*(pal + (pos * 4) + 1 ); if(w < vinfo.xres && h < vinfo.yres) { bytes_per_pixel ); b=*(pal + (pos * 4) + 2 ); S fromx=(int) ( ((float)vinfo.xres / 2.0) - ((float)w / 2.0) ); Jedynym problemem jest odczyt kolo- Pozostaje już tylko postawić punkt S fromy=(int) ( ((float)vinfo.yres / 2.0) rów. Pod koniec poprzedniego punktu w buforze ramki za pomocą funkcji set- - ((float)h / 2.0) ); wyznaczyliśmy indeksy kolorów, pixel32. A jaka jest różnica w przypad- } więc skorzystamy z tych wartości doda- ku palety RGB24? Mamy tu trzy bajty jąc je do wyznaczonej pozycji w obra- zamiast czterech, zatem pozycji nie Pozostał nam jeszcze jeden ważny ele- zie: mnożymy przez cztery, ale przez trzy. ment, a mianowicie opis kolorów Reszta kodu pozostaje oczywiście bez w samym obrazie. Biblioteka DevIL zakła- r=*(data + pos + off_r); zmian. da, iż kolory mogą być opisane za pomocą g=*(data + pos + off_g); dwóch układów: RGB bądz BGR, podob- b=*(data + pos + off_b); Podsumowanie nie jak przy palecie. Z tego powodu za Jak widać, napisanie prostej przeglądar- pomocą ilGetInteger z parametrem IL_ Jak widać, proces odczytu jest niemal ki plików graficznych okazało się dosyć IMAGE_FORMAT sprawdzamy, z jakim typem identyczny ze stawianiem punktów. łatwe. Jedyny kłopot, jaki należało ułożenia kolorów mamy do czynienia rozwiązać, to kolory. Oczywiście, i odpowiednio ustalamy indeksy, np. dla Wyświetlanie obrazu jeśli stosujemy inny model kolorów, kolorów BGR wygląda to następująco: opartego o paletę np. tryb szesnastobitowy, to operacje W naszej aplikacji obsługiwane się dwa na bitach będą bardziej skompliko- S if(ilGetInteger(IL_IMAGE_FORMAT)== przypadki palety: RGB24 oraz RGBA32. wane. Program możemy wyposażyć S IL_BGR || Zajmiemy się tylko jednym przypad- w więcej opcji, np.: zapis obrazu ilGetInteger(IL_IMAGE_FORMAT)==IL_BGRA) { kiem (RGBA32), ponieważ różnią się do innego formatu. Jest to dość off_r=2; off_g=1; off_b=0; one od siebie zaledwie jednym szczegó- łatwe zadanie, gdyż taką potencjalną } łem. Wskaznik do danych został juz przez możliwość daje nam biblioteka DevIL. nas odczytany, ale potrzebny jest jeszcze Zachęcam do modyfikacji naszej prze- W przypadku ułożenia RGB, indeksy wskaznik do palety: glądarki i wprowadzania nowych ele- kolorów w pojedynczym pikselu przyj- mentów. mują wartości odpowiednio: 0, 1, 2. pal=ilGetPalette(); W Internecie: Wyświetlanie obrazu Pozycję w obrazie wyznaczamy tak jak Pozostała nam do wykonania ostatnia poprzednio, ale tym razem do zmien- " Informacje na temat polecenia mmap: czynność, ale jakże ważna, a mianowicie nej pos przepisujemy wartość, jaka jest http://www.opengroup.org/onlinepubs/ przeniesienie danych obrazu do bufora zawarta pod tym adresem: 009695399/functions/mmap.html ramki. Pętlę przenoszącą dane z obrazu " Strona domowa biblioteki DevIL: S do bufora dla przypadku bez palety pos=*(data + ((y * bytes_per_line) + http://openil.sourceforge.net/ zawiera Listing 5. Zanim zaczniemy ( x * bytes_per_pixel ))); 68 lipiec 2004