// SPDX-License-Identifier: LGPL-3.0-linking-exception
{
  Created by BGRA Controls Team
  Dibo, Circular, lainz (007) and contributors.
  For detailed information see readme.txt

  Site: https://sourceforge.net/p/bgra-controls/
  Wiki: http://wiki.lazarus.freepascal.org/BGRAControls
  Forum: http://forum.lazarus.freepascal.org/index.php/board,46.0.html
}
{******************************* CONTRIBUTOR(S) ******************************
- Edivando S. Santos Brasil | mailedivando@gmail.com
  (Compatibility with delphi VCL 11/2018)
- FreeMan35

***************************** END CONTRIBUTOR(S) *****************************}
unit BGRASpriteAnimation;

{$I bgracontrols.inc}

interface

uses
  Classes, Controls, Dialogs, ExtCtrls, Forms, {$IFDEF FPC}LCLIntF, LResources,{$ENDIF} Graphics,
  {$IFNDEF FPC}Types, BGRAGraphics, GraphType, FPImage, {$ENDIF}
  BCBaseCtrls, BGRABitmap, BGRABitmapTypes, BCTypes, BGRAAnimatedGif;

type
  TBGRASpriteAnimation = class;

  { TSpriteBitmap }

  TSpriteBitmap = class(TBitmap)
  private
    FOwner: TBGRASpriteAnimation;
  protected
    procedure AssignTo(Dest: TPersistent); override;
  public
    constructor Create(AOwner: TBGRASpriteAnimation); overload;
    procedure Assign(Source: TPersistent); override;
  end;

  TFlipMode = (flNone, flHorizontal, flVertical, flBoth);
  TRotationMode = (rtNone, rtClockWise, rtCounterClockWise, rt180);

  { TBGRASpriteAnimation }

  TBGRASpriteAnimation = class(TBGRAGraphicCtrl)
  private
    { Private declarations }
    FAnimInvert: boolean;
    FAnimPosition: cardinal;
    FAnimRepeat: cardinal;
    FAnimRepeatLap: cardinal;
    FAnimSpeed: cardinal;
    FAnimStatic: boolean;
    FAnimTimer: TTimer;
    FCenter: boolean;
    FOnLapChanged: TNotifyEvent;
    FOnLapChanging: TNotifyEvent;
    FOnPositionChanged: TNotifyEvent;
    FOnPositionChanging: TNotifyEvent;
    FOnRedrawAfter: TBGRARedrawEvent;
    FOnRedrawBefore: TBGRARedrawEvent;
    FProportional: boolean;
    FSprite: TBitmap;
    FSpriteCount: cardinal;
    FSpriteFillOpacity: byte;
    FSpriteFlipMode: TFlipMode;
    FSpriteKeyColor: TColor;
    FSpriteResampleFilter: TResampleFilter;
    FSpriteResampleMode: TResampleMode;
    FSpriteRotation: TRotationMode;
    FStretch: boolean;
    FTile: boolean;
    function DoCalculateDestRect(AWidth, AHeight: integer): TRect;
    function DoCalculatePosition(AValue: integer): integer;
    function DoCalculateSize(AValue: cardinal): cardinal;
    procedure DoAnimTimerOnTimer({%H-}Sender: TObject);
    procedure DoSpriteDraw(ABitmap: TBGRABitmap);
    procedure DoSpriteFillOpacity(ABitmap: TBGRABitmap);
    procedure DoSpriteFlip(ABitmap: TBGRABitmap);
    procedure DoSpriteKeyColor(ABitmap: TBGRABitmap);
    procedure DoSpriteResampleFilter(ABitmap: TBGRABitmap);
    procedure SetFAnimInvert(const AValue: boolean);
    procedure SetFAnimPosition(const AValue: cardinal);
    procedure SetFAnimRepeat(const AValue: cardinal);
    procedure SetFAnimRepeatLap(const AValue: cardinal);
    procedure SetFAnimSpeed(const AValue: cardinal);
    procedure SetFAnimStatic(const AValue: boolean);
    procedure SetFCenter(const AValue: boolean);
    procedure SetFProportional(const AValue: boolean);
    procedure SetFSprite(const AValue: TBitmap);
    procedure SetFSpriteCount(const AValue: cardinal);
    procedure SetFSpriteFillOpacity(const AValue: byte);
    procedure SetFSpriteFlipMode(const AValue: TFlipMode);
    procedure SetFSpriteKeyColor(const AValue: TColor);
    procedure SetFSpriteResampleFilter(const AValue: TResampleFilter);
    procedure SetFSpriteResampleMode(const AValue: TResampleMode);
    procedure SetFSpriteRotation(const AValue: TRotationMode);
    procedure SetFStretch(const AValue: boolean);
    procedure SetFTile(const AValue: boolean);
    procedure SpriteChange(Sender: TObject);
  protected
    { Protected declarations }
    procedure Paint; override;
    procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer;
      {%H-}WithThemeSpace: Boolean); override;
  public
    { Public declarations }
    procedure GifImageToSprite(Gif: TBGRAAnimatedGif);
    procedure SpriteToGifImage(Gif: TBGRAAnimatedGif);
    procedure LoadFromResourceName(Instance: THandle; const ResName: string); overload;
    procedure LoadFromBitmapResource(const Resource: string); overload;
    {$IF BGRABitmapVersion > 11030100}
    procedure LoadFromBitmapStream(AStream: TStream);
    {$ENDIF}
    procedure LoadFromBGRABitmap(const BGRA: TBGRABitmap);
    procedure SpriteToAnimatedGif(Filename: string);
    procedure AnimatedGifToSprite(Filename: string);
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    { Published declarations }
    property AnimInvert: boolean read FAnimInvert write SetFAnimInvert;
    property AnimPosition: cardinal read FAnimPosition write SetFAnimPosition;
    property AnimRepeat: cardinal read FAnimRepeat write SetFAnimRepeat;
    property AnimRepeatLap: cardinal read FAnimRepeatLap write SetFAnimRepeatLap;
    property AnimSpeed: cardinal read FAnimSpeed write SetFAnimSpeed;
    property AnimStatic: boolean read FAnimStatic write SetFAnimStatic;
    property Center: boolean read FCenter write SetFCenter;
    property Proportional: boolean read FProportional write SetFProportional;
    property Sprite: TBitmap read FSprite write SetFSprite;
    property SpriteCount: cardinal read FSpriteCount write SetFSpriteCount;
    property SpriteFillOpacity: byte read FSpriteFillOpacity write SetFSpriteFillOpacity;
    property SpriteFlipMode: TFlipMode read FSpriteFlipMode write SetFSpriteFlipMode;
    property SpriteKeyColor: TColor read FSpriteKeyColor write SetFSpriteKeyColor;
    property SpriteResampleFilter: TResampleFilter
      read FSpriteResampleFilter write SetFSpriteResampleFilter;
    property SpriteResampleMode: TResampleMode
      read FSpriteResampleMode write SetFSpriteResampleMode;
    property SpriteRotation: TRotationMode read FSpriteRotation write SetFSpriteRotation;
    property Stretch: boolean read FStretch write SetFStretch;
    property Tile: boolean read FTile write SetFTile;
  published
    property Align;
    property Anchors;
    property AutoSize;
    property Caption;
    property Color;
    property Enabled;
    property OnClick;
    property OnDblClick;
    property OnLapChanged: TNotifyEvent read FOnLapChanged write FOnLapChanged;
    property OnLapChanging: TNotifyEvent read FOnLapChanging write FOnLapChanging;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnPositionChanged: TNotifyEvent
      read FOnPositionChanged write FOnPositionChanged;
    property OnPositionChanging: TNotifyEvent
      read FOnPositionChanging write FOnPositionChanging;
    property OnRedrawAfter: TBGRARedrawEvent read FOnRedrawAfter write FOnRedrawAfter;
    property OnRedrawBefore: TBGRARedrawEvent read FOnRedrawBefore write FOnRedrawBefore;
    property PopupMenu;
    property Visible;
  end;

