O mapach pikselowych wspomnieliśmy już w poprzednim odcinku kursu. W odróżnieniu od map bitowych pozwalają one na przechowywanie znacznie większej ilości informacji o obrazie, w szczególności mapa pikselowa może zawierać dane o obrazie w formacie RGB i RGBA. Ponadto biblioteka OpenGL zawiera znacznie większe możliwości operacji na mapach pikselowych niż na mapach bitowych.
Rysowanie mapy pikselowej
Rysowanie mapy pikselowej w bieżącej pozycji rastra realizuje funkcja:
void glDrawPixels( GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid * pixels )
której parametry oznaczają odpowiednio:
Do określania pozycji rastra przy rysowaniu map pikselowych OpenGL wykorzystuje opisane już przy mapach bitowych funkcje z grup glRasterPos i glWindowPos. Dane mapy pikselowej domyślnie ułożone są podobnie jak dane mapy bitowej tj. kolejno w wierszach od najniższego do najwyższego. Obowiązują także te same reguły dotyczące wyrównywania i organizacji każdego wiersza danych, regulowane przez funkcje z grupy glPixelStore.
Funkcja glDrawPixels umożliwia transfer danych mapy pikselowej nie tylko do bufora koloru, ale także do buforów szablonu i głębokości. Format zapisywanych danych (parametr format) określają poniższe stałe:
Zapis do buforów głębokości i szablonu wymaga oczywiście ich obecności w buforze ramki. W przeciwnym wypadku biblioteka OpenGL wygeneruje błąd GL INVALID OPERATION. Taki sam błąd zostanie wygenerowany, jeżeli wystąpi niezgodność pomiędzy trybem pracy bufora kolorów (RGB/indeksowy a wartością parametru format.
Bardzo przydatne w praktyce formaty danych pikseli opisane stałymi
GL BGR i GL BGRA zostały wprowadzone w wersji 1.2 biblioteki OpenGL. Wcześniej tą samą funkcjonalność oferowało rozszerzenie EXT bgra, które definiowało stałe: GL BGR EXT i GL BGRA EXT. Warto także wspomnieć o rozszerzeniu EXT abgr, które dodaje kolejny format danych pikseli opisany stałą GL ABGR EXT, gdzie kolejność składowych RGBA jest odwrócona.
Obsługiwane formaty pikseli mapy pikselowej (parametr type funkcji glDrawPixels) przedstawia tabela 1.
Tabela 1: Formaty pikseli map pikselowych
Typowo składowe pikseli są w formacie GL_UNSIGNED_BYTE. Pozostałe formaty, umownie nazwijmy je liczbowymi (GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, GL_FLOAT), są stosunkowo rzadko wykorzystywane. Spowodowane jest m.in. tym, że jedynie nieliczne formaty plików graficznych pozwalają na przechowywanie danych w takich formatach. Także niewiele programów graficznych potrafi operować na tego rodzaju plikach graficznych.
Odrębny format składowych pikseli określa stała GL BITMAP. Piksel mapy bitowej jest w tym przypadku reprezentowany przez jeden bit i nie zawiera żadnych informacji o kolorze (piksele są czarne lub białe). Mapa pikselowa w tym formacie ma taką samą budowę jak mapa bitowa.
Osobną grupę stałych zawartych w tabeli 1 stanowią tzw. upakowane formaty pikseli RGB, RGBA i BGRA. Zostały one wprowadzone w wersji 1.2 biblioteki OpenGL, a wcześniej część upakowanych formatów pikseli definiowało rozszerzenie EXT packed pixels. Idea upakowanego formatu pikseli sprowadza się do umieszczenia składowych pikseli w formacie RGB, RGBA lub BGRA w ściśle określonej ilości bitów. Ilość użytych bitów oraz ilość składowych określa bezpośrednio nazwa stałej. Nazwa informuje jednocześnie jakiego rodzaju typ liczbowy jest używany do przechowywania całości informacji o pikselu. Elastyczność rozwiązania zwiększają formaty reprezentowane przez stałe z przyrostkiem REV, w których odwrócono kolejność składowych pikseli. Zestawienie dozwolonych kombinacji parametrów type i format dla upakowanych formatów pikseli przedstawia tabela 2.
Tabela 2: Upakowane formaty pikseli
Kolejność i ilość bitów przeznaczonych na każdą ze składowych RGB w pikselach formatu GL_UNSIGNED_BYTE_3_3_2 i GL_UNSIGNED_BYTE_3_3_2 - REV przedstawiają tabele 3 i 4. Warto zwrócić uwagę, że biblioteka OpenGL nie przewiduje umieszczenia w 8 bitowych danych pikseli w formacie GL_RGBA i GL_BGRA.
Tabela 3: Bity pikseli GL_UNSIGNED_BYTE_3_3_2
Tabela 4: Bity pikseli GL_UNSIGNED_BYTE_2_3_3_REV
W przypadku liczb 16-bitowych biblioteka OpenGL zawiera kilka różnych schematów upakowania składowych RGB, RGBA i BGRA pikseli. Różnice polegają przede wszystkim na ilości bitów przeznaczonych na poszczególne składowe. Całość przedstawiają tabele: 5, 6, 7, 8, 9 i 10.
Tabela 5: Bity pikseli GL_UNSIGNED_SHORT_5_6_5
Tabela 6: Bity pikseli GL_UNSIGNED_SHORT_5_6_5_REV
Tabela 7: Bity pikseli GL_UNSIGNED_SHORT_4_4_4_4
Tabela 8: Bity pikseli GL_UNSIGNED_SHORT_4_4_4_4_REV
Tabela 9: Bity pikseli GL_UNSIGNED_SHORT_5_5_5_1
Tabela 10: Bity pikseli GL_UNSIGNED_SHORT_1_5_5_5_REV
Ostatnia grupa schematów upakowania składowych RGBA i BGRA pikseli wykorzystuje liczby 32-bitowe. Warto zauważyć, że schematy przedstawione w tabelach 11 i 12 odpowiadają typowym danym zawartym w plikach graficznych. Zdecydowanie mniej popularne są natomiast schematy opisane w tabelach 13 i 14.
Tabela 11: Bity pikseli GL_UNSIGNED_INT_8_8_8_8
Tabela 12: Bity pikseli GL_UNSIGNED_INT_8_8_8_8_REV
Tabela 13: Bity pikseli GL_UNSIGNED_INT_10_10_10_2
Tabela 14: Bity pikseli GL_UNSIGNED_INT_2_10_10_10_REV
Wybór docelowego bufora kolorów
Domyślnie jeżeli bufor ramki zawiera pojedynczy bufor koloru zapis pikseli mapy dokonywany jest w przednim buforze (GL FRONT). W trybie z podwójnym buforowaniem zapis dokonywany jest domyślnie w tylnym buforze koloru (GL BACK). Selektywny wybór docelowego bufora kolorów (lub bufora pomocniczego) umożliwia funkcja:
void glDrawBuffer( GLenum mode )
której parametr mode przyjmuje jedną z poniższych wartości:
Tabela 15 przedstawia docelowe bufory kolorów dla poszczególnych wartości parametru mode funkcji glDrawBuffer. Zauważmy, że w przypadku dostępności podwójnych buforów stereoskopowych stałe GL FRONT, GL BACK, GL LEFT, GL RIGHT i GL FRONT AND BACK oznaczają więcej niż jeden docelowy bufor kolorów. Gdy dana implementacja udostępnia jedynie monoskopowe bufory kolorów, zapis dokonywany jest w lewym (lewych) buforach koloru.
Tabela 15: Docelowe bufory kolorów
Odczyt mapy pikselowej
Odczyt mapy pikselowej do bufora pamięci realizuje funkcja:
void glReadPixels( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid * pixels )
której parametry oznaczają:
Parametry format i type przyjmują takie same wartości jak analogiczne parametry funkcji glDrawPixels. Oczywiście bufor, na który wskazuje parametr pixels musi być wystarczająco duży aby pomieścić wszystkie dane odczytanej mapy pikselowej.
Podobnie jak w przypadku funkcji glDrawPixels, odczyt z buforów głębokości i szablonu wymaga ich obecności w buforze ramki. W przeciwnym wypadku biblioteka OpenGL wygeneruje błąd GL INVALID OPERATION. Taki sam błąd zostanie wygenerowany, jeżeli wystąpi niezgodność pomiędzy trybem pracy bufora kolorów (indeksowy/RGB) a wartością parametru format.
Domyślnie jeżeli bufor koloru jest pojedynczy odczyt pikseli mapy jest dokonywany z przedniego bufora (GL FRONT). W trybie z podwójnym buforowaniem odczyt dokonywany jest domyślnie z tylnego bufora koloru (GL BACK). Selektywny wybór źródłowego bufora kolorów (lub bufora pomocniczego) umożliwia funkcja:
której parametr mode przyjmuje jedną z poniższych wartości:
Kopiowanie mapy pikselowej
Kopiowanie prostokątnego obszaru z jednego obszaru bufora ramki do drugiego realizuje funkcja:
void glCopyPixels( GLint x, GLint y, GLsizei width, GLsizei height, GLenum type )
Parametry x i y oznaczają współrzędne lewego dolnego wierzchołka kopiowanego obszaru, a width i height odpowiednio jego szerokość i wysokość. Parametr type określa bufor, z którego mają być kopiowane dane:
W przypadku, gdy bufor ramki zawiera pojedynczy bufor koloru kopiowanie mapy realizowane jest w przednim buforze (GL_FRONT). W trybie z podwójnym buforowaniem kopiowanie realizowane jest domyślnie w tylnym buforze koloru (GL_BACK). Selektywny wybór bufora kolorów (lub bufora pomocniczego) umożliwia opisana wyżej funkcja glReadBuffer.
Kopiowanie z buforów głębokości i szablonu wymaga ich obecności w buforze ramki. W przeciwnym wypadku biblioteka OpenGL wygeneruje błąd GL_INVALID_OPERATION. Docelowe położenie kopiowanej mapy pikselowej określa bieżąca pozycja rastra wyznaczana za pomocą funkcji z grupy glRasterPos i glWindowPos.
Skalowanie mapy pikselowej
Współczynnik skalowania mapy pikselowej podczas odczytu, zapisu i kopiowania określa funkcja:
void glPixelZoom( GLfloat xfactor, GLfloat yfactor )
Parametr xfactor zawiera współczynnik skalowania mapy pikselowej w poziomie a yfactor współczynnik skalowania w pionie. Oba argumenty mogą przyjmować wartości ujemne, co powoduje zmianę orientacji współrzędnych położenia rastra. Domyślnie współrzędne położenia rastra odpowiadają lewemu dolnemu wierzchołkowi mapy pikselowej. Przykładowo użycie ujemnej wartości parametru yfactor, przy dodatniej wartości xfactor oznacza, że mapa bitowa rysowana jest „do góry nogami”, a pozycja rastra odpowiada lewemu górnemu wierzchołkowi mapy.
Do skalowania danych mapy pikselowej biblioteka OpenGL używa prostego i bardzo szybkiego algorytmu najbliższego sąsiada. Algorytm ten daje najlepsze rezultaty, gdy współczynniki skalowania mają wartości całkowite.
Transfer pikseli
Obsługę map pikselowych w bibliotece OpenGL uzupełniają funkcje wykonujące podstawowe operacje na obrazie tj. skalowanie i przesuwanie wartości składowych pikseli. Operacje te wykonywane są w trakcie transferu pikseli m.in. przy użyciu funkcji glCopyPixels, glDrawPixels i glReadPixels, a realizują to funkcje z grupy glPixelTransfer:
void glPixelTransferf( GLenum pname, GLfloat param )
void glPixelTransferi( GLenum pname, GLint param )
Parametr pname określa tryb transferu pikseli czyli wykonywane na składowych pikseli operacje. Przyjmuje on jedną z poniższych wartości:
Tabela 16 zawiera zestawienie wartości początkowych oraz zakresy dopuszczalnych wartości funkcji glPixelTransferf i glPixelTransferi. Wartości początkowe współczynników skalowania i wartości przesunięcia są neutralne dla wartości składowych pikseli.
Tabela 16: Parametry funkcji z grupy glPixelTransfer
Mapę (tablicę) przekształceń elementów mapy pikselowej określają funkcje z grupy glPixelMap:
void glPixelMapfv( GLenum map, GLint mapsize, const GLfloat * values )
void glPixelMapuiv( GLenum map, GLint mapsize, const GLuint * values )
void glPixelMapusv( GLenum map, GLint mapsize, const GLushort * values )
Parametr map określa rodzaj definiowanej mapy przekształceń i przyjmuje jedną z poniższych wartości:
Natomiast parametr mapsize określa ilość elementów mapy przekształceń, do której wskaźnik zawiera parametr values. Wielkość map przekształceń związanych z indeksami kolorów oraz wartościami bufora szablonu musi być potęgą liczby 2. W przeciwnym wypadku rezultat przekształceń jest nieokreślony a biblioteka OpenGL generuje błąd GL INVALID VALUE. Wielkość każdej mapy przekształceń jest określana indywidualnie, ale nie może być większa niż wartość zwrócona przez funkcję z grupy glGet z parametrem GL MAX PIXEL MAP TABLE. Początkowo rozmiar każdej z map przekształceń wynosi 1.
Aktualny rozmiar wybranej mapy przekształceń zwraca funkcja z grupy glGet z odpowiednim parametrem: GL PIXEL MAP I TO I SIZE, GL PIXEL - MAP S TO S SIZE, GL PIXEL MAP I TO R SIZE, GL PIXEL MAP I TO G SIZE, GL PIXEL MAP I TO B SIZE, GL PIXEL MAP I TO A SIZE, GL PIXEL MAP R - TO R SIZE, GL PIXEL MAP G TO G SIZE, GL PIXEL MAP B TO B SIZE, GL PIXEL MAP A TO A SIZE.
Odczyt skłądowych mapy przekształceń umożliwiają funkcje z grupy glGetPixelMap:
void glGetPixelMapfv( GLenum map, GLfloat * values ) void glGetPixelMapuiv( GLenum map, GLuint * values ) void glGetPixelMapusv( GLenum map, GLushort * values )
których parametr map przyjmuje takie same wartości jak parametr map funkcji z grupy glPixelMap. Wartości elementów mapy przekształceń umieszczane są do tablicy przekazywanej w parametrze values. Oczywiście program musi zapewnić odpowiednią wielkość tej tablicy.
Czytelnik znający podstawowe techniki cyfrowego przetwarzania obrazów zauważył z pewnością podobieństwo map przekształceń do tablic LUT (ang. LookUp Table). Jest to to faktycznie ta sama technika. W wersji 1.2 biblioteki OpenGL dodano opcjonalny zbiór funkcji odpowiedzialnych za przetwarzanie obrazów (ang. imaging subset), który wcześniej opisany był w rozszerzeniu ARB imaging. W jego skład wchodzą zarówno zupełnie nowe funkcje jak też i rozszerzenia wcześniejszych funkcji (m.in. opisywanych funkcji z grupy glPixelTransfer). Z uwagi na swoją obszerność funkcje te zostaną omówione oddzielnie.
Przykładowy program
Przykładowy program (plik targa view.cpp pozwala na odczyt i zapis plików graficznych w formacie TARGA oraz proste operacje na obrazie odczytanym z pliku. Wybór tego formatu pliku graficznego jest nieprzypadkowy. Pliki TARGA mają bardzo prostą budowę i standardowo przechowują dane obrazu w postaci nieskompresowanej. Opcjonalnie w plikach TARGA można wykorzystywać algorytm kompresji długości serii RLE (ang. Run-Length Encoding). Czytelnika zainteresowanego budową pliku TARGA odsyłam do oficjalnej specyfikacji zawartej w pracy [13].
Niestety biblioteka OpenGL ani opisywane biblioteki narzędziowe nie zawierają funkcji obsługujących pliki graficzne. Jedynie nieprzedstawiana bliżej, a wspomniana na początku kursu biblioteka AUX, poprzedniczka biblioteki GLUT, posiada dwie funkcje pozwalające na odczyt plików w formacie BMP (DIB) i SGI RGB:
AUX_RGBImageRec * auxDIBImageLoad( const char * filename )
AUX_RGBImageRec * auxRGBImageLoad( const char * filename )
których parametr filename oznacza nazwę wczytywanego pliku. Obie funkcje zwracają wskaźnik do struktury AUX RGBImageRec, która posiada następujące pola:
Funkcje obsługujące grafikę w formacie TARGA zawarte są w plikach targa.h i targa.cpp. Pierwsza z dostępnych funkcji służy do odczytu pliku:
GLboolean load_targa( const char * filename, GLsizei & width, GLsizei & height, GLenum & format, GLenum & type, GLvoid *& pixels )
Parametr filename zawiera oczywiście nazwę odczytywanego pliku, a pozostałe parametry są dokładnym odpowiednikiem parametrów funkcji glDrawPixels. Druga z dostępnych funkcji służy do zapisu plików TARGA:
GLboolean save_targa( const char * filename, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid * pixels )
Jej parametry są takie same jak parametry funkcji load targa. Obie funkcje zwracają wartość GL TRUE w przypadku pomyślnego wykonania operacji odczytu/zapisu pliku lub GL FALSE w przypadku błędu. W prezentowanej postaci funkcje obsługują wyłącznie nieskompresowane pliki TARGA z wyłączeniem plików posiadających mapę kolorów. Czytelnik może oczywiście rozszerzyć ich funkcjonalność stosownie do swoich potrzeb.
Poza możliwością odczytu i wyświetlenia zawartości pliku graficznego w formacie TARGA przykładowy program pozwala na dwie proste operacje na obrazie: negatyw i konwersję obrazu RGB do odcieni szarości. Przy konwersji do odcieni szarości wykorzystywane jest przekształcenie składowych RGB pikseli określone wzorem:
0.229*R + 0.587*G + 0.114*B
które realizowane jest poprzez odpowiednie skalowanie składowych pikseli:
glPixelTransferf( GL_RED_SCALE, 0.229 );
glPixelTransferf( GL_GREEN_SCALE, 0.587 );
glPixelTransferf( GL_BLUE_SCALE, 0.114 );
Po dokonaniu skalowania składowych pikseli konieczny jest powrót do przekształcenia neutralnego:
glPixelTransferf( GL_RED_SCALE, 1.0 );
glPixelTransferf( GL_GREEN_SCALE, 1.0 );
glPixelTransferf( GL_BLUE_SCALE, 1.0 );
Całość operacji zawiera funkcja Menu. Wygląd obrazu testowego „Lena” (rysunek 1) w odcieniach szarości przedstawia rysunek 2.
Przy tworzeniu negatywu obrazu wykorzystywana jest mapa przekształceń (tablica inverse map), która zawiera kolejnych 256 wartości składowych pikseli, przy czym pierwszy jej element wynosi 1, a ostatni 0. Zawartość tablicy tworzy poniższy kod:
GLfloat inverse_map[ 256 ];
inverse_map[ 0 ] = 1.0;
for( int i = 1; i < 256; i++ )
inverse_map[ i ] = 1.0 - 1.0 /( GLfloat ) 255 * i;
Po utworzeniu mapy przekształceń należy ustalić które składowe będą podlegały transformacji:
glPixelMapfv( GL_PIXEL_MAP_R_TO_R, 256, inverse_map );
glPixelMapfv( GL_PIXEL_MAP_G_TO_G, 256, inverse_map );
glPixelMapfv( GL_PIXEL_MAP_B_TO_B, 256, inverse_map );
oraz określić tryb transferu pikseli:
glPixelTransferi( GL_MAP_COLOR, GL_TRUE );
Po zakończeniu kopiowania pikseli należy wyłączyć tryb transferu pikseli z użyciem mapy przekształceń:
glPixelTransferi( GL_MAP_COLOR, GL_FALSE );
Całość operacji generujących negatyw obrazu zawiera funkcja Menu. Wygląd negatywu obrazu testowego „Lena” zawiera rysunek 3), a negatyw tego samego obrazu w odcieniach szarości przedstawia rysunek 4.
Kolejną cechą programu testowego jest automatyczne dopasowanie rozmiarów rysowanego obrazu do rozmiarów okna, przy czym wielkość początkowa okna programu odpowiada wielkości obrazu zawartego we wczytanym pliku TARGA. Powiększanie obrazu wykonywane jest w funkcji Display:
glPixelZoom(( float ) glutGet( GLUT_WINDOW_WIDTH ) /( float ) width,( float ) glutGet( GLUT_WINDOW_HEIGHT ) /( float ) height );
gdzie width i height to zmienne globalne zawierające rozmiary obrazu odczytanego z pliku TARGA.
Poza tradycyjnym sposobem dowolnej zmiany rozmiarów okna programu przy pomocy myszki, użytkownik ma także możliwość wyboru kilku domyślnych wielkości tego okna. Są to powiększenie dwu i czterokrotne oraz rozmiar odpowiadający rozmiarom obrazu zawartego ww wczytanym pliku graficznym. Wybór rozmiaru okna programu w dowolnym momencie umożliwia funkcja biblioteki GLUT:
void glutReshapeWindow( int width, int height )
której parametry width i height określają odpowiednio nową szerokość i wysokość okna.
Ciekawym efektem zaimplementowanym w programie jest podręczna „lupa” powiększająca fragment obrazu wskazany lewym przyciskiem myszki. Przykład działania „lupy” przedstawia rysunek 5. Efekt powiększenia wybranego fragmentu obrazu wykorzystuje funkcję glCopyPixels w połączeniu ze skalowaniem pikseli za pomocą funkcji glPixelZoom. Całość jest zawarta w funkcji ZoomImage.
Do prawidłowego działania programu wymagana jest obsługa rozszerzenia EXT bgra lub co najmniej wersja 1.3 biblioteki OpenGL. Sprawdzenie tego odbywa się w funkcji CheckImageFormat programu. Dlatego też moduł obsługujący pliki graficzne TARGA do kompilacji wymaga pliku nagłówkowego glext.h. Jako ćwiczenie może Czytelnik przygotować wersję programu, która jest niezależna od wersji biblioteki OpenGL.
Plik targa.h
#ifndef __TARGA__H__
#define __TARGA__H__
#include <GL/gl.h>
GLboolean load_targa( const char * filename, GLsizei & width, GLsizei & height,
GLenum & format, GLenum & type, GLvoid *& pixels );
GLboolean save_targa( const char * filename, GLsizei width, GLsizei height,
GLenum format, GLenum type, GLvoid * pixels );
#endif
Plik targa.cpp
#include "targa.h"
#include <GL/glext.h>
#include <stdio.h>
#include <string.h>
#define TARGA_HEADER_SIZE 0x12
#define TARGA_UNCOMP_RGB_IMG 0x02
#define TARGA_UNCOMP_BW_IMG 0x03
GLboolean load_targa( const char * filename, GLsizei & width, GLsizei & height,
GLenum & format, GLenum & type, GLvoid *& pixels )
{
pixels = NULL;
width = 0;
height = 0;
FILE * tga = fopen( filename, "rb" );
if( !tga )
return GL_FALSE;
unsigned char header[ TARGA_HEADER_SIZE ];
fread( header, TARGA_HEADER_SIZE, 1, tga );
fseek( tga, header[ 0 ], SEEK_CUR );
width = header[ 12 ] +( header[ 13 ] << 8 );
height = header[ 14 ] +( header[ 15 ] << 8 );
if( header[ 2 ] == TARGA_UNCOMP_RGB_IMG && header[ 16 ] == 24 )
{
pixels = new unsigned char[ width * height * 3 ];
fread(( void * ) pixels, width * height * 3, 1, tga );
format = GL_BGR;
type = GL_UNSIGNED_BYTE;
}
else
if( header[ 2 ] == TARGA_UNCOMP_RGB_IMG && header[ 16 ] == 32 )
{
pixels = new unsigned char[ width * height * 4 ];
fread(( void * ) pixels, width * height * 4, 1, tga );
format = GL_BGRA;
type = GL_UNSIGNED_BYTE;
}
else
if( header[ 2 ] == TARGA_UNCOMP_BW_IMG && header[ 16 ] == 8 )
{
pixels = new unsigned char[ width * height ];
fread(( void * ) pixels, width * height, 1, tga );
format = GL_LUMINANCE;
type = GL_UNSIGNED_BYTE;
}
else
return GL_FALSE;
fclose( tga );
return GL_TRUE;
}
GLboolean save_targa( const char * filename, GLsizei width, GLsizei height,
GLenum format, GLenum type, GLvoid * pixels )
{
if( format != GL_BGR && format != GL_BGRA && format != GL_LUMINANCE )
return GL_FALSE;
if( type != GL_UNSIGNED_BYTE )
return GL_FALSE;
FILE * tga = fopen( filename, "wb" );
if( tga == NULL )
return GL_FALSE;
unsigned char header[ TARGA_HEADER_SIZE ];
memset( header, 0, TARGA_HEADER_SIZE );
if( format == GL_BGR || format == GL_BGRA )
header[ 2 ] = TARGA_UNCOMP_RGB_IMG;
else
if( format == GL_LUMINANCE )
header[ 2 ] = TARGA_UNCOMP_BW_IMG;
header[ 12 ] =( unsigned char ) width;
header[ 13 ] =( unsigned char )( width >> 8 );
header[ 14 ] =( unsigned char ) height;
header[ 15 ] =( unsigned char )( height >> 8 );
if( format == GL_BGRA )
header[ 16 ] = 32;
else
if( format == GL_BGR )
header[ 16 ] = 24;
else
if( format == GL_LUMINANCE )
header[ 16 ] = 8;
fwrite( header, TARGA_HEADER_SIZE, 1, tga );
if( format == GL_BGRA )
fwrite( pixels, width * height * 4, 1, tga );
else
if( format == GL_BGR )
fwrite( pixels, width * height * 3, 1, tga );
else
if( format == GL_LUMINANCE )
fwrite( pixels, width * height, 1, tga );
fclose( tga );
return GL_TRUE;
}
Plik targa_view.cpp
#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>
#include <GL/glext.h>
#include "targa.h"
GLfloat inverse_map[ 256 ] =
{
1.000000, 0.996078, 0.992157, 0.988235, 0.984314, 0.980392, 0.976471, 0.972549,
0.968627, 0.964706, 0.960784, 0.956863, 0.952941, 0.949020, 0.945098, 0.941176,
0.937255, 0.933333, 0.929412, 0.925490, 0.921569, 0.917647, 0.913725, 0.909804,
0.905882, 0.901961, 0.898039, 0.894118, 0.890196, 0.886275, 0.882353, 0.878431,
0.874510, 0.870588, 0.866667, 0.862745, 0.858824, 0.854902, 0.850980, 0.847059,
0.843137, 0.839216, 0.835294, 0.831373, 0.827451, 0.823529, 0.819608, 0.815686,
0.811765, 0.807843, 0.803922, 0.800000, 0.796078, 0.792157, 0.788235, 0.784314,
0.780392, 0.776471, 0.772549, 0.768627, 0.764706, 0.760784, 0.756863, 0.752941,
0.749020, 0.745098, 0.741176, 0.737255, 0.733333, 0.729412, 0.725490, 0.721569,
0.717647, 0.713726, 0.709804, 0.705882, 0.701961, 0.698039, 0.694118, 0.690196,
0.686275, 0.682353, 0.678431, 0.674510, 0.670588, 0.666667, 0.662745, 0.658824,
0.654902, 0.650980, 0.647059, 0.643137, 0.639216, 0.635294, 0.631373, 0.627451,
0.623529, 0.619608, 0.615686, 0.611765, 0.607843, 0.603922, 0.600000, 0.596078,
0.592157, 0.588235, 0.584314, 0.580392, 0.576471, 0.572549, 0.568627, 0.564706,
0.560784, 0.556863, 0.552941, 0.549020, 0.545098, 0.541176, 0.537255, 0.533333,
0.529412, 0.525490, 0.521569, 0.517647, 0.513726, 0.509804, 0.505882, 0.501961,
0.498039, 0.494118, 0.490196, 0.486275, 0.482353, 0.478431, 0.474510, 0.470588,
0.466667, 0.462745, 0.458824, 0.454902, 0.450980, 0.447059, 0.443137, 0.439216,
0.435294, 0.431373, 0.427451, 0.423529, 0.419608, 0.415686, 0.411765, 0.407843,
0.403922, 0.400000, 0.396078, 0.392157, 0.388235, 0.384314, 0.380392, 0.376471,
0.372549, 0.368627, 0.364706, 0.360784, 0.356863, 0.352941, 0.349020, 0.345098,
0.341176, 0.337255, 0.333333, 0.329412, 0.325490, 0.321569, 0.317647, 0.313726,
0.309804, 0.305882, 0.301961, 0.298039, 0.294118, 0.290196, 0.286275, 0.282353,
0.278431, 0.274510, 0.270588, 0.266667, 0.262745, 0.258824, 0.254902, 0.250980,
0.247059, 0.243137, 0.239216, 0.235294, 0.231373, 0.227451, 0.223529, 0.219608,
0.215686, 0.211765, 0.207843, 0.203922, 0.200000, 0.196078, 0.192157, 0.188235,
0.184314, 0.180392, 0.176471, 0.172549, 0.168627, 0.164706, 0.160784, 0.156863,
0.152941, 0.149020, 0.145098, 0.141176, 0.137255, 0.133333, 0.129412, 0.125490,
0.121569, 0.117647, 0.113725, 0.109804, 0.105882, 0.101961, 0.098039, 0.094118,
0.090196, 0.086275, 0.082353, 0.078431, 0.074510, 0.070588, 0.066667, 0.062745,
0.058824, 0.054902, 0.050980, 0.047059, 0.043137, 0.039216, 0.035294, 0.031373,
0.027451, 0.023529, 0.019608, 0.015686, 0.011765, 0.007843, 0.003922, 0.000000
};
GLsizei width;
GLsizei height;
GLenum format;
GLenum type;
GLvoid * pixels;
enum
{
ZOOM_X2 = 2,
ZOOM_X3,
ZOOM_X4,
ZOOM_X5,
SIZE_X05,
SIZE_X1,
SIZE_X2,
SIZE_X4,
GRAY_CONVERT,
NEGATIVE_CONVERT,
SAVE_FILE,
EXIT
};
int button_state = GLUT_UP;
int button_x, button_y;
int zoom = ZOOM_X2;
void LoadTARGA( int argc, char * argv[] )
{
if( argc < 2 )
{
printf( "Brak nazwy pliku TARGA\n" );
exit( 1 );
}
if( !load_targa( argv[ 1 ], width, height, format, type, pixels ) )
{
#ifdef WIN32
printf( "Błąd odczytu lub błędny format pliku: %s\n", argv[ 1 ] );
#else
printf( "Blad odczytu lub bledny format pliku: %s\n", argv[ 1 ] );
#endif
exit( 1 );
}
}
void CheckImageFormat()
{
if( format == GL_BGR || format == GL_BGRA )
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifdef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 1 );
}
if( major <= 1 && minor < 2 )
{
if( !glutExtensionSupported( "GL_EXT_bgra" ) )
{
printf( "Brak rozszerzenia GL_EXT_bgra\n" );
exit( 1 );
}
}
}
}
void SaveTARGA( GLenum format )
{
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
GLvoid * pixels;
GLsizei width = glutGet( GLUT_WINDOW_WIDTH );
GLsizei height = glutGet( GLUT_WINDOW_HEIGHT );
if( format == GL_BGRA )
pixels = new unsigned char[ width * height * 4 ];
else
if( format == GL_BGR )
pixels = new unsigned char[ width * height * 3 ];
else
if( format == GL_LUMINANCE )
pixels = new unsigned char[ width * height ];
else
return;
if( format == GL_LUMINANCE )
{
glPixelTransferf( GL_RED_SCALE, 0.229 );
glPixelTransferf( GL_GREEN_SCALE, 0.587 );
glPixelTransferf( GL_BLUE_SCALE, 0.114 );
}
glReadPixels( 0, 0, width, height, format, GL_UNSIGNED_BYTE, pixels );
if( format == GL_LUMINANCE )
{
glPixelTransferf( GL_RED_SCALE, 1.0 );
glPixelTransferf( GL_GREEN_SCALE, 1.0 );
glPixelTransferf( GL_BLUE_SCALE, 1.0 );
}
if( format == GL_BGRA )
save_targa( "test_BGRA.tga", width, height, format, type, pixels );
else
if( format == GL_BGR )
save_targa( "test_BGR.tga", width, height, format, type, pixels );
else
if( format == GL_LUMINANCE )
save_targa( "test_LUMINANCE.tga", width, height, format, type, pixels );
delete[]( unsigned char * ) pixels;
}
void ZoomImage()
{
int x = button_x - 100;
int y = glutGet( GLUT_WINDOW_HEIGHT ) - button_y - 100;
if( x < 0 )
x = 0;
if( y < 0 )
y = 0;
if( x > glutGet( GLUT_WINDOW_WIDTH ) - 200 )
x = glutGet( GLUT_WINDOW_WIDTH ) - 200;
if( y > glutGet( GLUT_WINDOW_HEIGHT ) - 200 )
y = glutGet( GLUT_WINDOW_HEIGHT ) - 200;
glRasterPos2i( x, y );
glPixelZoom( zoom, zoom );
int posx = button_x - 100 / zoom;
int posy = glutGet( GLUT_WINDOW_HEIGHT ) - button_y - 100 / zoom;
if( posx < 0 )
posx = 0;
if( posy < 0 )
posy = 0;
if( posx > glutGet( GLUT_WINDOW_WIDTH ) - 100 )
posx = glutGet( GLUT_WINDOW_WIDTH ) - 100;
if( posy > glutGet( GLUT_WINDOW_HEIGHT ) - 100 )
posy = glutGet( GLUT_WINDOW_HEIGHT ) - 100;
glCopyPixels( posx, posy, 200 / zoom, 200 / zoom, GL_COLOR );
}
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT );
glRasterPos2i( 0, 0 );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glPixelZoom(( float ) glutGet( GLUT_WINDOW_WIDTH ) /( float ) width,
( float ) glutGet( GLUT_WINDOW_HEIGHT ) /( float ) height );
glDrawPixels( width, height, format, type, pixels );
if( button_state == GLUT_DOWN )
ZoomImage();
glFinish();
glutSwapBuffers();
}
void Reshape( int Width, int Height )
{
glViewport( 0, 0, Width, Height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D( 0.0, Width, 0.0, Height );
Display();
}
void MouseButton( int button, int state, int x, int y )
{
if( button == GLUT_LEFT_BUTTON )
{
button_state = state;
if( state == GLUT_DOWN )
{
button_x = x;
button_y = y;
}
glutPostRedisplay();
}
}
void MouseMotion( int x, int y )
{
if( button_state == GLUT_DOWN )
{
button_x = x;
button_y = y;
glutPostRedisplay();
}
}
void Menu( int value )
{
switch( value )
{
case SIZE_X05:
glutReshapeWindow( width / 2, height / 2 );
Display();
break;
case SIZE_X1:
glutReshapeWindow( width, height );
Display();
break;
case SIZE_X2:
glutReshapeWindow( width * 2, height * 2 );
Display();
break;
case SIZE_X4:
glutReshapeWindow( width * 4, height * 4 );
Display();
break;
case ZOOM_X2:
zoom = ZOOM_X2;
break;
case ZOOM_X3:
zoom = ZOOM_X3;
break;
case ZOOM_X4:
zoom = ZOOM_X4;
break;
case ZOOM_X5:
zoom = ZOOM_X5;
break;
case SAVE_FILE:
SaveTARGA( format );
break;
case GRAY_CONVERT:
if( format == GL_LUMINANCE )
break;
glPixelZoom( 1.0, 1.0 );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glPixelTransferf( GL_RED_SCALE, 0.229 );
glPixelTransferf( GL_GREEN_SCALE, 0.587 );
glPixelTransferf( GL_BLUE_SCALE, 0.114 );
glDrawPixels( width, height, format, type, pixels );
glPixelTransferf( GL_RED_SCALE, 1.0 );
glPixelTransferf( GL_GREEN_SCALE, 1.0 );
glPixelTransferf( GL_BLUE_SCALE, 1.0 );
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
glReadPixels( 0, 0, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels );
format = GL_LUMINANCE;
Display();
break;
case NEGATIVE_CONVERT:
glPixelZoom( 1.0, 1.0 );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glPixelMapfv( GL_PIXEL_MAP_R_TO_R, 256, inverse_map );
glPixelMapfv( GL_PIXEL_MAP_G_TO_G, 256, inverse_map );
glPixelMapfv( GL_PIXEL_MAP_B_TO_B, 256, inverse_map );
glPixelTransferi( GL_MAP_COLOR, GL_TRUE );
glDrawPixels( width, height, format, type, pixels );
glPixelTransferi( GL_MAP_COLOR, GL_FALSE );
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
if( format == GL_LUMINANCE )
{
glPixelTransferf( GL_RED_SCALE, 0.229 );
glPixelTransferf( GL_GREEN_SCALE, 0.587 );
glPixelTransferf( GL_BLUE_SCALE, 0.114 );
}
glReadPixels( 0, 0, width, height, format, GL_UNSIGNED_BYTE, pixels );
if( format == GL_LUMINANCE )
{
glPixelTransferf( GL_RED_SCALE, 1.0 );
glPixelTransferf( GL_GREEN_SCALE, 1.0 );
glPixelTransferf( GL_BLUE_SCALE, 1.0 );
}
Display();
break;
case EXIT:
exit( 0 );
break;
}
}
int main( int argc, char * argv[] )
{
LoadTARGA( argc, argv );
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_ALPHA );
glutInitWindowSize( width, height );
glutCreateWindow( argv[ 1 ] );
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
CheckImageFormat();
int MenuWindowScale = glutCreateMenu( Menu );
glutAddMenuEntry( "Pomniejszenie X2", SIZE_X05 );
glutAddMenuEntry( "Rozmiar normalny", SIZE_X1 );
#ifdef WIN32
glutAddMenuEntry( "Powiększenie X2", SIZE_X2 );
glutAddMenuEntry( "Powiększenie X4", SIZE_X4 );
#else
glutAddMenuEntry( "Powiekszenie X2", SIZE_X2 );
glutAddMenuEntry( "Powiekszenie X4", SIZE_X4 );
#endif
int MenuZoom = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Powiększenie X2", ZOOM_X2 );
glutAddMenuEntry( "Powiększenie X3", ZOOM_X3 );
glutAddMenuEntry( "Powiększenie X4", ZOOM_X4 );
glutAddMenuEntry( "Powiększenie X5", ZOOM_X5 );
#else
glutAddMenuEntry( "Powiekszenie X2", ZOOM_X2 );
glutAddMenuEntry( "Powiekszenie X3", ZOOM_X3 );
glutAddMenuEntry( "Powiekszenie X4", ZOOM_X4 );
glutAddMenuEntry( "Powiekszenie X5", ZOOM_X5 );
#endif
glutCreateMenu( Menu );
#ifdef WIN32
glutAddSubMenu( "Wielkość okna", MenuWindowScale );
glutAddSubMenu( "Efekt zoom", MenuZoom );
glutAddMenuEntry( "Odcienie szarości", GRAY_CONVERT );
glutAddMenuEntry( "Negatyw", NEGATIVE_CONVERT );
glutAddMenuEntry( "Zapisz", SAVE_FILE );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddSubMenu( "Wielkosc okna", MenuWindowScale );
glutAddSubMenu( "Efekt zoom", MenuZoom );
glutAddMenuEntry( "Odcienie szarosci", GRAY_CONVERT );
glutAddMenuEntry( "Negatyw", NEGATIVE_CONVERT );
glutAddMenuEntry( "Zapisz", SAVE_FILE );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
glutMainLoop();
return 0;
}