{
  Copyright (C) 2009 Laurent Jacques

  This source is free software; you can redistribute it and/or modify it under
  the terms of the GNU General Public License as published by the Free
  Software Foundation; either version 2 of the License, or (at your option)
  any later version.

  This code is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
  details.

  A copy of the GNU General Public License is available on the World Wide Web
  at <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing
  to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
  MA 02111-1307, USA.

  Version 1.4
}
unit GifAnim;

{$mode objfpc}{$H+}

interface

uses
  Classes, LCLProc, Lresources, SysUtils, Controls, Graphics, ExtCtrls,
  IntfGraphics, FPimage, Contnrs, GraphType, dialogs, types;

const

  EXT_INTRODUCER  = $21;
  EXT_GRAPHICS_CONTROL = $F9;
  EXT_PLAIN_TEXT  = $01;
  EXT_APPLICATION = $FF;
  EXT_COMMENT     = $FE;

  DSC_LOCAL_IMAGE = $2C;

  ID_TRANSPARENT = $01;
  ID_COLOR_TABLE_SIZE = $07;
  ID_SORT    = $20;
  ID_INTERLACED = $40;
  ID_COLOR_TABLE = $80;
  ID_IMAGE_DESCRIPTOR = $2C;
  ID_TRAILER = $3B;

  CODE_TABLE_SIZE = 4096;