{$IFDEF FPC}procedure Register;{$ENDIF}

implementation

{$IFDEF FPC}
procedure Register;
begin
  //{$I icons\bgraspriteanimation_icon.lrs}
  RegisterComponents('BGRA Controls', [TBGRASpriteAnimation]);
end;

{ TSpriteBitmap }

procedure TSpriteBitmap.AssignTo(Dest: TPersistent);
begin
  if Dest is TBGRAAnimatedGif then
    FOwner.SpriteToGifImage(TBGRAAnimatedGif(Dest));
  inherited AssignTo(Dest);
end;

constructor TSpriteBitmap.Create(AOwner: TBGRASpriteAnimation);
begin
  inherited Create;
  FOwner := AOwner;
end;

procedure TSpriteBitmap.Assign(Source: TPersistent);
begin
  if Source is TBGRAAnimatedGif then
    FOwner.GifImageToSprite(TBGRAAnimatedGif(Source))
  else
    inherited Assign(Source);
end;

{$ENDIF}

{ TBGRASpriteAnimation }

{ Animation Variables }

procedure TBGRASpriteAnimation.SetFAnimInvert(const AValue: boolean);
begin
  if FAnimInvert = AValue then
    Exit;
  FAnimInvert := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFAnimPosition(const AValue: cardinal);
