1152 lines
29 KiB
ObjectPascal
1152 lines
29 KiB
ObjectPascal
{
|
||
Copyright (C) 2009 Laurent Jacques
|
||
|
||
This source is free software; you can redistribute it and/or modify it under
|
||
the terms of the GNU General Public License as published by the Free
|
||
Software Foundation; either version 2 of the License, or (at your option)
|
||
any later version.
|
||
|
||
This code is distributed in the hope that it will be useful, but WITHOUT ANY
|
||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||
details.
|
||
|
||
A copy of the GNU General Public License is available on the World Wide Web
|
||
at <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing
|
||
to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
|
||
MA 02111-1307, USA.
|
||
|
||
Version 1.4
|
||
}
|
||
unit GifAnim;
|
||
|
||
{$mode objfpc}{$H+}
|
||
|
||
interface
|
||
|
||
uses
|
||
Classes, LCLProc, Lresources, SysUtils, Controls, Graphics, ExtCtrls,
|
||
IntfGraphics, FPimage, Contnrs, GraphType, dialogs, types;
|
||
|
||
const
|
||
|
||
EXT_INTRODUCER = $21;
|
||
EXT_GRAPHICS_CONTROL = $F9;
|
||
EXT_PLAIN_TEXT = $01;
|
||
EXT_APPLICATION = $FF;
|
||
EXT_COMMENT = $FE;
|
||
|
||
DSC_LOCAL_IMAGE = $2C;
|
||
|
||
ID_TRANSPARENT = $01;
|
||
ID_COLOR_TABLE_SIZE = $07;
|
||
ID_SORT = $20;
|
||
ID_INTERLACED = $40;
|
||
ID_COLOR_TABLE = $80;
|
||
ID_IMAGE_DESCRIPTOR = $2C;
|
||
ID_TRAILER = $3B;
|
||
|
||
CODE_TABLE_SIZE = 4096;
|
||
|
||
type
|
||
TRGB = packed record
|
||
Red, Green, Blue: byte;
|
||
end;
|
||
|
||
TGIFHeader = packed record
|
||
Signature: array[0..2] of char; //* Header Signature (always "GIF") */
|
||
Version: array[0..2] of char; //* GIF format version("87a" or "89a") */
|
||
ScreenWidth: word; //* Width of Display Screen in Pixels */
|
||
ScreenHeight: word; //* Height of Display Screen in Pixels */
|
||
Packedbit, //* Screen and Color Map Information */
|
||
BackgroundColor, //* Background Color Index */
|
||
AspectRatio: byte; //* Pixel Aspect Ratio */
|
||
end;
|
||
|
||
TGifImageDescriptor = packed record
|
||
Left, //* X position of image on the display */
|
||
Top, //* Y position of image on the display */
|
||
Width, //* Width of the image in pixels */
|
||
Height: word; //* Height of the image in pixels */
|
||
Packedbit: byte; //* Image and Color Table Data Information */
|
||
end;
|
||
|
||
TGifGraphicsControlExtension = packed record
|
||
BlockSize, //* Size of remaining fields (always 04h) */
|
||
Packedbit: byte; //* Method of graphics disposal to use */
|
||
DelayTime: word; //* Hundredths of seconds to wait */
|
||
ColorIndex, //* Transparent Color Index */
|
||
Terminator: byte; //* Block Terminator (always 0) */
|
||
end;
|
||
|
||
TGifAnim = class;
|
||
|
||
{ TGifImage }
|
||
|
||
TGifImage = class
|
||
private
|
||
FBitmap: TBitmap;
|
||
FPosX: word;
|
||
FPosY: word;
|
||
FDelay: word;
|
||
FMethod: byte;
|
||
public
|
||
constructor Create;
|
||
destructor Destroy; override;
|
||
property Bitmap: TBitmap Read FBitmap;
|
||
property Delay: word Read FDelay;
|
||
property Method: byte Read FMethod;
|
||
property PosX: word Read FPosX;
|
||
property PosY: word Read FPosY;
|
||
end;
|
||
|
||
{ TGifList }
|
||
|
||
TGifList = class(TObjectList)
|
||
private
|
||
protected
|
||
function GetItems(Index: integer): TGifImage;
|
||
procedure SetItems(Index: integer; AGifImage: TGifImage);
|
||
public
|
||
function Add(AGifImage: TGifImage): integer;
|
||
function Extract(Item: TGifImage): TGifImage;
|
||
function Remove(AGifImage: TGifImage): integer;
|
||
function IndexOf(AGifImage: TGifImage): integer;
|
||
function First: TGifImage;
|
||
function Last: TGifImage;
|
||
procedure Insert(Index: integer; AGifImage: TGifImage);
|
||
property Items[Index: integer]: TGifImage Read GetItems Write SetItems; default;
|
||
end;
|
||
|
||
|
||
{ TGifLoader }
|
||
|
||
TGifLoader = class
|
||
private
|
||
FGifHeader: TGIFHeader;
|
||
FGifDescriptor: TGifImageDescriptor;
|
||
FGifGraphicsCtrlExt: TGifGraphicsControlExtension;
|
||
FGifUseGraphCtrlExt: boolean;
|
||
FGifBackgroundColor: byte;
|
||
FInterlaced: boolean;
|
||
FScanLine: PByte;
|
||
FLineSize: integer;
|
||
FDisposalMethod: byte;
|
||
FEmpty: boolean;
|
||
FFileName: string;
|
||
FHeight: integer;
|
||
FIsTransparent: boolean;
|
||
FWidth: integer;
|
||
FPalette: TFPPalette;
|
||
FLocalHeight: integer;
|
||
FLocalWidth: integer;
|
||
procedure ReadPalette(Stream: TStream; Size: integer);
|
||
procedure ReadScanLine(Stream: TStream);
|
||
procedure ReadHeader(Stream: TStream);
|
||
procedure ReadGlobalPalette(Stream: TStream);
|
||
procedure ReadGraphCtrlExt;
|
||
procedure SetInterlaced(const AValue: boolean);
|
||
procedure SetTransparent(const AValue: boolean);
|
||
function SkipBlock(Stream: TStream): byte;
|
||
procedure WriteScanLine(Img: TFPCustomImage);
|
||
procedure ReadGifBitmap(Stream: TStream);
|
||
public
|
||
constructor Create(const FileName: string);
|
||
destructor Destroy; override;
|
||
function LoadAllBitmap(var AGifList: TGifList): boolean;
|
||
function LoadFromLazarusResource(const ResName: String; var AGifList: TGifList): boolean;
|
||
function LoadFirstBitmap(var ABitmap: TBitmap): boolean;
|
||
property Empty: boolean Read FEmpty;
|
||
property Height: integer Read FHeight;
|
||
property Width: integer Read FWidth;
|
||
property IsTransparent: boolean Read FIsTransparent Write SetTransparent;
|
||
property Interlaced: boolean Read FInterlaced Write SetInterlaced;
|
||
end;
|
||
|
||
{ TGifAnim }
|
||
|
||
TGifAnim = class(TGraphicControl)
|
||
private
|
||
{ Private declarations }
|
||
FAnimate: boolean;
|
||
FEmpty: boolean;
|
||
FFileName: string;
|
||
FGifBitmaps: TGifList;
|
||
FOnFrameChanged: TNotifyEvent;
|
||
FOnStart: TNotifyEvent;
|
||
FOnStop: TNotifyEvent;
|
||
FWait: TTimer;
|
||
FCurrentImage: integer;
|
||
FGifHeight: integer;
|
||
FGifWidth: integer;
|
||
procedure OnTime(Sender: TObject);
|
||
procedure SetAnimate(const AValue: boolean);
|
||
procedure SetFileName(const AValue: string);
|
||
procedure DefineSize(AWidth, AHeight: integer);
|
||
protected
|
||
{ Protected declarations }
|
||
BufferImg: TBitmap;
|
||
CurrentView: TBitmap;
|
||
procedure CalculatePreferredSize(
|
||
var PreferredWidth, PreferredHeight: integer;
|
||
WithThemeSpace: boolean); override;
|
||
procedure DoAutoSize; override;
|
||
procedure DoStartAnim;
|
||
procedure DoStopAnim;
|
||
class function GetControlClassDefaultSize: TSize; override;
|
||
procedure GifChanged;
|
||
procedure LoadFromFile(const Filename: string); virtual;
|
||
procedure Paint; override;
|
||
procedure ResetImage;
|
||
procedure SetColor(Value: TColor); override;
|
||
public
|
||
{ Public declarations }
|
||
constructor Create(AOwner: TComponent); override;
|
||
destructor Destroy; override;
|
||
procedure NextFrame;
|
||
procedure PriorFrame;
|
||
property Empty: boolean Read FEmpty;
|
||
property GifBitmaps: TGifList Read FGifBitmaps;
|
||
property GifIndex: integer Read FCurrentImage;
|
||
function LoadFromLazarusResource(const ResName: String): boolean;
|
||
published
|
||
{ Published declarations }
|
||
property Anchors;
|
||
property AutoSize default True;
|
||
property Animate: boolean Read FAnimate Write SetAnimate default True;
|
||
property BorderSpacing;
|
||
property Color default clBtnFace;
|
||
property Constraints;
|
||
property FileName: string Read FFileName Write SetFileName;
|
||
property Height;
|
||
property OnClick;
|
||
property OnDblClick;
|
||
property OnFrameChanged: TNotifyEvent Read FOnFrameChanged Write FOnFrameChanged;
|
||
property OnMouseDown;
|
||
property OnMouseEnter;
|
||
property OnMouseLeave;
|
||
property OnMouseMove;
|
||
property OnMouseUp;
|
||
property OnStartAnim: TNotifyEvent Read FOnStart Write FOnStart;
|
||
property OnStopAnim: TNotifyEvent Read FOnStop Write FOnStop;
|
||
property ParentShowHint;
|
||
property ShowHint;
|
||
property Visible;
|
||
property Width;
|
||
end;
|
||
|
||
procedure Register;
|
||
|
||
implementation
|
||
|
||
procedure Register;
|
||
begin
|
||
RegisterComponents('Wile64', [TGifAnim]);
|
||
end;
|
||
|
||
{ TGifAnim }
|
||
|
||
constructor TGifAnim.Create(AOwner: TComponent);
|
||
begin
|
||
inherited Create(AOwner);
|
||
ControlStyle := [csCaptureMouse, csClickEvents, csDoubleClicks];
|
||
AutoSize := True;
|
||
SetInitialBounds(0, 0, GetControlClassDefaultSize.CX, GetControlClassDefaultSize.CY);
|
||
FEmpty := True;
|
||
FCurrentImage := 0;
|
||
CurrentView := TBitmap.Create;
|
||
if not (csDesigning in ComponentState) then
|
||
begin
|
||
BufferImg := TBitmap.Create;
|
||
FWait := TTimer.Create(Self);
|
||
with FWait do
|
||
begin
|
||
Interval := 100;
|
||
OnTimer := @OnTime;
|
||
Enabled := False;
|
||
end;
|
||
end;
|
||
Animate := True;
|
||
end;
|
||
|
||
destructor TGifAnim.Destroy;
|
||
begin
|
||
inherited Destroy;
|
||
if assigned(FGifBitmaps) then
|
||
FreeAndNil(FGifBitmaps);
|
||
BufferImg.Free;
|
||
CurrentView.Free;
|
||
end;
|
||
|
||
procedure TGifAnim.NextFrame;
|
||
begin
|
||
if (not FEmpty) and Visible and (not FAnimate) then
|
||
begin
|
||
if FCurrentImage >= GifBitmaps.Count - 1 then
|
||
FCurrentImage := 0
|
||
else
|
||
Inc(FCurrentImage);
|
||
if Assigned(FOnFrameChanged) then
|
||
FOnFrameChanged(Self);
|
||
Repaint;
|
||
end;
|
||
end;
|
||
|
||
procedure TGifAnim.PriorFrame;
|
||
var
|
||
DesiredImage: Integer;
|
||
begin
|
||
if (not FEmpty) and Visible and (not FAnimate) then
|
||
begin
|
||
if FCurrentImage = 0 then
|
||
DesiredImage:= GifBitmaps.Count - 1
|
||
else
|
||
DesiredImage:= FCurrentImage - 1;
|
||
// For proper display repaint image from first frame to desired frame
|
||
FCurrentImage:= 0;
|
||
while FCurrentImage < DesiredImage do
|
||
begin
|
||
with GifBitmaps.Items[FCurrentImage] do
|
||
begin
|
||
BufferImg.Canvas.Brush.Color := (Self.Color);
|
||
if FCurrentImage = 0 then
|
||
BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
|
||
if Delay <> 0 then
|
||
FWait.Interval := Delay * 10;
|
||
BufferImg.Canvas.Draw(PosX, PosY, Bitmap);
|
||
case Method of
|
||
//0 : Not specified...
|
||
//1 : No change Background
|
||
2: BufferImg.Canvas.FillRect(
|
||
Rect(PosX, PosY, Bitmap.Width + PosX, Bitmap.Height + PosY));
|
||
|
||
3: BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
|
||
end;
|
||
end;
|
||
Inc(FCurrentImage);
|
||
end;
|
||
if Assigned(FOnFrameChanged) then
|
||
FOnFrameChanged(Self);
|
||
Repaint;
|
||
end;
|
||
end;
|
||
|
||
function TGifAnim.LoadFromLazarusResource(const ResName: String): boolean;
|
||
var
|
||
GifLoader: TGifLoader;
|
||
StateAnimate: boolean;
|
||
Resource: TLResource;
|
||
begin
|
||
Result:=false;
|
||
StateAnimate:= Animate;
|
||
FWait.Enabled:= false;
|
||
ResetImage;
|
||
Resource:=nil;
|
||
Resource:=LazarusResources.Find(ResName);
|
||
if Resource <> nil then
|
||
if CompareText(LazarusResources.Find(ResName).ValueType, 'gif')=0 then begin
|
||
GifLoader := TGifLoader.Create(Filename);
|
||
FEmpty := not GifLoader.LoadFromLazarusResource(ResName, FGifBitmaps);
|
||
DefineSize(GifLoader.Width, GifLoader.Height);
|
||
GifLoader.Free;
|
||
Result:= FEmpty;
|
||
end;
|
||
if not Empty then
|
||
GifChanged;
|
||
FWait.Enabled:= StateAnimate;
|
||
end;
|
||
|
||
procedure TGifAnim.LoadFromFile(const Filename: string);
|
||
var
|
||
GifLoader: TGifLoader;
|
||
begin
|
||
FEmpty := True;
|
||
if not FileExists(Filename) then
|
||
Exit;
|
||
GifLoader := TGifLoader.Create(Filename);
|
||
if (csDesigning in ComponentState) then
|
||
FEmpty := not GifLoader.LoadFirstBitmap(CurrentView)
|
||
else
|
||
FEmpty := not GifLoader.LoadAllBitmap(FGifBitmaps);
|
||
DefineSize(GifLoader.Width, GifLoader.Height);
|
||
GifLoader.Free;
|
||
end;
|
||
|
||
procedure TGifAnim.OnTime(Sender: TObject);
|
||
begin
|
||
if (not Empty) and Visible then
|
||
begin
|
||
if FCurrentImage >= GifBitmaps.Count - 1 then
|
||
FCurrentImage := 0
|
||
else
|
||
Inc(FCurrentImage);
|
||
if Assigned(FOnFrameChanged) then
|
||
FOnFrameChanged(Self);
|
||
Repaint;
|
||
end;
|
||
end;
|
||
|
||
procedure TGifAnim.SetAnimate(const AValue: boolean);
|
||
begin
|
||
if FAnimate = AValue then
|
||
exit;
|
||
FAnimate := AValue;
|
||
if not (csDesigning in ComponentState) then
|
||
begin
|
||
FWait.Enabled := Animate;
|
||
if Animate then
|
||
DoStartAnim
|
||
else
|
||
DoStopAnim;
|
||
end;
|
||
end;
|
||
|
||
procedure TGifAnim.SetFileName(const AValue: string);
|
||
begin
|
||
if (FFileName = AValue) then Exit;
|
||
FFileName := AValue;
|
||
ResetImage;
|
||
if (FFileName = '') then Exit;
|
||
LoadFromFile(FFileName);
|
||
if not Empty then
|
||
GifChanged;
|
||
end;
|
||
|
||
procedure TGifAnim.DefineSize(AWidth, AHeight: integer);
|
||
begin
|
||
if (AWidth = FGifWidth) and (AHeight = FGifHeight) then
|
||
Exit;
|
||
FGifWidth := AWidth;
|
||
FGifHeight := AHeight;
|
||
Height := FGifHeight;
|
||
Width := FGifWidth;
|
||
if not (csDesigning in ComponentState) then
|
||
begin
|
||
BufferImg.Height := Height;
|
||
BufferImg.Width := Width;
|
||
end;
|
||
end;
|
||
|
||
procedure TGifAnim.CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer;
|
||
WithThemeSpace: boolean);
|
||
begin
|
||
PreferredWidth := FGifWidth;
|
||
PreferredHeight := FGifHeight;
|
||
end;
|
||
|
||
procedure TGifAnim.DoAutoSize;
|
||
var
|
||
ModifyWidth, ModifyHeight: boolean;
|
||
NewWidth: integer;
|
||
NewHeight: integer;
|
||
begin
|
||
if AutoSizing then
|
||
Exit; // we shouldn't come here in the first place
|
||
|
||
BeginAutoSizing;
|
||
try
|
||
GetPreferredSize(NewWidth, NewHeight);
|
||
ModifyWidth := [akLeft, akRight] * (Anchors + AnchorAlign[Align]) <> [akLeft, akRight];
|
||
ModifyHeight := [akTop, akBottom] * (Anchors + AnchorAlign[Align]) <> [akTop, akBottom];
|
||
|
||
if not ModifyWidth then
|
||
NewWidth := Width;
|
||
if not ModifyHeight then
|
||
NewHeight := Height;
|
||
|
||
if (NewWidth <> Width) or (NewHeight <> Height) then
|
||
begin
|
||
SetBounds(Left, Top, NewWidth, NewHeight);
|
||
end;
|
||
finally
|
||
EndAutoSizing;
|
||
end;
|
||
end;
|
||
|
||
class function TGifAnim.GetControlClassDefaultSize: TSize;
|
||
begin
|
||
Result.CX := 90;
|
||
Result.CY := 90;
|
||
end;
|
||
|
||
procedure TGifAnim.GifChanged;
|
||
begin
|
||
if not (csDesigning in ComponentState) then
|
||
begin
|
||
BufferImg.Canvas.Brush.Color := (self.Color);
|
||
BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
|
||
with GifBitmaps.Items[FCurrentImage] do
|
||
BufferImg.Canvas.Draw(PosX, PosY, Bitmap);
|
||
CurrentView.Assign(BufferImg);
|
||
end;
|
||
InvalidatePreferredSize;
|
||
AdjustSize;
|
||
end;
|
||
|
||
procedure TGifAnim.Paint;
|
||
begin
|
||
if (not Empty) and Visible then
|
||
begin
|
||
if not (csDesigning in ComponentState) then
|
||
begin
|
||
if (FCurrentImage < GifBitmaps.Count) then
|
||
with GifBitmaps.Items[FCurrentImage] do
|
||
begin
|
||
BufferImg.Canvas.Brush.Color := (self.Color);
|
||
if FCurrentImage = 0 then
|
||
BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
|
||
if Delay <> 0 then
|
||
FWait.Interval := Delay * 10;
|
||
BufferImg.Canvas.Draw(PosX, PosY, Bitmap);
|
||
CurrentView.Assign(BufferImg);
|
||
case Method of
|
||
//0 : Not specified...
|
||
//1 : No change Background
|
||
2: BufferImg.Canvas.FillRect(
|
||
Rect(PosX, PosY, Bitmap.Width + PosX, Bitmap.Height + PosY));
|
||
|
||
3: BufferImg.Canvas.FillRect(Rect(0, 0, Width, Height));
|
||
end;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
Canvas.Brush.Color := (self.Color);
|
||
Canvas.FillRect(Rect(0, 0, Width, Height));
|
||
end;
|
||
Canvas.Draw(0, 0, CurrentView);
|
||
end;
|
||
inherited Paint;
|
||
end;
|
||
|
||
procedure TGifAnim.ResetImage;
|
||
begin
|
||
if assigned(FGifBitmaps) then
|
||
FreeAndNil(FGifBitmaps);
|
||
FCurrentImage:=0;
|
||
with CurrentView do
|
||
begin
|
||
Canvas.Brush.Color := (self.Color);
|
||
Canvas.FillRect(Rect(0, 0, Width, Height));
|
||
end;
|
||
end;
|
||
|
||
procedure TGifAnim.SetColor(Value: TColor);
|
||
begin
|
||
inherited SetColor(Value);
|
||
end;
|
||
|
||
procedure TGifAnim.DoStartAnim;
|
||
begin
|
||
if assigned(OnStartAnim) then
|
||
OnStartAnim(Self);
|
||
end;
|
||
|
||
procedure TGifAnim.DoStopAnim;
|
||
begin
|
||
if assigned(OnStopAnim) then
|
||
OnStartAnim(Self);
|
||
end;
|
||
|
||
{ TGifLoader }
|
||
|
||
constructor TGifLoader.Create(const FileName: string);
|
||
begin
|
||
FFileName := FileName;
|
||
FGifUseGraphCtrlExt := False;
|
||
FPalette := TFPPalette.Create(0);
|
||
FHeight := 20;
|
||
FWidth := 20;
|
||
end;
|
||
|
||
destructor TGifLoader.Destroy;
|
||
begin
|
||
inherited Destroy;
|
||
FPalette.Free;
|
||
end;
|
||
|
||
function TGifLoader.LoadAllBitmap(var AGifList: TGifList): boolean;
|
||
var
|
||
GifStream: TMemoryStream;
|
||
Introducer: byte;
|
||
FPImage: TLazIntfImage;
|
||
ImgFormatDescription: TRawImageDescription;
|
||
GifBitmap: TGifImage;
|
||
begin
|
||
Result := False;
|
||
if not FileExists(FFileName) then
|
||
exit;
|
||
if not assigned(AGifList) then
|
||
AGifList := TGifList.Create(True);
|
||
|
||
GifStream := TMemoryStream.Create;
|
||
GifStream.LoadFromFile(FFileName);
|
||
GifStream.Position := 0;
|
||
|
||
ReadHeader(GifStream);
|
||
if (FGifHeader.Version <> '89a') then
|
||
Exit;
|
||
|
||
// skip first block extention if exist
|
||
repeat
|
||
Introducer := SkipBlock(GifStream);
|
||
until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);
|
||
|
||
repeat
|
||
ReadGifBitmap(GifStream);
|
||
// decode Gif bitmap in Scanline buffer
|
||
ReadScanLine(GifStream);
|
||
// Create temp Fp Image for put scanline pixel
|
||
FPImage := TLazIntfImage.Create(FLocalWidth, FLocalHeight);
|
||
ImgFormatDescription.Init_BPP32_B8G8R8A8_BIO_TTB(FLocalWidth,
|
||
FLocalHeight);
|
||
FPImage.DataDescription := ImgFormatDescription;
|
||
|
||
WriteScanLine(FPImage);
|
||
|
||
GifBitmap := TGifImage.Create;
|
||
GifBitmap.FBitmap.LoadFromIntfImage(FPImage);
|
||
GifBitmap.FPosX := FGifDescriptor.Left;
|
||
GifBitmap.FPosY := FGifDescriptor.Top;
|
||
GifBitmap.FMethod := FDisposalMethod;
|
||
GifBitmap.FDelay := FGifGraphicsCtrlExt.DelayTime;
|
||
|
||
AGifList.Add(GifBitmap);
|
||
|
||
FPImage.Free;
|
||
FreeMem(FScanLine, FLineSize);
|
||
// reset FGifUseGraphCtrlExt flag
|
||
FGifUseGraphCtrlExt := False;
|
||
|
||
repeat
|
||
Introducer := SkipBlock(GifStream);
|
||
until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);
|
||
|
||
until (Introducer = ID_TRAILER);
|
||
GifStream.Free;
|
||
Result := True;
|
||
end;
|
||
|
||
function TGifLoader.LoadFromLazarusResource(const ResName: String; var AGifList: TGifList): boolean;
|
||
var
|
||
GifStream: TLazarusResourceStream;
|
||
Introducer: byte;
|
||
FPImage: TLazIntfImage;
|
||
ImgFormatDescription: TRawImageDescription;
|
||
GifBitmap: TGifImage;
|
||
begin
|
||
Result := False;
|
||
if not assigned(AGifList) then
|
||
AGifList := TGifList.Create(True);
|
||
|
||
GifStream := TLazarusResourceStream.Create(ResName, nil);
|
||
GifStream.Position := 0;
|
||
|
||
ReadHeader(GifStream);
|
||
if (FGifHeader.Version <> '89a') then
|
||
Exit;
|
||
|
||
// skip first block extention if exist
|
||
repeat
|
||
Introducer := SkipBlock(GifStream);
|
||
until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);
|
||
|
||
repeat
|
||
ReadGifBitmap(GifStream);
|
||
// decode Gif bitmap in Scanline buffer
|
||
ReadScanLine(GifStream);
|
||
// Create temp Fp Image for put scanline pixel
|
||
FPImage := TLazIntfImage.Create(FLocalWidth, FLocalHeight);
|
||
ImgFormatDescription.Init_BPP32_B8G8R8A8_BIO_TTB(FLocalWidth,
|
||
FLocalHeight);
|
||
FPImage.DataDescription := ImgFormatDescription;
|
||
|
||
WriteScanLine(FPImage);
|
||
|
||
GifBitmap := TGifImage.Create;
|
||
GifBitmap.FBitmap.LoadFromIntfImage(FPImage);
|
||
GifBitmap.FPosX := FGifDescriptor.Left;
|
||
GifBitmap.FPosY := FGifDescriptor.Top;
|
||
GifBitmap.FMethod := FDisposalMethod;
|
||
GifBitmap.FDelay := FGifGraphicsCtrlExt.DelayTime;
|
||
|
||
AGifList.Add(GifBitmap);
|
||
|
||
FPImage.Free;
|
||
FreeMem(FScanLine, FLineSize);
|
||
// reset FGifUseGraphCtrlExt flag
|
||
FGifUseGraphCtrlExt := False;
|
||
|
||
repeat
|
||
Introducer := SkipBlock(GifStream);
|
||
until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);
|
||
|
||
until (Introducer = ID_TRAILER);
|
||
GifStream.Free;
|
||
Result := True;
|
||
end;
|
||
|
||
function TGifLoader.LoadFirstBitmap(var ABitmap: TBitmap): boolean;
|
||
var
|
||
GifStream: TMemoryStream;
|
||
Introducer: byte;
|
||
FPImage: TLazIntfImage;
|
||
ImgFormatDescription: TRawImageDescription;
|
||
begin
|
||
Result := False;
|
||
if not FileExists(FFileName) then
|
||
exit;
|
||
if not assigned(ABitmap) then
|
||
ABitmap := TBitmap.Create;
|
||
|
||
GifStream := TMemoryStream.Create;
|
||
GifStream.LoadFromFile(FFileName);
|
||
GifStream.Position := 0;
|
||
|
||
ReadHeader(GifStream);
|
||
if (FGifHeader.Version <> '89a') then
|
||
Exit;
|
||
|
||
// skip first block extention if exist
|
||
repeat
|
||
Introducer := SkipBlock(GifStream);
|
||
until (Introducer = ID_IMAGE_DESCRIPTOR) or (Introducer = ID_TRAILER);
|
||
|
||
ReadGifBitmap(GifStream);
|
||
// decode Gif bitmap in Scanline buffer
|
||
ReadScanLine(GifStream);
|
||
// Create temp Fp Image for put scanline pixel
|
||
FPImage := TLazIntfImage.Create(FLocalWidth, FLocalHeight);
|
||
ImgFormatDescription.Init_BPP32_B8G8R8A8_BIO_TTB(FLocalWidth,
|
||
FLocalHeight);
|
||
FPImage.DataDescription := ImgFormatDescription;
|
||
|
||
WriteScanLine(FPImage);
|
||
|
||
ABitmap.LoadFromIntfImage(FPImage);
|
||
FPImage.Free;
|
||
FreeMem(FScanLine, FLineSize);
|
||
// reset FGifUseGraphCtrlExt flag
|
||
FGifUseGraphCtrlExt := False;
|
||
|
||
GifStream.Free;
|
||
Result := True;
|
||
end;
|
||
|
||
procedure TGifLoader.SetTransparent(const AValue: boolean);
|
||
begin
|
||
if FIsTransparent = AValue then
|
||
exit;
|
||
FIsTransparent := AValue;
|
||
end;
|
||
|
||
function TGifLoader.SkipBlock(Stream: TStream): byte;
|
||
var
|
||
Introducer, Labels, SkipByte: byte;
|
||
begin
|
||
Introducer := 0;
|
||
Labels := 0;
|
||
SkipByte := 0;
|
||
Stream.Read(Introducer, 1);
|
||
if Introducer = EXT_INTRODUCER then
|
||
begin
|
||
Stream.Read(Labels, 1);
|
||
case Labels of
|
||
EXT_COMMENT,
|
||
EXT_APPLICATION:
|
||
while True do
|
||
begin
|
||
Stream.Read(SkipByte, 1);
|
||
if SkipByte = 0 then
|
||
Break;
|
||
Stream.Seek(SkipByte, soFromCurrent);
|
||
end;
|
||
EXT_GRAPHICS_CONTROL:
|
||
begin
|
||
Stream.Read(FGifGraphicsCtrlExt, SizeOf(FGifGraphicsCtrlExt));
|
||
FGifUseGraphCtrlExt := True;
|
||
end;
|
||
EXT_PLAIN_TEXT:
|
||
begin
|
||
Stream.Read(SkipByte, 1);
|
||
Stream.Seek(SkipByte, soFromCurrent);
|
||
while True do
|
||
begin
|
||
Stream.Read(SkipByte, 1);
|
||
if SkipByte = 0 then
|
||
Break;
|
||
Stream.Seek(SkipByte, soFromCurrent);
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
Result := Introducer;
|
||
end;
|
||
|
||
procedure TGifLoader.ReadScanLine(Stream: TStream);
|
||
var
|
||
OldPos, UnpackedSize, PackedSize: longint;
|
||
I: integer;
|
||
Data, Bits, Code: cardinal;
|
||
SourcePtr: PByte;
|
||
InCode: cardinal;
|
||
|
||
CodeSize: cardinal;
|
||
CodeMask: cardinal;
|
||
FreeCode: cardinal;
|
||
OldCode: cardinal;
|
||
Prefix: array[0..CODE_TABLE_SIZE - 1] of cardinal;
|
||
Suffix, Stack: array [0..CODE_TABLE_SIZE - 1] of byte;
|
||
StackPointer: PByte;
|
||
DataComp, Target: PByte;
|
||
B, FInitialCodeSize, FirstChar: byte;
|
||
ClearCode, EOICode: word;
|
||
|
||
begin
|
||
FInitialCodeSize := 0;
|
||
B := 0;
|
||
DataComp := nil;
|
||
|
||
// initialisation du dictionnaire de decompression
|
||
Stream.Read(FInitialCodeSize, 1);
|
||
|
||
// Recherche la taille des donn<6E>es compresser
|
||
OldPos := Stream.Position;
|
||
PackedSize := 0;
|
||
repeat
|
||
Stream.Read(B, 1);
|
||
if B > 0 then
|
||
begin
|
||
Inc(PackedSize, B);
|
||
Stream.Seek(B, soFromCurrent);
|
||
end;
|
||
until B = 0;
|
||
|
||
Getmem(DataComp, PackedSize);
|
||
// lecture des donn<6E>es conpresser
|
||
SourcePtr := DataComp;
|
||
Stream.Position := OldPos;
|
||
repeat
|
||
Stream.Read(B, 1);
|
||
if B > 0 then
|
||
begin
|
||
Stream.ReadBuffer(SourcePtr^, B);
|
||
Inc(SourcePtr, B);
|
||
end;
|
||
until B = 0;
|
||
|
||
SourcePtr := DataComp;
|
||
Target := FScanLine;
|
||
CodeSize := FInitialCodeSize + 1;
|
||
ClearCode := 1 shl FInitialCodeSize;
|
||
EOICode := ClearCode + 1;
|
||
FreeCode := ClearCode + 2;
|
||
OldCode := CODE_TABLE_SIZE;
|
||
CodeMask := (1 shl CodeSize) - 1;
|
||
UnpackedSize := FLocalWidth * FLocalHeight;
|
||
for I := 0 to ClearCode - 1 do
|
||
begin
|
||
Prefix[I] := CODE_TABLE_SIZE;
|
||
Suffix[I] := I;
|
||
end;
|
||
StackPointer := @Stack;
|
||
FirstChar := 0;
|
||
Data := 0;
|
||
Bits := 0;
|
||
//Decompression LZW gif
|
||
while (UnpackedSize > 0) and (PackedSize > 0) do
|
||
begin
|
||
Inc(Data, SourcePtr^ shl Bits);
|
||
Inc(Bits, 8);
|
||
while Bits >= CodeSize do
|
||
begin
|
||
Code := Data and CodeMask;
|
||
Data := Data shr CodeSize;
|
||
Dec(Bits, CodeSize);
|
||
if Code = EOICode then
|
||
Break;
|
||
if Code = ClearCode then
|
||
begin
|
||
CodeSize := FInitialCodeSize + 1;
|
||
CodeMask := (1 shl CodeSize) - 1;
|
||
FreeCode := ClearCode + 2;
|
||
OldCode := CODE_TABLE_SIZE;
|
||
Continue;
|
||
end;
|
||
if Code > FreeCode then
|
||
Break;
|
||
if OldCode = CODE_TABLE_SIZE then
|
||
begin
|
||
FirstChar := Suffix[Code];
|
||
Target^ := FirstChar;
|
||
Inc(Target);
|
||
Dec(UnpackedSize);
|
||
OldCode := Code;
|
||
Continue;
|
||
end;
|
||
InCode := Code;
|
||
if Code = FreeCode then
|
||
begin
|
||
StackPointer^ := FirstChar;
|
||
Inc(StackPointer);
|
||
Code := OldCode;
|
||
end;
|
||
while Code > ClearCode do
|
||
begin
|
||
StackPointer^ := Suffix[Code];
|
||
Inc(StackPointer);
|
||
Code := Prefix[Code];
|
||
end;
|
||
FirstChar := Suffix[Code];
|
||
StackPointer^ := FirstChar;
|
||
Inc(StackPointer);
|
||
Prefix[FreeCode] := OldCode;
|
||
Suffix[FreeCode] := FirstChar;
|
||
if (FreeCode = CodeMask) and (CodeSize < 12) then
|
||
begin
|
||
Inc(CodeSize);
|
||
CodeMask := (1 shl CodeSize) - 1;
|
||
end;
|
||
if FreeCode < CODE_TABLE_SIZE - 1 then
|
||
Inc(FreeCode);
|
||
OldCode := InCode;
|
||
repeat
|
||
Dec(StackPointer);
|
||
Target^ := StackPointer^;
|
||
Inc(Target);
|
||
Dec(UnpackedSize);
|
||
until StackPointer = @Stack;
|
||
end;
|
||
Inc(SourcePtr);
|
||
Dec(PackedSize);
|
||
end;
|
||
FreeMem(DataComp);
|
||
end;
|
||
|
||
procedure TGifLoader.ReadHeader(Stream: TStream);
|
||
begin
|
||
Stream.Read(FGifHeader, SizeOf(FGifHeader));
|
||
|
||
with FGifHeader do
|
||
begin
|
||
FGifBackgroundColor := BackgroundColor;
|
||
|
||
FWidth := ScreenWidth;
|
||
FHeight := ScreenHeight;
|
||
|
||
FLocalWidth := ScreenWidth;
|
||
FLocalHeight := ScreenHeight;
|
||
|
||
IsTransparent := False;
|
||
end;
|
||
ReadGlobalPalette(Stream);
|
||
end;
|
||
|
||
procedure TGifLoader.ReadGlobalPalette(Stream: TStream);
|
||
var
|
||
ColorTableSize: integer;
|
||
begin
|
||
if (FGifHeader.Packedbit and ID_COLOR_TABLE) <> 0 then
|
||
begin
|
||
ColorTableSize := FGifHeader.Packedbit and ID_COLOR_TABLE_SIZE + 1;
|
||
ReadPalette(Stream, 1 shl ColorTableSize);
|
||
end;
|
||
end;
|
||
|
||
procedure TGifLoader.ReadGraphCtrlExt;
|
||
var
|
||
C: TFPColor;
|
||
begin
|
||
IsTransparent := (FGifGraphicsCtrlExt.Packedbit and ID_TRANSPARENT) <> 0;
|
||
|
||
FDisposalMethod := (FGifGraphicsCtrlExt.Packedbit and $1C) shr 2;
|
||
|
||
if IsTransparent then
|
||
begin
|
||
// if Transparent bitmap change alpha channel
|
||
FGifBackgroundColor := FGifGraphicsCtrlExt.ColorIndex;
|
||
C := FPalette[FGifBackgroundColor];
|
||
C.alpha := alphaTransparent;
|
||
FPalette[FGifBackgroundColor] := C;
|
||
end;
|
||
end;
|
||
|
||
procedure TGifLoader.SetInterlaced(const AValue: boolean);
|
||
begin
|
||
if FInterlaced = AValue then
|
||
exit;
|
||
FInterlaced := AValue;
|
||
end;
|
||
|
||
procedure TGifLoader.ReadPalette(Stream: TStream; Size: integer);
|
||
var
|
||
RGBEntry: TRGB;
|
||
I: integer;
|
||
C: TFPColor;
|
||
begin
|
||
FPalette.Clear;
|
||
FPalette.Count := 0;
|
||
Fillchar(RGBEntry, SizeOf(RGBEntry), 0);
|
||
for I := 0 to Size - 1 do
|
||
begin
|
||
Stream.Read(RGBEntry, SizeOf(RGBEntry));
|
||
with C do
|
||
begin
|
||
Red := RGBEntry.Red or (RGBEntry.Red shl 8);
|
||
Green := RGBEntry.Green or (RGBEntry.Green shl 8);
|
||
Blue := RGBEntry.Blue or (RGBEntry.Blue shl 8);
|
||
Alpha := alphaOpaque;
|
||
end;
|
||
FPalette.Add(C);
|
||
end;
|
||
end;
|
||
|
||
procedure TGifLoader.WriteScanLine(Img: TFPCustomImage);
|
||
var
|
||
Row, Col: integer;
|
||
Pass, Every: byte;
|
||
P: PByte;
|
||
begin
|
||
P := FScanLine;
|
||
if Interlaced then
|
||
begin
|
||
for Pass := 1 to 4 do
|
||
begin
|
||
case Pass of
|
||
1:
|
||
begin
|
||
Row := 0;
|
||
Every := 8;
|
||
end;
|
||
2:
|
||
begin
|
||
Row := 4;
|
||
Every := 8;
|
||
end;
|
||
3:
|
||
begin
|
||
Row := 2;
|
||
Every := 4;
|
||
end;
|
||
4:
|
||
begin
|
||
Row := 1;
|
||
Every := 2;
|
||
end;
|
||
end;
|
||
repeat
|
||
for Col := 0 to FLocalWidth - 1 do
|
||
begin
|
||
Img.Colors[Col, Row] := FPalette[P^];
|
||
Inc(P);
|
||
end;
|
||
Inc(Row, Every);
|
||
until Row >= FLocalHeight;
|
||
end;
|
||
end
|
||
else
|
||
begin
|
||
for Row := 0 to FLocalHeight - 1 do
|
||
for Col := 0 to FLocalWidth - 1 do
|
||
begin
|
||
Img.Colors[Col, Row] := FPalette[P^];
|
||
Inc(P);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure TGifLoader.ReadGifBitmap(Stream: TStream);
|
||
var
|
||
ColorTableSize: integer;
|
||
begin
|
||
Stream.Read(FGifDescriptor, SizeOf(FGifDescriptor));
|
||
|
||
with FGifDescriptor do
|
||
begin
|
||
FLocalWidth := Width;
|
||
FLocalHeight := Height;
|
||
Interlaced := (Packedbit and ID_INTERLACED = ID_INTERLACED);
|
||
end;
|
||
|
||
FLineSize := FLocalWidth * (FLocalHeight + 1);
|
||
GetMem(FScanLine, FLineSize);
|
||
|
||
if (FGifDescriptor.Packedbit and ID_COLOR_TABLE) <> 0 then
|
||
begin
|
||
ColorTableSize := FGifDescriptor.Packedbit and ID_COLOR_TABLE_SIZE + 1;
|
||
ReadPalette(Stream, 1 shl ColorTableSize);
|
||
end;
|
||
if FGifUseGraphCtrlExt then
|
||
ReadGraphCtrlExt;
|
||
|
||
end;
|
||
|
||
{ TGifImage }
|
||
|
||
constructor TGifImage.Create;
|
||
begin
|
||
FBitmap := TBitmap.Create;
|
||
FPosX := 0;
|
||
FPosY := 0;
|
||
FDelay := 0;
|
||
FMethod := 0;
|
||
end;
|
||
|
||
destructor TGifImage.Destroy;
|
||
begin
|
||
inherited Destroy;
|
||
FBitmap.Free;
|
||
end;
|
||
|
||
{ TGifList }
|
||
|
||
function TGifList.GetItems(Index: integer): TGifImage;
|
||
begin
|
||
Result := TGifImage(inherited Items[Index]);
|
||
end;
|
||
|
||
procedure TGifList.SetItems(Index: integer; AGifImage: TGifImage);
|
||
begin
|
||
Put(Index, AGifImage);
|
||
end;
|
||
|
||
function TGifList.Add(AGifImage: TGifImage): integer;
|
||
begin
|
||
Result := inherited Add(AGifImage);
|
||
end;
|
||
|
||
function TGifList.Extract(Item: TGifImage): TGifImage;
|
||
begin
|
||
Result := TGifImage(inherited Extract(Item));
|
||
end;
|
||
|
||
function TGifList.Remove(AGifImage: TGifImage): integer;
|
||
begin
|
||
Result := inherited Remove(AGifImage);
|
||
end;
|
||
|
||
function TGifList.IndexOf(AGifImage: TGifImage): integer;
|
||
begin
|
||
Result := inherited IndexOf(AGifImage);
|
||
end;
|
||
|
||
function TGifList.First: TGifImage;
|
||
begin
|
||
Result := TGifImage(inherited First);
|
||
end;
|
||
|
||
function TGifList.Last: TGifImage;
|
||
begin
|
||
Result := TGifImage(inherited Last);
|
||
end;
|
||
|
||
procedure TGifList.Insert(Index: integer; AGifImage: TGifImage);
|
||
begin
|
||
inherited Insert(Index, AGifImage);
|
||
end;
|
||
|
||
initialization
|
||
{$I gifanim.lrs}
|
||
|
||
end.
|