-Writing a Texture class-
OK. You want to load textures, and and do cool effects with them. But each time you want to do some project, you find yourself recoding the texture loader over and over again. And even this would be permissible for simple demos. What about when you want to multitexture? What about state sorting? After finishing this texture class, you will simply find yourself writing extensions, and not reinventing the wheel. OpenGL extensions, etc. will be easier to use and code. Without furthur ado,let us launch into the code. We will be supporting three formats, BMP, TGA and the Quake2 WAL.
As before, we will dicuss briefly why we are doing this, because if this wasn't of much use, you should probably not read the rest of this tutorial. We do this for several reasons, a few of them listed below.
  • You and your program requirements are definitely going to grow, and it's no good trying to find out what logic you used last time, by piecing together scraps of code (It sure as hell happens to me)
  • Sooner or later you are going to make something better than little demos, and then you'll have to find places to put your variables, flags and various other assorted knick-knacks.
  • And a horde of other things I'd rather not mention since I've done them :">
OK. By now we all agree that this class is a good thing to have around.
We will now take a look at the declarations of the texture class.
    //This will be the structure that will hold the basic image data that
    //we will give to OpenGL
    TTextureImage=record
                        ImageData:array of GLubyte;
                        BPP:GLuint;
                        Width,Height:GLuint;
                        TexID:GLuint;
    end;
//This is the TGA header TTGAHeader=array[0..11] of byte; //This is the header for the Quake2 WAL format. TWALHeader=record Name:array[0..31] of char; Width,Height:longword; Offset:array[0..3] of longint; NextName:array[0..31] of char; Glags, Contents, Value:longword; end;
We next declare the header that is used in uncompressed TGAs. We will declare it as a const.
const
     TGAHeader:TTGAHeader=(0,0,2,0,0,0,0,0,0,0,0,0);
     //Come on, are you  telling you  dont want to multitexture ?
     MultiTextureAvailable:boolean=true;
     WAL_Brightness:integer=0;
The use of the other two constants will become clear when I write the respective loading routines.
    TTexture=class
                  private
                  protected
                  fTextureData:TTextureImage;
                  fMIPMapping:boolean;
                  fName:string;
                  function GetID:GLuint;
                  public
                  function LoadTexture(aFilename:string):boolean;
                  function LoadTGA(aFileName:string):boolean;
                  function LoadBitmap(aFilename:string):boolean;
                  function LoadWAL(aFilename,PaletteFilename:string):boolean;
                  function LoadJPEG(aFilename:string):Boolean;
                  procedure Enable;virtual;
                  procedure Disable;virtual;
                  procedure Transparency;
                  procedure Translucency;
                  procedure LightMap;
                  procedure Opaque;
                  destructor Destroy;override;
                  property ID:GLuint
                           read GetID;
                  property  Width:GLuint
                            read fTextureData.Width;
                  property Height:GLuint
                           read fTextureData.Height;
                  property MIPMapping:boolean
                           read fMIPMapping
                           write fMIPMapping;
                  property Name:string
                           read fName
                           write fName;
    end;
After that little(?) header, I will explain why I chose to make it this way. First things first. Why are Enable and Disable virtual? As you may know, switching states in OpenGL, especially texture states, is very expensive. Currently, all these routines do are call glBindTexture. Later, when you have a complicated scene graph, you'll definitely want to optimize texture state changes. Descendants of this TTexture class can use Enable and Disable to notify a manager of a state change so that other polys with that texture dont have to switch states.
OK. The TGA loader first.
function TTexture.LoadTGA(aFileName:string):boolean;
var
   hdr:TTGAHeader;
   useful:array[0..5] of byte;
   BytesPerPixel,
   ImageSize,
   temp,
   imagetype:GLuint;
   f:file;
   i:GLuint;
Declare the header first, along with an array to hold useful stuff. Add miscellaneous variables as and when required.
begin
     result:=false;
     imagetype:=GL_RGBA;
     if not FileExists(aFileName) then exit;     Assign(f,aFileName);
     try
        Reset(f,1);
        BlockRead(f,hdr,SizeOf(hdr));
        if not CompareMem(@hdr[0],@TGAHeader[0],SizeOf(hdr)) then exit;
        BlockRead(f,useful,SizeOf(useful));
     except
           exit;
     end;
