{$MODE OBJFPC}
{$MODESWITCH AUTODEREF}
unit dtga_reader;

interface

uses
  dtypes,
  dstrnum,
  dcolors,
  dstream,
  dstream_op;

const
  TGA_IMAGE_TYPE_NO                      = 0;  // No image data included
  TGA_IMAGE_TYPE_COLOR_MAPPED            = 1;  // Uncompressed color-mapped
  TGA_IMAGE_TYPE_RGB                     = 2;  // Uncompressed RGB
  TGA_IMAGE_TYPE_BLACK_WHITE             = 3;  // Uncompressed black and white
  TGA_IMAGE_TYPE_RUNLENGTH_COLOR_MAPPED  = 9;
  TGA_IMAGE_TYPE_RUNLENGTH_RGB           = 10;
  TGA_IMAGE_TYPE_COMPRESSED_BLACK_WHITE  = 11;
  TGA_IMAGE_TYPE_COMPRESSED_COLOR_MAPPED = 32; // Huffman, Delta, and runlength
  TGA_IMAGE_TYPE_QUADTREE_COLOR_MAPPED   = 33;

  TGA_COLOR_MAP_NO                       = 0;
  TGA_COLOR_MAP_INCLUDED                 = 1;

  // for Color-mapped
  TGA_IMAGE_DESC_ATTRIBUTE_MASK          = 15;
  TGA_IMAGE_DESC_SCREEN_ORIGIN_MASK      = 1 shl 5;
  TGA_IMAGE_DESC_INTERLEAVING_MASK       = 3 shl 6;
  TGA_IMAGE_DESC_INTERLEAVING_NO         = 0;
  TGA_IMAGE_DESC_INTERLEAVING_TWO        = 1; // even/odd
  TGA_IMAGE_DESC_INTERLEAVING_FOUR       = 2;

  TGA_RLE_MASK                           = 128;

type
PTgaHeader = ^TTgaHeader;
TTgaHeader = packed record
  IDLength:             UInt8;
  ColorMapType:         UInt8;
  ImageType:            UInt8;  // aka DataTypeCode
  ColorMapIndex:        UInt16; // aka ColourMapOrigin
  ColorMapLength:       UInt16;
  ColorMapEntrySize:    UInt8;  // aka ColourMapDepth
  Xorigin:              UInt16;
  Yorigin:              UInt16;
  Width:                UInt16;
  Height:               UInt16;
  PixelDepth:           UInt8;  // BitsPerPixel, 16, 24 or 32
  ImageDesc:            UInt8;
end;

PTgaReader = ^TTgaReader;
TTgaReader = object
private
  FData: TData;
  FStream: PStream;
  FHeader: TTgaHeader;
  FValid: Boolean;
public
  procedure Init(const Data: TData);
  procedure Reinit(const Data: TData); inline;
  procedure Done; inline;
  function GetHeader: PTgaHeader; inline;
  function Width: LongInt; inline;
  function Height: LongInt; inline;
  function SkipImageIdentificationField: Boolean; inline;
  function NeedVerticalSwap: Boolean;
  property Stream: PStream read FStream;
  property Header: PTgaHeader read GetHeader;
  property Valid: Boolean read FValid;
end;

implementation

procedure TTgaReader.Init(const Data: TData);
begin
  FData := Data;
  FStream := FData.Stream;
  FValid := FStream.Read(@FHeader, SizeOf(FHeader)) = SizeOf(FHeader);
  if FValid then
    with FHeader do
      FValid := FValid and (ColorMapType < 2) and
          ((ImageType = 0) or (ImageType = 1) or (ImageType = 2) or
           (ImageType = 3) or (ImageType = 9) or (ImageType = 10) or
           (ImageType = 11)) and
          ((PixelDepth = 32) or (PixelDepth = 24));
end;

procedure TTgaReader.Reinit(const Data: TData);
begin
  Done;
  Init(Data);
end;

procedure TTgaReader.Done;
begin
  FData.Done;
end;

function TTgaReader.GetHeader: PTgaHeader;
begin
  Result := @FHeader;
end;

function TTgaReader.Width: LongInt;
begin
  Result := FHeader.Width;
end;

function TTgaReader.Height: LongInt;
begin
  Result := FHeader.Height;
end;

function TTgaReader.SkipImageIdentificationField: Boolean;
begin
  // TODO return success of Skip method from Stream
  FStream.Skip(FHeader.IDLength);
  Result := True;
end;

function TTgaReader.NeedVerticalSwap: Boolean;
begin
  Result := FHeader.ImageDesc and TGA_IMAGE_DESC_SCREEN_ORIGIN_MASK = 0;
end;

end.