begin
  if FAnimPosition = AValue then
    Exit;
  if (AValue < 1) or (AValue > FSpriteCount) then
    FAnimPosition := 1
  else
    FAnimPosition := AValue;

  if Assigned(FOnPositionChanged) then
    FOnPositionChanged(Self);

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFAnimRepeat(const AValue: cardinal);
begin
  if FAnimRepeat = AValue then
    Exit;
  FAnimRepeat := AValue;
end;

procedure TBGRASpriteAnimation.SetFAnimRepeatLap(const AValue: cardinal);
begin
  if (FAnimRepeatLap = AValue) then
    Exit;
  FAnimRepeatLap := AValue;

  if (AValue = FAnimRepeat) and (AValue <> 0) then
  begin
    if csDesigning in ComponentState then
      Exit;
    SetFAnimStatic(True);
  end;

  if Assigned(FOnLapChanged) then
    FOnLapChanged(Self);
end;

procedure TBGRASpriteAnimation.SetFAnimSpeed(const AValue: cardinal);
begin
  if FAnimSpeed = AValue then
    Exit;
  FAnimSpeed := AValue;
  FAnimTimer.Interval := AValue;
end;

procedure TBGRASpriteAnimation.SetFAnimStatic(const AValue: boolean);
begin
  if FAnimStatic = AValue then
    Exit;
  FAnimStatic := AValue;

  if csDesigning in ComponentState then
    Exit;
  FAnimTimer.Enabled := not AValue;
end;

{ Sprite Variables }

procedure TBGRASpriteAnimation.SetFSprite(const AValue: TBitmap);
begin
  if (FSprite = AValue) or (AValue = nil) then
    Exit;

  FSprite.Assign(AValue);
end;

procedure TBGRASpriteAnimation.SetFSpriteCount(const AValue: cardinal);
begin
  if (FSpriteCount = AValue) or (FSprite = nil) then
    Exit;

  if (AValue < 1) or (AValue > cardinal(FSprite.Width)) then
    FSpriteCount := 1
  else
    FSpriteCount := AValue;

  if AnimPosition > AValue then
    SetFAnimPosition(1);

  Invalidate;
  InvalidatePreferredSize;
  AdjustSize;
end;

procedure TBGRASpriteAnimation.SetFSpriteFillOpacity(const AValue: byte);
begin
  if FSpriteFillOpacity = AValue then
    Exit;
  FSpriteFillOpacity := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFSpriteFlipMode(const AValue: TFlipMode);