Read in the header, and if everything is hunky-dory, then read the 6 useful bytes into the previously declared array.
     fTextureData.Width:=useful[1]*256 + useful[0];
     fTextureData.Height:=useful[3]*256 + useful[2];
     if (fTextureData.Width<=0)or
        (fTextureData.Height<=0) or
        ((useful[4]<>24) and (useful[4]<>32)) then
        exit;
Calculate the width and height of the TGA. Also, check if the BPP(Bits Per Pixel) is anything other than 24 or 32. We dont support anything else, so we get out as quickly as we can.
     fTextureData.BPP:=useful[4];
     BytesPerPixel:=fTextureData.BPP div 8;
     ImageSize:=fTextureData.Width*fTextureData.Height*BytesPerPixel;
     try
        SetLength(fTextureData.ImageData,ImageSize);
     except
           exit;
     end;
     try
        BlockRead(f,fTextureData.ImageData[0],ImageSize)
     except
           fTextureData.ImageData:=nil;
           exit;
     end;
Now we have all the required info to read the actual image information. We set the size of the array and read the image data in.
     i:=0;
     while (i < ImageSize) do
     begin
          temp:=fTextureData.Imagedata[i];
          fTextureData.Imagedata[i]:=fTextureData.Imagedata[i+2];
          fTextureData.Imagedata[i+2]:=temp;
          Inc(i,BytesPerPixel);
     end;
     Close(f);

What we do here is to swap the R and B components of each quad. This is because the TGA is stored as BGRA quads, and we require RGBA's. After we have done this we close the file. We have no furthur use for it.

     glGenTextures(1,@fTextureData.TexID);
     glBindTexture(GL_TEXTURE_2D, fTextureData.TexID);
     if not fMIPMapping then
     begin
          if fTextureData.BPP=24 then
             imagetype:=GL_RGB;
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,MINFILTER);
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,MAGFILTER);
          glTexImage2D(GL_TEXTURE_2D,0,imagetype,fTextureData.Width,fTextureData.Height,0,imagetype,GL_UNSIGNED_BYTE,@fTextureData.ImageData[0]);
     end
     else
     begin
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,MINFILTER);
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,MAGFILTER);
          if fTextureData.BPP=24 then
             imagetype:=GL_RGB;
          gluBuild2DMipmaps(GL_TEXTURE_2D, 3, fTextureData.Width, fTextureData.Height, imagetype ,GL_UNSIGNED_BYTE, @fTextureData.ImageData[0]);
     end;
