|
@@ -0,0 +1,2785 @@
|
|
|
|
+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.
|