doublecmd/components/gifview/source/uGifViewer.pas
2025-07-08 00:08:22 +03:00

2785 lines
95 KiB
ObjectPascal

Unit uGifViewer;
(*==============================================================================
DESCRIPTION : Visual component for displaying an animated image in the
GIF (Graphic Interchange Format) format
DATE : 17/06/2018
UPDATE : 01/07/2025
VERSION : 1.0
AUTHOR : J.Delauney (BeanzMaster)
CONTRIBUTORS : Jipete, Jurassik Pork, bpranoto, Alexander Koblov
LICENSE : MPL 2.0
================================================================================
*)
{$mode objfpc}{$H+}
Interface
Uses
Types, Classes, SysUtils, Graphics, Math, Contnrs, Dialogs,
Controls, ExtCtrls,
Lresources, GifViewerStrConsts,
uFastBitmap;
{%region=====[ Définitions des types et constantes utiles pour le format GIF ]===================================}
Const
GIF_MaxColors : Integer = 256; // Nombre de couleurs maximum supportées. NE PAS TOUCHER A CETTE VALEUR
GIF_DelayFactor : Integer = 10; // Facteur de multiplication pour les délais en ms entre chaque image de l'animation
GIF_DefaultDelay : Integer = 100; // 10*10
Type
TGIFVersion = (gvUnknown, gv87a, gv89a);
TGIFVersionRec = Array[0..2] Of AnsiChar;
Const
GIFVersions : Array[gv87a..gv89a] Of TGIFVersionRec = ('87a', '89a');
Type
{ En-tête }
TGIFFileHeader = Packed Record
Signature: Array[0..2] Of AnsiChar; // 'GIF'
Version: TGIFVersionRec; // '87a' ou '89a' }
End;
{ Description globale de l'image }
TGIFLogicalScreenDescriptorRec = Packed Record
ScreenWidth: Word; // Largeur de l'image en pixels // Width
ScreenHeight: Word; // Hauteur de l'image en pixels // Height
PackedFields: Byte; // champs compactés // Compacted field
BackgroundColorIndex: Byte; // Index globale de la couleur de fond // Index of background color
AspectRatio: Byte; // Ratio d'échelle = (AspectRatio + 15) / 64
End;
{ Description d'une image }
TGIFImageDescriptorRec = Packed Record
//Separator: byte; // On lis toujours un byte avant // we always read it before
Left: Word; // Colonne en pixels par rapport au bord gauche de l'écran // Column in pixels from the left edge of the screen
Top: Word; // Rangée en pixels par rapport au haut de l'écran // Row in pixels from the top edge of the screen
Width: Word; // Largeur de l'image en cours en pixels // image width
Height: Word; // Hauteur de l'image en cours pixels // Image height
PackedFields: Byte; // Champs compactés // Compacted field
End;
{ Graphic Control Extension bloc a.k.a GCE }
TGIFGraphicControlExtensionRec = Packed Record
// BlockSize: byte; // Normalement toujours 4 octets // Always 4 bytes
PackedFields: Byte; // Champs compacté // Compacted field
DelayTime: Word; // Délai entre chaque image en centième de secondes // Delay between each image in hundredths of a second
TransparentColorIndex: Byte; // Index dans la palette si plus petit ou égale // Delay between each image in hundredths of a second
// Terminator: Byte; // Normalement toujours ZERO // Normally always ZERO
End;
TGIFDisposalFlag = (dmNone, dmKeep, dmErase, dmRestore); // Methodes pour l'affichage des images lors de l'animation
{ Plain Text Extension }
TGIFPlainTextExtensionRec = Packed Record
// BlockSize: byte; // Normalement égal à 12 octets // Normally equal to 12 bytes
Left, Top, Width, Height: Word; // Positions et dimensions du texte // position and dimension of text
CellWidth, CellHeight: Byte; // Dimensions d'une cellule dans l'image // Size of cell
TextFGColorIndex, // Index de la couleur de fond dans la palette // Index of the background color
TextBGColorIndex: Byte; // Index de la couleur du texte dans la palette // Index of the text color
End;
{ Application Extension }
TGIFApplicationExtensionRec = Packed Record
AppID: Array [0..7] Of AnsiChar; // Identification de l'application majoritairement 'NETSCAPE' ou ''
AppAuthenticationCode: Array [0..2] Of AnsiChar; // Code d'authentification ou numero de version
End;
{ Informations de "l'application extension" si disponible }
TGIFNSLoopExtensionRec = Packed Record
Loops: Word; // Nombre de boucle de l'animation 0 = infinie // nb loop
BufferSize: DWord; // Taille du tampon. Usage ?????
End;
Const
// Description des masques pour la description globale de l'image
GIF_GLOBALCOLORTABLE = $80; // Défini si la table de couleurs globale suit la description globale
GIF_COLORRESOLUTION = $70; // Résolution de la couleur (BitsPerPixel) - 3 bits
GIF_GLOBALCOLORTABLESORTED = $08; // Définit si la palette globale est triée - 1 bit
GIF_COLORTABLESIZE = $07; // Taille de la palette - 3 bits
GIF_RESERVED = $0C; // Réservé - doit être défini avec $00 - Taille des données = 2^value+1 - 3 bits
// Descption des masques pour les images
GIF_LOCALCOLORTABLE = $80; // Défini si la table de couleurs locale suit la description de l'image
GIF_INTERLACED = $40; // Défini si l'image est entrelacée
GIF_LOCALCOLORTABLESORTED = $20; // Définit si la palette locale est triée
// Identification des blocs
GIF_PLAINTEXT = $01;
GIF_GRAPHICCONTROLEXTENSION = $F9;
GIF_COMMENTEXTENSION = $FE;
GIF_APPLICATIONEXTENSION = $FF;
GIF_IMAGEDESCRIPTOR = $2C; // ','
GIF_EXTENSIONINTRODUCER = $21; // '!'
GIF_TRAILER = $3B; // ';'
// Graphic Control Extension - Définition des masques pour les paramètres
GIF_NO_DISPOSAL = $00; // 0
GIF_DO_NOT_DISPOSE = $04; // 1
GIF_RESTORE_BACKGROUND_COLOR = $08; // 2
GIF_RESTORE_PREVIOUS = $12; // 3
GIF_DISPOSAL_ALL = $1C; // bits 2-4 ($1C)
GIF_USER_INPUT_FLAG = $02;
GIF_TRANSPARENT_FLAG = $01;
GIF_RESERVED_FLAG = $E0;
// Identification des sous-blocs pour "Application Extension"
GIF_LOOPEXTENSION = 1;
GIF_BUFFEREXTENSION = 2;
Const
GifGCEDisposalModeStr : Array[TGIFDisposalFlag] Of String = ('None', 'Keep', 'Erase', 'Restore');
Type
{ Informations sur une image de l'animation }
TGIFFrameInformations = Record
Left, Top, // Position de l'image
Width, Height: Integer; // Dimension de l'image
HasLocalPalette: Boolean; // Palette locale disponible
IsTransparent: Boolean; // Image transparente
UserInput: Boolean; // Données personnelle
BackgroundColorIndex: Byte; // Normalement seulement valide si une palette globale existe
TransparentColorIndex: Byte; // Index de la couleur transparente
DelayTime: Word; // Délai d'animation
Disposal: TGIFDisposalFlag; // Methode d'affichage
Interlaced: Boolean; // Image entrelacée
End;
PGifFrameInformations = ^TGifFrameInformations;
{%endregion%}
{ TGIFFastMemoryStream }
{ Classe d'aide à la lecture des données dans un flux en mémoire }
TGIFFastMemoryStream = Class
Private
FBuffer: PByte;
FPosition: Int64;
FBytesRead, FBytesLeft, FSize: Int64;
Public
Constructor Create(AStream : TStream);
Destructor Destroy; Override;
{ Lit un Byte dans le tampon / Read a byte in buffer }
Function ReadByte: Byte;
{ Lit un Word dans le tampon / Read a word in buffer}
Function ReadWord: Word;
{ Lit un DWord dans le tampon / Read a DWord in buffer }
Function ReadDWord: DWord;
{ Lit et retourne un tampon "Buffer" de taille "Count" octets / Read a buffer of size "count" }
Function Read(Var Buffer; Count : Int64): Int64;
{ Déplacement dans le flux de "Offset" depuis "Origin"
TSeekOrigin =
- soBeginning : Depuis le début du flux
- soCurrent : a partir de la position courante
- soEnd : A partir de la fin du flux
}
Function Seek(Const Offset : Int64; Origin : TSeekOrigin): Int64;
{ Déplacement dans le flux vers l'avant de "Cnt" octet depuis la position courrante }
Procedure SeekForward(Cnt : Integer);
{ Indique si la fin du flux est atteinte (EOS = End Of Stream) }
Function EOS: Boolean;
{ Retourne la taille du flux en octet // Size in byte of the buffer}
Property Size: Int64 read FSize;
{ Retourne la position courrante de lecture dans le tampon // Current position in buffer }
Property Position: Int64 read FPosition;
End;
{ TGIFLoadErrorEvent : Fonction d'évènement levée en cas d'erreur(s) dans le chargement // Event raise on error }
TGIFLoadErrorEvent = Procedure(Sender : TObject; Const ErrorCount : Integer; Const ErrorList : TStringList) Of Object;
{ TGIFImageListItem }
{ Définition d'une image contenue dans le fichier GIF }
TGIFImageListItem = Class
Private
FBitmap: TFastBitmap;
FDrawMode: TGIFDisposalFlag;
FLeft, FTop: Integer;
FComment: TStringList;
FDelay: Integer;
FTransparent: Boolean;
FIsCorrupted : Boolean;
Protected
Public
Constructor Create;
Destructor Destroy; Override;
{ Objet contenant l'image }
Property Bitmap: TFastBitmap read FBitmap write FBitmap;
{ Mode de rendu de l'image // Render Mode}
Property DrawMode: TGIFDisposalFlag read FDrawMode write FDrawMode;
{ Position gauche de l'image }
Property Left: Integer read FLeft write FLeft;
{ Position Haut de l'image }
Property Top: Integer read FTop write FTop;
{ Temps d'attente entre deux image de l'animation }
Property Delay: Integer read FDelay write FDelay;
{ Commentaire sur l'image }
Property Comment: TStringList read FComment write FComment;
{ Retourne TRUE si l'image utilise la transparence }
Property IsTransparent: Boolean read FTransparent write FTransparent;
{ Indique si l'image est corrompue }
property IsCorrupted : Boolean read FIsCorrupted write FIsCorrupted;
End;
{ TGIFImageList }
{ Classe d'aide à la gestion des images contenues dans le fichier GIF }
{ Helper class for manage image in GIF }
TGIFImageList = Class(TObjectList)
Private
Protected
Function GetItems(Index : Integer): TGIFImageListItem;
Procedure SetItems(Index : Integer; AGifImage : TGIFImageListItem);
Public
{ Efface la liste }
Procedure Clear; Override;
{ Ajoute une nouvelle image vide à la liste }
Function AddNewImage: TGIFImageListItem;
{ Ajout d'une image dans la liste }
Function Add(AGifImage : TGIFImageListItem): Integer;
{ Extraction d'une image de la liste }
Function Extract(Item : TGIFImageListItem): TGIFImageListItem;
{ Effacement d'une image dans la liste }
Function Remove(AGifImage : TGIFImageListItem): Integer;
{ Retourne l'index de l'image recherchée (retourne -1 si non trouvé) }
Function IndexOf(AGifImage : TGIFImageListItem): Integer;
{ Retourne la première image }
Function First: TGIFImageListItem;
{ Retourne la dernière image }
Function Last: TGIFImageListItem;
{ Insertion d'une image à la position "Index" }
Procedure Insert(Index : Integer; AGifImage : TGIFImageListItem);
{ Liste des images }
Property Items[Index: Integer]: TGIFImageListItem read GetItems write SetItems; Default;
End;
{ TGIFImageLoader }
{ Classe spécialisée pour la lecture d'une image au format GIF }
{ Special class for read a GIF }
TGIFImageLoader = Class
Private
FCurrentLayerIndex: Integer;
FGIFFIleHeader: TGIFFileHeader;
FLogicalScreenChunk: TGIFLogicalScreenDescriptorRec;
FHasGlobalPalette: Boolean;
FTransparent: Boolean;
FGlobalPalette: TColor32List;
FVersion: String;
FWidth, FHeight: Integer;
FBackgroundColor: TColor32;
FFrames: TGIFImageList;
FErrorList: TStringList;
FErrorCount: Integer;
FOnLoadError: TGIFLoadErrorEvent;
Procedure SetCurrentLayerIndex(AValue : Integer);
Protected
Memory: TGIFFastMemoryStream;
CurrentFrameInfos: TGifFrameInformations;
Function GetFrameCount: Integer;
Procedure LoadFromMemory();
Function CheckFormat(): Boolean;
Function ReadImageProperties: Boolean;
Procedure AddError(Msg : String);
Procedure NotifyError;
Public
Constructor Create;
Destructor Destroy; Override;
{ LoadFromStream : Charge les données depuis un flux }
Procedure LoadFromStream(aStream : TStream); Virtual;
{ LoadFromFile : Charge les données depuis un fichier physique }
Procedure LoadFromFile(Const FileName : String); Virtual;
{ Chargement depuis une Resource Lazarus }
Procedure LoadFromResource(Const ResName : String);
{ Retourne la version du fichier GIF }
Property Version: String read FVersion;
{ Retourne la largeur de l'image GIF }
Property Width: Integer read FWidth;
{ Retourne la hauteur de l'image GIF }
Property Height: Integer read FHeight;
{ Retourne la couleur de l'image GIF si elle existe,. Sinon retourne une couleur transparente (clrTransparent) }
Property BackgroundColor: TColor32 read FBackgroundColor write FBackgroundColor;
{ Prise en charge de la transparence dans l'image GIF // Take transparency in account}
Property Transparent: Boolean read FTransparent write FTransparent;
{ Retourne l'index courrant de l'image de l'animation traité // Return the current index frame}
Property CurrentFrameIndex: Integer read FCurrentLayerIndex write SetCurrentLayerIndex;
{ Liste des images de l'animation // List of frame}
Property Frames: TGIFImageList read FFrames;
{ Nombre d'image de l'animation // Nb frames }
Property FrameCount: Integer read GetFrameCount;
{ Nombre d'erreur produite loars d'un cahrgement ou d'un enregistrement // Nb error }
Property ErrorCount: Integer read FErrorCount;
{ Liste des erreurs // List of error }
Property Errors: TStringList read FErrorList;
{ Evenement pour intercepter les erreurs notifiées lors du chargement des données // Error Event }
Property OnLoadError: TGIFLoadErrorEvent read FOnLoadError write FOnLoadError;
End;
{ TGIFRenderCacheListItem }
{ Définition d'une image cache de l'animation }
{ Image cache class }
TGIFRenderCacheListItem = Class
Private
FBitmap: Graphics.TBitmap;
FDelay: Integer;
FIsCorrupted : Boolean;
Public
Constructor Create;
Destructor Destroy; Override;
{ Image cache prérendu de l'animation }
Property Bitmap: Graphics.TBitmap read FBitmap write FBitmap;
{ Temps d'attente en ms avec l'image suivante }
Property Delay: Integer read FDelay write FDelay;
{ Indique si l'image est corrompue }
property IsCorrupted : Boolean read FIsCorrupted write FIsCorrupted;
End;
{ TGIFRenderCacheList }
{ Classe d'aide à la gestion des images rendues de l'animation }
{ Helper class for manage list of image cache }
TGIFRenderCacheList = Class(TObjectList)
Private
Protected
Function GetItems(Index : Integer): TGIFRenderCacheListItem;
Procedure SetItems(Index : Integer; AGIFRenderCache : TGIFRenderCacheListItem);
Public
{ Efface la liste }
Procedure Clear; Override;
{ Ajoute un nouvel objet cache vide }
Function AddNewCache: TGIFRenderCacheListItem;
{ Ajoute un nouveau cache }
Function Add(AGIFRenderCache : TGIFRenderCacheListItem): Integer;
{ Extrait un cache de la liste }
Function Extract(Item : TGIFRenderCacheListItem): TGIFRenderCacheListItem;
{ Supprime un cache de la liste }
Function Remove(AGIFRenderCache : TGIFRenderCacheListItem): Integer;
{ Retourne l'index du cache recherchée (retourne -1 si non trouvé) }
Function IndexOf(AGIFRenderCache : TGIFRenderCacheListItem): Integer;
{ Retourne le premier élément de la liste }
Function First: TGIFRenderCacheListItem;
{ Retourne le dernier élément de la liste }
Function Last: TGIFRenderCacheListItem;
{ Insertion d'un cache à la position "Index" }
Procedure Insert(Index : Integer; AGIFRenderCache : TGIFRenderCacheListItem);
{ Vérifie si "anIndex" ne dépasse pas la nombre d'élément dans la liste. Retroune FALSE si l'index est hors limite }
{ Check if 'anIndex' does not exceed the number of items in the list. Retrieve FALSE if the index is out of range }
function IsIndexOk(anIndex : Integer) : Boolean;
{ Supprime les éléments dont le drapeau "IsCorrupted" est vrai }
{ Remove items wich "IsCorrupted" flag is on True }
procedure Pack;
{ Liste des caches }
Property Items[Index: Integer]: TGIFRenderCacheListItem read GetItems write SetItems; Default;
End;
{ TGIFAutoStretchMode
Mode de redimensionnement automatique}
TGIFAutoStretchMode = (smManual, smStretchAll, smStretchOnlyBigger, smStretchOnlySmaller );
TOnStretchChanged = procedure (Sender:TObject; IsStretched : Boolean) of object;
{ TGIFViewer }
{ Composant visuel pour afficher une image GIF animée }
{ Visual component for display the animated GIF }
TGIFViewer = Class(TGraphicControl)
Private
FAutoStretchMode: TGIFAutoStretchMode;
FGIFLoader: TGIFImageLoader;
FLastDrawMode : TGIFDisposalFlag;
FFileName: String;
FRestoreBitmap, FVirtualView: TFastBitmap;
FRenderCache: TGIFRenderCacheList;
FCurrentFrameIndex: Integer;
FGIFWidth, FGIFHeight: Integer;
FCurrentView: Graphics.TBitmap;
FAnimateTimer: TTimer;
FAnimateSpeed: Integer;
FAnimated, FPause: Boolean;
FAutoPlay: Boolean;
FCache: Boolean;
FDisplayInvalidFrames : Boolean;
FAutoRemoveInvalidFrame : Boolean;
FPainting: Boolean;
FBorderShow: Boolean;
FBorderColor: TColor;
FBorderWidth: Byte;
FBevelInner, FBevelOuter: TPanelBevel;
FBevelWidth: TBevelWidth;
FBevelColor, FColor: TColor;
FCenter, FStretch, FTransparent: Boolean;
FOnStart, FOnStop, FOnPause, FOnFrameChange: TNotifyEvent;
FOnLoadError : TGIFLoadErrorEvent;
FOnStretchChanged : TOnStretchChanged;
Function GetCanvas: TCanvas;
Function GetFrameCount: Integer;
Function GetGIFVersion: String;
Function GetRawFrameItem(Index : Integer): TGIFImageListItem;
Procedure SetAutoStretchMode(AValue: TGIFAutoStretchMode);
Procedure SetCenter(Const Value : Boolean);
Procedure SetStretch(Const Value : Boolean);
Procedure SetPause(Const Value : Boolean);
Procedure SetFileName(Const Value : String);
Function GetFrame(Const Index : Integer): Graphics.TBitmap;
Procedure SetTransparent(Const Value : Boolean);
Procedure SetBevelInner(Const Value : TPanelBevel);
Procedure SetBevelOuter(Const Value : TPanelBevel);
Procedure SetBevelWidth(Const Value : TBevelWidth);
procedure ResetCurrentView;
Protected
Procedure DoInternalOnLoadError(Sender : TObject; Const ErrorCount : Integer; Const ErrorList : TStringList);
Procedure DoTimerAnimate(Sender : TObject);
{ Rendu d'une image de l'animation }
procedure RenderFrame(Index : Integer); Virtual;
{ Creation des image cache pour l'animation }
Procedure ComputeCache; Virtual;
{ Calcul de la postion et de la dimension pour l'afficchage sur le "Canvas" }
Function DestRect: TRect; Virtual;
{ Fonctions hérités }
Procedure CalculatePreferredSize(Var PreferredWidth, PreferredHeight : Integer; {%H-}WithThemeSpace : Boolean); Override;
Class Function GetControlClassDefaultSize: TSize; Override;
Procedure Paint; Override;
procedure Loaded; override;
procedure BeforeLoad;
procedure AfterLoad;
Public
{ Création du composant }
Constructor Create(AOwner : TComponent); Override;
{ Destruction du composant }
Destructor Destroy; Override;
{ Mise à jour de la surface de dessin (Canvas) du composant }
Procedure Invalidate; Override;
{ LoadFromStream : Charge les données depuis un flux }
Procedure LoadFromStream(aStream : TStream);
{ Chargement depuis un fichier }
Procedure LoadFromFile(Const aFileName : String);
{ Chargement depuis une Resource Lazarus }
Procedure LoadFromResource(Const ResName : String);
{ Joue l'animation }
Procedure Start;
{ Arrête l'animation }
Procedure Stop;
{ Met en pause l'animation }
Procedure Pause;
Procedure NextFrame;
Procedure PriorFrame;
{ Retourne l'image brute du GIF à la position Index }
Function GetRawFrame(Index : Integer): TBitmap;
{ Affiche l'image de l'animation mise en cache à la position Index }
Procedure DisplayFrame(Index : Integer);
{ Affiche l'image brute de l'animation à la position Index }
Procedure DisplayRawFrame(Index : Integer);
{ Extrait l'image de l'animation mise en cache à la position Index vers un TBitmap }
procedure ExtractFrame(Index : Integer; Var bmp:TBitmap) ;
{ Extrait l'image brute de l'animation à la position Index vers un TBitmap}
procedure ExtractRawFrame(Index : Integer; Var bmp:TBitmap);
{ Retourne le Canvas du composant }
Property Canvas: TCanvas read GetCanvas;
{ Retourne TRUE si l'animation est en pause }
Property Paused: Boolean read FPause;
{ Retourne TRUE si l'animation est en cours }
Property Playing: Boolean read FAnimated;
{ Retourne l'index actuel de l'image affichée // Current Index of displayed frame }
Property CurrentFrameIndex: Integer read FCurrentFrameIndex;
{ Liste des images de l'animation // List of frame}
Property Frames[Index: Integer]: TBitmap read GetFrame;
{ Retourne le nombre d'image de l'animation // Number of frames }
Property FrameCount: Integer read GetFrameCount;
{ Retourne la version du fichier GIF chargé // version of the gif }
Property Version: String read GetGIFVersion;
{ Image courante de l'animation affichée // Current displayed image }
Property CurrentView: Graphics.TBitmap read FCurrentView;
property RawFrames[Index : Integer] : TGIFImageListItem read GetRawFrameItem;
Published
Property Color: TColor read FColor write FColor;
{ Bordure visible autour du composant // Border visible around component }
Property Border: Boolean read FBorderShow write FBorderShow;
{ Couleur de la bordure // Color of border }
Property BorderColor: TColor read FBorderColor write FBorderColor;
{ Epaisseur de la bordure // Width of border }
Property BorderWidth: Byte read FBorderWidth write FBorderWidth;
Property BevelColor: TColor read FBevelColor write FBevelColor;
Property BevelInner: TPanelBevel read FBevelInner write SetBevelInner Default bvNone;
Property BevelOuter: TPanelBevel read FBevelOuter write SetBevelOuter Default bvRaised;
Property BevelWidth: TBevelWidth read FBevelWidth write SetBevelWidth Default 1;
Property Cache: Boolean read FCache write FCache;
{ Joue l'animation automatiquement lors du chargement d'une image GIF animée }
{ Play animation automatically when loading an animated GIF image }
Property AutoPlay: Boolean read FAutoPlay write FAutoPlay;
{ Affichage du GIF avec prise en charge de la transparence }
{ GIF view with transparency support }
Property Transparent: Boolean read FTransparent write SetTransparent;
{ Centrer l'affichage // Center display }
Property Center: Boolean read FCenter write SetCenter;
{ Mode du redimensionnement // Automatic stretch mode
smManual : Adpatation Manuelle via la propriété stretch
smStretchAll : Adapte toute les images
smStretchOnlyBigger : Adapte seulement les images plus grande
smStretchOnlySmaller : Adapte seulement les images plus petite
}
property AutoStretchMode : TGIFAutoStretchMode read FAutoStretchMode write SetAutoStretchMode;
{ Redimensionner l'affichage proportionnellement // Resize the display proportionally }
Property Stretch: Boolean read FStretch write SetStretch;
{ Nom du fichier à charger // Name of file to load }
Property FileName: String read FFileName write SetFileName;
{ Définis si les images corrompues doivent être affichées. Si le GIF contient que une seule image ce paramètre n'est pas appliqué. Par defaut FALSE }
property DisplayInvalidFrames : Boolean read FDisplayInvalidFrames write FDisplayInvalidFrames;
{ Définis si les images corrompues doivent être effacées de la liste de l'animation automatiquement. Par defaut TRUE }
property AutoRemoveInvalidFrame : Boolean Read FAutoRemoveInvalidFrame write FAutoRemoveInvalidFrame;
{ Evènement déclenché lorsque l'animation débute }
{ Event triggered when the animation starts }
Property OnStart: TNotifyEvent read FOnStart write FOnStart;
{ Evènement déclenché lorsque l'animation s'arrête }
{ Event triggered when the animation stops }
Property OnStop: TNotifyEvent read FOnStop write FOnStop;
{ Evènement déclenché lorsque l'animation est mise en pause }
{ Event triggered when the animation is paused }
Property OnPause: TNotifyEvent read FOnPause write FOnPause;
{ Evènement déclenché lorsque une nouvelle image est affiché lors de l'animation }
{ Event triggered when a new image is displayed during the animation }
Property OnFrameChange: TNotifyEvent read FOnFrameChange write FOnFrameChange;
{ Evenement pour intercepter les erreurs notifiées lors du chargement des données }
Property OnLoadError: TGIFLoadErrorEvent read FOnLoadError write FOnLoadError;
{ Evenement pour intercepter le changement du mode stretch. Uniquement si AutoStretchMode <> smManual }
{ Event to intercept the change of the stretch mode. Only if AutoStretchMode <> smManual }
property OnStretchChanged : TOnStretchChanged read FOnStretchChanged write FOnStretchChanged;
{ Propriétés héritées }
Property Align;
Property Anchors;
Property AutoSize;
Property Constraints;
Property BorderSpacing;
Property Visible;
Property ParentShowHint;
Property ShowHint;
{ Evènements héritées }
Property OnClick;
Property OnMouseDown;
Property OnMouseEnter;
Property OnMouseLeave;
Property OnMouseMove;
Property OnMouseUp;
Property OnMouseWheel;
Property OnMouseWheelDown;
Property OnMouseWheelUp;
End;
TGIFView = Class(TGIFViewer);
Procedure Register;
Implementation
Uses
GraphType;
{$R ../gifview.res}
{%region=====[ Constantes et types internes ]===================================}
Type
// Statut de décodage / encodage LZW
TLZWDecoderStatus = (
dsOK, // Tout va bien
dsNotEnoughInput, // Tampon d'entrée trop petit
dsOutputBufferTooSmall, // Tampon de sortie trop petit
dsInvalidInput, // Donnée corrompue
dsBufferOverflow, // débordement de tampon
dsInvalidBufferSize, // Taille d'un des tampons invalide
dsInvalidInputBufferSize, // Taille du tampon d'entrée invalide
dsInvalidOutputBufferSize,// Taille du tampon de sortie invalide
dsInternalError // Erreur interne signifiant qu'il y a un défaut dans le code
);
{%endregion%}
{%region=====[ Fonctions utiles ]===============================================}
Function FixPathDelimiter(S : String): String;
Var
I: Integer;
Begin
Result := S;
For I := Length(Result) Downto 1 Do
Begin
If (Result[I] = '/') Or (Result[I] = '\') Then Result[I] := PathDelim;
End;
End;
Function CreateFileStream(Const fileName : String; mode : Word = fmOpenRead + fmShareDenyNone): TStream;
Var
fn: String;
Begin
fn := filename;
FixPathDelimiter(fn);
If ((mode And fmCreate) = fmCreate) Or FileExists(fn) Then Result := TFileStream.Create(fn, mode)
Else
Raise Exception.Create('Fichier non trouvé : "' + fn + '"');
End;
{%endregion%}
{%region=====[ TGIFFastMemoryStream ]==============================================}
Constructor TGIFFastMemoryStream.Create(AStream : TStream);
Var
ms: TMemoryStream;
Begin
ms := TMemoryStream.Create;
With ms Do
Begin
CopyFrom(aStream, 0);
Position := 0;
End;
FSize := ms.Size;
FPosition := 0;
FBytesLeft := FSize;
FBytesRead := 0;
FBuffer := nil;
ReAllocMem(FBuffer, FSize);
Move(PByte(ms.Memory)^, FBuffer^, FSize);
FreeAndNil(ms);
End;
Destructor TGIFFastMemoryStream.Destroy;
Begin
If FBuffer <> nil Then
Begin
FreeMem(FBuffer);
FBuffer := nil;
End;
Inherited Destroy;
End;
Function TGIFFastMemoryStream.ReadByte: Byte;
Begin
Result := 0;
If FBytesLeft > 0 Then
Begin
Result := PByte(FBuffer + FPosition)^;
Inc(FPosition);
Inc(FBytesRead);
Dec(FBytesLeft);
End;
End;
Function TGIFFastMemoryStream.ReadWord: Word;
Begin
Result := 0;
If (FBytesLeft >= 2) Then
Begin
Result := PWord(FBuffer + FPosition)^;
Inc(FPosition, 2);
Inc(FBytesRead, 2);
Dec(FBytesLeft, 2);
End;
End;
Function TGIFFastMemoryStream.ReadDWord: DWord;
Begin
Result := 0;
If (FBytesLeft >= 4) Then
Begin
Result := PDWord(FBuffer + FPosition)^;
Inc(FPosition, 4);
Inc(FBytesRead, 4);
Dec(FBytesLeft, 4);
End;
End;
Function TGIFFastMemoryStream.Read(Var Buffer; Count : Int64): Int64;
Var
NumOfBytesToCopy, NumOfBytesLeft: Longint;
CachePtr, BufferPtr: PByte;
Begin
Result := 0;
If (Count > FBytesLeft) Then NumOfBytesLeft := FBytesLeft
Else
NumOfBytesLeft := Count;
BufferPtr := @Buffer;
While NumOfBytesLeft > 0 Do
Begin
// On copie les données
NumOfBytesToCopy := Min(FSize - FPosition, NumOfBytesLeft);
CachePtr := FBuffer;
Inc(CachePtr, FPosition);
Move(CachePtr^, BufferPtr^, NumOfBytesToCopy);
Inc(Result, NumOfBytesToCopy);
Inc(FPosition, NumOfBytesToCopy);
Inc(BufferPtr, NumOfBytesToCopy);
// On met à jour les marqueur de notre tampon
Inc(FBytesRead, NumOfBytesToCopy);
Dec(FBytesLeft, NumOfBytesToCopy);
Dec(NumOfBytesLeft, NumOfBytesToCopy);
End;
End;
Function TGIFFastMemoryStream.Seek(Const Offset : Int64; Origin : TSeekOrigin): Int64;
Var
NewPos: Integer;
Begin
// Calcul de la nouvelle position
Case Origin Of
soBeginning: NewPos := Offset;
soCurrent: NewPos := FPosition + Offset;
soEnd: NewPos := pred(FSize) - Offset;
Else
Raise Exception.Create('TFastStream.Seek: Origine Invalide');
End;
Result := NewPos;
If Offset = 0 Then exit;
FPosition := NewPos;
FBytesLeft := FSize - FPosition;
Result := NewPos;
End;
Procedure TGIFFastMemoryStream.SeekForward(Cnt : Integer);
Begin
Seek(Cnt, soCurrent);
End;
Function TGIFFastMemoryStream.EOS: Boolean;
Begin
Result := ((FBytesLeft <= 0) Or (FPosition >= Pred(FSize)));
End;
{%endregion%}
{%region=====[ TGIFImageListItem ]==============================================}
Constructor TGIFImageListItem.Create;
Begin
FBitmap := TFastBitmap.Create;
FLeft := 0;
FTop := 0;
FDelay := 0;
FDrawMode := dmNone;
FComment := TStringList.Create;
FComment.Clear;
FIsCorrupted := False;
End;
Destructor TGIFImageListItem.Destroy;
Begin
FreeAndNil(FComment);
FreeAndNil(FBitmap);
Inherited Destroy;
End;
{%endregion%}
{%region=====[ TGIFImageList ]==================================================}
Function TGIFImageList.GetItems(Index : Integer): TGIFImageListItem;
Begin
Result := TGIFImageListItem(Inherited Items[Index]);
End;
Procedure TGIFImageList.SetItems(Index : Integer; AGifImage : TGIFImageListItem);
Begin
Put(Index, AGifImage);
End;
Procedure TGIFImageList.Clear;
Var
anItem: TGIFImageListItem;
i: Integer;
Begin
If Count > 0 Then
Begin
For i := Count - 1 Downto 0 do
Begin
AnItem := Items[i];
If anItem <> nil Then anItem.Free;
End;
End;
Inherited Clear;
End;
Function TGIFImageList.AddNewImage: TGIFImageListItem;
Var
anItem: TGIFImageListItem;
Begin
anitem := TGIFImageListItem.Create;
Add(anItem);
Result := Items[Self.Count - 1];
End;
Function TGIFImageList.Add(AGifImage : TGIFImageListItem): Integer;
Begin
Result := Inherited Add(AGifImage);
End;
Function TGIFImageList.Extract(Item : TGIFImageListItem): TGIFImageListItem;
Begin
Result := TGIFImageListItem(Inherited Extract(Item));
End;
Function TGIFImageList.Remove(AGifImage : TGIFImageListItem): Integer;
Begin
Result := Inherited Remove(AGifImage);
End;
Function TGIFImageList.IndexOf(AGifImage : TGIFImageListItem): Integer;
Begin
Result := Inherited IndexOf(AGifImage);
End;
Function TGIFImageList.First: TGIFImageListItem;
Begin
Result := TGIFImageListItem(Inherited First);
End;
Function TGIFImageList.Last: TGIFImageListItem;
Begin
Result := TGIFImageListItem(Inherited Last);
End;
Procedure TGIFImageList.Insert(Index : Integer; AGifImage : TGIFImageListItem);
Begin
Inherited Insert(Index, AGifImage);
End;
{%endregion%}
{%region=====[ TGIFImageLoader ]================================================}
Constructor TGIFImageLoader.Create;
Begin
Inherited Create;
FFrames := TGIFImageList.Create(False);
FErrorList := TStringList.Create;
FErrorCount := 0;
FGlobalPalette := nil;
FTransparent := True;
FBackgroundColor := clrTransparent;
End;
Destructor TGIFImageLoader.Destroy;
Begin
FreeAndNil(FFrames);
FreeAndNil(FErrorList);
Inherited Destroy;
End;
Function TGIFImageLoader.CheckFormat(): Boolean;
Begin
Result := False;
// Chargement de l'en-tête
Memory.Read(FGIFFileHeader, SizeOf(TGIFFileHeader));
// Vérification de quelques paramètres
Result := uppercase(String(FGIFFileHeader.Signature)) = 'GIF';
If Result Then
Begin
// Le fichier est valide
// On sauvegarde la version du GIF
FVersion := String(FGIFFileHeader.Version);
If (FVersion = GIFVersions[gv87a]) Or (FVersion = GIFVersions[gv89a]) Then Result := ReadImageProperties // On lit les propriétés
Else
Raise Exception.Create(rsUnknownVersion);
End
Else
Begin
// Signature du fichier GIF Invalide. On lève une exception
Raise Exception.Create(Format(rsBadSignature,[uppercase(String(FGIFFileHeader.Signature))]));
End;
End;
Function TGIFImageLoader.ReadImageProperties: Boolean;
Begin
Result := False;
Memory.Read(FLogicalScreenChunk, SizeOf(TGIFLogicalScreenDescriptorRec));
// On sauvegarde en local les dimensions de l'image, pour plus tard
FWidth := FLogicalScreenChunk.ScreenWidth;
FHeight := FLogicalScreenChunk.ScreenHeight;
If (FWidth < 1) Or (FHeight < 1) Then
Begin
// Dimensions incorrectes on lève une exception
Raise Exception.Create(Format(rsBadScreenSize,[FWidth,FHeight]));
exit;
End;
FHasGlobalPalette := (FLogicalScreenChunk.PackedFields And GIF_GLOBALCOLORTABLE) <> 0;
Result := True;
End;
Procedure TGIFImageLoader.AddError(Msg : String);
Begin
FErrorList.Add(Msg);
End;
Procedure TGIFImageLoader.NotifyError;
Begin
If FErrorList.Count > 0 Then
Begin
If Assigned(FOnLoadError) Then FOnLoadError(Self, FErrorList.Count, FErrorList);
End;
End;
Procedure TGIFImageLoader.LoadFromStream(aStream : TStream);
Begin
If Memory <> nil Then FreeAndNil(Memory);
Memory := TGIFFastMemoryStream.Create(aStream);
If CheckFormat Then LoadFromMemory;
FreeAndNil(Memory);
End;
Procedure TGIFImageLoader.LoadFromFile(Const FileName : String);
Var
Stream: TStream;
Begin
FErrorList.Clear;
FErrorCOunt := 0;
Stream := CreateFileStream(FileName);
Try
LoadFromStream(Stream);
Finally
FreeAndNil(Stream);
End;
End;
Procedure TGIFImageLoader.LoadFromResource(Const ResName : String);
Var
Stream: TLazarusResourceStream;
Begin
FErrorList.Clear;
FErrorCOunt := 0;
Stream := TLazarusResourceStream.Create(ResName, nil);
Try
LoadFromStream(Stream);
Finally
FreeAndNil(Stream);
End;
End;
Function TGIFImageLoader.GetFrameCount: Integer;
Begin
Result := FFrames.Count;
End;
Procedure TGIFImageLoader.SetCurrentLayerIndex(AValue : Integer);
Begin
If FCurrentLayerIndex = AValue Then Exit;
FCurrentLayerIndex := AValue;
End;
Procedure TGIFImageLoader.LoadFromMemory();
Var
aRGBColor: TColorRGB24;
aColor: TColor32;
PaletteCount: Integer;
Done: Boolean;
BlockID: Byte;
BlockSize: Byte;
Terminator{%H-}: Byte;
CurrentLayer: TGIFImageListItem;
ImageDescriptor: TGIFImageDescriptorRec;
GraphicControlExtensionChunk: TGIFGraphicControlExtensionRec;
ApplicationExtensionChunk: TGIFApplicationExtensionRec;
NSLoopExtensionChunk: TGIFNSLoopExtensionRec;
PlainTextChunk: TGIFPlainTextExtensionRec;
LocalPalette: TColor32List;
ColorCount: Integer;
DMode: Byte;
ret: TLZWDecoderStatus;
{ Chargement palette globale }
Procedure LoadGlobalPalette;
Var
J: Byte;
Begin
If FHasGlobalPalette Then
Begin
// Remise à zero de la palette globale si elle existe sinon création de celle-ci
If FGlobalPalette = nil Then FGlobalPalette := TColor32List.Create
Else
FGlobalPalette.Clear;
PaletteCount := 2 Shl (FLogicalScreenChunk.PackedFields And GIF_COLORTABLESIZE);
// Le cas ou le nombre de couleurs serait plus grand que 256. On prend en charge.
If (PaletteCount < 2) Then //or (PaletteCount>256) then
Raise Exception.Create(rsScreenBadColorSize + ' : ' + IntToStr(PaletteCount));
// On charge la palette
For J := 0 To PaletteCount - 1 Do
Begin
Memory.Read(aRGBColor, SizeOF(TColorRGB24));
aColor.Create(aRGBColor);
FGlobalPalette.AddColor(aColor);
End;
End;
End;
{ Chargement palette locale }
Procedure LoadLocalPalette;
Var
J: Byte;
Begin
// Aucune palette locale n'a été assignée. On en créer une nouvelle. Sinon on efface simplement son contenu.
If LocalPalette = nil Then LocalPalette := TColor32List.Create
Else
LocalPalette.Clear;
// On verifie que le nombre de couleur dans la palette est correcte
ColorCount := (2 Shl (ImageDescriptor.PackedFields And GIF_COLORTABLESIZE));
// Le cas ou le nombre de couleurs serait plus grand que 256. On prend en charge qudn même et on charge la palette.
If (ColorCount < 2) Then //or (ColorCount>256) then
Raise Exception.Create(rsImageBadColorSize + ' : ' + IntToStr(ColorCount));
// On charge la palette
For J := 0 To ColorCount - 1 Do
Begin
Memory.Read(aRGBColor, SizeOF(TColorRGB24));
aColor.Create(aRGBColor);
LocalPalette.AddColor(aColor);
End;
End;
{ Lecture des extensions }
Procedure ReadExtension;
Var
ExtensionID, BlockType: Byte;
BufStr: Array[0..255] Of Char;
Loops: Word;
CurrentExtension : String;
Begin
// On lit les extension jusqu'a ce qu'un bloc de description d'une image soit détecter ou que jusqu'a la fin du fichier
Repeat
//showmessage('Read extension at '+ Memory.Position.ToString);
ExtensionID := Memory.ReadByte;
CurrentExtension :='';
// Si c'est un nouveau marqueur d'introduction d'extension. On lit le nouvel ID
If (ExtensionID = GIF_EXTENSIONINTRODUCER) Then ExtensionID := Memory.ReadByte;
If (ExtensionID = 0) Then
Begin
// On Saute les ID Nul
Repeat
ExtensionID := Memory.ReadByte;
Until (ExtensionID <> 0);
End;
Case ExtensionID Of
GIF_PLAINTEXT:
Begin
BlockSize := Memory.ReadByte;
Memory.Read(PlainTextChunk, SizeOf(TGIFPlainTextExtensionRec));
Repeat
// On lit la taille du bloc. Si Zero alors fin des données de l'extension
BlockSize := Memory.ReadByte;
// On lit la chaine de caractères
If (BlockSize > 0) Then
Begin
fillchar({%H-}BufStr, 256, 0);
Memory.Read(BufStr, BlockSize);
BufStr[BlockSize] := #0;
// On place le texte dans les commentaires
CurrentLayer.Comment.Add(String(BufStr));
End;
Until (BlockSize = 0);
// On ajoute une ligne vide de séparation
CurrentLayer.Comment.Add('');
End;
GIF_COMMENTEXTENSION:
Begin
Repeat
// On lit la taille du commentaire. Si Zero alors fin des données de l'extension
BlockSize := Memory.ReadByte;
// On lit la chaine de caractères
If (BlockSize > 0) Then
Begin
Memory.Read(BufStr, BlockSize);
BufStr[BlockSize] := #0;
// On place le texte dans les commentaires
CurrentLayer.Comment.Add(String(BufStr));
End;
Until (BlockSize <= 0);
// On ajoute une ligne vide de séparation
CurrentLayer.Comment.Add('');
End;
GIF_APPLICATIONEXTENSION:
Begin
BlockSize := Memory.ReadByte;
// Certains vieux filtres d'exportation Adobe, ou d'autres logiciels utilisent par erreur une valeur de 10, ou plus petite ou trop grande
If (BlockSize <> 11) Then
Begin
FillChar(ApplicationExtensionChunk, SizeOf(TGIFApplicationExtensionRec), 0);
End;
//else if (BlockSize<11) then
// Raise Exception.Create('Bad extension size' + ' : ' + inttostr(BlockSize) +' octets. ( Taille valide = 11 octets )');
Memory.Read(ApplicationExtensionChunk, SizeOf(TGIFApplicationExtensionRec));
CurrentExtension := ApplicationExtensionChunk.AppAuthenticationCode;
Repeat
// On lit la taille du bloc. Zero si il n'y a pas de données supplémentaires
BlockSize := Memory.ReadByte;
If (BlockSize > 0) Then
Begin
if UpperCase(CurrentExtension) = 'NETSCAPE' then
begin
BlockType := Memory.ReadByte;
Dec(BlockSize);
Case (BlockType And $07) Of
GIF_LOOPEXTENSION:
Begin
// Lecture du nombre de boucle, Si Zero alors boucle infinie
Loops := Memory.ReadWord;
If Loops > 0 Then Inc(NSLoopExtensionChunk.Loops);
Dec(BlockSize, SizeOf(Loops));
End;
GIF_BUFFEREXTENSION:
Begin
// Lecture de la taille du tampon. Utilisé pour ??????
NSLoopExtensionChunk.BufferSize := Memory.ReadDWord;
Dec(BlockSize, SizeOF(NSLoopExtensionChunk.BufferSize));
End;
else // Extension NETSCAPE inconnue
begin
Memory.SeekForward(BlockSize);
//BlockSize := 0;
end;
End;
end
else
// On saute et on ignore les donnée non lues
If (BlockSize > 0) Then
Begin
Memory.SeekForward(BlockSize);
//BlockSize := 0;
End;
End;
Until (BlockSize = 0);
End;
GIF_GRAPHICCONTROLEXTENSION:
Begin
// On lit la taille de l'extension. Normalement 4 Octets. Cette valeur peut-être erronée. On en tient pas compte ici et on lit les données.
BlockSize := Memory.ReadByte;
//if BlockSize = 4 then
//begin
Memory.Read(GraphicControlExtensionChunk, SizeOf(TGIFGraphicControlExtensionRec));
// On renseigne notre tampon d'informations pour les prochaines images décodées
DMode := ((GraphicControlExtensionChunk.PackedFields And GIF_DISPOSAL_ALL) Shr 2);
With CurrentFrameInfos Do
Begin
// Ces valeurs peuvent être utilisées pour plusieurs image. Elles restent valides jusqu'a la lecture du prochain "GCE" trouvé.
Disposal := TGIFDisposalFlag(DMode);
IsTransparent := (GraphicControlExtensionChunk.PackedFields And GIF_TRANSPARENT_FLAG) <> 0;
UserInput := (GraphicControlExtensionChunk.PackedFields And GIF_USER_INPUT_FLAG) <> 0;
TransparentColorIndex := GraphicControlExtensionChunk.TransparentColorIndex;
BackgroundColorIndex := FLogicalScreenChunk.BackgroundColorIndex;
DelayTime := GraphicControlExtensionChunk.DelayTime;
End;
// Lecture de l'octet de fin de l'extension
Terminator := Memory.ReadByte;
End;
End;
Until (ExtensionID = GIF_IMAGEDESCRIPTOR) Or Memory.EOS;
// Si l'ID pour la description de l'image est détecter on revient en arrière pour la prise en charge par le traitement des données
If (ExtensionID = GIF_IMAGEDESCRIPTOR) Then Memory.Seek(-1, soCurrent);
End;
{ Chargement d'une image }
Procedure LoadImage;
Var
DecoderStatus{%H-}: TLZWDecoderStatus;
BufferSize, TargetBufferSize, BytesRead: Int64;
InitCodeSize: Byte;
OldPosition: Int64;
Buffer, BufferPtr: PByte;
TargetBuffer, TargetBufferPtr: PByte;
LinePtr: PColor32;
Pass, Increment: Byte;
x: Integer;
TargetColor: TColor32;
ColIdx: Byte;
CurrentLine: Integer;
OutBmp: TFastBitmap;
// Decodeur GIF LZW. Basé sour le code source de la bibliothèque GraphicEX pour Delphi
Function DecodeLZW(Var Source, Dest : Pointer; PackedSize, UnpackedSize : Integer): TLZWDecoderStatus;
Const
{ Constantes pour la décompression LZW }
_LZWGIFCodeBits = 12; // Nombre maximal de bits par code d'un jeton (12 bits = 4095)
_LZWGIFCodeMax = 4096; // Nombre maximum de jeton
_LZWGIFStackSize = (2 Shl _LZWGIFCodeBits); // Taille de la pile de décompression
_LZWGIFTableSize = (1 Shl _LZWGIFCodeBits); // Taille de la table de décompression
Var
J: Integer;
Data, // Données actuelle
Bits, // Compteur de bit
Code: Cardinal; // Valeur courrante du Code
SourcePtr: PByte;
InCode: Cardinal; // Tampon pour passé le Code
CodeSize: Cardinal;
CodeMask: Cardinal;
FreeCode: Cardinal;
OldCode: Cardinal;
Prefix: Array[0.._LZWGIFTableSize] Of Cardinal; // LZW prefix
Suffix, // LZW suffix
Stack: Array [0.._LZWGIFStackSize] Of Byte;
StackPointer: PByte;
MaxStackPointer: PBYte;
Target: PByte;
FirstChar: Byte; // Tampon de décodage d'un octet
ClearCode, EOICode: Word;
MaxCode: Boolean;
Begin
Result := dsOk;
DecoderStatus := dsOk;
If (PackedSize <= 0) Or (UnpackedSize <= 0) Then
Begin
// Taille des tampons invalides
If (PackedSize <= 0) And (UnpackedSize <= 0) Then Result := dsInvalidBufferSize
Else If PackedSize <= 0 Then Result := dsInvalidInputBufferSize
Else If UnpackedSize <= 0 Then Result := dsInvalidOutputBufferSize;
Exit;
End;
// Initialisation des paramètres pour la décompression
CodeSize := InitCodeSize + 1;
ClearCode := 1 Shl InitCodeSize;
EOICode := ClearCode + 1;
FreeCode := ClearCode + 2;
OldCode := _LZWGIFCodeMax - 1;
CodeMask := (1 Shl CodeSize) - 1;
MaxCode := False;
Code := 0;
Target := PByte(Dest);
SourcePtr := PByte(Source);
// Initialisation des tables de Code
For J := 0 To _LZWGIFTableSize Do
Begin
Prefix[J] := _LZWGIFCodeMax;
Suffix[J] := J;
End;
// Initalisation de la pile
StackPointer := @Stack;
MaxStackPointer := @Stack[_LZWGIFStackSize];
FirstChar := 0;
Data := 0;
Bits := 0;
While (UnpackedSize > 0) And (PackedSize > 0) Do
Begin
// On lit le "Code" dans le tampon d'entrée
Inc(Data, SourcePtr^ Shl Bits);
Inc(Bits, 8);
While (Bits > CodeSize) And (UnpackedSize > 0) Do
Begin
// Code actuel
Code := Data And CodeMask;
// Préparation pour la donnée suivante
Data := Data Shr CodeSize;
Dec(Bits, CodeSize);
// Décompression finie ?
If Code = EOICode Then
Begin
// Si nous arrivons ici, il y a probablement quelque chose de suspect avec l'image GIF
// Car normalement on stoppe dès que le tampon de sortie est plein.
// Cela signifie que nous ne lirons jamais l'EOICode de fermeture dans les images normales.
// Comme l'état du buffer est déjà vérifié après la boucle principale, nous ne le ferons pas ici.
Break;
End;
// On vérifie s'il s'agit d'un code valide déjà enregistré
If Code > FreeCode Then
Begin
// Code ne peux à être supérieur à FreeCode. Nous avons donc une image cassée.
// On notifie l'erreur à l'utilisateur. Et on considère qu'il n'ya pas d'erreur.
DecoderStatus := dsInvalidInput;
AddError(Format(rsLZWInvalidInput,[CurrentFrameIndex]));
//NotifyUser('Le décodeur a rencontré une entrée invalide (données corrompues)');
Code := ClearCode;
//Break; //Ici, on continue le chargement du reste de l'image au lieu de le stopper
End;
// RAZ
If Code = ClearCode Then
Begin
// réinitialisation de toutes les variables
CodeSize := InitCodeSize + 1;
CodeMask := (1 Shl CodeSize) - 1; //CodeMasks[CodeSize];
FreeCode := ClearCode + 2;
OldCode := _LZWGIFCodeMax;
MaxCode := False;
End
Else If OldCode = _LZWGIFCodeMax Then
Begin
// Gestion du premier Code LZW : On le définit dans le tampon de sortie et on le conserve
FirstChar := Suffix[Code];
Target^ := FirstChar;
Inc(Target);
Dec(UnpackedSize);
OldCode := Code;
End
Else
Begin
//On conserve le Code LZW actuel
InCode := Code;
// On place le nouveau code LZW sur la pile sauf quand nous avons déjà utilisé tous les codes disponibles
If (Code = FreeCode) And Not MaxCode Then
Begin
StackPointer^ := FirstChar;
Inc(StackPointer);
Code := OldCode;
End;
// boucle pour placer les octets décodés sur la pile
While Code > ClearCode Do
Begin
StackPointer^ := Suffix[Code];
If StackPointer >= MaxStackPointer Then
Begin
// Ne doit jamais arriver, c'est juste une précaution au cas ou.
Result := dsBufferOverflow;
break;
End;
Inc(StackPointer);
Code := Prefix[Code];
End;
If Result <> dsOK Then break; // Si il ya eu des erreurs on ne va pas plus loin
// Place le nouveau Code dans la table
FirstChar := Suffix[Code];
StackPointer^ := FirstChar;
Inc(StackPointer);
//Transfert des données décodées vers notre tampon de sortie
Repeat
If UnpackedSize <= 0 Then
Begin
// Le tampon de sortie est trop petit. On ne va pas plus loin
// On notifie l'erreur à l'utilisateur. Et on considère qu'il n'ya pas d'erreur.
// Afin de pouvoir afficher le GIF et continuer le chargement des images suivantes
Result := dsOutputBufferTooSmall;
AddError(Format(rsLZWOutputBufferTooSmall,[CurrentFrameIndex]));
break;
End;
Dec(StackPointer);
Target^ := StackPointer^;
Inc(Target);
Dec(UnpackedSize);
Until StackPointer = @Stack;
If Result <> dsOK Then break;
If Not MaxCode Then
Begin
If FreeCode <= _LZWGIFCodeMax Then
Begin
Prefix[FreeCode] := OldCode;
Suffix[FreeCode] := FirstChar;
End
Else If FreeCode > _LZWGIFCodeMax Then
Begin
// On a intercepter une donnée corrompue. On continue quand la même décompression sans en tenir compte.
// On notifie juste l'erreur à l'utilisateur
DecoderStatus := dsInvalidInput;
AddError(Format(rsLZWInvalidInput,[CurrentFrameIndex]));
FreeCode := _LZWGIFCodeMax;
Prefix[FreeCode] := OldCode;
Suffix[FreeCode] := FirstChar;
//MaxCode := True;
End;
// On augmente la taille du Code si nécessaire
If (FreeCode = CodeMask) And Not (MaxCode) Then
Begin
If (CodeSize < _LZWGIFCodeBits) Then
Begin
Inc(CodeSize);
CodeMask := (1 Shl CodeSize) - 1;//CodeMasks[CodeSize];
End
Else //On a atteind la limite maximum
MaxCode := True;
End;
If FreeCode < _LZWGIFTableSize Then Inc(FreeCode);
End;
OldCode := InCode;
End;
End;
Inc(SourcePtr);
Dec(PackedSize);
If (Result <> dsOK) Or (Code = EOICode) Then Break;
End;
If Result = dsOK Then
Begin
// On vérifie seulement si il n'ya pas eu d'erreur. Si ce n'est pas le cas, nous savons déjà que quelque chose ne va pas.
// Notez qu'il est normal que PackedSize soit un peu> 0 parce que nous pouvons
// pas lire l'EOICode mais arrêter dès que notre tampon de sortie est plein et
// qui devrait normalement être le code juste avant l'EOICode.
If PackedSize < 0 Then
Begin
Result := dsInternalError;
// C'est une erreur sérieuse : nous avons eu un dépassement de tampon d'entrée que nous aurions dû intercepter. Nous devons arrêter maintenant.
Raise Exception.Create(rsLZWInternalErrorInputBufferOverflow);
Exit;
End;
If UnpackedSize <> 0 Then
Begin
//if UnpackedSize > 0 then
//begin
// // Image corrompue
// DecoderStatus := dsNotEnoughInput;
// AddError('Image #'+CurrentFrameIndex)+' : Le décodeur n''a pas pu décoder toutes les données car le tampon d''entrée est trop petit');
// //NotifyUser('Le décodeur n''a pas pu décoder toutes les données car le tampon d''entrée est trop petit');
//End
//else
If UnpackedSize < 0 Then
Begin
Result := dsInternalError;
// C'est une erreur sérieuse : nous avons eu un dépassement de tampon de sortie que nous aurions dû intercepter. Nous devons arrêter maintenant.
Raise Exception.Create(rsLZWInternalErrorOutputBufferOverFlow);
End;
End;
End;
End;
Begin
BufferSize := 0;
TargetBufferSize := 0;
// On lit la description de l'image
Memory.Read(ImageDescriptor, SizeOf(TGIFImageDescriptorRec));
// On vérifie que les dimensions sont correctes.
// Si on trouve des dimensions à zero, il se peut qu'il faudra traiter
// une extension PlainText et dessiner ce texte en fonction des paramètres
If (ImageDescriptor.Height = 0) Or (ImageDescriptor.Width = 0) Then
Begin
// On assigne les dimensions par défaut du GIF
ImageDescriptor.Width := FLogicalScreenChunk.ScreenWidth;
ImageDescriptor.Height := FLogicalScreenChunk.ScreenHeight;
// On notifie à l'utilisateur que les dimensions de l'image sont erronée. Mais on tente le chargement quand même
// ShowMessage
End;
// Dans le cas ou les dimensions de l'image sont incorrectes dans "l'image descriptor". Ou que la taille des données compressées soit erronée.
If (ImageDescriptor.Width > FLogicalScreenChunk.ScreenWidth) Or (ImageDescriptor.Height > FLogicalScreenChunk.ScreenHeight) Then
Begin
// On assigne les dimensions par défaut du GIF
If (ImageDescriptor.Width > FLogicalScreenChunk.ScreenWidth) Then ImageDescriptor.Width := FLogicalScreenChunk.ScreenWidth;
If (ImageDescriptor.Height > FLogicalScreenChunk.ScreenHeight) Then ImageDescriptor.Height := FLogicalScreenChunk.ScreenHeight;
// On notifie à l'utilisateur que les dimensions de l'image sont erronée. Mais on tente le chargement quand même
// ShowMessage
End;
// On renseigne notre tampon d'informations
With CurrentFrameInfos Do
Begin
Left := ImageDescriptor.Left;
Top := ImageDescriptor.Top;
Width := ImageDescriptor.Width;
Height := ImageDescriptor.Height;
Interlaced := (ImageDescriptor.PackedFields And GIF_INTERLACED) = GIF_INTERLACED;
HasLocalPalette := (ImageDescriptor.PackedFields And GIF_LOCALCOLORTABLE) = GIF_LOCALCOLORTABLE;
End;
// L'image possède-t-elle sa propre palette de couleur ? Si oui on la charge.
If CurrentFrameInfos.HasLocalPalette Then LoadLocalPalette;
// Decompression de l'image
// On ajoute une nouvelle image si besoin
If (FCurrentLayerIndex > 0) And (FCurrentLayerIndex > FFrames.Count - 1) Then CurrentLayer := FFrames.AddNewImage;
// On assigne la nouvelle image au Bitmap de travail
OutBmp := FFrames.Items[CurrentFrameIndex].Bitmap;
// On met à jour les informations
With FFrames.Items[FCurrentLayerIndex] Do
Begin
Drawmode := CurrentFrameInfos.Disposal;
// Showmessage('#'+inttostr(FCurrentLayerIndex) + 'DrawMode : '+ GifGCEDisposalModeStr[Drawmode]);
Left := CurrentFrameInfos.Left;
Top := CurrentFrameInfos.Top;
IsTransparent := CurrentFrameInfos.IsTransparent;
If CurrentFrameInfos.DelayTime = 0 Then Delay := GIF_DefaultDelay
Else
Delay := CurrentFrameInfos.DelayTime * GIF_DelayFactor;
End;
// On lit le code d'initalisation de la compression LZW
InitCodeSize := Memory.ReadByte;
If InitCodeSize < 2 Then InitCodeSize := 2;
If InitCodeSize > 8 Then InitCodeSize := 8;
// On sauve la position actuelle dans le flux
OldPosition := Memory.position;
BufferSize := 0;
// 1) On comptabilise la taille totale des données compresser. Afin de les décompresser en une seule fois.
// On lit la taille du premier bloc
BlockSize := Memory.ReadByte;
While (BlockSize > 0) And Not (Memory.EOS) Do
Begin
Inc(BufferSize, BlockSize);
// On saute les données
Memory.SeekForward(BlockSize);
If Not (Memory.EOS) Then BlockSize := Memory.ReadByte
Else
blocksize := 0;
End;
// 2) On initalise notre bitmap avec les bonnes dimensions
OutBmp.SetSize(CurrentFrameInfos.Width, CurrentFrameInfos.Height);
BufferPtr := nil;
Buffer := nil;
// 3) On alloue notre tampon pour les données compressées
If (BufferSize > 0) Then Reallocmem(Buffer, BufferSize);
// 4) On charge toutes les données dans notre tampon
// On se replace au début des données
Memory.Seek(OldPosition, soBeginning);
// On travail toujours sur une copie du "pointer"
BufferPtr := Buffer;
// On lit la taille du premier bloque
BlockSize := Memory.ReadByte;
While (BlockSize > 0) And Not (Memory.EOS) Do
Begin
// On charge les données dans le tampon. On previent des erreurs en cas de dépassements
BytesRead := Memory.Read(BufferPtr^, BlockSize);
Inc(BufferPtr, BytesRead);
If Not (Memory.EOS) Then BlockSize := Memory.ReadByte
Else
blocksize := 0;
End;
// On se replace au debut du tampon
BufferPtr := Buffer;
// 5) On decompresse les données
// On initialise notre buffer ou seront décompressées les données
TargetBufferSize := Int64(CurrentFrameInfos.Width) * Int64(CurrentFrameInfos.Height);
TargetBufferPtr := nil;
TargetBuffer := nil;
// Si la taille est plus grande que zero, on alloue l'espace nécessaire à notre tampon
If (TargetBufferSize > 0) Then Reallocmem(TargetBuffer, TargetBufferSize);
// Décodage des données compressées
Ret := DecodeLZW(Buffer, TargetBuffer, BufferSize, TargetBufferSize);
// 6) On transfert les données de l'image vers notre bitmap. Si il n'y a pas eu d'erreurs
If (Ret = dsOk) Then
Begin
TargetBufferPtr := TargetBuffer;
OutBmp.Clear(clrTransparent);
// Image non entrelacée
If Not (CurrentFrameInfos.Interlaced) Then
Begin
CurrentLine := 0;
While (CurrentLine <= CurrentFrameInfos.Height - 1) Do
Begin
LinePtr := OutBmp.GetScanLine(CurrentLine);// FFrames.Items[CurrentFrameIndex].Bitmap.GetScanLine(CurrentLine);
For x := 0 To (CurrentFrameInfos.Width - 1) Do
Begin
// Lecture de l'index de la couleur dans la palette
ColIdx := TargetBufferPtr^;
// On utilise la palette de couleur locale
If CurrentFrameInfos.HasLocalPalette Then
Begin
If LocalPalette <> nil Then // La palette est-elle chargée ?
Begin
//if (ColIdx> ColorCount-1) then ColIdx := ColorCount -1;
If (ColIdx < ColorCount) Then TargetColor := LocalPalette.Colors[ColIdx].Value
Else
TargetColor := clrTransparent;
End
Else If FGlobalPalette <> nil Then // Non, alors on utilise la palette globale si elle est présente
Begin
//if (ColIdx> PaletteCount-1) then ColIdx := PaletteCount -1;
If (ColIdx < PaletteCount) Then TargetColor := FGlobalPalette.Colors[ColIdx].Value
Else
TargetColor := clrTransparent;
End
Else
Begin
AddError(rsEmptyColorMap);
Exit;
End;
End
Else // On utilise la palette de couleur globale
Begin
If FGlobalPalette <> nil Then
Begin
//if (ColIdx> PaletteCount-1) then ColIdx := PaletteCount -1;
If (ColIdx < PaletteCount) Then TargetColor := FGlobalPalette.Colors[ColIdx].Value
Else
TargetColor := clrTransparent;
End
Else If LocalPalette <> nil Then
Begin
//if (ColIdx> ColorCount-1) then ColIdx := ColorCount -1;
If (ColIdx > ColorCount - 1) Then //ColIdx := ColorCount -1;
TargetColor := LocalPalette.Colors[ColIdx].Value
Else
TargetColor := clrTransparent;
End
Else
Begin
AddError(rsEmptyColorMap);
Exit;
End;
End;
If CurrentFrameInfos.IsTransparent Then
Begin
If FHasGlobalPalette Then If ColIdx < FGlobalPalette.Count Then OutBmp.TransparentColor := FGlobalPalette.Colors[ColIdx].Value.ToColor
Else If ColIdx < LocalPalette.Count Then OutBmp.TransparentColor := LocalPalette.Colors[ColIdx].Value.ToColor;
If (Self.FTransparent) Then
Begin
If (ColIdx = CurrentFrameInfos.TransparentColorIndex) Then
begin
TargetColor.Alpha := 0; // clrTransparent;
end;
If (CurrentFrameInfos.TransparentColorIndex = CurrentFrameInfos.BackgroundColorIndex) Then FbackgroundColor.Alpha := 0; //clrTransparent;
End;
End;
LinePtr^ := TargetColor;
// On avance de 1 élément dans nos "pointer"
Inc(TargetBufferPtr);
Inc(LinePtr);
End;
Inc(CurrentLine);
End;
End
Else // Image entrelacée
Begin
CurrentLine := 0;
For pass := 0 To 3 Do
Begin
Case Pass Of
0:
Begin
CurrentLine := 0;
Increment := 8;
End;
1:
Begin
CurrentLine := 4;
Increment := 8;
End;
2:
Begin
CurrentLine := 2;
Increment := 4;
End;
Else
Begin
CurrentLine := 1;
Increment := 2;
End;
End;
While (CurrentLine < CurrentFrameInfos.Height) Do
Begin
LinePtr :=OutBmp.GetScanLine(CurrentLine); // FFrames.Items[CurrentFrameIndex].Bitmap
For x := 0 To (FFrames.Items[CurrentFrameIndex].Bitmap.Width - 1) Do
Begin
// Lecture de l'index de la couleur dans la palette
ColIdx := TargetBufferPtr^;
// On utilise la palette de couleur locale
If CurrentFrameInfos.HasLocalPalette Then
Begin
If LocalPalette <> nil Then // La palette est-elle chargée ?
Begin
If (ColIdx < ColorCount) Then // Dans le cas contraire il s'agit d'un index pour la transparence
TargetColor := LocalPalette.Colors[ColIdx].Value;
End
Else If FGlobalPalette <> nil Then // Non, alors on utilise la palette globale si elle est présente
Begin
If (ColIdx < PaletteCount) Then //if (ColIdx< PaletteCount-1) then ColIdx := PaletteCount -1;
TargetColor := FGlobalPalette.Colors[ColIdx].Value;
End
Else
Begin
AddError(rsEmptyColorMap);
Exit;
End;
End
Else // On utilise la palette de couleur globale
Begin
If FGlobalPalette <> nil Then
Begin
If (ColIdx > PaletteCount - 1) Then ColIdx := PaletteCount - 1;
TargetColor := FGlobalPalette.Colors[ColIdx].Value;
End
Else If LocalPalette <> nil Then
Begin
If (ColIdx > ColorCount - 1) Then ColIdx := ColorCount - 1;
TargetColor := LocalPalette.Colors[ColIdx].Value;
End
Else
Begin
AddError(rsEmptyColorMap);
Exit;
End;
End;
If CurrentFrameInfos.IsTransparent Then
Begin
If FHasGlobalPalette Then If ColIdx < FGlobalPalette.Count Then OutBmp.TransparentColor := FGlobalPalette.Colors[ColIdx].Value.ToColor
Else If ColIdx < LocalPalette.Count Then OutBmp.TransparentColor := LocalPalette.Colors[ColIdx].Value.ToColor;
If (FTransparent) Then
Begin
If CurrentFrameInfos.TransparentColorIndex = colIdx Then
begin
TargetColor.Alpha := 0; // := clrTransparent;
End;
If (CurrentFrameInfos.TransparentColorIndex = CurrentFrameInfos.BackgroundColorIndex) Then FBackgroundColor.Alpha := 0;
End;
End;
LinePtr^ := TargetColor;
Inc(TargetBufferPtr);
If (CurrentLine < CurrentFrameInfos.Height - 1) Then Inc(LinePtr);
End;
Inc(CurrentLine, Increment);
End;
End;
End;
if DecoderStatus <> dsOk then
begin
//outBmp.Clear(ClrTransparent);
FFrames.Items[FCurrentLayerIndex].IsCorrupted := True;
FFrames.Items[FCurrentLayerIndex].Delay:= 1;
End;
Inc(FCurrentLayerIndex); // Index pour la prochaine image
End
Else
Begin
Case Ret Of
dsInvalidBufferSize: AddError(Format(rsInvalidBufferSize,[CurrentFrameIndex]));
dsInvalidInputBufferSize: AddError(Format(rsInvalidInputBufferSize,[CurrentFrameIndex]));
dsInvalidOutputBufferSize: AddError(Format(rsInvalidOutputBufferSize,[CurrentFrameIndex]));
dsBufferOverflow: AddError(Format(rsBufferOverFlow,[CurrentFrameIndex]));
dsOutputBufferTooSmall :
(* begin
// On supprime l'image. Le tampon de sortie étant trop petit, cela va générer des erreurs lors du transfert des données décompressées vers l'image
//FFrames.Delete(CurrentFrameIndex);
end;*)
dec(FCurrentLayerIndex);
End;
if Ret<>dsOutputBufferTooSmall then
begin
FFrames.Items[FCurrentLayerIndex].IsCorrupted := True;
FFrames.Items[FCurrentLayerIndex].Delay:= 1;
end;
End;
// On libére la mémoire allouée pour nos tampons
If (TargetBufferSize > 0) And (targetBuffer <> nil) Then FreeMem(TargetBuffer);
If (BufferSize > 0) And (Buffer <> nil) Then FreeMem(Buffer);
End;
Begin
PaletteCount := 0;
ColorCount := 0;
LocalPalette := nil;
FFrames.Clear;
// Par defaut, on considère que la couleur de fond est totalement transparente
FBackgroundColor := clrTransparent;
// Si une palette globale existe, alors on charge
LoadGlobalPalette;
If FHasGlobalPalette Then
Begin
If FLogicalScreenChunk.BackgroundColorIndex < PaletteCount - 1 Then FBackgroundColor := FGlobalPalette.Colors[FLogicalScreenChunk.BackgroundColorIndex].Value
Else
Begin
FBackgroundColor := clrTransparent; //FGlobalPalette.Colors[FLogicalScreenChunk.BackgroundColorIndex].Value;
End;
End;
// Les valeurs suivante seront renseignées lors du chargement d'une image
// On réinitialise juste les valeurs par défaut des informations de l'image en cours au cas ou il n'y aurait pas de GCE
With CurrentFrameInfos Do
Begin
Left := 0;
Top := 0;
Width := FLogicalScreenChunk.ScreenWidth;
Height := FLogicalScreenChunk.ScreenHeight;
Interlaced := False;
HasLocalPalette := False;
IsTransparent := False;
End;
// On ajoute l'image de départ afin de pouvoir assigner les valeurs des premières extensions (Extensions déclarées avant l'image)
CurrentLayer := FFrames.AddNewImage;
// On efface l'image avec la couleur de fond
//CurrentLayer.Bitmap.Clear(FBackgroundColor);
FCurrentLayerIndex := 0;
// On lit le 1er octet
Done := False;
While Not (Done) Do
Begin
// On verifie l'existence d'extensions avant les données de l'image (Application, Graphic Control, PlainText, Comment)
If Not (Memory.EOS) Then BlockID := Memory.ReadByte
Else
BlockID := GIF_Trailer;
If (BlockID = GIF_Trailer) Then
Begin
Done := True;
End;
If (BlockID = 0) Then
Begin
// On Saute les ID Nul
While (BlockId = 0) Do BlockId := Memory.ReadByte;
End
Else If (BlockID = GIF_IMAGEDESCRIPTOR) Then // C'est une image
Begin
// On charge l'image
LoadImage;
End
Else If (BlockID = GIF_EXTENSIONINTRODUCER) Then // c'est une extension
Begin
ReadExtension; // On charge toutes les extensions qui sont à la suite
End
Else
Begin
// Extension inconnue on saute jusqu'a trouver un ZERO.
// A Verifier avec le flag UseInput dans le "Graphic Control Extension"
// Ici on ignore simplement les données
While BlockID <> 0 Do
Begin
BlockID := Memory.ReadByte;
End;
End;
End;
// Si il y a des erreurs elles seront notifier à l'utilisateur
NotifyError;
// Il n'y a aucune images on notifie l'erreur
If FFrames.Count = 0 Then Raise Exception.Create(rsEmptyImage);
// On libere la mémoire, prise par nos palettes de couleurs si besoin
If (LocalPalette <> nil) Then
Begin
FreeAndNil(LocalPalette);
End;
If (FGlobalPalette <> nil) Then
Begin
FreeAndNil(FGlobalPalette);
End;
End;
{%endregion%}
{%region=====[ TGIFRenderCacheListItem ]========================================}
Constructor TGIFRenderCacheListItem.Create;
Begin
Inherited Create;
FBitmap := Graphics.TBitmap.Create;
FDelay := 0;
End;
Destructor TGIFRenderCacheListItem.Destroy;
Begin
FreeAndNil(FBitmap);
Inherited Destroy;
End;
{%endregion%}
{%region=====[ TGIFRenderCacheList ]============================================}
Function TGIFRenderCacheList.GetItems(Index : Integer): TGIFRenderCacheListItem;
Begin
Result := TGIFRenderCacheListItem(Inherited Items[Index]);
End;
Procedure TGIFRenderCacheList.SetItems(Index : Integer; AGIFRenderCache : TGIFRenderCacheListItem);
Begin
Put(Index, AGIFRenderCache);
End;
Procedure TGIFRenderCacheList.Clear;
Var
anItem: TGIFRenderCacheListItem;
i: Integer;
Begin
If Count > 0 Then
Begin
For i := Count - 1 Downto 0 do
Begin
AnItem := Items[i];
If anItem <> nil Then anItem.Free;
End;
End;
Inherited Clear;
End;
Function TGIFRenderCacheList.AddNewCache: TGIFRenderCacheListItem;
Var
anItem: TGIFRenderCacheListItem;
Begin
anitem := TGIFRenderCacheListItem.Create;
Add(anItem);
Result := Items[Self.Count - 1];
End;
Function TGIFRenderCacheList.Add(AGIFRenderCache : TGIFRenderCacheListItem): Integer;
Begin
Result := Inherited Add(AGIFRenderCache);
End;
Function TGIFRenderCacheList.Extract(Item : TGIFRenderCacheListItem): TGIFRenderCacheListItem;
Begin
Result := TGIFRenderCacheListItem(Inherited Extract(Item));
End;
Function TGIFRenderCacheList.Remove(AGIFRenderCache : TGIFRenderCacheListItem): Integer;
Begin
Result := Inherited Remove(AGIFRenderCache);
End;
Function TGIFRenderCacheList.IndexOf(AGIFRenderCache : TGIFRenderCacheListItem): Integer;
Begin
Result := Inherited IndexOf(AGIFRenderCache);
End;
Function TGIFRenderCacheList.First: TGIFRenderCacheListItem;
Begin
Result := TGIFRenderCacheListItem(Inherited First);
End;
Function TGIFRenderCacheList.Last: TGIFRenderCacheListItem;
Begin
Result := TGIFRenderCacheListItem(Inherited Last);
End;
Procedure TGIFRenderCacheList.Insert(Index : Integer; AGIFRenderCache : TGIFRenderCacheListItem);
Begin
Inherited Insert(Index, AGIFRenderCache);
End;
Function TGIFRenderCacheList.IsIndexOk(anIndex: Integer): Boolean;
Begin
Result := True;
If (anIndex < 0) or (anIndex > Count-1) then result := False;
End;
Procedure TGIFRenderCacheList.Pack;
Var
i: Integer;
Begin
if Count>1 then
begin
I := 0;
While I<Count do
begin
if Items[I].IsCorrupted then
begin
Remove(Items[I]);
break;
End;
inc(I);
End;
if I<Count then Pack;
End;
End;
{%endregion%}
{%region=====[ TGIFViewer ]=====================================================}
Constructor TGIFViewer.Create(AOwner: TComponent);
Begin
Inherited Create(AOwner);
ControlStyle := [csCaptureMouse, csClickEvents, csDoubleClicks];
AutoSize := False;
FCenter := False;
FStretch := False;
FTransparent := True;
With GetControlClassDefaultSize Do SetInitialBounds(0, 0, CX, CY);
FRestoreBitmap := nil;
FRenderCache := TGIFRenderCacheList.Create(False);
FGIFLoader := TGIFImageLoader.Create;
FGIFLoader.OnLoadError := @DoInternalOnLoadError;
FVirtualView := TFastBitmap.Create;
FCurrentView := nil;
FCurrentView := Graphics.TBitmap.Create;
FRestoreBitmap := nil;
FAutoPlay := False;
FBorderShow := False;
FBorderColor := clBlack;
FBorderWidth := 1;
FBevelInner := bvNone;
FBevelOuter := bvNone;
FBevelWidth := 1;
FColor := clNone;
FDisplayInvalidFrames := False;
FAutoRemoveInvalidFrame := True;
FLastDrawMode := dmNone;
FAnimateTimer := TTimer.Create(nil);
With FAnimateTimer Do
Begin
Enabled := False;
Interval := 1000;
OnTimer := @DoTimerAnimate;
End;
FAnimateSpeed := 1;
FCurrentFrameIndex := 0;
FGIFWidth := 90;
FGIFHeight := 90;
FAutoStretchMode := smManual;
End;
Destructor TGIFViewer.Destroy;
Begin
FAnimateTimer.Enabled := False;
FreeAndNil(FAnimateTimer);
If FCurrentView <> nil Then FreeAndNil(FCurrentView);
If FRestoreBitmap <> nil Then FreeAndNil(FRestoreBitmap);
FreeAndNil(FVirtualView);
FRenderCache.Clear;
FreeAndNil(FRenderCache);
FreeAndNil(FGIFLoader);
Inherited Destroy;
End;
Procedure TGIFViewer.SetCenter(Const Value: Boolean);
Begin
If Value = FCenter Then exit;
FCenter := Value;
Invalidate;
End;
Function TGIFViewer.GetCanvas: TCanvas;
Begin
Result := Inherited Canvas;// FCurrentView.Canvas
End;
Function TGIFViewer.GetFrameCount: Integer;
Begin
If FCache Then
Result := FRenderCache.Count
Else Begin
Result := FGifLoader.FrameCount;
End;
End;
Function TGIFViewer.GetGIFVersion: String;
Begin
Result := FGIFLoader.Version;
End;
Function TGIFViewer.GetRawFrameItem(Index : Integer): TGIFImageListItem;
Begin
Result := nil;
If (Index >= 0) And (Index < FGIFLoader.FrameCount) Then Result := FGIFLoader.Frames[Index];
end;
Procedure TGIFViewer.SetAutoStretchMode(AValue: TGIFAutoStretchMode);
Begin
If FAutoStretchMode = AValue Then Exit;
FAutoStretchMode := AValue;
Invalidate;
End;
Procedure TGIFViewer.SetStretch(Const Value: Boolean);
Begin
If Value = FStretch Then exit;
FStretch := Value;
Invalidate;
End;
Procedure TGIFViewer.SetPause(Const Value: Boolean);
Begin
If Value = FPause Then exit;
FPause := Value;
If FPause Then FAnimateTimer.Enabled := False;
If Assigned(FOnPause) Then FOnPause(Self);
End;
Procedure TGIFViewer.SetFileName(Const Value: String);
Begin
If Value = FFileName Then exit;
FFileName := Value;
LoadFromFile(FFileName);
End;
Function TGIFViewer.GetFrame(Const Index: Integer): Graphics.TBitmap;
Begin
Result := nil;
If (Index >= 0) And (Index < FrameCount) Then Result := FRenderCache.Items[Index].Bitmap;
End;
Procedure TGIFViewer.SetTransparent(Const Value: Boolean);
Begin
If FTransparent = Value Then exit;
FTransparent := Value;
FGIFLoader.Transparent := Value;
If FFileName <> '' Then LoadFromFile(FFileName);
End;
Procedure TGIFViewer.SetBevelWidth(Const Value: TBevelWidth);
Begin
If FBevelWidth <> Value Then
Begin
FBevelWidth := Value;
Invalidate;
End;
End;
Procedure TGIFViewer.ResetCurrentView;
Var
I: Integer;
Corrupted : Boolean;
begin
if FRenderCache.Count>1 then
begin
if not(FDisplayInvalidFrames) then
begin
Corrupted := false;
i := 0;
Repeat
Corrupted := FRenderCache.Items[i].IsCorrupted;
inc(i);
until (i>FRenderCache.Count-1) or (Corrupted = false);
if (i>FRenderCache.Count-1) and (Corrupted = true) then
begin
Raise Exception.Create(rsAllFrameCorrupted);
exit;
end
else
begin
Dec(i);
FCurrentframeIndex := i;
FAnimateTimer.Interval := FRenderCache.Items[i].Delay;
FCurrentView.Assign(FRenderCache.Items[i].Bitmap);
end;
end
else
begin
FAnimateTimer.Interval := FRenderCache.Items[0].Delay;
FCurrentView.Assign(FRenderCache.Items[0].Bitmap);
end;
end
else
begin
FCurrentView.Assign(FRenderCache.Items[0].Bitmap);
end;
FLastDrawMode := dmNone;
End;
Procedure TGIFViewer.SetBevelInner(Const Value: TPanelBevel);
Begin
If BevelInner <> Value Then
Begin
FBevelInner := Value;
Invalidate;
End;
End;
Procedure TGIFViewer.SetBevelOuter(Const Value: TPanelBevel);
Begin
If BevelOuter <> Value Then
Begin
FBevelOuter := Value;
Invalidate;
End;
End;
Procedure TGIFViewer.DoInternalOnLoadError(Sender: TObject; Const ErrorCount: Integer; Const ErrorList: TStringList);
Begin
If Assigned(FOnLoadError) Then FOnloadError(Self, ErrorCount, ErrorList);
End;
Procedure TGIFViewer.DoTimerAnimate(Sender: TObject);
Begin
Inc(FCurrentFrameIndex);
If FCurrentFrameIndex > (FGIFLoader.FrameCount - 1) Then FCurrentFrameIndex := 0;
If (not FCache) and (FCurrentFrameIndex >= FRenderCache.Count) Then
Begin
RenderFrame(FCurrentFrameIndex);
End;
If Assigned(FOnFrameChange) Then FOnFrameChange(Self);
if not(FDisplayInvalidFrames) then
begin
if not(FRenderCache.Items[FCurrentFrameIndex].IsCorrupted) then
begin
FAnimateTimer.Interval := FRenderCache.Items[FCurrentFrameIndex].Delay;
FCurrentView.Assign(FRenderCache.Items[FCurrentFrameIndex].Bitmap);
End
else FAnimateTimer.Interval := FRenderCache.Items[FCurrentFrameIndex].Delay;
end
else
begin
FAnimateTimer.Interval := FRenderCache.Items[FCurrentFrameIndex].Delay;
FCurrentView.Assign(FRenderCache.Items[FCurrentFrameIndex].Bitmap);
end;
Invalidate;
End;
Procedure TGIFViewer.RenderFrame(Index: Integer);
Var
Src: TFastBitmap;
pTop, pLeft: Integer;
iDrawMode: TFastBitmapDrawMode;
TmpBmp : Graphics.TBitmap;
Begin
Src := FGIFLoader.Frames.Items[Index].Bitmap;
pLeft := FGIFLoader.Frames.Items[Index].Left;
pTop := FGIFLoader.Frames.Items[Index].Top;
FRenderCache.AddNewCache;
FRenderCache.Items[Index].Delay := FGIFLoader.Frames[Index].Delay * FAnimateSpeed;
FRenderCache.Items[Index].IsCorrupted := FGIFLoader.Frames[Index].IsCorrupted;
If (FTransparent) Then
Begin
iDrawMode := dmAlphaCheck;
End
Else
Begin
iDrawMode := dmSet;
End;
If Index = 0 Then
Begin
If (FTransparent) Then
Begin
FVirtualView.Clear(clrTransparent);
End
Else
Begin
FVirtualView.Clear(FGIFLoader.BackgroundColor);
End;
FVirtualView.PutImage(Src, 0, 0, Src.Width, Src.Height, pLeft, pTop, dmSet);
if FGIFLoader.Frames.Items[0].DrawMode = dmKeep then begin
if Assigned( FRestoreBitmap) then begin
FRestoreBitmap.Free;
end;
FRestoreBitmap := FVirtualView.Clone;
end;
End
Else
Begin
With FGIFLoader.Frames.Items[Index] Do
Begin
Case DrawMode Of
dmNone:
Begin
FVirtualView.PutImage(Src, 0, 0, Src.Width, Src.Height, pLeft, pTop, iDrawMode);
End;
dmKeep:
Begin
if FLastDrawMode = dmErase then
begin
If (FGIFLoader.Frames.Items[Index].IsTransparent And FTransparent) Then FVirtualView.Clear(clrTransparent)
Else
FVirtualView.Clear(FGIFLoader.BackgroundColor);
end;
FVirtualView.PutImage(Src, 0, 0, Src.Width, Src.Height, pLeft, pTop, iDrawMode);
If Assigned(FRestoreBitmap) Then FreeAndNil(FRestoreBitmap);
FRestoreBitmap := FVirtualView.Clone;
End;
dmErase:
Begin
If (FGIFLoader.Frames.Items[Index].IsTransparent And FTransparent) Then FVirtualView.Clear(clrTransparent)
Else
FVirtualView.Clear(FGIFLoader.BackgroundColor);
FVirtualView.PutImage(Src, 0, 0, Src.Width, Src.Height, pLeft, pTop, iDrawMode);
End;
dmRestore:
Begin
if FLastDrawMode = dmErase then
begin
If (FGIFLoader.Frames.Items[Index].IsTransparent And FTransparent) Then FVirtualView.Clear(clrTransparent)
Else
FVirtualView.Clear(FGIFLoader.BackgroundColor);
End;
If Assigned(FRestoreBitmap) Then FVirtualView.PutImage(FRestoreBitmap, 0, 0, FRestoreBitmap.Width, FRestoreBitmap.Height, 0, 0, dmSet)
else
begin
If (FGIFLoader.Frames.Items[Index].IsTransparent And FTransparent) Then FVirtualView.Clear(clrTransparent)
Else
FVirtualView.Clear(FGIFLoader.BackgroundColor);
end;
FVirtualView.PutImage(Src, 0, 0, Src.Width, Src.Height, pLeft, pTop, iDrawMode);
End;
Else
FVirtualView.PutImage(Src, 0, 0, Src.Width, Src.Height, pLeft, pTop, dmSet);
End;
FLastDrawMode := DrawMode;
End;
End;
// Note : Sous MacOS on ne peux pas assigner FRenderCache.Items[Index].Bitmap directement avec
// FVirtualView.GetBitmap; On est obligé de créer le bitmap de destination et utiliser Assign.
// Dans le cas contraire seulment la première image sera affichée.
//TmpBmp := Graphics.TBitmap.Create; <== MEMORY LEAK
TmpBmp := FVirtualView.GetBitmap;
FRenderCache.Items[Index].Bitmap.Assign(TmpBmp);
FreeAndNil(TmpBmp);
End;
Procedure TGIFViewer.ComputeCache;
Var
I: Integer;
Begin
FCurrentFrameIndex := 0;
FRenderCache.Clear;
If FGIFLoader.FrameCount > 0 Then
Begin
For I := 0 To Pred(FGIFLoader.FrameCount) Do
Begin
RenderFrame(I);
End;
end;
if AutoRemoveInvalidFrame then FRenderCache.Pack;
ResetCurrentView;
End;
Procedure TGIFViewer.CalculatePreferredSize(Var PreferredWidth, PreferredHeight: Integer; WithThemeSpace: Boolean);
Var
extraWidth: Integer;
Begin
extraWidth := - 2;
if FBorderShow then extraWidth := (FBorderWidth * 2) + (FBevelWidth * 2);
PreferredWidth := FGIFWidth + extraWidth + 2;
PreferredHeight := FGIFHeight + extraWidth + 2;
End;
Class Function TGIFViewer.GetControlClassDefaultSize: TSize;
Begin
Result.CX := 90; // = ClientWidth
Result.CY := 90; // = ClientHeight
End;
Function TGIFViewer.DestRect: TRect;
Var
PicWidth, PicHeight: Integer;
ImgWidth, ImgHeight: Integer;
n: Integer;
procedure KeepAspectRatio( Var aWidth, aHeight : Integer; MaxWidth, MaxHeight : Integer);
var
w, h : Integer;
begin
w := MaxWidth;
h := (aHeight * w) Div aWidth;
If h > MaxHeight Then
Begin
h := MaxHeight;
w := (aWidth * h) Div aHeight;
End;
aWidth := w;
aHeight := h;
End;
Begin
PicWidth := FCurrentView.Width;
PicHeight := FCurrentView.Height;
ImgWidth := ClientWidth;
ImgHeight := ClientHeight;
If (PicWidth = 0) Or (PicHeight = 0) Then Exit(Rect(0, 0, 0, 0));
if FAutoStretchMode <> smManual then
begin
Case FAutoStretchMode of
smStretchAll : FStretch := True;
smStretchOnlyBigger : if (PicWidth > ImgWidth) or (PicHeight > ImgHeight) then FStretch := True else FStretch := False;
smStretchOnlySmaller : if (PicWidth < ImgWidth) and (PicHeight < ImgHeight) then FStretch := True else FStretch := False;
end;
if Assigned(FOnStretchChanged) then FOnStretchChanged(Self,FStretch);
End;
If FStretch Then
Begin
KeepAspectRatio(PicWidth, PicHeight,ImgWidth, ImgHeight);
End;
n := FBorderWidth + FBevelWidth;
If FBorderShow Then
Begin
Result := Rect(n, n, n + PicWidth, n + PicHeight);
End
Else
Result := Rect(0, 0, PicWidth, PicHeight);
If FCenter Then
Begin
If FBorderShow Then
Begin
Result.Left := n + ((ClientWidth -(n+n)) - PicWidth) shr 1;
Result.Top := n + ((ClientHeight-(n+n)) - PicHeight) shr 1;
end
else
begin
Result.Left := ((ClientWidth - PicWidth) shr 1);
Result.Top := ((ClientHeight - PicHeight) shr 1);
end;
Result.Right := Result.Left + PicWidth;
Result.Bottom := Result.Top + PicHeight;
End;
End;
Procedure TGIFViewer.Paint;
Procedure DrawFrame;
Begin
With Inherited Canvas Do
Begin
Pen.Color := clBlack;
Pen.Style := 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
R: TRect;
C: TCanvas;
ARect: TRect;
w: Integer;
Begin
If csDesigning In ComponentState Then DrawFrame;
C := Inherited Canvas;
FPainting := True;
R := DestRect;
Try
C.Lock;
// Fond
If (FColor <> clNone) Then //and Not(FTransparent)
Begin
With C Do
Begin
Brush.Style := bsSolid;
Brush.Color := FColor;
FillRect(0, 0, ClientWidth, ClientHeight);
End;
End;
// Bitmap
FCurrentView.Transparent := FTransparent;
C.StretchDraw(R, FCurrentView);
// Bordures
If FBorderShow Then
Begin
ARect := rect(0, 0, ClientWidth, ClientHeight);
w := FBevelWidth;
If (FBevelInner <> bvNone) And (w > 0) Then C.Frame3d(ARect, w, BevelInner); // Note: Frame3D inflates ARect
InflateRect(ARect, -(FBorderWidth + 1), -(FBorderWidth + 1));
If (FBevelOuter <> bvNone) And (w > 0) Then C.Frame3d(ARect, w, BevelOuter);
If FBorderWidth > 0 Then With C Do
Begin
Pen.Style := psSolid;
Pen.Width := FBorderWidth;
Pen.Color := FBorderColor;
Brush.Style := bsClear;
Rectangle(0, 0, ClientWidth, ClientHeight);
End;
End;
C.UnLock;
Finally
FPainting := False;
End;
Inherited Paint;
End;
Procedure TGIFViewer.Loaded;
begin
if FFileName<>'' then LoadFromFile(FFileName);
inherited Loaded;
end;
procedure TGIFViewer.BeforeLoad;
begin
FAnimateTimer.Enabled := False;
FPause := False;
FAnimated := False;
FCurrentFrameIndex := 0;
end;
procedure TGIFViewer.AfterLoad;
begin
FGIFWidth := FGIFLoader.Width;
FGIFHeight := FGIFLoader.Height;
FVirtualView.SetSize(FGIFWidth, FGIFHeight);
if FCache then
ComputeCache
else begin
FRenderCache.Clear;
FCurrentFrameIndex := 0;
RenderFrame(0);
ResetCurrentView;
end;
If AutoSize Then
Begin
InvalidatePreferredSize;
AdjustSize;
End;
Invalidate;
If FAutoPlay Then Start;
end;
Procedure TGIFViewer.Invalidate;
Begin
If FPainting Then exit;
Inherited Invalidate;
End;
Procedure TGIFViewer.LoadFromStream(aStream : TStream);
Begin
BeforeLoad;
FGIFLoader.FErrorList.Clear;
FGIFLoader.FErrorCOunt := 0;
FGIFLoader.LoadFromStream(aStream);
AfterLoad;
End;
Procedure TGIFViewer.LoadFromFile(Const aFileName: String);
Begin
BeforeLoad;
if Not(FileExists(aFileName)) then
begin
MessageDlg(Format(rsFileNotFound,[aFileName]), mtError, [mbOK],0);
Exit;
end;
FGIFLoader.LoadFromFile(aFileName);
FFileName := aFileName;
AfterLoad;
End;
Procedure TGIFViewer.LoadFromResource(Const ResName: String);
Var
Resource: TLResource;
Begin
BeforeLoad;
Resource := LazarusResources.Find(ResName);
If Resource = nil Then Raise Exception.Create(Format(rsResourceNotFound,[ResName]))
Else If CompareText(LazarusResources.Find(ResName).ValueType, 'gif') = 0 Then
Begin
FGIFLoader.LoadFromResource(ResName);
AfterLoad;
End;
End;
Procedure TGIFViewer.Start;
Begin
If Not (FPause) Then FCurrentFrameIndex := 0;
FPause := False;
FAnimated := True;
FAnimateTimer.Enabled := True;
If Assigned(FOnStart) Then FOnStart(Self);
End;
Procedure TGIFViewer.Stop;
Begin
FAnimateTimer.Enabled := False;
FAnimated := False;
FPause := False;
If Assigned(FOnStop) Then FOnStop(Self);
FCurrentframeIndex := 0;
ResetCurrentView;
Invalidate;
End;
Procedure TGIFViewer.Pause;
Begin
FAnimateTimer.Enabled := False;
FPause := True;
End;
Procedure TGIFViewer.NextFrame;
begin
if FCurrentFrameIndex < FGifLoader.FrameCount - 1 then
begin
Inc(FCurrentFrameIndex);
repeat
If (not FCache) and (FCurrentFrameIndex >= FRenderCache.Count) Then
begin
RenderFrame(FCurrentFrameIndex);
end;
If Assigned(FOnFrameChange) Then FOnFrameChange(Self);
if not(FDisplayInvalidFrames) then
begin
if not(FRenderCache.Items[FCurrentFrameIndex].IsCorrupted) then
begin
FCurrentView.Assign(FRenderCache.Items[FCurrentFrameIndex].Bitmap);
End
Else If FCurrentFrameIndex > 0 Then
Begin
Inc(FCurrentFrameIndex);
Continue;
End;
end
else
begin
FCurrentView.Assign(FRenderCache.Items[FCurrentFrameIndex].Bitmap);
end;
Break;
until False;
FAnimateTimer.Interval := FRenderCache.Items[FCurrentFrameIndex].Delay;
end;
Invalidate;
end;
Procedure TGIFViewer.PriorFrame;
begin
if FCurrentFrameIndex > 0 then
begin
Dec(FCurrentFrameIndex);
repeat
If Assigned(FOnFrameChange) Then FOnFrameChange(Self);
if not(FDisplayInvalidFrames) then
begin
if not(FRenderCache.Items[FCurrentFrameIndex].IsCorrupted) then
begin
FCurrentView.Assign(FRenderCache.Items[FCurrentFrameIndex].Bitmap);
End
Else If FCurrentFrameIndex > 0 Then
Begin
Dec(FCurrentFrameIndex);
Continue;
End;
end
else
begin
FCurrentView.Assign(FRenderCache.Items[FCurrentFrameIndex].Bitmap);
end;
Break;
until False;
FAnimateTimer.Interval := FRenderCache.Items[FCurrentFrameIndex].Delay;
end;
Invalidate;
end;
Function TGIFViewer.GetRawFrame(Index: Integer): TBitmap;
Begin
Result := FGIFLoader.Frames[Index].Bitmap.GetBitmap;
End;
Procedure TGIFViewer.DisplayFrame(Index: Integer);
Begin
If not(FRenderCache.IsIndexOk(Index)) then exit;
if Not(DisplayInvalidFrames) then
begin
if FRenderCache.Items[Index].IsCorrupted then
begin
inc(Index);
DisplayFrame(Index);
End
else
begin
FCurrentView.Assign(FRenderCache.Items[Index].Bitmap);
End;
end
else
begin
FCurrentView.Assign(FRenderCache.Items[Index].Bitmap);
End;
Invalidate;
End;
Procedure TGIFViewer.DisplayRawFrame(Index: Integer);
Var
Tmp: Graphics.TBitmap;
Begin
If not(FRenderCache.IsIndexOk(Index)) Then exit;
Tmp := GetRawFrame(Index);
FCurrentView.Assign(Tmp);
FreeAndNil(Tmp);
Invalidate;
End;
Procedure TGIFViewer.ExtractFrame(Index: Integer; Var bmp: TBitmap);
Begin
If not(FRenderCache.IsIndexOk(Index)) then exit;
Bmp.Assign(FRenderCache.Items[Index].Bitmap);
End;
Procedure TGIFViewer.ExtractRawFrame(Index: Integer; Var bmp: TBitmap);
Var
Tmp: Graphics.TBitmap;
Begin
If not(FRenderCache.IsIndexOk(Index)) Then exit;
Tmp := GetRawFrame(Index);
Bmp.Assign(Tmp);
FreeAndNil(Tmp);
End;
{%endregion}
Procedure Register;
Begin
RegisterComponents('Misc', [TGIFView]);
End;
End.