begin
  if FSpriteFlipMode = AValue then
    Exit;
  FSpriteFlipMode := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFSpriteKeyColor(const AValue: TColor);
begin
  if FSpriteKeyColor = AValue then
    Exit;
  FSpriteKeyColor := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFSpriteResampleFilter(const AValue: TResampleFilter);
begin
  if FSpriteResampleFilter = AValue then
    Exit;
  FSpriteResampleFilter := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFSpriteResampleMode(const AValue: TResampleMode);
begin
  if FSpriteResampleMode = AValue then
    Exit;
  FSpriteResampleMode := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFSpriteRotation(const AValue: TRotationMode);
begin
  if FSpriteRotation = AValue then
    Exit;
  FSpriteRotation := AValue;

  if csDesigning in ComponentState then
    Invalidate;
  InvalidatePreferredSize;
  AdjustSize;
end;

{ General Variables }

procedure TBGRASpriteAnimation.SetFCenter(const AValue: boolean);
begin
  if FCenter = AValue then
    Exit;
  FCenter := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFProportional(const AValue: boolean);
begin
  if FProportional = AValue then
    Exit;
  FProportional := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFStretch(const AValue: boolean);
begin
  if FStretch = AValue then
    Exit;
  FStretch := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SetFTile(const AValue: boolean);
begin
  if FTile = AValue then
    Exit;
  FTile := AValue;

  if csDesigning in ComponentState then
    Invalidate;
end;

procedure TBGRASpriteAnimation.SpriteChange(Sender: TObject);
begin
  Invalidate;
  InvalidatePreferredSize;
  AdjustSize;
end;

{ Utils }

function TBGRASpriteAnimation.DoCalculateDestRect(AWidth, AHeight: integer): TRect;
var
  PicWidth: integer;
  PicHeight: integer;
  ImgWidth: integer;
  ImgHeight: integer;
  w: integer;
  h: integer;
begin
  PicWidth := AWidth;
  PicHeight := AHeight;
  ImgWidth := ClientWidth;
  ImgHeight := ClientHeight;
  if Stretch or (Proportional and ((PicWidth > ImgWidth) or
    (PicHeight > ImgHeight))) then
  begin
    if Proportional and (PicWidth > 0) and (PicHeight > 0) then
    begin
      w := ImgWidth;
      h := (PicHeight * w) div PicWidth;
      if h > ImgHeight then
      begin
        h := ImgHeight;
        w := (PicWidth * h) div PicHeight;
      end;
      PicWidth := w;
      PicHeight := h;
    end
    else
    begin
      PicWidth := ImgWidth;
      PicHeight := ImgHeight;
    end;
  end;

  Result := Rect(0, 0, PicWidth, PicHeight);

  if Center then
    OffsetRect(Result, (ImgWidth - PicWidth) div 2, (ImgHeight - PicHeight) div 2);
end;

function TBGRASpriteAnimation.DoCalculatePosition(AValue: integer): integer;
begin
  if FAnimInvert then
    Result := -AValue * (FSpriteCount - FAnimPosition)
  else
    Result := -AValue * (FAnimPosition - 1);
end;

function TBGRASpriteAnimation.DoCalculateSize(AValue: cardinal): cardinal;
begin
  Result := AValue div FSpriteCount;
end;

procedure TBGRASpriteAnimation.DoSpriteResampleFilter(ABitmap: TBGRABitmap);
begin
  ABitmap.ResampleFilter := FSpriteResampleFilter;
end;

procedure TBGRASpriteAnimation.DoSpriteFillOpacity(ABitmap: TBGRABitmap);
begin
  if FSpriteFillOpacity <> 255 then
    ABitmap.ApplyGlobalOpacity(FSpriteFillOpacity);
end;

procedure TBGRASpriteAnimation.DoSpriteFlip(ABitmap: TBGRABitmap);
begin
  case FSpriteFlipMode of
    flNone: Exit;
    flHorizontal: ABitmap.HorizontalFlip;
    flVertical: ABitmap.VerticalFlip;
    flBoth:
    begin
      ABitmap.HorizontalFlip;
      ABitmap.VerticalFlip;
    end;
  end;
