unit e_textures;

{ This unit provides interface to load 24-bit and 32-bit uncompressed images
  from Truevision Targa (TGA) graphic files, and create OpenGL textures
  from it's data. }

interface

uses
  Windows, dglOpenGL, SysUtils, e_log;

var
  fUseMipmaps: Boolean = False;
  TEXTUREFILTER: Integer = GL_NEAREST;

// Standard set of images loading functions
function LoadTexture( Filename: String; var Texture: GLuint;
                      var pWidth, pHeight: Word ): Boolean;

function LoadTextureEx( Filename: String; var Texture: GLuint;
                        fX, fY, fWidth, fHeight: Word ): Boolean;

function LoadTextureMem( pData: Pointer; var Texture: GLuint;
                         var pWidth, pHeight: Word ): Boolean;

function LoadTextureMemEx( pData: Pointer; var Texture: GLuint;
                           fX, fY, fWidth, fHeight: Word ): Boolean;

implementation

type
  TTGAHeader = packed record
    FileType:     Byte;
    ColorMapType: Byte;
    ImageType:    Byte;
    ColorMapSpec: array[0..4] of Byte;
    OrigX:        array[0..1] of Byte;
    OrigY:        array[0..1] of Byte;
    Width:        array[0..1] of Byte;
    Height:       array[0..1] of Byte;
    BPP:          Byte;
    ImageInfo:    Byte;
  end;

// This is auxiliary function that creates OpenGL texture from raw image data
function CreateTexture( Width, Height, Format: Word; pData: Pointer ): Integer;
var
  Texture: GLuint;
begin
  glGenTextures( 1, @Texture );
  glBindTexture( GL_TEXTURE_2D, Texture );

    {Texture blends with object background}
  glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
    {Texture does NOT blend with object background}
 // glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

  {
    Select a filtering type.
    BiLinear filtering produces very good results with little performance impact

    GL_NEAREST               - Basic texture (grainy looking texture)
    GL_LINEAR                - BiLinear filtering
    GL_LINEAR_MIPMAP_NEAREST - Basic mipmapped texture
    GL_LINEAR_MIPMAP_LINEAR  - BiLinear Mipmapped texture
  }

  // for GL_TEXTURE_MAG_FILTER only first two can be used
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, TEXTUREFILTER );
  // for GL_TEXTURE_MIN_FILTER all of the above can be used
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, TEXTUREFILTER );

  if Format = GL_RGBA then
  begin
    if fUseMipmaps then
      gluBuild2DMipmaps( GL_TEXTURE_2D, GL_RGBA, Width, Height, GL_RGBA,
                         GL_UNSIGNED_BYTE, pData )
    else
      glTexImage2D( GL_TEXTURE_2D, 0, 4, Width, Height,
                    0, GL_RGBA, GL_UNSIGNED_BYTE, pData );
  end else
  begin
    if fUseMipmaps then
      gluBuild2DMipmaps( GL_TEXTURE_2D, 3, Width, Height, GL_RGB,
                         GL_UNSIGNED_BYTE, pData )
    else
      glTexImage2D( GL_TEXTURE_2D, 0, 3, Width, Height,
                    0, GL_RGB, GL_UNSIGNED_BYTE, pData );
  end;

  Result := Texture;
end;

function LoadTextureMem( pData: Pointer; var Texture: GLuint;
                         var pWidth, pHeight: Word ): Boolean;
var
  TGAHeader:     TTGAHeader;
  image:         Pointer;
  Width, Height: Integer;
  ImageSize:     Integer;
  i:             Integer;
  Front:         ^Byte;
  Back:          ^Byte;
  Temp:          Byte;
  BPP:           Byte;

begin
  Result := False;
  pWidth := 0;
  pHeight := 0;

  CopyMemory( @TGAHeader, pData, SizeOf(TGAHeader) );

  if ( TGAHeader.ImageType <> 2 ) then
  begin
    e_WriteLog( 'Error loading texture: Bad ImageType', MSG_WARNING );
    Exit;
  end;

  if ( TGAHeader.ColorMapType <> 0 ) then
  begin
    e_WriteLog( 'Error loading texture: Bad ColorMapType', MSG_WARNING );
    Exit;
  end;

  if ( TGAHeader.BPP < 24 ) then
  begin
    e_WriteLog( 'Error loading texture: BPP less than 24', MSG_WARNING );
    Exit;
  end;

  Width  := TGAHeader.Width[0]  + TGAHeader.Width[1]  * 256;
  Height := TGAHeader.Height[0] + TGAHeader.Height[1] * 256;
  BPP := TGAHeader.BPP;

  ImageSize := Width * Height * (BPP div 8);

  GetMem( Image, ImageSize );
  CopyMemory( Image, Pointer( Integer(pData) + SizeOf(TGAHeader) ), ImageSize );

  for i := 0 to Width * Height - 1 do
  begin
    Front := Pointer( Integer(Image) + i*(BPP div 8) );
    Back  := Pointer( Integer(Image) + i*(BPP div 8) + 2 );
    Temp   := Front^;
    Front^ := Back^;
    Back^  := Temp;
  end;

  if ( BPP = 24 ) then
    Texture := CreateTexture( Width, Height, GL_RGB, Image )
  else
    Texture := CreateTexture( Width, Height, GL_RGBA, Image );

  FreeMem( Image );

  pWidth := Width;
  pHeight := Height;

  Result := True;