type
  TRGB = packed record
    Red, Green, Blue: byte;
  end;

  TGIFHeader = packed record
    Signature:    array[0..2] of char;  //* Header Signature (always "GIF") */
    Version:      array[0..2] of char;  //* GIF format version("87a" or "89a") */
    ScreenWidth:  word;                 //* Width of Display Screen in Pixels */
    ScreenHeight: word;                 //* Height of Display Screen in Pixels */
    Packedbit,                          //* Screen and Color Map Information */
    BackgroundColor,                    //* Background Color Index */
    AspectRatio:  byte;                 //* Pixel Aspect Ratio */
  end;

  TGifImageDescriptor = packed record
    Left,                 //* X position of image on the display */
    Top,                  //* Y position of image on the display */
    Width,                //* Width of the image in pixels */
    Height:    word;      //* Height of the image in pixels */
    Packedbit: byte;      //* Image and Color Table Data Information */
  end;

  TGifGraphicsControlExtension = packed record
    BlockSize,          //* Size of remaining fields (always 04h) */
    Packedbit:  byte;   //* Method of graphics disposal to use */
    DelayTime:  word;   //* Hundredths of seconds to wait  */
    ColorIndex,         //* Transparent Color Index */
    Terminator: byte;   //* Block Terminator (always 0) */
  end;

  TGifAnim = class;

  { TGifImage }

  TGifImage = class
  private
    FBitmap: TBitmap;
    FPosX:   word;
    FPosY:   word;
    FDelay:  word;
    FMethod: byte;
  public
    constructor Create;
    destructor Destroy; override;
    property Bitmap: TBitmap Read FBitmap;
    property Delay: word Read FDelay;
    property Method: byte Read FMethod;
    property PosX: word Read FPosX;
    property PosY: word Read FPosY;
  end;

  { TGifList }

  TGifList = class(TObjectList)
  private
  protected
    function GetItems(Index: integer): TGifImage;
    procedure SetItems(Index: integer; AGifImage: TGifImage);
  public
    function Add(AGifImage: TGifImage): integer;
    function Extract(Item: TGifImage): TGifImage;
    function Remove(AGifImage: TGifImage): integer;
    function IndexOf(AGifImage: TGifImage): integer;
    function First: TGifImage;
    function Last: TGifImage;
    procedure Insert(Index: integer; AGifImage: TGifImage);
    property Items[Index: integer]: TGifImage Read GetItems Write SetItems; default;
  end;


  { TGifLoader }

  TGifLoader = class
  private
    FGifHeader:  TGIFHeader;
    FGifDescriptor: TGifImageDescriptor;
    FGifGraphicsCtrlExt: TGifGraphicsControlExtension;
    FGifUseGraphCtrlExt: boolean;
    FGifBackgroundColor: byte;
    FInterlaced: boolean;
    FScanLine:   PByte;
    FLineSize:   integer;
    FDisposalMethod: byte;
    FEmpty:      boolean;
    FFileName:   string;
    FHeight:     integer;
    FIsTransparent: boolean;
    FWidth:      integer;
    FPalette:    TFPPalette;
    FLocalHeight: integer;
    FLocalWidth: integer;
    procedure ReadPalette(Stream: TStream; Size: integer);
    procedure ReadScanLine(Stream: TStream);
    procedure ReadHeader(Stream: TStream);
    procedure ReadGlobalPalette(Stream: TStream);
    procedure ReadGraphCtrlExt;
    procedure SetInterlaced(const AValue: boolean);
    procedure SetTransparent(const AValue: boolean);
    function SkipBlock(Stream: TStream): byte;
    procedure WriteScanLine(Img: TFPCustomImage);
    procedure ReadGifBitmap(Stream: TStream);
  public
    constructor Create(const FileName: string);
    destructor Destroy; override;
    function LoadAllBitmap(var AGifList: TGifList): boolean;
    function LoadFromLazarusResource(const ResName: String; var AGifList: TGifList): boolean;
    function LoadFirstBitmap(var ABitmap: TBitmap): boolean;
    property Empty: boolean Read FEmpty;
    property Height: integer Read FHeight;
    property Width: integer Read FWidth;
    property IsTransparent: boolean Read FIsTransparent Write SetTransparent;
    property Interlaced: boolean Read FInterlaced Write SetInterlaced;
  end;

  { TGifAnim }

  TGifAnim = class(TGraphicControl)
  private
    { Private declarations }
    FAnimate:   boolean;
    FEmpty:     boolean;
    FFileName:  string;
    FGifBitmaps: TGifList;
    FOnFrameChanged: TNotifyEvent;
    FOnStart:   TNotifyEvent;
    FOnStop:    TNotifyEvent;
    FWait:      TTimer;
    FCurrentImage: integer;
    FGifHeight: integer;
    FGifWidth:  integer;
    procedure OnTime(Sender: TObject);
    procedure SetAnimate(const AValue: boolean);
    procedure SetFileName(const AValue: string);
    procedure DefineSize(AWidth, AHeight: integer);
  protected
    { Protected declarations }
    BufferImg:   TBitmap;
    CurrentView: TBitmap;
    procedure CalculatePreferredSize(
      var PreferredWidth, PreferredHeight: integer;
      WithThemeSpace: boolean); override;
    procedure DoAutoSize; override;
    procedure DoStartAnim;
    procedure DoStopAnim;
    class function GetControlClassDefaultSize: TSize; override;
    procedure GifChanged;
    procedure LoadFromFile(const Filename: string); virtual;
    procedure Paint; override;
    procedure ResetImage;
    procedure SetColor(Value: TColor); override;
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure NextFrame;
    procedure PriorFrame;
    property Empty: boolean Read FEmpty;
    property GifBitmaps: TGifList Read FGifBitmaps;
    property GifIndex: integer Read FCurrentImage;
    function LoadFromLazarusResource(const ResName: String): boolean;
  published
    { Published declarations }
    property Anchors;
    property AutoSize default True;
    property Animate: boolean Read FAnimate Write SetAnimate default True;
    property BorderSpacing;
    property Color default clBtnFace;
    property Constraints;
    property FileName: string Read FFileName Write SetFileName;
    property Height;
    property OnClick;
    property OnDblClick;
    property OnFrameChanged: TNotifyEvent Read FOnFrameChanged Write FOnFrameChanged;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnStartAnim: TNotifyEvent Read FOnStart Write FOnStart;
    property OnStopAnim: TNotifyEvent Read FOnStop Write FOnStop;
    property ParentShowHint;
    property ShowHint;
    property Visible;
    property Width;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Wile64', [TGifAnim]);
end;

{ TGifAnim }

constructor TGifAnim.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := [csCaptureMouse, csClickEvents, csDoubleClicks];
  AutoSize     := True;
  SetInitialBounds(0, 0, GetControlClassDefaultSize.CX, GetControlClassDefaultSize.CY);
  FEmpty      := True;
  FCurrentImage := 0;
  CurrentView := TBitmap.Create;
  if not (csDesigning in ComponentState) then
  begin
    BufferImg := TBitmap.Create;
    FWait     := TTimer.Create(Self);
    with FWait do
    begin
      Interval := 100;
      OnTimer  := @OnTime;
      Enabled  := False;
    end;
  end;
  Animate := True;
end;