end;

procedure TBGRASpriteAnimation.DoSpriteKeyColor(ABitmap: TBGRABitmap);
begin
  if FSpriteKeyColor <> clNone then
    ABitmap.ReplaceColor(ColorToBGRA(ColorToRGB(FSpriteKeyColor), 255),
      BGRAPixelTransparent);
end;

{ Main }

procedure TBGRASpriteAnimation.Paint;

  procedure DrawFrame;
  begin
    with inherited Canvas do
    begin
      Pen.Color := clBlack;
      Pen.Style := graphics.psDash;
      MoveTo(0, 0);
      LineTo(Self.Width - 1, 0);
      LineTo(Self.Width - 1, Self.Height - 1);
      LineTo(0, Self.Height - 1);
      LineTo(0, 0);
    end;
  end;

var
  TempSprite, TempSpriteBGRA: TBGRABitmap;
  TempSpriteWidth, TempSpriteHeight, TempSpritePosition: integer;
begin
  if (Color <> clNone) and (Color <> clDefault) then
  begin
    Canvas.Brush.Color := Color;
    Canvas.Brush.Style := bsSolid;
    Canvas.FillRect(ClientRect);
  end;

  if csDesigning in ComponentState then
    DrawFrame;

  if FSprite = nil then
    Exit;

  if (Width > 0) and (Height > 0) then
  begin
    TempSpriteWidth := DoCalculateSize(FSprite.Width);
    TempSpriteHeight := FSprite.Height;
    TempSpritePosition := DoCalculatePosition(TempSpriteWidth);

    TempSpriteBGRA := TBGRABitmap.Create(FSprite);
    TempSprite := TBGRABitmap.Create(TempSpriteWidth, TempSpriteHeight);
    TempSprite.BlendImage(TempSpritePosition, 0, TempSpriteBGRA, boLinearBlend);
    TempSpriteBGRA.Free;

    if Assigned(FOnRedrawBefore) then
      FOnRedrawBefore(Self, TempSprite);

    DoSpriteDraw(TempSprite);
  end;
end;

procedure TBGRASpriteAnimation.CalculatePreferredSize(var PreferredWidth,
  PreferredHeight: integer; WithThemeSpace: Boolean);
begin
  if SpriteRotation in [rtClockWise,rtCounterClockWise] then
  begin
    PreferredWidth := Sprite.Height;
    PreferredHeight := Sprite.Width div SpriteCount;
  end else
  begin
    PreferredWidth := Sprite.Width div SpriteCount;
    PreferredHeight := Sprite.Height;
  end;
end;

procedure TBGRASpriteAnimation.GifImageToSprite(Gif: TBGRAAnimatedGif);
{$IF BGRABitmapVersion > 11030100}
var
  TempBitmap: TBGRABitmap;
  n: integer;
begin
  if Gif.Count = 0 then exit;
  TempBitmap := TBGRABitmap.Create(Gif.Width * Gif.Count, Gif.Height);
  try
    for n := 0 to Gif.Count-1 do
    begin
      Gif.CurrentImage := n;
      TempBitmap.PutImage(Gif.Width * n, 0, Gif.MemBitmap, dmSet);
    end;
    TempBitmap.AssignToBitmap(FSprite);
    SpriteCount := Gif.Count;
    AnimSpeed := Gif.TotalAnimationTimeMs div Gif.Count;
  finally
    TempBitmap.Free;
  end;
{$ELSE}
var
  TempBitmap: TBGRABitmap;
  n: integer;