end;

function LoadTextureMemEx( pData: Pointer; var Texture: GLuint;
                           fX, fY, fWidth, fHeight: Word ): Boolean;
var
  TGAHeader:     TTGAHeader;
  image, image2: Pointer;
  Width, Height: Integer;
  ImageSize:     Integer;
  i, a, b:       Integer;
  Front:         ^Byte;
  Back:          ^Byte;
  Temp:          Byte;
  BPP:           Byte;
  Base:          Integer;

begin
  Result := False;

  CopyMemory( @TGAHeader, pData, SizeOf(TGAHeader) );

  if ( TGAHeader.ImageType <> 2 ) then
  begin
    e_WriteLog( 'Error loading texture: Bad ImageType', MSG_WARNING );
    Exit;
  end;

  if ( TGAHeader.ColorMapType <> 0 ) then
  begin
    e_WriteLog( 'Error loading texture: Bad ColorMapType', MSG_WARNING );
    Exit;
  end;

  if ( TGAHeader.BPP < 24 ) then
  begin
    e_WriteLog( 'Error loading texture: BPP less than 24', MSG_WARNING );
    Exit;
  end;

  Width  := TGAHeader.Width[0]  + TGAHeader.Width[1]  * 256;
  Height := TGAHeader.Height[0] + TGAHeader.Height[1] * 256;
  BPP := TGAHeader.BPP;

  if fX > Width then Exit;
  if fY > Height then Exit;
  if fX+fWidth > Width then Exit;
  if fY+fHeight > Height then Exit;

  ImageSize := Width * Height * (BPP div 8);
  GetMem( Image2, ImageSize );
  CopyMemory( Image2, Pointer( Integer(pData) + SizeOf(TGAHeader) ), ImageSize );

  a := BPP div 8;

  for i := 0 to Width * Height - 1 do
  begin
    Front := Pointer( Integer(Image2) + i * a );
    Back  := Pointer( Integer(Image2) + i * a + 2 );
    Temp   := Front^;
    Front^ := Back^;
    Back^  := Temp;
  end;

  fY := Height - (fY + fHeight);

  ImageSize := fHeight * fWidth * (BPP div 8);
  GetMem( Image, ImageSize );

  Base := Integer( Image2 ) + fY * Width * (BPP div 8) + fX * (BPP div 8);
  a := fWidth * (BPP div 8);
  b := Width * (BPP div 8);

  for i := 0 to fHeight-1 do
    CopyMemory( Pointer( Integer(image) + a*i ), Pointer( Base + b*i ), a );

  if ( BPP = 24 ) then
    Texture := CreateTexture( fWidth, fHeight, GL_RGB, image )
  else
    Texture := CreateTexture( fWidth, fHeight, GL_RGBA, image );

  FreeMem( Image );
  FreeMem( Image2 );

  Result := True;
end;

function LoadTexture( Filename: String; var Texture: GLuint;
                      var pWidth, pHeight: Word ): Boolean;
var
  TGAHeader:     TTGAHeader;
  TGAFile:       File;
  bytesRead:     Integer;
  image:         Pointer;
  Width, Height: Integer;
  ImageSize:     Integer;
  i:             Integer;
  Front:         ^Byte;
  Back:          ^Byte;
  Temp:          Byte;
  BPP:           Byte;

