2004.07_Konsolowa przeglądarka plików graficznych_[Programowanie].pdf

(678 KB) Pobierz
439034402 UNPDF
dla programistów
Konsolowa
przeglądarka plików
graicznych
Marek Sawerwain
pracować w trybie gra-
ficznym, zazwyczaj sto-
sujemy system X Win-
dow . Jądro Linuksa pozwala nam jednak
w bardzo łatwy sposób uruchomić stan-
dardową konsolę tekstową w dowol-
nie wybranym przez nas trybie graficz-
nym. Bardzo często nazywa się to trybem
bufora ramki z tego powodu, że mamy
bezpośredni dostęp do pamięci karty
graficznej. Mówiąc wprost, korzysta-
my z trybu graficznego, ale bez pośred-
nictwa systemu X Window. W obecnej
chwili najpopularniejszym zastosowa-
niem graficznej konsoli jest wyświetlanie
graficznego logo podczas startu systemu,
ale niektórzy z czytelników z pewno-
ścią stosowali przeglądarkę Links właśnie
w trybie graficznym konsoli (wystarczy
dodać podczas wywołania parametr „ -g
links -g ). Programów korzystających
z takiego trybu graficznego jest naturalnie
więcej. Istnieją również gotowe biblioteki
widgetów , jak choćby QT czy GTK+ , które
potrafią wykorzystać bufor ramki.
W tym artykule chciałbym pokazać,
jak napisać prosty, ale przydatny pro-
gram, pracujący na poziomie graficznej
konsoli. Będzie to przeglądarka obraz-
ków w wielu formatach, takich jak png,
jpeg, tiff itd. Wbrew pozorom, programo-
wanie na poziomie bufora ramki nie jest
trudne. Biblioteka podstawowych funkcji
jest niewielka – to tylko 5 kB kodu źró-
dłowego.
zakładamy, że zawsze będzie on korzy-
stał z trybu o 32-bitowym kolorze. Posłu-
giwanie się tymi funkcjami jest bardzo
proste.
Uzyskanie dostępu do bufora to zada-
nie funkcji open_fb , więc jedną z pierw-
szych czynności, którą trzeba wykonać,
to wywołanie tej funkcji:
int h;
h=open_fb();
W zmiennej h znajdzie się uchwyt repre-
zentujący bufor ramki. Dzięki wartości,
którą on zawiera, możemy łatwo spraw-
dzić, czy udało się nam uzyskać dostęp
do karty graficznej. Obsługa sytuacji,
gdy nie udało się „otworzyć” bufora
ramki, sprowadza się do jednej instruk-
cji if :
CD/DVD
Po uruchomieniu dystrybucji
Linux+ Live CD/DVD i przełą-
czeniu się na konsolę tekstową,
będzie można przetestować dzia-
łanie omawianego programu.
if(!h) {
printf("Błąd otwarcia bufora S
ramki!!!\n");
return -1;
}
Listing 1. Fragment programu „Hello
World!”
Na płycie CD/DVD
Na płycie CD/DVD znajdują się
pliki źródłowe i binarne bibliotek,
kompletny kod omawianego pro-
gramu oraz listingi z artykułu.
for ( i = 0 ; i < 9999999 ; i ++) {
x = rand () % vinfo.xres ;
y = rand () % vinfo.yres ;
r = rand () % 255 ;
g = rand () % 255 ;
b = rand () % 255 ;
setpixel32 ( x,y, r,b,g, 0 );
if ( ( i % 1000000 )== 0 )
draw_msg ( vinfo.xres + 50, S
( vinfo.yres / 2 )- 25 );
}
O autorze
Autor zajmuje się tworzeniem
oprogramowania dla WIN32 i
Linuksa. Zainteresowania: teoria
języków programowania oraz
dobra literatura. Kontakt z auto-
rem: autorzy@linux.com.pl.
Małe co nieco
na rozgrzewkę
Do podstawowej obsługi bufora ramki
wystarczy osiem funkcji. Ich spis znajdu-
je się w Tabeli 1. W naszym programie
62 lipiec 2004
W Linuksie, gdy chcemy
439034402.027.png 439034402.028.png 439034402.029.png 439034402.030.png 439034402.001.png 439034402.002.png
przeglądarka plików
dla programistów
Tabela 1. Spis funkcji omawianych w artykule
Obsługa bufora ramki (fb_lib.c)
Nazwa funk-
cji
Listing 2. Funkcja rysująca komunikat
Krótki opis
void draw_msg ( int xx, int yy ) {
int x,y,i = 0 ;
line,size_line = sizeof ( msg )/ 5 ;
x = xx ;
y = yy ;
for ( i = 1 ; i <= sizeof ( msg ); i ++) {
if ( msg [ i - 1 ]== 1 )
whiteblock ( x,y,block_size );
x = x + block_size ;
if (( i % size_line )== 0 )
{ x = xx ; y = y + block_size ; }
}
}
open_fb otwarcie bufora ramki
get_basic_
info
map_fb utworzenie odwzorowania pamięci graicznej
unmap_fb usunięcie odwzorowania pamięci graicznej
setpixel32 postawienie piksela pod podanymi współrzędnymi
whiteblock narysowanie białego kwadratu pod wskazanymi współrzędnymi
clear32 kasowanie ekranu
close_fb zamknięcie bufora ramki
get_buffer_
ptr
wyświetlenia. Fragment tej tablicy znaj-
duje się poniżej:
Obsługa plików graicznych
ilInit inicjalizacja podstawowych funkcji biblioteki DevIL
iluInit inicjalizacja dodatkowych funkcji biblioteki DevIL
ilEnable włączanie dodatkowych funkcji
ilOriginFunc ustalenie początku obrazu
iluImagePa-
rameter
ustalanie dodatkowych parametrów
np. iltru używanego podczas skalowania
ilGenImages generowanie identyikatorów
ilBindImage ustalanie bieżącego identyikatora
ilDeleteIma-
ges
char msg[]={1001...
1001...
1111...
1001...
1001...
};
usuwanie identyikatora
Jedynka oznacza, że ma zostać wyświe-
tlony biały kwadrat, a zero – odwrotnie,
ilGetInteger pobieranie parametrów o obrazie
ilConvertPal konwersja palety
iluScale skalowanie obrazu
ilGetData pobieranie wskaźnika na dane obrazu
ilGetPalette pobieranie wskaźnika na paletę kolorów
Instalacja biblioteki
DevIL
Instalacja tej biblioteki w systemie jest
zadaniem trywialnym, a to ze względu na
obecność skryptu conigure . Wobec tego
wydajemy tylko trzy polecenia:
./configure –prefix=/katalog/do/ S
instalacji
make
make install
Jeśli nie mieliśmy kłopotów, to możemy
korzystać z funkcji setpixel32 . Postawie-
nie białego piksela pod współrzędnymi
100,100 wygląda tak:
wyświetla losowo na ekranie punkty
o dowolnych kolorach oraz co pewien
czas wyświetla na ekranie komunikat tek-
stowy, ale zbudowany z białych kwadra-
tów (to dlatego w naszej „mikro” bibliote-
ce znajduje się funkcja whiteblock ).
Listing 1 przedstawia najważniej-
szą pętlę w naszym pierwszym progra-
mie. Pętla ta rysuje dziesięć milionów
pikseli, ale po każdym milionie punk-
tów rysowany jest także napis „Hello
World!”. To zadanie wykonuje funkcja
draw_msg .
setpixel32(100, 100, 255, 255, 255, 0);
Jeśli zastosowana została ostatnio naj-
nowsza wersja 1.6.6, to podczas kompi-
lacji naszego programu picview, a dokład-
niej w momencie łączenia, możemy otrzy-
mać błąd braku funkcji _vsnprintf . Roz-
wiązanie tego problemu wymaga niewiel-
kiej modyikacji kodu biblioteki DevIL ,
a dokładnej pliku in_tiff.c . Odszukujemy
w nim wystąpienia funkcji _vsnprintf
i zamieniamy je na poprawną nazwę bez
podkreślenia, czyli vsnprintf . Po ponow-
nej kompilacji całej biblioteki wystarczy
zainstalować bibliotekę raz jeszcze i pro-
blem zniknie. Poprzednie wersje DevIL
nie posiadają tego błędu, za to nie obsłu-
gują formatu GIF. Z tego powodu, jeśli
chcemy, aby nasza przeglądarka wczy-
tywała ten rodzaj pliku, musimy korzystać
z wersji 1.6.6 DevIL .
Po zakończeniu pracy z trybem graficz-
nym możemy go zamknąć. Czynność tę
wykonujemy funkcją close_fb podając
za argument zmienną uchwytu.
W naszej docelowej aplikacji nie
potrzebujemy dodatkowych funkcji –
wystarczy nam tylko stawianie punktów,
ale mamy jeszcze funkcję clear32 , kasu-
jącą zawartość ekranu, oraz whiteblock ,
która „wie”, jak narysować biały kwadrat
o podanej długości boku.
Uzbrojeni w taką wiedzę możemy
napisać niewielki program wyświetlają-
cy „Hello World!” dla trybu bufora ramki.
Pełny kod źródłowy zawiera plik hw.c .
Program działa w następujący sposób:
Jak działa draw_msg?
Następnym istotnym fragmentem nasze-
go pierwszego programu jest funkcja
draw_msg . Jej pełny kod zawiera Listing
2. Jak widać, funkcja jest dość krótka
i nie zawiera żadnego ciągu znaków,
który stanowiłby treść naszego komuni-
katu. Korzystamy tylko z tablicy msg – to
w niej został zakodowany tekst do
www.lpmagazine.org
63
pobranie podstawowych informacji
odczytanie aktualnego wskaźnika na obszar pamięci graicznej
439034402.003.png 439034402.004.png
 
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 graicznego,
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 graicznej. Trzeba jednak pamię-
tać, że obsługiwanych jest zaledwie kilka
typów kart graicznych. 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.
Szczęśliwi posiadacze najnowszych kart,
np. GeForceFX 5900 , obejdą się sma-
kiem, ponieważ moduł rivafb nie rozpo-
znaje tego typu kart. Z tego powodu naj-
lepiej korzystać z trybów VESA.
W przypadku braku obsługi trybów
VESA, czeka nas kompilacja jądra.
W pierwszej kolejności należy odzna-
czyć opcję Prompt for development
and/or incomplete code/drivers w sekcji
Code maturity level options . Następnie,
w zależności od typu jądra, trzeba przejść
do Console Drivers/Frame-buffer-options
dla jądra w wersji 2.4.x bądź Device
drivers/Graphics support w przypadku
nowych jąder 2.6.x (należy zaznaczyć
obsługę bufora ramki dla trybu VESA, ale
nie w trybie modułu, bowiem nie będzie-
my mogli podać numeru trybu podczas
uruchamiania systemu). W artykule znaj-
dują się dwa rysunki pokazujące, jakie
opcje trzeba zaznaczyć w przypadku
tych dwóch serii jądra Linuksa.
Rysunek 1. Koniguracja obsługi bufora ramki VESA dla jądra 2.4.x
W pętli odczytujemy kolejne znaki z tabli-
cy msg , więc aby poznać, czy już prze-
kroczyliśmy linię, musimy znać liczbę
znaków w linii. W tablicy msg sam nie
wiem, ile wpisałem znaków, ale każdy
przeglądając kod źródłowy łatwo spraw-
dzi, że mamy wpisane pięć linii, toteż obli-
czenie, ile znaków jest w linii jest bardzo
proste: size_line=sizeof(msg)/5 . Gdy
mamy tę informację, narysowanie zawar-
tości tablicy msg jest już bardzo proste.
Odczytując zawartość msg sprawdza-
my, czy aktualny i-ty element tablicy to
jedynka, a jeśli tak, to wyświetlamy biały
kwadrat. W każdej iteracji pętli zwiększa-
my wartość współrzędnej x o wielkość
bloku: x=x+block_size; . Oczywiście,
w przypadku, gdy osiągniemy koniec
linii komunikatu, wykonujemy dwie
czynności: powracamy na początek miej-
sca, od którego zaczęliśmy rysować nasz
komunikat ( x=xx; ), oraz przechodzimy
linię niżej ( y=y+block_size; ).
przybliżyć sposób implementacji funkcji
związanych z obsługą trybu graficznego.
W Linuksie, a ogólnie w systemach unik-
sowych, urządzenia są reprezentowane
jako pliki. Nie inaczej jest w przypadku
bufora ramki. Zanim zaczniemy z niego
korzystać, należy otworzyć plik, który go
reprezentuje:
fb_handle=open("/dev/fb0", O_RDWR);
Następnie możemy uzyskać podsta-
wowe informacje (np. rozdzielczość,
sposób kodowania kolorów itd.). Tego
typu dane zawierają struktury o typie:
fb_var_screeninfo vinfo oraz fb_fix_
screeninfo .
Aby wypełnić pierwszą strukturę
potrzebnymi informacjami, stosujemy
funkcję ioctl w następującej postaci:
czyli nic nie zostanie wyświetlone. Trzeba
pamiętać o jednym fakcie: cały komunikat,
choć został w kodzie źródłowym odpo-
wiednio sformatowany, nadal jest tabli-
cą jednowymiarową. Podczas wyświetla-
nia komunikatu po wyświetleniu pierw-
szej linii musimy przejść linię niżej.
ioctl(fb_handle, FBIOGET_VSCREENINFO, S
&vinfo)
Programowanie bufora
ramki
Zanim na dobre rozpoczniemy pisać
naszą przeglądarkę, chciałbym jeszcze
Dość trudno będzie operować na bufo-
rze ramki z poziomu pliku. Dlatego, aby
normalnie operować pamięcią graficz-
ną, musimy ją jeszcze odwzorować za
pomocą zwykłego wskaźnika. W naszym
przypadku będzie to typ char . Pomoże to
nam w implementacji funkcji setpixel32 .
Odwzorowanie pliku w pamięci wyko-
nujemy za pomocą funkcji mmap :
Tabela 2. Spis najważniejszych trybów VESA
Bitów na kolor 640x480 800x600 1024x768 1280x1024
8
769 771
773 775
15
784 787
790 793
16
785 788
791 794
char *fb_map=(char*)mmap(0,sc,PROT_READ S
| PROT_WRITE, MAP_SHARED, fb_handle, 0);
24/32
786 789
792 795
64
lipiec 2004
439034402.005.png 439034402.006.png 439034402.007.png 439034402.008.png 439034402.009.png 439034402.010.png 439034402.011.png
 
przeglądarka plików
dla programistów
Listing 3. Funkcja setpixel32
blue_idx32 = vinfo.blue.offset / 8;
transp_idx32 = vinfo.transp.offset / 8;
void setpixel32 ( int x, int y, int r,
int g, int b, int a ) {
int pos ;
pos = ( x + vinfo.xoffset ) * S
( vinfo.bits_per_pixel/ 8 ) + S
( y + vinfo.yoffset ) * finfo.line_length ;
*( fb_map + pos + red_idx32 ) = r ;
*( fb_map + pos + green_idx32 ) = g ;
*( fb_map + pos + blue_idx32 ) = b ;
*( fb_map + pos + transp_idx32 ) = a ;
}
Stawiamy piksele
Podczas wyświetlania obrazu na ekra-
nie będziemy używać tylko jednej
pomocniczej funkcji, a mianowicie set-
pixel32 . Kod tej funkcji przedstawia
Listing 3. Jedynym bardziej skompli-
kowanym elementem, jaki tam napo-
tykamy, jest wyznaczenie miejsca
w pamięci na podstawie współrzęd-
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ądź, 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;
green_idx32 = vinfo.green.offset / 8;
Rysunek 2. Schemat działania przeglądarki plików graicznych
www.lpmagazine.org
65
439034402.012.png 439034402.013.png 439034402.014.png 439034402.015.png 439034402.016.png 439034402.017.png 439034402.018.png 439034402.019.png 439034402.020.png 439034402.021.png
dla programistów
podano argument (nazwę pliku) do
wyświetlenia:
picview obrazek.jpg
Rysunek 3. Koniguracja obsługi bufora ramki VESA dla jądra 2.6.x
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
programu, sprowadza się tylko do spraw-
dzenia, czy parametr argc funkcji main
jest mniejszy bądź większy od dwóch. Po
tym teście dokonujemy inicjalizacji biblio-
teki DevIL:
chodzi, wykonuje ten fragment kodu:
(y+vinfo.yoffset) * finfo.line_length .
W pierwszej części wyznaczamy odpo-
wiednie przesunięcie w pamięci y+vin-
fo.yoffset , a następnie mnożymy tę
wielkość przez całkowitą długość linii
w bajtach finfo.line_length . W ten
sposób wiemy, w którym miejscu zaczy-
na się linia, w której chcemy umieścić
nasz punkt. Wystarczy do tej wielkości
dodać wyrażenie (x+vinfo.xoffset) *
(vinfo.bits_per_pixel/8) , aby popraw-
nie odszukać odpowiedni adres w
pamięci bufora ramki. Jak widać, w tej
części obliczeń ponownie wyznaczamy
wartość przesunięcia x+vinfo.xoffset ,
ale mnożymy to przez liczbę bajtów
vinfo.bits_per_pixel/8 (dzielimy całko-
witą liczbę bitów na jeden piksel
przez osiem) jaka przypada na jeden
piksel.
Pozostaje nam tylko wykorzy-
stać wcześniej wyznaczoną wartość
numerów składowych kolorów, aby
poprawnie postawić punkt w potrzeb-
nym kolorze. Przykładowo, dla zielo-
nej składowej koloru przedstawia się to
następująco:
ilInit();
iluInit();
*(fb_map + pos + green_idx32) = g;
Zadanie główne
– przeglądarka plików
graicznych
Nasz program ma obsługiwać wiele róż-
nych formatów graficznych, a jak już
wspomniałem, sam kod źródłowy jest
niewielki. Oznacza to, że korzysta-
my z dodatkowej biblioteki do obsłu-
gi plików graficznych o nazwie DevIL .
Zanim przystąpimy do tworzenia kodu,
warto zastanowić się, jakie czynno-
ści będzie wykonywać nasz program.
Ogólny schemat działania przeglądar-
ki prezentuje Rysunek 2. Na samym
początku programu sprawdzamy, czy
Pierwsza linia odpowiada za podstawo-
wy zbiór instrukcji, a druga uruchamia
dodatkowy zestaw funkcji, w którym
znajduje się bardzo wygodna funkcja do
przeskalowania obrazu. Nie martwimy
się o deinicjalizację, ponieważ obydwie
funkcje podłączają się do łańcucha funk-
cji wyjściowych za pomocą atexit .
Następnym elementem jest ustale-
nie początku obrazu tak, aby znajdował
się on w lewym górnym rogu. Musimy
tak postąpić, ponieważ niektóre forma-
ty, np. bmp i tga , zapamiętują obraz „do
góry nogami”. Z tego powodu za pomocą
ilEnable i parametru IL_ORIGIN_SET oraz
funkcji ilOriginFunc nakazujemy biblio-
tece DevIL , aby obraz miał swój początek
w lewym górnym rogu (co jest zgodne
z działaniem funkcji setpixel32 ; gdy
podamy współrzędne 0,0, to postawimy
punkt w lewym górnym rogu). Ważną
operacją jest także ustalenie typu filtro-
wania obrazu podczas zmiany jego roz-
miarów. Filtrem, który daje obraz wyso-
kiej jakości, jest LANCZOS. Z tego
powodu w naszej aplikacji stosujemy ten
typ filtru. Pamiętajmy jednak, że choć
daje on bardzo dobry i ostry obraz, to
działa dość wolno, więc przy skalowaniu
Listing 4. Konwersja formatu palety kolorów
if ( ilGetInteger ( IL_PALETTE_TYPE )== IL_PAL_BGR24 ) {
ilConvertPal ( IL_PAL_RGB24 );
}
if ( ilGetInteger ( IL_PALETTE_TYPE )== IL_PAL_BGRA32 ||
ilGetInteger ( IL_PALETTE_TYPE )== IL_PAL_RGB32 ||
ilGetInteger ( IL_PALETTE_TYPE )== IL_PAL_BGR32 ||
ilGetInteger ( IL_PALETTE_TYPE )== IL_PAL_BGRA32 ) {
ilConvertPal ( IL_PAL_RGBA32 );
}
66
lipiec 2004
439034402.022.png 439034402.023.png 439034402.024.png 439034402.025.png 439034402.026.png
 
Zgłoś jeśli naruszono regulamin