begin
  if Gif.Count = 0 then exit;

  TempBitmap := TBGRABitmap.Create(Gif.Width * Gif.Count, Gif.Height);
  for n := 0 to Gif.Count do
  begin
    Gif.CurrentImage := n;
    TempBitmap.BlendImage(Gif.Width * n, 0, Gif.MemBitmap, boLinearBlend);
  end;

  AnimSpeed := Gif.TotalAnimationTimeMs div Gif.Count;
  FSpriteCount := Gif.Count;
  FSprite.Width := Gif.Width * Gif.Count;
  FSprite.Height := Gif.Height;
  FSprite.Canvas.Brush.Color := SpriteKeyColor;
  FSprite.Canvas.FillRect(Rect(0, 0, FSprite.Width, FSprite.Height));
  FSprite.Canvas.Draw(0, 0, TempBitmap.Bitmap);
  TempBitmap.Free;
{$ENDIF}
end;

procedure TBGRASpriteAnimation.SpriteToGifImage(Gif: TBGRAAnimatedGif);
var
  i: integer;
  TempSpriteWidth: Integer;
  TempSpritePosition: Integer;
  TempSpriteBGRA, TempSprite: TBGRABitmap;
begin
  gif.Clear;
  if AnimRepeat > high(Word) then
    gif.LoopCount := 0
  else
    gif.LoopCount := AnimRepeat;
  TempSpriteBGRA := TBGRABitmap.Create(FSprite);
  TempSpriteWidth := TempSpriteBGRA.Width div FSpriteCount;
  gif.SetSize(TempSpriteWidth, TempSpriteBGRA.Height);
  for i:=0 to FSpriteCount-1 do
  begin
    TempSpritePosition := -TempSpriteWidth * i;
    TempSprite := TBGRABitmap.Create(TempSpriteWidth, TempSpriteBGRA.Height);
    TempSprite.BlendImage(TempSpritePosition, 0, TempSpriteBGRA, boLinearBlend);
    gif.AddFullFrame(TempSprite, FAnimSpeed);
    TempSprite.Free;
  end;
  TempSpriteBGRA.Free;
end;

procedure TBGRASpriteAnimation.LoadFromResourceName(Instance: THandle;
  const ResName: string);
var
  TempGif: TBGRAAnimatedGif;
begin
  TempGif := TBGRAAnimatedGif.Create;
  {$IFDEF FPC}//#
  TempGif.LoadFromResourceName(Instance, ResName);
  {$ENDIF}
  GifImageToSprite(TempGif);

  TempGif.Free;
end;

procedure TBGRASpriteAnimation.LoadFromBitmapResource(const Resource: string);
{$IF BGRABitmapVersion > 11030100}
var
  stream: TStream;
begin
  stream := BGRAResource.GetResourceStream(Resource);
  try
    LoadFromBitmapStream(stream);
  finally
    stream.Free;
  end;
{$ELSE}
var
  tempGif: TBGRAAnimatedGif;
begin
  tempGif := TBGRAAnimatedGif.Create;
  try
    tempGif.LoadFromResource(Resource);
    GifImageToSprite(tempGif);
  finally
    tempGif.Free;
  end;
{$ENDIF}
end;

{$IF BGRABitmapVersion > 11030100}
procedure TBGRASpriteAnimation.LoadFromBitmapStream(AStream: TStream);
var
  tempGif: TBGRAAnimatedGif;
  tempBGRA: TBGRABitmap;
begin
  if DetectFileFormat(AStream) = ifGif then
  begin
    tempGif := TBGRAAnimatedGif.Create;
    try
      tempGif.LoadFromStream(AStream);
      GifImageToSprite(tempGif);
    finally
      tempGif.Free;
    end;
  end else
  begin
    tempBGRA := TBGRABitmap.Create;
    try
      tempBGRA.LoadFromStream(AStream);
      tempBGRA.AssignToBitmap(FSprite);
    finally
      tempBGRA.FRee;
    end;
  end;
end;
{$ENDIF}

procedure TBGRASpriteAnimation.LoadFromBGRABitmap(const BGRA: TBGRABitmap);
begin
  {$IF BGRABitmapVersion > 11030100}
  BGRA.AssignToBitmap(FSprite);
  {$ELSE}
  FSprite.Width := BGRA.Width;
  FSprite.Height := BGRA.Height;
  BGRA.Draw(FSprite.Canvas, 0, 0, False);
  {$ENDIF}