destructor TGifAnim.Destroy;
begin
  inherited Destroy;
  if assigned(FGifBitmaps) then
    FreeAndNil(FGifBitmaps);
  BufferImg.Free;
  CurrentView.Free;
end;

procedure TGifAnim.NextFrame;
begin
  if (not FEmpty) and Visible and (not FAnimate) then
  begin
    if FCurrentImage >= GifBitmaps.Count - 1 then
      FCurrentImage := 0
    else
      Inc(FCurrentImage);
    if Assigned(FOnFrameChanged) then
      FOnFrameChanged(Self);
    Repaint;
  end;
end;

procedure TGifAnim.PriorFrame;
var
  DesiredImage: Integer;
begin
  if (not FEmpty) and Visible and (not FAnimate) then
  begin
    if FCurrentImage = 0 then
      DesiredImage:= GifBitmaps.Count - 1
    else
      DesiredImage:= FCurrentImage - 1;
    // For proper display repaint image from first frame to desired frame
    FCurrentImage:= 0;
    while FCurrentImage < DesiredImage do
    begin
      with GifBitmaps.Items[FCurrentImage] do
        begin
          BufferImg.Canvas.Brush.Color := (Self.Color);
          if FCurrentImage = 0 then
            BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
          if Delay <> 0 then
            FWait.Interval := Delay * 10;
          BufferImg.Canvas.Draw(PosX, PosY, Bitmap);
          case Method of
            //0 : Not specified...
            //1 : No change Background
            2: BufferImg.Canvas.FillRect(
                Rect(PosX, PosY, Bitmap.Width + PosX, Bitmap.Height + PosY));

            3: BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
          end;
        end;
      Inc(FCurrentImage);
    end;
    if Assigned(FOnFrameChanged) then
      FOnFrameChanged(Self);
    Repaint;
  end;
end;

function TGifAnim.LoadFromLazarusResource(const ResName: String): boolean;
var
  GifLoader: TGifLoader;
  StateAnimate: boolean;
  Resource: TLResource;
begin
  Result:=false;
  StateAnimate:= Animate;
  FWait.Enabled:= false;
  ResetImage;
  Resource:=nil;
  Resource:=LazarusResources.Find(ResName);
  if Resource <> nil then
  if CompareText(LazarusResources.Find(ResName).ValueType, 'gif')=0 then begin
    GifLoader := TGifLoader.Create(Filename);
    FEmpty := not GifLoader.LoadFromLazarusResource(ResName, FGifBitmaps);
    DefineSize(GifLoader.Width, GifLoader.Height);
    GifLoader.Free;
    Result:= FEmpty;
  end;
  if not Empty then
    GifChanged;
  FWait.Enabled:= StateAnimate;
end;

procedure TGifAnim.LoadFromFile(const Filename: string);
var
  GifLoader: TGifLoader;
begin
  FEmpty    := True;
  if not FileExists(Filename) then
    Exit;
  GifLoader := TGifLoader.Create(Filename);
  if (csDesigning in ComponentState) then
    FEmpty := not GifLoader.LoadFirstBitmap(CurrentView)
  else
    FEmpty := not GifLoader.LoadAllBitmap(FGifBitmaps);
  DefineSize(GifLoader.Width, GifLoader.Height);
  GifLoader.Free;
end;

procedure TGifAnim.OnTime(Sender: TObject);
begin
  if (not Empty) and Visible then
  begin
    if FCurrentImage >= GifBitmaps.Count - 1 then
      FCurrentImage := 0
    else
      Inc(FCurrentImage);
    if Assigned(FOnFrameChanged) then
      FOnFrameChanged(Self);
    Repaint;
  end;
end;

procedure TGifAnim.SetAnimate(const AValue: boolean);
begin
  if FAnimate = AValue then
    exit;
  FAnimate := AValue;
  if not (csDesigning in ComponentState) then
  begin
    FWait.Enabled := Animate;
    if Animate then
      DoStartAnim
    else
      DoStopAnim;
  end;
end;

procedure TGifAnim.SetFileName(const AValue: string);
begin
  if (FFileName = AValue) then Exit;
  FFileName := AValue;
  ResetImage;
  if (FFileName = '') then Exit;
  LoadFromFile(FFileName);
  if not Empty then
    GifChanged;
end;