We use glGenTextures to retrieve an ID for our texture, and depending upon the colordepth of the
input image, we provide image data either as RGB or RGBA. We also generate mipmaps for the texture if the fMipMapping flag is set
Here's where we load bitmaps. I'm not going into much detail here, since most of it is a lot like the previous loading.
function TTexture.LoadBitmap(aFilename:string):boolean;
var
   FileHeader: BITMAPFILEHEADER;
   InfoHeader: BITMAPINFOHEADER;
   Palette: array of RGBQUAD;
   BitmapFile: THandle;
   BitmapLength: LongWord;
   PaletteLength: LongWord;
   ReadBytes: LongWord;
   Front: ^Byte;
   Back: ^Byte;
   Temp: Byte;
   I : Integer;
   Width, Height : Integer;
   pData : Pointer;begin
     result :=false;
     BitmapFile := CreateFile(PChar(aFilename), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0);
     if (BitmapFile = INVALID_HANDLE_VALUE) then
        exit;     // Get header information
     ReadFile(BitmapFile, FileHeader, SizeOf(FileHeader), ReadBytes, nil);
     ReadFile(BitmapFile, InfoHeader, SizeOf(InfoHeader), ReadBytes, nil);     // Get palette
     PaletteLength := InfoHeader.biClrUsed;
     SetLength(Palette, PaletteLength);
     ReadFile(BitmapFile, Palette, PaletteLength, ReadBytes, nil);
     if (ReadBytes <> PaletteLength) then
        exit;     Width  := InfoHeader.biWidth;
     Height := InfoHeader.biHeight;
     BitmapLength := InfoHeader.biSizeImage;
     if BitmapLength = 0 then
       BitmapLength := Width * Height * InfoHeader.biBitCount Div 8;     // Get the actual pixel data
     GetMem(pData, BitmapLength);
     ReadFile(BitmapFile, pData^, BitmapLength, ReadBytes, nil);
     if (ReadBytes <> BitmapLength) then
        exit;     CloseHandle(BitmapFile);     // Bitmaps are stored BGR and not RGB, so swap the R and B bytes.
     for I :=0 to Width * Height - 1 do
     begin
          Front := Pointer(Integer(pData) + I*3);
          Back := Pointer(Integer(pData) + I*3 + 2);
          Temp := Front^;
          Front^ := Back^;
          Back^ := Temp;
     end;
     fTextureData.Width:=Width;
     fTextureData.Height:=Height;
     fTextureData.ImageData:=pData;
     glGenTextures(1,@fTextureData.TexID);
     glBindTexture(GL_TEXTURE_2D, fTextureData.TexID);
     if not fMIPMapping then
     begin
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,MINFILTER);
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,MAGFILTER);
          glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,fTextureData.Width,fTextureData.Height,0,GL_RGB,GL_UNSIGNED_BYTE,@fTextureData.ImageData[0])
     end
     else
     begin
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,MINFILTER);
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,MAGFILTER);
          gluBuild2DMipmaps(GL_TEXTURE_2D, 3, fTextureData.Width, fTextureData.Height, GL_RGB,GL_UNSIGNED_BYTE, @fTextureData.ImageData[0]);
     end;     result :=true;
end;
As you can see, there isnt much difference after we have loaded the image. Now we come to a format worth loading, the Quake2 WAL It's an extermely handy format to have around, since there are lot of great textures in the quake2. PAK. But, of course it has its own peculiarities. The quake2 WAL uses only 256 colors, from a palette common to all textures. So, what we will be doing is to load the palette file, and convert the corresponding color indices into RGB values, before we, as usual hand over the data to OpenGL.
function TTexture.LoadWAL(aFilename,PaletteFilename:string):boolean;
var
   TexFile,PalFile:file;
   header:TWALHeader;
   tempImageData:array of GLubyte;
   i,CurIndex:integer;
   Palette:array[0..255,0..2] of byte;
The function will also need to load the palette, so we are supplying both paths. As before, we declare the required headers. We will store the image data temporarily in tempImageData before transferring it to our main ImageData array. The Palette aray declared merits some attention. In a palette, colors are stored as RGB triads. There are 255 such triads.
     result:=false;
     if  not FileExists(aFilename) then exit;
     if not FileExists(PaletteFilename) then exit;     Assign(TexFile,aFilename);
     Reset(TexFile,1);     Assign(PalFile,PaletteFilename);
     Reset(PalFile,1);
Open both the files after making sure they exist.
     BlockRead(TexFile,header,SizeOf(header));
     fTextureData.Width:=header.Width;
     fTextureData.Height:=header.Height;     SetLength(tempImagedata,header.Width*header.Height);
     SetLength(fTextureData.ImageData,header.Width*header.Height*3);     BlockRead(TexFile,tempImageData[0],header.Width*header.Height);
     BlockRead(PalFile,Palette,SizeOf(Palette));
Read in the header of the WAL file, and allocate memory appropriately. After allocating memory, we read in the color indices from the texture file. Remember, we still have to convert to RGB values before giving it to OpenGL.We also read in the entire palette in one go.
     if WAL_Brightness<>0 then
        for CurIndex:=0 to 255  do
        begin
             if Palette[CurIndex,0]+WAL_Brightness<255 then
                Inc(Palette[CurIndex,0],WAL_Brightness);
             if Palette[CurIndex,1]+WAL_Brightness<255 then
                Inc(Palette[CurIndex,1],WAL_Brightness);
             if Palette[CurIndex,2]+WAL_Brightness<255 then
                Inc(Palette[CurIndex,2],WAL_Brightness);
        end;