end;

procedure TBGRASpriteAnimation.SpriteToAnimatedGif(Filename: string);
var
  gif : TBGRAAnimatedGif;
begin
  gif := TBGRAAnimatedGif.Create;
  SpriteToGifImage(Gif);
  gif.SaveToFile(Filename);
  gif.Free;
end;

procedure TBGRASpriteAnimation.AnimatedGifToSprite(Filename: string);
var
  TempGif: TBGRAAnimatedGif;
begin
  TempGif := TBGRAAnimatedGif.Create(Filename);
  try
    GifImageToSprite(TempGif);
  finally
    TempGif.Free;
  end;
end;

procedure TBGRASpriteAnimation.DoSpriteDraw(ABitmap: TBGRABitmap);
var
  TempRect: TRect;
begin
  DoSpriteResampleFilter(ABitmap);
  DoSpriteKeyColor(ABitmap);
  DoSpriteFillOpacity(ABitmap);
  DoSpriteFlip(ABitmap);

  case FSpriteRotation of
    rtClockWise: BGRAReplace(ABitmap, ABitmap.RotateCW);
    rtCounterClockWise: BGRAReplace(ABitmap, ABitmap.RotateCCW);
    rt180: ABitmap.RotateUDInplace;
  end;

  { TODO -oLainz : If there is no Sprite loaded and you set 'Tile' to true a division by cero error is shown }
  if Tile then
    BGRAReplace(ABitmap, ABitmap.GetPart(rect(0, 0, Width, Height)));

  TempRect := DoCalculateDestRect(ABitmap.Width, ABitmap.Height);

  if Assigned(FOnRedrawAfter) then
    FOnRedrawAfter(Self, ABitmap);

  if Stretch and (FSpriteResampleMode = rmFineResample) then
    BGRAReplace(ABitmap, ABitmap.Resample(Width, Height, FSpriteResampleMode));

  ABitmap.Draw(Canvas, TempRect, False);
  ABitmap.Free;
end;

procedure TBGRASpriteAnimation.DoAnimTimerOnTimer(Sender: TObject);
begin
  Invalidate;

  if Assigned(FOnPositionChanging) then
    FOnPositionChanging(Self);
  SetFAnimPosition(FAnimPosition + 1);

  if FAnimPosition = FSpriteCount then
  begin
    if Assigned(FOnLapChanging) then
      FOnLapChanging(Self);
    SetFAnimRepeatLap(FAnimRepeatLap + 1);
  end;
end;

{ Create / Destroy }

constructor TBGRASpriteAnimation.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  with GetControlClassDefaultSize do
    SetInitialBounds(0, 0, CX, CY);

  FAnimInvert := False;
  FAnimPosition := 1;
  FAnimRepeat := 0;
  FAnimRepeatLap := 0;
  FAnimSpeed := 1000;
  FAnimStatic := False;
  FAnimTimer := TTimer.Create(Self);
  FAnimTimer.Interval := FAnimSpeed;
  FAnimTimer.OnTimer := DoAnimTimerOnTimer;
  FCenter := True;
  FProportional := True;
  FStretch := True;
  FSprite := TSpriteBitmap.Create(self);
  FSprite.OnChange:=SpriteChange;
  FSpriteCount := 1;
  FSpriteFillOpacity := 255;
  FSpriteFlipMode := flNone;
  FSpriteKeyColor := clNone;
  FSpriteResampleFilter := rfLinear;
  FSpriteResampleMode := rmSimpleStretch;
  FSpriteRotation := rtNone;
  FTile := False;

  if csDesigning in ComponentState then
    FAnimTimer.Enabled := False;
end;

destructor TBGRASpriteAnimation.Destroy;
begin
  FAnimTimer.Enabled := False;
  FAnimTimer.OnTimer := nil;
  FAnimTimer.Free;
  FSprite.Free;
  inherited Destroy;
end;

end.