procedure TGifAnim.DefineSize(AWidth, AHeight: integer);
begin
  if (AWidth = FGifWidth) and (AHeight = FGifHeight) then
    Exit;
  FGifWidth  := AWidth;
  FGifHeight := AHeight;
  Height     := FGifHeight;
  Width      := FGifWidth;
  if not (csDesigning in ComponentState) then
  begin
    BufferImg.Height := Height;
    BufferImg.Width  := Width;
  end;
end;

procedure TGifAnim.CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer;
  WithThemeSpace: boolean);
begin
  PreferredWidth  := FGifWidth;
  PreferredHeight := FGifHeight;
end;

procedure TGifAnim.DoAutoSize;
var
  ModifyWidth, ModifyHeight: boolean;
  NewWidth:  integer;
  NewHeight: integer;
begin
  if AutoSizing then
    Exit;    // we shouldn't come here in the first place

  BeginAutoSizing;
  try
    GetPreferredSize(NewWidth, NewHeight);
    ModifyWidth  := [akLeft, akRight] * (Anchors + AnchorAlign[Align]) <> [akLeft, akRight];
    ModifyHeight := [akTop, akBottom] * (Anchors + AnchorAlign[Align]) <> [akTop, akBottom];

    if not ModifyWidth then
      NewWidth := Width;
    if not ModifyHeight then
      NewHeight := Height;

    if (NewWidth <> Width) or (NewHeight <> Height) then
    begin
      SetBounds(Left, Top, NewWidth, NewHeight);
    end;
  finally
    EndAutoSizing;
  end;
end;

class function TGifAnim.GetControlClassDefaultSize: TSize;
begin
  Result.CX := 90;
  Result.CY := 90;
end;

procedure TGifAnim.GifChanged;
begin
  if not (csDesigning in ComponentState) then
  begin
    BufferImg.Canvas.Brush.Color := (self.Color);
    BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
    with GifBitmaps.Items[FCurrentImage] do
      BufferImg.Canvas.Draw(PosX, PosY, Bitmap);
    CurrentView.Assign(BufferImg);
  end;
  InvalidatePreferredSize;
  AdjustSize;
end;

procedure TGifAnim.Paint;
begin
  if (not Empty) and Visible then
  begin
    if not (csDesigning in ComponentState) then
    begin
      if (FCurrentImage < GifBitmaps.Count) then
        with GifBitmaps.Items[FCurrentImage] do
        begin
          BufferImg.Canvas.Brush.Color := (self.Color);
          if FCurrentImage = 0 then
            BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
          if Delay <> 0 then
            FWait.Interval := Delay * 10;
          BufferImg.Canvas.Draw(PosX, PosY, Bitmap);
          CurrentView.Assign(BufferImg);
          case Method of
            //0 : Not specified...
            //1 : No change Background
            2: BufferImg.Canvas.FillRect(
                Rect(PosX, PosY, Bitmap.Width + PosX, Bitmap.Height + PosY));

            3: BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
          end;
        end;
    end
    else
    begin
      Canvas.Brush.Color := (self.Color);
      Canvas.FillRect(Rect(0, 0, Width, Height));
    end;
    Canvas.Draw(0, 0, CurrentView);
  end;
  inherited Paint;
end;

procedure TGifAnim.ResetImage;
begin
  if assigned(FGifBitmaps) then
    FreeAndNil(FGifBitmaps);
  FCurrentImage:=0;
  with CurrentView do
  begin
    Canvas.Brush.Color := (self.Color);
    Canvas.FillRect(Rect(0, 0, Width, Height));
  end;
end;

procedure TGifAnim.SetColor(Value: TColor);
begin
  inherited SetColor(Value);
end;

procedure TGifAnim.DoStartAnim;
begin
  if assigned(OnStartAnim) then
    OnStartAnim(Self);
end;

procedure TGifAnim.DoStopAnim;
begin
  if assigned(OnStopAnim) then
    OnStartAnim(Self);
end;

{ TGifLoader }

constructor TGifLoader.Create(const FileName: string);
begin
  FFileName := FileName;
  FGifUseGraphCtrlExt := False;
  FPalette  := TFPPalette.Create(0);
  FHeight   := 20;
  FWidth    := 20;
end;

destructor TGifLoader.Destroy;
begin
  inherited Destroy;
  FPalette.Free;
end;