Most textures loaded directly look really dark (I mean luminance-wise ;) so we brighten up the palette by the requested amount.
     i:=0;
     for CurIndex:=0 to header.Width*header.Height do
     begin
          fTextureData.ImageData[i]:=Palette[tempImageData[CurIndex]][0];
          Inc(i);
          fTextureData.ImageData[i]:=Palette[tempImageData[CurIndex]][1];
          Inc(i);
          fTextureData.ImageData[i]:=Palette[tempImageData[CurIndex]][2];
          Inc(i);
     end;
     Close(TexFile);
     Close(PalFile);
Now, using the indices from tempImageData, we index into the palette and recover the RGB values and put them in the main ImageData array. We now have the (brightened) RGB values, and are ready to hand it over to OpenGL.
     glGenTextures(1,@fTextureData.TexID);
     glBindTexture(GL_TEXTURE_2D, fTextureData.TexID);
     glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
     glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
     glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,fTextureData.Width,
fTextureData.Height,0,GL_RGB,GL_UNSIGNED_BYTE,
fTextureData.ImageData); SetLength(fTextureData.ImageData,0); result:=true;
Now we come to the last file format our Texture class is to support, JPG's. You might this is hard, but its surprisingly similar to loading BMPs. Wathc and learn :)
function TTexture.LoadJPEG(aFilename: String):Boolean;
var
   Data : Array of LongWord;
   W, Width : Integer;
   H, Height : Integer;
   BMP : TBitmap;
   JPG : TJPEGImage;
   C : LongWord;
   Line : ^LongWord;
begin
     result :=false;
     JPG:=TJPEGImage.Create;     try
        JPG.LoadFromFile(aFilename);
     except
           exit;
     end;     // Create Bitmap
     BMP:=TBitmap.Create;
     BMP.pixelformat:=pf32bit;
     BMP.width:=JPG.width;
     BMP.height:=JPG.height;
     BMP.canvas.draw(0,0,JPG);        // Copy the JPEG onto the Bitmap     Width :=BMP.Width;
     Height :=BMP.Height;
     SetLength(Data, Width*Height);     For H:=0 to Height-1 do
     begin
          Line :=BMP.scanline[Height-H-1];   // flip JPEG
          for W:=0 to Width-1 do
          begin
               c:=Line^ and $FFFFFF; // Need to do a color swap
               Data[W+(H*Width)] :=(((c and $FF) shl 16)+(c shr 16)+(c and $FF00)) or $FF000000;  // 4 channel.
               inc(Line);
          end;
     end;     fTextureData.Width:=Width;
     fTextureData.Height:=Height;
     fTextureData.ImageData:=addr(Data[0]);     glGenTextures(1,@fTextureData.TexID);
     glBindTexture(GL_TEXTURE_2D, fTextureData.TexID);
     if not fMIPMapping then
     begin
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,MINFILTER);
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,MAGFILTER);
          glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,fTextureData.Width,fTextureData.Height,0,GL_RGBA,GL_UNSIGNED_BYTE,fTextureData.ImageData)
     end
     else
     begin
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,MINFILTER);
          glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,MAGFILTER);
          gluBuild2DMipmaps(GL_TEXTURE_2D, 3, fTextureData.Width, fTextureData.Height, GL_RGBA,GL_UNSIGNED_BYTE, fTextureData.ImageData);
     end;     BMP.free;
     JPG.free;     result :=true;
end;
That was so easy because we used a TJPEGImage to convert the image into a TBitmap, from where it was smooth sailing, since we have already gone throught the rigmarole of writing a BMP loader.
That's that. We can now relax. The main part of the work is over. The rest of the methods of TTexture are pretty much basic, so I'm not going to go into that here.What we will do, however, is to see how easy this class makes texture loading.
     //create the texture
     tex:=TTexture.Create;
     //Load the TGA
     tex.LoadTexture('fan3.TGA');
     //Set up the blend function
     tex.Transparency;
     //Enable it!!!
     tex.Enable;
Well?!!! What do you think? Is that easy or what? Next lesson will be a tut about a camera class. No more gluLookAt :)

>>Download the tutorial source
You'll also need, in case you dont have them already
>>Download the OpenGL 1.2 headers