begin
  Result := False;
  pWidth := 0;
  pHeight := 0;

  if not FileExists(Filename) then
  begin
    e_WriteLog('Texture ' + Filename + ' not found', MSG_WARNING);
    Exit;
  end;

  AssignFile( TGAFile, Filename );
  Reset( TGAFile, 1 );
  BlockRead( TGAFile, TGAHeader, SizeOf(TGAHeader) );

  if ( TGAHeader.ImageType <> 2 ) then
  begin
    CloseFile( TGAFile );
    e_WriteLog( 'Error loading texture: Bad ImageType', MSG_WARNING );
    Exit;
  end;

  if ( TGAHeader.ColorMapType <> 0 ) then
  begin
    CloseFile( TGAFile );
    e_WriteLog( 'Error loading texture: Bad ColorMapType', MSG_WARNING );
    Exit;
  end;

  if ( TGAHeader.BPP < 24 ) then
  begin
    CloseFile( TGAFile );
    e_WriteLog( 'Error loading texture: BPP less than 24', MSG_WARNING );
    Exit;
  end;

  Width  := TGAHeader.Width[0]  + TGAHeader.Width[1]  * 256;
  Height := TGAHeader.Height[0] + TGAHeader.Height[1] * 256;
  BPP := TGAHeader.BPP;

  ImageSize := Width * Height * (BPP div 8);

  GetMem( Image, ImageSize );

  BlockRead( TGAFile, image^, ImageSize, bytesRead );
  if ( bytesRead <> ImageSize ) then
  begin
    CloseFile( TGAFile );
    Exit;
  end;

  CloseFile( TGAFile );

  for i := 0 to Width * Height - 1 do
  begin
    Front := Pointer( Integer(Image) + i * (BPP div 8) );
    Back  := Pointer( Integer(Image) + i * (BPP div 8) + 2 );
    Temp   := Front^;
    Front^ := Back^;
    Back^  := Temp;
  end;

  if ( BPP = 24 ) then
    Texture := CreateTexture( Width, Height, GL_RGB, Image )
  else
    Texture := CreateTexture( Width, Height, GL_RGBA, Image );

  FreeMem( Image );

  pWidth := Width;
  pHeight := Height;

  Result := True;
end;

function LoadTextureEx( Filename: String; var Texture: GLuint;
                        fX, fY, fWidth, fHeight: Word ): Boolean;
var
  TGAHeader:     TTGAHeader;
  TGAFile:       File;
  image, image2: Pointer;
  Width, Height: Integer;
  ImageSize:     Integer;
  i:             Integer;
  Front:         ^Byte;
  Back:          ^Byte;
  Temp:          Byte;
  BPP:           Byte;
  Base:          Integer;
  
begin
  Result := False;

  if not FileExists(Filename) then
  begin
    e_WriteLog( 'Texture ' + Filename + ' not found', MSG_WARNING );
    Exit;
  end;

  AssignFile( TGAFile, Filename );
  Reset( TGAFile, 1 );
  BlockRead( TGAFile, TGAHeader, SizeOf(TGAHeader) );

  if ( TGAHeader.ImageType <> 2 ) then
  begin
    CloseFile( TGAFile );
    e_WriteLog( 'Error loading texture: Bad ImageType', MSG_WARNING );
    Exit;
  end;

  if ( TGAHeader.ColorMapType <> 0 ) then
  begin
    CloseFile( TGAFile );
    e_WriteLog( 'Error loading texture: Bad ColorMapType', MSG_WARNING );
    Exit;
  end;

  if ( TGAHeader.BPP < 24 ) then
  begin
    CloseFile( TGAFile );
    e_WriteLog( 'Error loading texture: BPP less than 24', MSG_WARNING );
    Exit;
  end;

  Width  := TGAHeader.Width[0]  + TGAHeader.Width[1]  * 256;
  Height := TGAHeader.Height[0] + TGAHeader.Height[1] * 256;
  BPP := TGAHeader.BPP;

  if fX > Width then Exit;
  if fY > Height then Exit;
  if fX+fWidth > Width then Exit;
  if fY+fHeight > Height then Exit;

  ImageSize := Width * Height * (BPP div 8);
  GetMem( Image2, ImageSize );
  BlockRead( TGAFile, Image2^, ImageSize );

  CloseFile( TGAFile );

  for i := 0 to Width * Height - 1 do
  begin
    Front := Pointer( Integer(Image2) + i * (BPP div 8) );
    Back  := Pointer( Integer(Image2) + i * (BPP div 8) + 2 );
    Temp   := Front^;
    Front^ := Back^;
    Back^  := Temp;
  end;

  fY := Height - (fY + fHeight);

  ImageSize := fHeight * fWidth * (BPP div 8);
  GetMem( Image, ImageSize );

  Base := Integer(Image2) + fY * Width * (BPP div 8) + fX * (BPP div 8);

  for i := 0 to fHeight-1 do
  begin
    CopyMemory( Pointer( Integer(image) + fWidth * (BPP div 8) * i ),
                Pointer( Base + Width * (BPP div 8) * i), fWidth * (BPP div 8) );
  end;                                                

  if ( BPP = 24 ) then
    Texture := CreateTexture( fWidth, fHeight, GL_RGB, Image )
  else
    Texture := CreateTexture( fWidth, fHeight, GL_RGBA, Image );

  FreeMem( Image );
  FreeMem( Image2 );

  Result := True;
end;

end.