function TGifLoader.LoadAllBitmap(var AGifList: TGifList): boolean;
var
  GifStream:  TMemoryStream;
  Introducer: byte;
  FPImage:    TLazIntfImage;
  ImgFormatDescription: TRawImageDescription;
  GifBitmap:  TGifImage;
begin
  Result := False;
  if not FileExists(FFileName) then
    exit;
  if not assigned(AGifList) then
    AGifList := TGifList.Create(True);

  GifStream := TMemoryStream.Create;
  GifStream.LoadFromFile(FFileName);
  GifStream.Position := 0;

  ReadHeader(GifStream);
  if (FGifHeader.Version <> '89a') then
    Exit;

  // skip first block extention if exist
  repeat
    Introducer := SkipBlock(GifStream);
  until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);

  repeat
    ReadGifBitmap(GifStream);
    // decode Gif bitmap in Scanline buffer
    ReadScanLine(GifStream);
    // Create temp Fp Image for put scanline pixel
    FPImage := TLazIntfImage.Create(FLocalWidth, FLocalHeight);
    ImgFormatDescription.Init_BPP32_B8G8R8A8_BIO_TTB(FLocalWidth,
      FLocalHeight);
    FPImage.DataDescription := ImgFormatDescription;

    WriteScanLine(FPImage);

    GifBitmap := TGifImage.Create;
    GifBitmap.FBitmap.LoadFromIntfImage(FPImage);
    GifBitmap.FPosX   := FGifDescriptor.Left;
    GifBitmap.FPosY   := FGifDescriptor.Top;
    GifBitmap.FMethod := FDisposalMethod;
    GifBitmap.FDelay  := FGifGraphicsCtrlExt.DelayTime;

    AGifList.Add(GifBitmap);

    FPImage.Free;
    FreeMem(FScanLine, FLineSize);
    // reset FGifUseGraphCtrlExt flag
    FGifUseGraphCtrlExt := False;

    repeat
      Introducer := SkipBlock(GifStream);
    until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);

  until (Introducer = ID_TRAILER);
  GifStream.Free;
  Result := True;
end;

function TGifLoader.LoadFromLazarusResource(const ResName: String; var AGifList: TGifList): boolean;
var
  GifStream:  TLazarusResourceStream;
  Introducer: byte;
  FPImage:    TLazIntfImage;
  ImgFormatDescription: TRawImageDescription;
  GifBitmap:  TGifImage;
begin
  Result := False;
  if not assigned(AGifList) then
    AGifList := TGifList.Create(True);

  GifStream := TLazarusResourceStream.Create(ResName, nil);
  GifStream.Position := 0;

  ReadHeader(GifStream);
  if (FGifHeader.Version <> '89a') then
    Exit;

  // skip first block extention if exist
  repeat
    Introducer := SkipBlock(GifStream);
  until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);

  repeat
    ReadGifBitmap(GifStream);
    // decode Gif bitmap in Scanline buffer
    ReadScanLine(GifStream);
    // Create temp Fp Image for put scanline pixel
    FPImage := TLazIntfImage.Create(FLocalWidth, FLocalHeight);
    ImgFormatDescription.Init_BPP32_B8G8R8A8_BIO_TTB(FLocalWidth,
      FLocalHeight);
    FPImage.DataDescription := ImgFormatDescription;

    WriteScanLine(FPImage);

    GifBitmap := TGifImage.Create;
    GifBitmap.FBitmap.LoadFromIntfImage(FPImage);
    GifBitmap.FPosX   := FGifDescriptor.Left;
    GifBitmap.FPosY   := FGifDescriptor.Top;
    GifBitmap.FMethod := FDisposalMethod;
    GifBitmap.FDelay  := FGifGraphicsCtrlExt.DelayTime;

    AGifList.Add(GifBitmap);

    FPImage.Free;
    FreeMem(FScanLine, FLineSize);
    // reset FGifUseGraphCtrlExt flag
    FGifUseGraphCtrlExt := False;

    repeat
      Introducer := SkipBlock(GifStream);
    until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);

  until (Introducer = ID_TRAILER);
  GifStream.Free;
  Result := True;
end;

function TGifLoader.LoadFirstBitmap(var ABitmap: TBitmap): boolean;
var
  GifStream:  TMemoryStream;
  Introducer: byte;
  FPImage:    TLazIntfImage;
  ImgFormatDescription: TRawImageDescription;
begin
  Result := False;
  if not FileExists(FFileName) then
    exit;
  if not assigned(ABitmap) then
    ABitmap := TBitmap.Create;

  GifStream := TMemoryStream.Create;
  GifStream.LoadFromFile(FFileName);
  GifStream.Position := 0;

  ReadHeader(GifStream);
  if (FGifHeader.Version <> '89a') then
    Exit;

  // skip first block extention if exist
  repeat
    Introducer := SkipBlock(GifStream);
  until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);

  ReadGifBitmap(GifStream);
  // decode Gif bitmap in Scanline buffer
  ReadScanLine(GifStream);
  // Create temp Fp Image for put scanline pixel
  FPImage := TLazIntfImage.Create(FLocalWidth, FLocalHeight);
  ImgFormatDescription.Init_BPP32_B8G8R8A8_BIO_TTB(FLocalWidth,
    FLocalHeight);
  FPImage.DataDescription := ImgFormatDescription;

  WriteScanLine(FPImage);

  ABitmap.LoadFromIntfImage(FPImage);
  FPImage.Free;
  FreeMem(FScanLine, FLineSize);
  // reset FGifUseGraphCtrlExt flag
  FGifUseGraphCtrlExt := False;

  GifStream.Free;
  Result := True;
end;

procedure TGifLoader.SetTransparent(const AValue: boolean);
begin
  if FIsTransparent = AValue then
    exit;
  FIsTransparent := AValue;
end;

function TGifLoader.SkipBlock(Stream: TStream): byte;
var
  Introducer, Labels, SkipByte: byte;
begin
  Introducer := 0;
  Labels     := 0;
  SkipByte   := 0;
  Stream.Read(Introducer, 1);
  if Introducer = EXT_INTRODUCER then
  begin
    Stream.Read(Labels, 1);
    case Labels of
      EXT_COMMENT,
      EXT_APPLICATION:
        while True do
        begin
          Stream.Read(SkipByte, 1);
          if SkipByte = 0 then
            Break;
          Stream.Seek(SkipByte, soFromCurrent);
        end;
      EXT_GRAPHICS_CONTROL:
      begin
        Stream.Read(FGifGraphicsCtrlExt, SizeOf(FGifGraphicsCtrlExt));
        FGifUseGraphCtrlExt := True;
      end;
      EXT_PLAIN_TEXT:
      begin
        Stream.Read(SkipByte, 1);
        Stream.Seek(SkipByte, soFromCurrent);
        while True do
        begin
          Stream.Read(SkipByte, 1);
          if SkipByte = 0 then
            Break;
          Stream.Seek(SkipByte, soFromCurrent);
        end;
      end;
    end;
  end;
  Result := Introducer;
end;

procedure TGifLoader.ReadScanLine(Stream: TStream);
var
  OldPos, UnpackedSize, PackedSize: longint;
  I:      integer;
  Data, Bits, Code: cardinal;
  SourcePtr: PByte;
  InCode: cardinal;

  CodeSize: cardinal;
  CodeMask: cardinal;
  FreeCode: cardinal;
  OldCode:  cardinal;
  Prefix:   array[0..CODE_TABLE_SIZE - 1] of cardinal;
  Suffix, Stack: array [0..CODE_TABLE_SIZE - 1] of byte;
  StackPointer: PByte;
  DataComp, Target: PByte;
  B, FInitialCodeSize, FirstChar: byte;
  ClearCode, EOICode: word;

begin
  FInitialCodeSize := 0;
  B := 0;
  DataComp := nil;

  // initialisation du dictionnaire de decompression
  Stream.Read(FInitialCodeSize, 1);

  // Recherche la taille des données compresser
  OldPos     := Stream.Position;
  PackedSize := 0;
  repeat
    Stream.Read(B, 1);
    if B > 0 then
    begin
      Inc(PackedSize, B);
      Stream.Seek(B, soFromCurrent);
    end;
  until B = 0;

  Getmem(DataComp, PackedSize);
  // lecture des données conpresser
  SourcePtr := DataComp;
  Stream.Position := OldPos;
  repeat
    Stream.Read(B, 1);
    if B > 0 then
    begin
      Stream.ReadBuffer(SourcePtr^, B);
      Inc(SourcePtr, B);
    end;
  until B = 0;

  SourcePtr := DataComp;
  Target    := FScanLine;
  CodeSize  := FInitialCodeSize + 1;
  ClearCode := 1 shl FInitialCodeSize;
  EOICode   := ClearCode + 1;
  FreeCode  := ClearCode + 2;
  OldCode   := CODE_TABLE_SIZE;
  CodeMask  := (1 shl CodeSize) - 1;
  UnpackedSize := FLocalWidth * FLocalHeight;
  for I := 0 to ClearCode - 1 do
  begin
    Prefix[I] := CODE_TABLE_SIZE;
    Suffix[I] := I;
  end;
  StackPointer := @Stack;
  FirstChar := 0;
  Data := 0;
  Bits := 0;
  //Decompression LZW gif
  while (UnpackedSize > 0) and (PackedSize > 0) do
  begin
    Inc(Data, SourcePtr^ shl Bits);
    Inc(Bits, 8);
    while Bits >= CodeSize do
    begin
      Code := Data and CodeMask;
      Data := Data shr CodeSize;
      Dec(Bits, CodeSize);
      if Code = EOICode then
        Break;
      if Code = ClearCode then
      begin
        CodeSize := FInitialCodeSize + 1;
        CodeMask := (1 shl CodeSize) - 1;
        FreeCode := ClearCode + 2;
        OldCode  := CODE_TABLE_SIZE;
        Continue;
      end;
      if Code > FreeCode then
        Break;
      if OldCode = CODE_TABLE_SIZE then
      begin
        FirstChar := Suffix[Code];
        Target^   := FirstChar;
        Inc(Target);
        Dec(UnpackedSize);
        OldCode := Code;
        Continue;
      end;
      InCode := Code;
      if Code = FreeCode then
      begin
        StackPointer^ := FirstChar;
        Inc(StackPointer);
        Code := OldCode;
      end;
      while Code > ClearCode do
      begin
        StackPointer^ := Suffix[Code];
        Inc(StackPointer);
        Code := Prefix[Code];
      end;
      FirstChar     := Suffix[Code];
      StackPointer^ := FirstChar;
      Inc(StackPointer);
      Prefix[FreeCode] := OldCode;
      Suffix[FreeCode] := FirstChar;
      if (FreeCode = CodeMask) and (CodeSize < 12) then
      begin
        Inc(CodeSize);
        CodeMask := (1 shl CodeSize) - 1;
      end;
      if FreeCode < CODE_TABLE_SIZE - 1 then
        Inc(FreeCode);
      OldCode := InCode;
      repeat
        Dec(StackPointer);
        Target^ := StackPointer^;
        Inc(Target);
        Dec(UnpackedSize);
      until StackPointer = @Stack;
    end;
    Inc(SourcePtr);
    Dec(PackedSize);
  end;
  FreeMem(DataComp);
end;

procedure TGifLoader.ReadHeader(Stream: TStream);
begin
  Stream.Read(FGifHeader, SizeOf(FGifHeader));

  with FGifHeader do
  begin
    FGifBackgroundColor := BackgroundColor;

    FWidth  := ScreenWidth;
    FHeight := ScreenHeight;

    FLocalWidth  := ScreenWidth;
    FLocalHeight := ScreenHeight;

    IsTransparent := False;
  end;
  ReadGlobalPalette(Stream);
end;

procedure TGifLoader.ReadGlobalPalette(Stream: TStream);
var
  ColorTableSize: integer;
begin
  if (FGifHeader.Packedbit and ID_COLOR_TABLE) <> 0 then
  begin
    ColorTableSize := FGifHeader.Packedbit and ID_COLOR_TABLE_SIZE + 1;
    ReadPalette(Stream, 1 shl ColorTableSize);
  end;
end;

procedure TGifLoader.ReadGraphCtrlExt;
var
  C: TFPColor;
begin
  IsTransparent := (FGifGraphicsCtrlExt.Packedbit and ID_TRANSPARENT) <> 0;

  FDisposalMethod := (FGifGraphicsCtrlExt.Packedbit and $1C) shr 2;

  if IsTransparent then
  begin
    // if Transparent bitmap change alpha channel
    FGifBackgroundColor := FGifGraphicsCtrlExt.ColorIndex;
    C := FPalette[FGifBackgroundColor];
    C.alpha := alphaTransparent;
    FPalette[FGifBackgroundColor] := C;
  end;
end;

procedure TGifLoader.SetInterlaced(const AValue: boolean);
begin
  if FInterlaced = AValue then
    exit;
  FInterlaced := AValue;
end;

procedure TGifLoader.ReadPalette(Stream: TStream; Size: integer);
var
  RGBEntry: TRGB;
  I: integer;
  C: TFPColor;
begin
  FPalette.Clear;
  FPalette.Count := 0;
  Fillchar(RGBEntry, SizeOf(RGBEntry), 0);
  for I := 0 to Size - 1 do
  begin
    Stream.Read(RGBEntry, SizeOf(RGBEntry));
    with C do
    begin
      Red   := RGBEntry.Red or (RGBEntry.Red shl 8);
      Green := RGBEntry.Green or (RGBEntry.Green shl 8);
      Blue  := RGBEntry.Blue or (RGBEntry.Blue shl 8);
      Alpha := alphaOpaque;
    end;
    FPalette.Add(C);
  end;
end;

procedure TGifLoader.WriteScanLine(Img: TFPCustomImage);
var
  Row, Col: integer;
  Pass, Every: byte;
  P: PByte;
begin
  P := FScanLine;
  if Interlaced then
  begin
    for Pass := 1 to 4 do
    begin
      case Pass of
        1:
        begin
          Row   := 0;
          Every := 8;
        end;
        2:
        begin
          Row   := 4;
          Every := 8;
        end;
        3:
        begin
          Row   := 2;
          Every := 4;
        end;
        4:
        begin
          Row   := 1;
          Every := 2;
        end;
      end;
      repeat
        for Col := 0 to FLocalWidth - 1 do
        begin
          Img.Colors[Col, Row] := FPalette[P^];
          Inc(P);
        end;
        Inc(Row, Every);
      until Row >= FLocalHeight;
    end;
  end
  else
  begin
    for Row := 0 to FLocalHeight - 1 do
      for Col := 0 to FLocalWidth - 1 do
      begin
        Img.Colors[Col, Row] := FPalette[P^];
        Inc(P);
      end;
  end;
end;

procedure TGifLoader.ReadGifBitmap(Stream: TStream);
var
  ColorTableSize: integer;
begin
  Stream.Read(FGifDescriptor, SizeOf(FGifDescriptor));

  with FGifDescriptor do
  begin
    FLocalWidth  := Width;
    FLocalHeight := Height;
    Interlaced   := (Packedbit and ID_INTERLACED = ID_INTERLACED);
  end;

  FLineSize := FLocalWidth * (FLocalHeight + 1);
  GetMem(FScanLine, FLineSize);

  if (FGifDescriptor.Packedbit and ID_COLOR_TABLE) <> 0 then
  begin
    ColorTableSize := FGifDescriptor.Packedbit and ID_COLOR_TABLE_SIZE + 1;
    ReadPalette(Stream, 1 shl ColorTableSize);
  end;
  if FGifUseGraphCtrlExt then
    ReadGraphCtrlExt;

end;

{ TGifImage }

constructor TGifImage.Create;
begin
  FBitmap := TBitmap.Create;
  FPosX   := 0;
  FPosY   := 0;
  FDelay  := 0;
  FMethod := 0;
end;

destructor TGifImage.Destroy;
begin
  inherited Destroy;
  FBitmap.Free;
end;

{ TGifList }

function TGifList.GetItems(Index: integer): TGifImage;
begin
  Result := TGifImage(inherited Items[Index]);
end;

procedure TGifList.SetItems(Index: integer; AGifImage: TGifImage);
begin
  Put(Index, AGifImage);
end;

function TGifList.Add(AGifImage: TGifImage): integer;
begin
  Result := inherited Add(AGifImage);
end;

function TGifList.Extract(Item: TGifImage): TGifImage;
begin
  Result := TGifImage(inherited Extract(Item));
end;

function TGifList.Remove(AGifImage: TGifImage): integer;
begin
  Result := inherited Remove(AGifImage);
end;

function TGifList.IndexOf(AGifImage: TGifImage): integer;
begin
  Result := inherited IndexOf(AGifImage);
end;

function TGifList.First: TGifImage;
begin
  Result := TGifImage(inherited First);
end;

function TGifList.Last: TGifImage;
begin
  Result := TGifImage(inherited Last);
end;

procedure TGifList.Insert(Index: integer; AGifImage: TGifImage);
begin
  inherited Insert(Index, AGifImage);
end;

initialization
{$I gifanim.lrs}

end.
