Files

3100 lines
82 KiB
ObjectPascal

{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.
The Original Code is: JvGIF.PAS, released on 2002-07-04.
The Initial Developers of the Original Code are: Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 1997, 1998 Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 2001,2002 SGB Software
All Rights Reserved.
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.delphi-jedi.org
Known Issues:
GIF support is native for VisualCLX so this file is VCL only // <-- wp: really?
Transparency does not seem to work
-----------------------------------------------------------------------------}
// $Id$
unit JvGIF;
{$mode objfpc}{$H+}
interface
uses
LCLType, LCLIntf, Types, RTLConsts,
SysUtils, Classes, Graphics, Controls;
const
RT_GIF = 'GIF'; { GIF Resource Type }
type
TGIFVersion = (gvUnknown, gv87a, gv89a);
TGIFBits = 1..8;
TDisposalMethod = (dmUndefined, dmLeave, dmRestoreBackground,
dmRestorePrevious, dmReserved4, dmReserved5, dmReserved6, dmReserved7);
TGIFColorItem = packed record
Red: Byte;
Green: Byte;
Blue: Byte;
end;
TGIFColorTable = packed record
Count: Integer;
Colors: packed array [Byte] of TGIFColorItem;
end;
TJvGIFFrame = class;
TGIFData = class;
TGIFItem = class;
TJvGIFImage = class(TGraphic)
private
FImage: TGIFData;
FVersion: TGIFVersion;
FItems: TList;
FFrameIndex: Integer;
FScreenWidth: Word;
FScreenHeight: Word;
FBackgroundColor: TColor;
FLooping: Boolean;
FCorrupted: Boolean;
FRepeatCount: Word;
FTransparent: Boolean;
function GetBitmap: TBitmap;
function GetCount: Integer;
function GetComment: TStrings;
function GetScreenWidth: Integer;
function GetScreenHeight: Integer;
function GetGlobalColorCount: Integer;
procedure UpdateScreenSize;
procedure SetComment(Value: TStrings);
function GetFrame(Index: Integer): TJvGIFFrame;
procedure SetFrameIndex(Value: Integer);
procedure SetBackgroundColor(Value: TColor);
procedure SetLooping(Value: Boolean);
procedure SetRepeatCount(Value: Word);
procedure ReadSignature(Stream: TStream);
procedure DoProgress(Stage: TProgressStage; PercentDone: Byte;
const Msg: string);
function GetCorrupted: Boolean;
function GetTransparentColor: TColor;
function GetBackgroundColor: TColor;
function GetPixelFormat: TPixelFormat;
procedure EncodeFrames(ReverseDecode: Boolean);
procedure ReadStream(Size: Longint; Stream: TStream; ForceDecode: Boolean);
procedure WriteStream(Stream: TStream; WriteSize: Boolean);
protected
procedure AssignTo(Dest: TPersistent); override;
procedure Draw(ACanvas: TCanvas; const ARect: TRect); override;
function Equals(Graphic: TGraphic): Boolean; override;
function GetEmpty: Boolean; override;
function GetHeight: Integer; override;
function GetWidth: Integer; override;
function GetPalette: HPALETTE; override;
function GetTransparent: Boolean; override;
procedure ClearItems;
procedure NewImage;
procedure UniqueImage;
procedure ReadData(Stream: TStream); override;
procedure SetHeight({%H-}Value: Integer); override;
procedure SetTransparent(Value: Boolean); override;
procedure SetWidth({%H-}Value: Integer); override;
procedure WriteData(Stream: TStream); override;
property Bitmap: TBitmap read GetBitmap; { volatile }
public
constructor Create; override;
destructor Destroy; override;
procedure Clear; override;
procedure DecodeAllFrames;
procedure EncodeAllFrames;
procedure Assign(Source: TPersistent); override;
procedure LoadFromStream(Stream: TStream); override;
procedure SaveToStream(Stream: TStream); override;
(*************** NOT CONVERTED ***
procedure LoadFromClipboardFormat(AFormat: Word; AData: THandle;
APalette: HPALETTE); override;
procedure SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
var APalette: HPALETTE); override;
**********************************)
procedure LoadFromResourceName(Instance: THandle; const ResName: string); override;
procedure LoadFromResourceID(Instance: THandle; ResID: Integer); override;
function AddFrame(Value: TGraphic): Integer; virtual;
procedure DeleteFrame(Index: Integer);
procedure MoveFrame(CurIndex, NewIndex: Integer);
procedure Grayscale(ForceEncoding: Boolean);
property BackgroundColor: TColor read GetBackgroundColor write SetBackgroundColor;
property Comment: TStrings read GetComment write SetComment;
property Corrupted: Boolean read GetCorrupted;
property Count: Integer read GetCount;
property Frames[Index: Integer]: TJvGIFFrame read GetFrame; default;
property FrameIndex: Integer read FFrameIndex write SetFrameIndex;
property GlobalColorCount: Integer read GetGlobalColorCount;
property Looping: Boolean read FLooping write SetLooping;
property PixelFormat: TPixelFormat read GetPixelFormat;
property RepeatCount: Word read FRepeatCount write SetRepeatCount;
property ScreenWidth: Integer read GetScreenWidth;
property ScreenHeight: Integer read GetScreenHeight;
property TransparentColor: TColor read GetTransparentColor;
property Version: TGIFVersion read FVersion;
end;
TJvGIFFrame = class(TPersistent)
private
FOwner: TJvGIFImage;
FBitmap: TBitmap;
FImage: TGIFItem;
FExtensions: TList;
FTopLeft: TPoint;
FInterlaced: Boolean;
FCorrupted: Boolean;
FGrayscale: Boolean;
FTransparentColor: TColor;
FAnimateInterval: Word;
FDisposal: TDisposalMethod;
FLocalColors: Boolean;
function GetBitmap: TBitmap;
function GetHeight: Integer;
function GetWidth: Integer;
function GetColorCount: Integer;
function FindComment(ForceCreate: Boolean): TStrings;
function GetComment: TStrings;
procedure SetComment(Value: TStrings);
procedure SetTransparentColor(Value: TColor);
procedure SetDisposalMethod(Value: TDisposalMethod);
procedure SetAnimateInterval(Value: Word);
procedure SetTopLeft(const Value: TPoint);
procedure NewBitmap;
procedure NewImage;
procedure SaveToBitmapStream(Stream: TMemoryStream);
procedure EncodeBitmapStream(Stream: TMemoryStream);
procedure EncodeRasterData;
procedure UpdateExtensions;
procedure WriteImageDescriptor(Stream: TStream);
procedure WriteLocalColorMap(Stream: TStream);
procedure WriteRasterData(Stream: TStream);
protected
procedure LoadFromStream(Stream: TStream);
procedure AssignTo(Dest: TPersistent); override;
procedure GrayscaleImage(ForceEncoding: Boolean);
public
constructor Create(AOwner: TJvGIFImage); virtual;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
procedure Draw(ACanvas: TCanvas; const ARect: TRect;
Transparent: Boolean);
property AnimateInterval: Word read FAnimateInterval write SetAnimateInterval;
property Bitmap: TBitmap read GetBitmap; { volatile }
property ColorCount: Integer read GetColorCount;
property Comment: TStrings read GetComment write SetComment;
property DisposalMethod: TDisposalMethod read FDisposal write SetDisposalMethod;
property Interlaced: Boolean read FInterlaced;
property Corrupted: Boolean read FCorrupted;
property TransparentColor: TColor read FTransparentColor write SetTransparentColor;
property Origin: TPoint read FTopLeft write SetTopLeft;
property Height: Integer read GetHeight;
property Width: Integer read GetWidth;
end;
TGIFData = class(TSharedRasterImage) //was: TSharedImage)
private
FComment: TStringList;
FAspectRatio: Byte;
FBitsPerPixel: Byte;
FColorResBits: Byte;
FColorMap: TGIFColorTable;
protected
procedure FreeHandle; override;
public
constructor Create; override;
destructor Destroy; override;
end;
TGIFItem = class(TSharedRasterImage) //was: TSharedImage)
private
FImageData: TMemoryStream;
FSize: TPoint;
FPackedFields: Byte;
FBitsPerPixel: Byte;
FColorMap: TGIFColorTable;
protected
procedure FreeHandle; override;
public
destructor Destroy; override;
end;
var
CF_JVGIF: UINT; { Clipboard format for GIF image }
{ Load incomplete or corrupted images without exceptions }
// (rom) changed to var to allow changes
var
GIFLoadCorrupted: Boolean = True;
function GIFVersionName(Version: TGIFVersion): string;
procedure JvGif_Dummy;
implementation
uses
//Consts,
FPImage, Math, ClipBrd,
JvJVCLUtils, JvResources, JvTypes;
// JvJCLUtils, JvJVCLUtils, JvAni, JvConsts, JvResources, JvTypes;
const
CrLf = #13#10;
{$RANGECHECKS OFF}
procedure JvGif_Dummy;
begin
end;
procedure GifError(const Msg: String);
begin
raise EInvalidGraphicOperation.Create(Msg);
end;
(*
procedure GifError(const Msg: string);
procedure ThrowException(const Msg: string; ReturnAddr: Pointer);
begin
raise EInvalidGraphicOperation.Create(Msg) at ReturnAddr;
end;
asm
{$IFDEF CPU32}
pop edx
{$ENDIF CPU32}
{$IFDEF CPU64}
pop rdx
{$ENDIF CPU64}
jmp ThrowException
end;
*)
{$IFDEF RANGECHECKS_ON}
{$RANGECHECKS ON}
{$ENDIF RANGECHECKS_ON}
//=== { TSharedImage } =======================================================
type
TGifSignature = array [0..2] of AnsiChar;
const
GIFSignature: TGifSignature = 'GIF';
GIFVersionStr: array [TGIFVersion] of TGifSignature = (#0#0#0, '87a', '89a');
function GIFVersionName(Version: TGIFVersion): string;
begin
Result := string(GIFVersionStr[Version]);
end;
const
CODE_TABLE_SIZE = 4096;
HASH_TABLE_SIZE = 17777;
MAX_LOOP_COUNT = 30000;
CHR_EXT_INTRODUCER = '!';
CHR_IMAGE_SEPARATOR = ',';
CHR_TRAILER = ';'; { indicates the end of the GIF Data stream }
{ Image descriptor bit masks }
ID_LOCAL_COLOR_TABLE = $80; { set if a local color table follows }
ID_INTERLACED = $40; { set if image is interlaced }
ID_SORT = $20; { set if color table is sorted }
ID_RESERVED = $0C; { reserved - must be set to $00 }
ID_COLOR_TABLE_SIZE = $07; { Size of color table as above }
{ Logical screen descriptor packed field masks }
LSD_GLOBAL_COLOR_TABLE = $80; { set if global color table follows L.S.D. }
LSD_COLOR_RESOLUTION = $70; { Color resolution - 3 bits }
LSD_SORT = $08; { set if global color table is sorted - 1 bit }
LSD_COLOR_TABLE_SIZE = $07; { Size of global color table - 3 bits }
{ Actual Size = 2^value+1 - value is 3 bits }
{ Graphic control extension packed field masks }
GCE_TRANSPARENT = $01; { whether a transparency Index is given }
GCE_USER_INPUT = $02; { whether or not user input is expected }
GCE_DISPOSAL_METHOD = $1C; { the way in which the graphic is to be treated after being displayed }
GCE_RESERVED = $E0; { reserved - must be set to $00 }
{ Application extension }
AE_LOOPING = $01; { looping Netscape extension }
GIFColors: array [TGIFBits] of Word = (2, 4, 8, 16, 32, 64, 128, 256);
function ColorsToBits(ColorCount: Word): Byte;
var
I: TGIFBits;
begin
Result := 0;
for I := Low(TGIFBits) to High(TGIFBits) do
if ColorCount = GIFColors[I] then
begin
Result := I;
Exit;
end;
GifError(RsEWrongGIFColors);
end;
function ColorsToPixelFormat(Colors: Word): TPixelFormat;
begin
if Colors <= 2 then
Result := pf1bit
else
if Colors <= 16 then
Result := pf4bit
else
if Colors <= 256 then
Result := pf8bit
else
Result := pf24bit;
end;
function ItemToRGB(Item: TGIFColorItem): Longint;
begin
with Item do
Result := RGB(Red, Green, Blue);
end;
function GrayColor(Color: TColor): TColor;
var
Index: Integer;
begin
Index := Byte(Longint(Word(GetRValue(Color)) * 77 +
Word(GetGValue(Color)) * 150 + Word(GetBValue(Color)) * 29) shr 8);
Result := RGB(Index, Index, Index);
end;
procedure GrayColorTable(var ColorTable: TGIFColorTable);
var
I: Byte;
Index: Integer;
begin
for I := 0 to ColorTable.Count - 1 do
begin
with ColorTable.Colors[I] do
begin
Index := Byte(Longint(Word(Red) * 77 + Word(Green) * 150 + Word(Blue) * 29) shr 8);
Red := Index;
Green := Index;
Blue := Index;
end;
end;
end;
function FindColorIndex(const ColorTable: TGIFColorTable;
Color: TColor): Integer;
begin
if Color <> clNone then
for Result := 0 to ColorTable.Count - 1 do
if ItemToRGB(ColorTable.Colors[Result]) = ColorToRGB(Color) then
Exit;
Result := -1;
end;
{ The following types and function declarations are used to call into
functions of the GIF implementation of the GIF image
compression/decompression standard. }
type
TGIFHeader = packed record
Signature: TGifSignature; { contains 'GIF' }
Version: TGifSignature; { '87a' or '89a' }
end;
TScreenDescriptor = packed record
ScreenWidth: Word; { logical screen width }
ScreenHeight: Word; { logical screen height }
PackedFields: Byte;
BackgroundColorIndex: Byte; { Index to global color table }
AspectRatio: Byte; { actual ratio = (AspectRatio + 15) / 64 }
end;
TImageDescriptor = packed record
ImageLeftPos: Word; { column in pixels in respect to left of logical screen }
ImageTopPos: Word; { row in pixels in respect to top of logical screen }
ImageWidth: Word; { width of image in pixels }
ImageHeight: Word; { height of image in pixels }
PackedFields: Byte;
end;
{ GIF Extensions support }
type
TExtensionType = (etGraphic, etPlainText, etApplication, etComment);
const
ExtLabels: array [TExtensionType] of Byte = ($F9, $01, $FF, $FE);
LoopExtNS: string[11] = 'NETSCAPE2.0';
LoopExtAN: string[11] = 'ANIMEXTS1.0';
type
TGraphicControlExtension = packed record
BlockSize: Byte; { should be 4 }
PackedFields: Byte;
DelayTime: Word; { in centiseconds }
TransparentColorIndex: Byte;
Terminator: Byte;
end;
TPlainTextExtension = packed record
BlockSize: Byte; { should be 12 }
Left: Word;
Top: Word;
Width: Word;
Height: Word;
CellWidth: Byte;
CellHeight: Byte;
FGColorIndex: Byte;
BGColorIndex: Byte;
end;
TAppExtension = packed record
BlockSize: Byte; { should be 11 }
AppId: array [1..8] of Byte;
Authentication: array [1..3] of Byte;
end;
TExtensionRecord = packed record
case ExtensionType: TExtensionType of
etGraphic:
(GCE: TGraphicControlExtension);
etPlainText:
(PTE: TPlainTextExtension);
etApplication:
(APPE: TAppExtension);
end;
//=== { TExtension } =========================================================
type
TExtension = class(TPersistent)
private
FExtType: TExtensionType;
FData: TStringList;
FExtRec: TExtensionRecord;
public
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
function IsLoopExtension: Boolean;
end;
destructor TExtension.Destroy;
begin
FData.Free;
inherited Destroy;
end;
procedure TExtension.Assign(Source: TPersistent);
begin
if (Source <> nil) and (Source is TExtension) then
begin
FExtType := TExtension(Source).FExtType;
FExtRec := TExtension(Source).FExtRec;
if TExtension(Source).FData <> nil then
begin
if FData = nil then
FData := TStringList.Create;
FData.Assign(TExtension(Source).FData);
end;
end
else
inherited Assign(Source);
end;
function TExtension.IsLoopExtension: Boolean;
begin
Result := (FExtType = etApplication) and (FData.Count > 0) and
(CompareMem(@FExtRec.APPE.AppId, @LoopExtNS[1], FExtRec.APPE.BlockSize) or
CompareMem(@FExtRec.APPE.AppId, @LoopExtAN[1], FExtRec.APPE.BlockSize)) and
(Length(FData[0]) >= 3) and (Byte(FData[0][1]) = AE_LOOPING);
end;
procedure FreeExtensions(Extensions: TList); {near;}
begin
if Extensions <> nil then
begin
while Extensions.Count > 0 do
begin
TObject(Extensions[0]).Free;
Extensions.Delete(0);
end;
Extensions.Free;
end;
end;
function FindExtension(Extensions: TList; ExtType: TExtensionType): TExtension;
var
I: Integer;
begin
if Extensions <> nil then
for I := Extensions.Count - 1 downto 0 do
begin
Result := TExtension(Extensions[I]);
if (Result <> nil) and (Result.FExtType = ExtType) then
Exit;
end;
Result := nil;
end;
{
function CopyExtensions(Source: TList): TList; near;
var
I: Integer;
Ext: TExtension;
begin
Result := TList.Create;
try
for I := 0 to Source.Count - 1 do
if (Source[I] <> nil) and (TObject(Source[I]) is TExtension) then
begin
Ext := TExtension.Create;
try
Ext.Assign(Source[I]);
Result.Add(Ext);
except
Ext.Free;
raise;
end;
end;
except
Result.Free;
raise;
end;
end;
}
type
TProgressProc = procedure(Stage: TProgressStage; PercentDone: Byte;
const Msg: string) of object;
{ GIF reading/writing routines
Procedures to read and write GIF files, GIF-decoding and encoding
based on freeware C source code of GBM package by Andy Key
(nyangau att interalpha dott co dott uk). The home page of GBM author is
at http://www.interalpha.net/customer/nyangau/. }
type
PIntCodeTable = ^TIntCodeTable;
TIntCodeTable = array [0..CODE_TABLE_SIZE - 1] of Word;
PReadContext = {%H-}^TReadContext;
TReadContext = record
Inx: Longint;
Size: Longint;
Buf: array [0..255 + 4] of Byte;
CodeSize: Longint;
ReadMask: Longint;
end;
PWriteContext = {%H-}^TWriteContext;
TWriteContext = record
Inx: Longint;
CodeSize: Longint;
Buf: array [0..255 + 4] of Byte;
end;
TOutputContext = record
W: Longint;
H: Longint;
X: Longint;
Y: Longint;
BitsPerPixel: Integer;
Pass: Integer;
Interlace: Boolean;
LineIdent: Longint;
Data: Pointer;
CurrLineData: Pointer;
end;
PImageDict = ^TImageDict;
TImageDict = record
Tail: Word;
Index: Word;
Col: Byte;
end;
PDictTable = ^TDictTable;
TDictTable = array [0..CODE_TABLE_SIZE - 1] of TImageDict;
function InitHash(P: Longint): Longint;
begin
Result := (P + 3) * 301;
end;
function InterlaceStep(Y, Height: Integer; var Pass: Integer): Integer;
begin
Result := Y;
case Pass of
0, 1:
Inc(Result, 8);
2:
Inc(Result, 4);
3:
Inc(Result, 2);
end;
if Result >= Height then
begin
if Pass = 0 then
begin
Pass := 1;
Result := 4;
if Result < Height then
Exit;
end;
if Pass = 1 then
begin
Pass := 2;
Result := 2;
if Result < Height then
Exit;
end;
if Pass = 2 then
begin
Pass := 3;
Result := 1;
end;
end;
end;
procedure ReadImageStream(Stream, Dest: TStream; var Desc: TImageDescriptor;
var Interlaced, LocalColors, Corrupted: Boolean; var BitsPerPixel: Byte;
var ColorTable: TGIFColorTable);
var
CodeSize: Byte = 0;
BlockSize: Byte = 0;
begin
Corrupted := False;
Stream.ReadBuffer(Desc, SizeOf(TImageDescriptor));
Interlaced := (Desc.PackedFields and ID_INTERLACED) <> 0;
if (Desc.PackedFields and ID_LOCAL_COLOR_TABLE) <> 0 then
begin
{ Local colors table follows }
BitsPerPixel := 1 + Desc.PackedFields and ID_COLOR_TABLE_SIZE;
LocalColors := True;
ColorTable.Count := 1 shl BitsPerPixel;
Stream.ReadBuffer(ColorTable.Colors[0],
ColorTable.Count * SizeOf(TGIFColorItem));
end
else
begin
LocalColors := False;
FillChar(ColorTable, SizeOf(ColorTable), 0);
end;
Stream.ReadBuffer(CodeSize, 1);
Dest.Write(CodeSize, 1);
repeat
Stream.Read(BlockSize, 1);
if (Stream.Position + BlockSize) > Stream.Size then
begin
Corrupted := True;
Stream.Position := Stream.Size;
Exit;
end;
Dest.Write(BlockSize, 1);
if (Stream.Position + BlockSize) > Stream.Size then
begin
BlockSize := Stream.Size - Stream.Position;
Corrupted := True;
end;
if BlockSize > 0 then
Dest.CopyFrom(Stream, BlockSize);
until (BlockSize = 0) or (Stream.Position >= Stream.Size);
end;
procedure FillRGBPalette(const ColorTable: TGIFColorTable;
var Colors: TRGBPalette);
var
I: Byte;
begin
FillChar(Colors, SizeOf(Colors), $80);
for I := 0 to ColorTable.Count - 1 do
begin
Colors[I].rgbRed := ColorTable.Colors[I].Red;
Colors[I].rgbGreen := ColorTable.Colors[I].Green;
Colors[I].rgbBlue := ColorTable.Colors[I].Blue;
Colors[I].rgbReserved := 0;
end;
end;
function ReadCode(Stream: TStream; var Context: TReadContext): Longint;
var
RawCode: Longint;
ByteIndex: Longint;
Bytes: Byte = 0;
BytesToLose: Longint;
begin
while (Context.Inx + Context.CodeSize > Context.Size) and
(Stream.Position < Stream.Size) do
begin
{ not enough bits in buffer - refill it }
{ Not very efficient, but infrequently called }
BytesToLose := Context.Inx shr 3;
{ Note biggest Code Size is 12 bits. And this can at worst span 3 Bytes }
Move(Context.Buf[Word(BytesToLose)], Context.Buf[0], 3);
Context.Inx := Context.Inx and 7;
Context.Size := Context.Size - (BytesToLose shl 3);
Stream.ReadBuffer(Bytes, 1);
if Bytes > 0 then
Stream.ReadBuffer(Context.Buf[Word(Context.Size shr 3)], Bytes);
Context.Size := Context.Size + (Bytes shl 3);
end;
ByteIndex := Context.Inx shr 3;
RawCode := Context.Buf[Word(ByteIndex)] +
(Word(Context.Buf[Word(ByteIndex + 1)]) shl 8);
if Context.CodeSize > 8 then
RawCode := RawCode + (Longint(Context.Buf[ByteIndex + 2]) shl 16);
RawCode := RawCode shr (Context.Inx and 7);
Context.Inx := Context.Inx + Byte(Context.CodeSize);
Result := RawCode and Context.ReadMask;
end;
procedure Output(Value: Byte; var Context: TOutputContext);
var
P: PByte;
begin
if Context.Y >= Context.H then
Exit;
case Context.BitsPerPixel of
1:
begin
P := PByte(PAnsiChar(Context.CurrLineData) + (Context.X shr 3));
if (Context.X and $07) <> 0 then
P^ := P^ or Word(Value shl (7 - (Word(Context.X and 7))))
else
P^ := Byte(Value shl 7);
end;
4:
begin
P := PByte(PAnsiChar(Context.CurrLineData) + (Context.X shr 1));
if (Context.X and 1) <> 0 then
P^ := P^ or Value
else
P^ := Byte(Value shl 4);
end;
8:
begin
P := PByte(PAnsiChar(Context.CurrLineData) + Context.X);
P^ := Value;
end;
end;
Inc(Context.X);
if Context.X < Context.W then
Exit;
Context.X := 0;
if Context.Interlace then
Context.Y := InterlaceStep(Context.Y, Context.H, Context.Pass)
else
Inc(Context.Y);
Context.CurrLineData := PAnsiChar(Context.Data) + (Context.H - 1 - Context.Y) * Context.LineIdent;
end;
procedure ReadGIFData(Stream: TStream; const Header: TBitmapInfoHeader;
Interlaced, LoadCorrupt: Boolean; IntBitPerPixel: Byte; Data: Pointer;
var Corrupted: Boolean; ProgressProc: TProgressProc);
var
MinCodeSize: Byte = 0;
Temp: Byte;
MaxCode, BitMask, InitCodeSize: Longint;
ClearCode, EndingCode, FirstFreeCode, FreeCode: Word;
I, OutCount, Code: Longint;
CurCode, OldCode, InCode, FinalChar: Word;
Prefix, Suffix, OutCode: PIntCodeTable;
ReadCtxt: TReadContext;
OutCtxt: TOutputContext;
TableFull: Boolean;
begin
Corrupted := False;
OutCount := 0;
OldCode := 0;
FinalChar := 0;
TableFull := False;
Prefix := AllocMem(SizeOf(TIntCodeTable));
try
Suffix := AllocMem(SizeOf(TIntCodeTable));
try
OutCode := AllocMem(SizeOf(TIntCodeTable) + SizeOf(Word));
try
if Assigned(ProgressProc) then
ProgressProc(psStarting, 0, '');
try
Stream.ReadBuffer(MinCodeSize, 1);
if (MinCodeSize < 2) or (MinCodeSize > 9) then
begin
if LoadCorrupt then
begin
Corrupted := True;
MinCodeSize := Max(2, Min(MinCodeSize, 9));
end
else
GifError(RsEBadGIFCodeSize);
end;
{ Initial read context }
ReadCtxt.Inx := 0;
ReadCtxt.Size := 0;
ReadCtxt.CodeSize := MinCodeSize + 1;
ReadCtxt.ReadMask := (1 shl ReadCtxt.CodeSize) - 1;
{ Initialise pixel-output context }
OutCtxt.X := 0;
OutCtxt.Y := 0;
OutCtxt.Pass := 0;
OutCtxt.W := Header.biWidth;
OutCtxt.H := Header.biHeight;
OutCtxt.BitsPerPixel := Header.biBitCount;
OutCtxt.Interlace := Interlaced;
OutCtxt.LineIdent := ((Header.biWidth * Header.biBitCount + 31)
div 32) * 4;
OutCtxt.Data := Data;
OutCtxt.CurrLineData := PAnsiChar(Data) + (Header.biHeight - 1) * OutCtxt.LineIdent;
BitMask := (1 shl IntBitPerPixel) - 1;
{ 2 ^ MinCodeSize accounts for all colours in file }
ClearCode := 1 shl MinCodeSize;
EndingCode := ClearCode + 1;
FreeCode := ClearCode + 2;
FirstFreeCode := FreeCode;
{ 2^ (MinCodeSize + 1) includes clear and eoi Code and space too }
InitCodeSize := ReadCtxt.CodeSize;
MaxCode := 1 shl ReadCtxt.CodeSize;
Code := ReadCode(Stream, ReadCtxt);
while (Code <> EndingCode) and (Code <> $FFFF) and
(OutCtxt.Y < OutCtxt.H) do
begin
if Code = ClearCode then
begin
ReadCtxt.CodeSize := InitCodeSize;
MaxCode := 1 shl ReadCtxt.CodeSize;
ReadCtxt.ReadMask := MaxCode - 1;
FreeCode := FirstFreeCode;
Code := ReadCode(Stream, ReadCtxt);
CurCode := Code;
OldCode := Code;
if Code = $FFFF then
Break;
FinalChar := (CurCode and BitMask);
Output(Byte(FinalChar), OutCtxt);
TableFull := False;
end
else
begin
CurCode := Code;
InCode := Code;
if CurCode >= FreeCode then
begin
CurCode := OldCode;
OutCode^[OutCount] := FinalChar;
Inc(OutCount);
end;
while CurCode > BitMask do
begin
if OutCount > CODE_TABLE_SIZE then
begin
if LoadCorrupt then
begin
CurCode := BitMask;
OutCount := 1;
Corrupted := True;
Break;
end
else
GifError(RsEGIFDecodeError);
end;
OutCode^[OutCount] := Suffix^[CurCode];
Inc(OutCount);
CurCode := Prefix^[CurCode];
end;
if Corrupted then
Break;
FinalChar := CurCode and BitMask;
OutCode^[OutCount] := FinalChar;
Inc(OutCount);
for I := OutCount - 1 downto 0 do
Output(Byte(OutCode^[I]), OutCtxt);
OutCount := 0;
{ Update dictionary }
if not TableFull then
begin
Prefix^[FreeCode] := OldCode;
Suffix^[FreeCode] := FinalChar;
{ Advance to next free slot }
Inc(FreeCode);
if FreeCode >= MaxCode then
begin
if ReadCtxt.CodeSize < 12 then
begin
Inc(ReadCtxt.CodeSize);
MaxCode := MaxCode shl 1;
ReadCtxt.ReadMask := (1 shl ReadCtxt.CodeSize) - 1;
end
else
TableFull := True;
end;
end;
OldCode := InCode;
end;
Code := ReadCode(Stream, ReadCtxt);
if Stream.Size > 0 then
begin
Temp := Trunc(100.0 * (Stream.Position / Stream.Size));
if Assigned(ProgressProc) then
ProgressProc(psRunning, Temp, '');
end;
end; { while }
if Code = $FFFF then
GifError(SReadError);
finally
if Assigned(ProgressProc) then
begin
if ExceptObject = nil then
ProgressProc(psEnding, 100, '')
else
ProgressProc(psEnding, 0, Exception(ExceptObject).Message);
end;
end;
finally
FreeMem(OutCode, SizeOf(TIntCodeTable) + SizeOf(Word));
end;
finally
FreeMem(Suffix, SizeOf(TIntCodeTable));
end;
finally
FreeMem(Prefix, SizeOf(TIntCodeTable));
end;
end;
procedure WriteCode(Stream: TStream; Code: Longint;
var Context: TWriteContext);
var
BufIndex: Longint;
Bytes: Byte;
begin
BufIndex := Context.Inx shr 3;
Code := Code shl (Context.Inx and 7);
Context.Buf[BufIndex] := Context.Buf[BufIndex] or Code;
Context.Buf[BufIndex + 1] := (Code shr 8);
Context.Buf[BufIndex + 2] := (Code shr 16);
Context.Inx := Context.Inx + Context.CodeSize;
if Context.Inx >= 255 * 8 then
begin
{ Flush out full buffer }
Bytes := 255;
Stream.WriteBuffer(Bytes, 1);
Stream.WriteBuffer(Context.Buf, Bytes);
Move(Context.Buf[255], Context.Buf[0], 2);
FillChar(Context.Buf[2], 255, 0);
Context.Inx := Context.Inx - (255 * 8);
end;
end;
procedure FlushCode(Stream: TStream; var Context: TWriteContext);
var
Bytes: Byte;
begin
Bytes := (Context.Inx + 7) shr 3;
if Bytes > 0 then
begin
Stream.WriteBuffer(Bytes, 1);
Stream.WriteBuffer(Context.Buf, Bytes);
end;
{ Data block terminator - a block of zero Size }
Bytes := 0;
Stream.WriteBuffer(Bytes, 1);
end;
procedure FillColorTable(var ColorTable: TGIFColorTable;
const Colors: TRGBPalette; Count: Integer);
var
I: Byte;
begin
FillChar(ColorTable, SizeOf(ColorTable), 0);
ColorTable.Count := Min(256, Count);
for I := 0 to ColorTable.Count - 1 do
begin
ColorTable.Colors[I].Red := Colors[I].rgbRed;
ColorTable.Colors[I].Green := Colors[I].rgbGreen;
ColorTable.Colors[I].Blue := Colors[I].rgbBlue;
end;
end;
procedure WriteGIFData(Stream: TStream; var Header: TBitmapInfoHeader;
Interlaced: Boolean; Data: Pointer; ProgressProc: TProgressProc);
{ LZW encode data }
var
LineIdent: Longint;
MinCodeSize, Col, Temp: Byte;
InitCodeSize, X, Y: Longint;
Pass: Integer;
MaxCode: Longint; { 1 shl CodeSize }
ClearCode, EndingCode, LastCode, Tail: Longint;
I, HashValue: Longint;
LenString: Word;
Dict: PDictTable;
HashTable: TList;
PData: PByte;
WriteCtxt: TWriteContext;
begin
LineIdent := ((Header.biWidth * Header.biBitCount + 31) div 32) * 4;
Tail := 0;
HashValue := 0;
Dict := AllocMem(SizeOf(TDictTable));
try
HashTable := TList.Create;
try
for I := 0 to HASH_TABLE_SIZE - 1 do
HashTable.Add(nil);
{ Initialise encoder variables }
InitCodeSize := Header.biBitCount + 1;
if InitCodeSize = 2 then
Inc(InitCodeSize);
MinCodeSize := InitCodeSize - 1;
Stream.WriteBuffer(MinCodeSize, 1);
ClearCode := 1 shl MinCodeSize;
EndingCode := ClearCode + 1;
LastCode := EndingCode;
MaxCode := 1 shl InitCodeSize;
LenString := 0;
{ Setup write context }
WriteCtxt.Inx := 0;
WriteCtxt.CodeSize := InitCodeSize;
FillChar(WriteCtxt.Buf, SizeOf(WriteCtxt.Buf), 0);
WriteCode(Stream, ClearCode, WriteCtxt);
for I := 0 to HASH_TABLE_SIZE - 1 do
HashTable[I] := nil;
Data := PAnsiChar(Data) + (Header.biHeight - 1) * LineIdent;
Y := 0;
Pass := 0;
if Assigned(ProgressProc) then
ProgressProc(psStarting, 0, '');
try
while Y < Header.biHeight do
begin
PData := PByte(PAnsiChar(Data) - (Y * LineIdent));
for X := 0 to Header.biWidth - 1 do
begin
case Header.biBitCount of
8:
begin
Col := PData^;
Inc(PData);
end;
4:
begin
if X and 1 <> 0 then
begin
Col := PData^ and $0F;
Inc(PData);
end
else
Col := PData^ shr 4;
end;
else { must be 1 }
begin
if X and 7 = 7 then
begin
Col := PData^ and 1;
Inc(PData);
end
else
Col := (PData^ shr (7 - (X and $07))) and $01;
end;
end;
Inc(LenString);
if LenString = 1 then
begin
Tail := Col;
HashValue := InitHash(Col);
end
else
begin
HashValue := HashValue * (Col + LenString + 4);
I := HashValue mod HASH_TABLE_SIZE;
HashValue := HashValue mod HASH_TABLE_SIZE;
while (HashTable[I] <> nil) and
((PImageDict(HashTable[I])^.Tail <> Tail) or
(PImageDict(HashTable[I])^.Col <> Col)) do
begin
Inc(I);
if I >= HASH_TABLE_SIZE then
I := 0;
end;
if HashTable[I] <> nil then { Found in the strings table }
Tail := PImageDict(HashTable[I])^.Index
else
begin
{ Not found }
WriteCode(Stream, Tail, WriteCtxt);
Inc(LastCode);
HashTable[I] := @Dict^[LastCode];
PImageDict(HashTable[I])^.Index := LastCode;
PImageDict(HashTable[I])^.Tail := Tail;
PImageDict(HashTable[I])^.Col := Col;
Tail := Col;
HashValue := InitHash(Col);
LenString := 1;
if LastCode >= MaxCode then
begin
{ Next Code will be written longer }
MaxCode := MaxCode shl 1;
Inc(WriteCtxt.CodeSize);
end
else
if LastCode >= CODE_TABLE_SIZE - 2 then
begin
{ Reset tables }
WriteCode(Stream, Tail, WriteCtxt);
WriteCode(Stream, ClearCode, WriteCtxt);
LenString := 0;
LastCode := EndingCode;
WriteCtxt.CodeSize := InitCodeSize;
MaxCode := 1 shl InitCodeSize;
for I := 0 to HASH_TABLE_SIZE - 1 do
HashTable[I] := nil;
end;
end;
end;
end; { for X loop }
if Interlaced then
Y := InterlaceStep(Y, Header.biHeight, Pass)
else
Inc(Y);
Temp := Trunc(100.0 * (Y / Header.biHeight));
if Assigned(ProgressProc) then
ProgressProc(psRunning, Temp, '');
end; { while Y loop }
WriteCode(Stream, Tail, WriteCtxt);
WriteCode(Stream, EndingCode, WriteCtxt);
FlushCode(Stream, WriteCtxt);
finally
if Assigned(ProgressProc) then
begin
if ExceptObject = nil then
ProgressProc(psEnding, 100, '')
else
ProgressProc(psEnding, 0, Exception(ExceptObject).Message);
end;
end;
finally
HashTable.Free;
end;
finally
FreeMem(Dict, SizeOf(TDictTable));
end;
end;
//=== { TGIFItem } ===========================================================
destructor TGIFItem.Destroy;
begin
FImageData.Free;
inherited Destroy;
end;
procedure TGIFItem.FreeHandle;
begin
if FImageData <> nil then
FImageData.SetSize(0);
end;
//=== { TGIFData } ===========================================================
constructor TGIFData.Create;
begin
inherited Create;
FComment := TStringList.Create;
end;
destructor TGIFData.Destroy;
begin
FComment.Free;
inherited Destroy;
end;
procedure TGIFData.FreeHandle;
begin
if FComment <> nil then
FComment.Clear;
end;
//=== { TJvGIFFrame } ========================================================
constructor TJvGIFFrame.Create(AOwner: TJvGIFImage);
begin
FOwner := AOwner;
inherited Create;
NewImage;
end;
destructor TJvGIFFrame.Destroy;
begin
FBitmap.Free;
FreeExtensions(FExtensions);
FImage.Release;
inherited Destroy;
end;
procedure TJvGIFFrame.SetAnimateInterval(Value: Word);
begin
if FAnimateInterval <> Value then
begin
FAnimateInterval := Value;
if Value > 0 then
FOwner.FVersion := gv89a;
FOwner.Changed(FOwner);
end;
end;
procedure TJvGIFFrame.SetDisposalMethod(Value: TDisposalMethod);
begin
if FDisposal <> Value then
begin
FDisposal := Value;
if Value <> dmUndefined then
FOwner.FVersion := gv89a;
FOwner.Changed(FOwner);
end;
end;
procedure TJvGIFFrame.SetTopLeft(const Value: TPoint);
begin
if (FTopLeft.X <> Value.X) or (FTopLeft.Y <> Value.Y) then
begin
FTopLeft.X := Value.X;
FTopLeft.Y := Value.Y;
FOwner.FScreenWidth := Max(FOwner.FScreenWidth,
FImage.FSize.X + FTopLeft.X);
FOwner.FScreenHeight := Max(FOwner.FScreenHeight,
FImage.FSize.Y + FTopLeft.Y);
FOwner.Changed(FOwner);
end;
end;
procedure TJvGIFFrame.SetTransparentColor(Value: TColor);
begin
if FTransparentColor <> Value then
begin
FTransparentColor := Value;
if Value <> clNone then
FOwner.FVersion := gv89a;
FOwner.Changed(FOwner);
end;
end;
function TJvGIFFrame.GetBitmap: TBitmap;
var
Mem: TMemoryStream;
begin
Result := FBitmap;
if (Result = nil) or Result.Empty then
begin
NewBitmap;
Result := FBitmap;
if Assigned(FImage.FImageData) then
try
Mem := TMemoryStream.Create;
try
SaveToBitmapStream(Mem);
FBitmap.LoadFromStream(Mem);
if not FBitmap.Monochrome then
FBitmap.HandleType := bmDDB;
finally
Mem.Free;
end;
except
raise;
end;
end;
end;
function TJvGIFFrame.GetHeight: Integer;
begin
if Assigned(FBitmap) or Assigned(FImage.FImageData) then
Result := Bitmap.Height
else
Result := 0;
end;
function TJvGIFFrame.GetWidth: Integer;
begin
if Assigned(FBitmap) or Assigned(FImage.FImageData) then
Result := Bitmap.Width
else
Result := 0;
end;
function TJvGIFFrame.GetColorCount: Integer;
begin
Result := FImage.FColorMap.Count;
if (Result = 0) and Assigned(FBitmap) and (FBitmap.Palette <> 0) then
Result := PaletteEntries(FBitmap.Palette);
end;
procedure TJvGIFFrame.GrayscaleImage(ForceEncoding: Boolean);
var
Mem: TMemoryStream;
TransIndex: Integer;
begin
if not FGrayscale and (Assigned(FBitmap) or
Assigned(FImage.FImageData)) then
begin
if Assigned(FImage.FImageData) and (FImage.FColorMap.Count > 0) then
begin
FBitmap.Free;
FBitmap := nil;
TransIndex := FindColorIndex(FImage.FColorMap, FTransparentColor);
GrayColorTable(FImage.FColorMap);
if TransIndex >= 0 then
FTransparentColor := ItemToRGB(FImage.FColorMap.Colors[TransIndex])
else
FTransparentColor := clNone;
FGrayscale := True;
try
GetBitmap;
except
on EAbort do
;
else
raise;
end;
end
else
begin
Mem := BitmapToMemoryStream(Bitmap, pf8bit, mmGrayscale);
try
FImage.Release;
FImage := TGIFItem.Create;
FImage.Reference;
if ForceEncoding then
EncodeBitmapStream(Mem);
FGrayscale := True;
if FTransparentColor <> clNone then
FTransparentColor := GrayColor(FTransparentColor);
FBitmap.LoadFromStream(Mem);
finally
Mem.Free;
end;
end;
end;
end;
procedure TJvGIFFrame.Assign(Source: TPersistent);
var
AComment: TStrings;
begin
if Source = nil then
begin
NewImage;
FBitmap.Free;
FBitmap := nil;
end
else
if Source is TJvGIFFrame then
begin
if Source <> Self then
begin
FImage.Release;
FImage := TJvGIFFrame(Source).FImage;
if TJvGIFFrame(Source).FOwner <> FOwner then
FLocalColors := True
else
FLocalColors := TJvGIFFrame(Source).FLocalColors;
FImage.Reference;
FTopLeft := TJvGIFFrame(Source).FTopLeft;
FInterlaced := TJvGIFFrame(Source).FInterlaced;
if TJvGIFFrame(Source).FBitmap <> nil then
begin
NewBitmap;
FBitmap.Assign(TJvGIFFrame(Source).FBitmap);
end;
FTransparentColor := TJvGIFFrame(Source).FTransparentColor;
FAnimateInterval := TJvGIFFrame(Source).FAnimateInterval;
FDisposal := TJvGIFFrame(Source).FDisposal;
FGrayscale := TJvGIFFrame(Source).FGrayscale;
FCorrupted := TJvGIFFrame(Source).FCorrupted;
AComment := TJvGIFFrame(Source).FindComment(False);
if (AComment <> nil) and (AComment.Count > 0) then
SetComment(AComment);
end;
end
else
if Source is TJvGIFImage then
begin
if TJvGIFImage(Source).Count > 0 then
begin
if TJvGIFImage(Source).FrameIndex >= 0 then
Assign(TJvGIFImage(Source).Frames[TJvGIFImage(Source).FrameIndex])
else
Assign(TJvGIFImage(Source).Frames[0]);
end
else
Assign(nil);
end
else
if Source is TGraphic then
begin
{ TBitmap, TJPEGImage... }
if TGraphic(Source).Empty then
begin
Assign(nil);
Exit;
end;
NewImage;
NewBitmap;
try
FBitmap.Assign(Source);
if Source is TBitmap then
FBitmap.Monochrome := TBitmap(Source).Monochrome;
except
FBitmap.Canvas.Brush.Color := clFuchsia;
FBitmap.Width := TGraphic(Source).Width;
FBitmap.Height := TGraphic(Source).Height;
FBitmap.Canvas.Draw(0, 0, TGraphic(Source));
end;
if TGraphic(Source).Transparent then
begin
if Source is TBitmap then
FTransparentColor := TBitmap(Source).TransparentColor
else
FTransparentColor := GetNearestColor(FBitmap.Canvas.Handle,
ColorToRGB(FBitmap.Canvas.Brush.Color));
end;
end
else
inherited Assign(Source);
if FOwner <> nil then
FOwner.UpdateScreenSize;
end;
procedure TJvGIFFrame.AssignTo(Dest: TPersistent);
begin
if (Dest is TJvGIFFrame) or (Dest is TJvGIFImage) then
Dest.Assign(Self)
else
if Dest is TGraphic then
begin
Dest.Assign(Bitmap);
if (Dest is TBitmap) and (FTransparentColor <> clNone) then
begin
TBitmap(Dest).TransparentColor := GetNearestColor(
TBitmap(Dest).Canvas.Handle, ColorToRGB(FTransparentColor));
TBitmap(Dest).Transparent := True;
end;
end
else
inherited AssignTo(Dest);
end;
procedure TJvGIFFrame.NewBitmap;
begin
FBitmap.Free;
FBitmap := TBitmap.Create;
end;
procedure TJvGIFFrame.NewImage;
begin
if FImage <> nil then
FImage.Release;
FImage := TGIFItem.Create;
FImage.Reference;
FGrayscale := False;
FCorrupted := False;
FTransparentColor := clNone;
FTopLeft := Point(0, 0);
FInterlaced := False;
FLocalColors := False;
FAnimateInterval := 0;
FDisposal := dmUndefined;
end;
function TJvGIFFrame.FindComment(ForceCreate: Boolean): TStrings;
var
Ext: TExtension;
begin
Ext := FindExtension(FExtensions, etComment);
if (Ext = nil) and ForceCreate then
begin
Ext := TExtension.Create;
try
Ext.FExtType := etComment;
if FExtensions = nil then
FExtensions := TList.Create;
FExtensions.Add(Ext);
except
Ext.Free;
raise;
end;
end;
if Ext <> nil then
begin
if (Ext.FData = nil) and ForceCreate then
Ext.FData := TStringList.Create;
Result := Ext.FData;
end
else
Result := nil;
end;
function TJvGIFFrame.GetComment: TStrings;
begin
Result := FindComment(True);
end;
procedure TJvGIFFrame.SetComment(Value: TStrings);
begin
GetComment.Assign(Value);
end;
procedure TJvGIFFrame.UpdateExtensions;
var
Ext: TExtension;
I: Integer;
begin
Ext := FindExtension(FExtensions, etGraphic);
if (FAnimateInterval > 0) or (FTransparentColor <> clNone) or
(FDisposal <> dmUndefined) then
begin
if Ext = nil then
begin
Ext := TExtension.Create;
Ext.FExtType := etGraphic;
if FExtensions = nil then
FExtensions := TList.Create;
FExtensions.Add(Ext);
with Ext.FExtRec.GCE do
begin
BlockSize := 4;
PackedFields := 0;
Terminator := 0;
end;
end;
end;
if Ext <> nil then
with Ext.FExtRec.GCE do
begin
DelayTime := FAnimateInterval div 10;
I := FindColorIndex(FImage.FColorMap, FTransparentColor);
if I >= 0 then
begin
TransparentColorIndex := I;
PackedFields := PackedFields or GCE_TRANSPARENT;
end
else
PackedFields := PackedFields and not GCE_TRANSPARENT;
PackedFields := (PackedFields and not GCE_DISPOSAL_METHOD) or
(Ord(FDisposal) shl 2);
end;
if FExtensions <> nil then
for I := FExtensions.Count - 1 downto 0 do
begin
Ext := TExtension(FExtensions[I]);
if (Ext <> nil) and (Ext.FExtType = etComment) and
((Ext.FData = nil) or (Ext.FData.Count = 0)) then
begin
Ext.Free;
FExtensions.Delete(I);
end;
end;
if (FExtensions <> nil) and (FExtensions.Count > 0) then
FOwner.FVersion := gv89a;
end;
procedure TJvGIFFrame.EncodeBitmapStream(Stream: TMemoryStream);
var
BI: PBitmapInfoHeader;
lColorCount, W, H: Integer;
Bits, Pal: Pointer;
begin
lColorCount := 0;
Stream.Position := 0;
BI := PBitmapInfoHeader(PAnsiChar(Stream.Memory) + SizeOf(TBitmapFileHeader));
W := BI^.biWidth;
H := BI^.biHeight;
Pal := PRGBPalette(PAnsiChar(BI) + SizeOf(TBitmapInfoHeader));
Bits := Pointer(PAnsiChar(Stream.Memory) + PBitmapFileHeader(Stream.Memory)^.bfOffBits);
case BI^.biBitCount of
1:
lColorCount := 2;
4:
lColorCount := 16;
8:
lColorCount := 256;
else
GifError(RsEGIFEncodeError);
end;
FInterlaced := False;
FillColorTable(FImage.FColorMap, PRGBPalette(Pal)^, lColorCount);
if FImage.FImageData = nil then
FImage.FImageData := TMemoryStream.Create
else
FImage.FImageData.SetSize(0);
try
WriteGIFData(FImage.FImageData, BI^, FInterlaced, Bits, @FOwner.DoProgress);
except
on EAbort do
begin
NewImage; { OnProgress can raise EAbort to cancel image save }
raise;
end
else
raise;
end;
FImage.FBitsPerPixel := 1;
while FImage.FColorMap.Count > 1 shl FImage.FBitsPerPixel do
Inc(FImage.FBitsPerPixel);
if FOwner.FImage.FColorMap.Count = 0 then
begin
FOwner.FImage.FColorMap := FImage.FColorMap;
FOwner.FImage.FBitsPerPixel := FImage.FBitsPerPixel;
FLocalColors := False;
end
else
FLocalColors := True;
FImage.FSize.X := W;
FImage.FSize.Y := H;
FOwner.FScreenWidth := Max(FOwner.FScreenWidth, FImage.FSize.X + FTopLeft.X);
FOwner.FScreenHeight := Max(FOwner.FScreenHeight, FImage.FSize.Y + FTopLeft.Y);
end;
procedure TJvGIFFrame.EncodeRasterData;
var
Method: TMappingMethod;
Mem: TMemoryStream;
begin
if not Assigned(FBitmap) or FBitmap.Empty then
GifError(RsENoGIFData);
if not (GetBitmapPixelFormat(FBitmap) in [pf1bit, pf4bit, pf8bit]) then
begin
if FGrayscale then
Method := mmGrayscale
else
Method := DefaultMappingMethod;
Mem := BitmapToMemoryStream(FBitmap, pf8bit, Method);
if Method = mmGrayscale then
FGrayscale := True;
end
else
Mem := TMemoryStream.Create;
try
if Mem.Size = 0 then
FBitmap.SaveToStream(Mem);
EncodeBitmapStream(Mem);
finally
Mem.Free;
end;
end;
procedure TJvGIFFrame.WriteImageDescriptor(Stream: TStream);
var
ImageDesc: TImageDescriptor;
begin
with ImageDesc do
begin
PackedFields := 0;
if FLocalColors then
begin
FImage.FBitsPerPixel := 1;
while FImage.FColorMap.Count > 1 shl FImage.FBitsPerPixel do
Inc(FImage.FBitsPerPixel);
PackedFields := (PackedFields or ID_LOCAL_COLOR_TABLE) +
(FImage.FBitsPerPixel - 1);
end;
if FInterlaced then
PackedFields := PackedFields or ID_INTERLACED;
ImageLeftPos := FTopLeft.X;
ImageTopPos := FTopLeft.Y;
ImageWidth := FImage.FSize.X;
ImageHeight := FImage.FSize.Y;
end;
Stream.Write(ImageDesc, SizeOf(TImageDescriptor));
end;
procedure TJvGIFFrame.WriteLocalColorMap(Stream: TStream);
begin
if FLocalColors then
with FImage.FColorMap do
Stream.Write(Colors[0], Count * SizeOf(TGIFColorItem));
end;
procedure TJvGIFFrame.WriteRasterData(Stream: TStream);
begin
Stream.WriteBuffer(FImage.FImageData.Memory^, FImage.FImageData.Size);
end;
procedure TJvGIFFrame.SaveToBitmapStream(Stream: TMemoryStream);
function ConvertBitsPerPixel: TPixelFormat;
begin
Result := pfDevice;
case FImage.FBitsPerPixel of
1:
Result := pf1bit;
2..4:
Result := pf4bit;
5..8:
Result := pf8bit;
else
GifError(RsEWrongGIFColors);
end;
end;
var
HeaderSize: Longword;
Length: Longword;
BI: TBitmapInfoHeader;
BitFile: TBitmapFileHeader;
Colors: TRGBPalette;
Bits: Pointer;
Corrupt: Boolean = false;
begin
with BI do
begin
biSize := SizeOf(TBitmapInfoHeader);
biWidth := FImage.FSize.X;
biHeight := FImage.FSize.Y;
biPlanes := 1;
biBitCount := 0;
case ConvertBitsPerPixel of
pf1bit:
biBitCount := 1;
pf4bit:
biBitCount := 4;
pf8bit:
biBitCount := 8;
end;
biCompression := BI_RGB;
biSizeImage := (((biWidth * biBitCount + 31) div 32) * 4) * biHeight;
biXPelsPerMeter := 0;
biYPelsPerMeter := 0;
biClrUsed := 0;
biClrImportant := 0;
end;
HeaderSize := SizeOf(TBitmapFileHeader) + SizeOf(TBitmapInfoHeader) +
SizeOf(TRGBQuad) * (1 shl BI.biBitCount);
Length := HeaderSize + BI.biSizeImage;
Stream.SetSize(0);
Stream.Position := 0;
with BitFile do
begin
bfType := $4D42; { BM }
bfSize := Length;
bfOffBits := HeaderSize;
end;
Stream.Write(BitFile, SizeOf(TBitmapFileHeader));
Stream.Write(BI, SizeOf(TBitmapInfoHeader));
Colors := Default(TRGBPalette);
FillRGBPalette(FImage.FColorMap, Colors);
Stream.Write(Colors, SizeOf(TRGBQuad) * (1 shl BI.biBitCount));
// Bits := GlobalAllocPtr(GMEM_ZEROINIT, BI.biSizeImage); // not in LCL
Bits := AllocMem(BI.biSizeImage);
try
FImage.FImageData.Position := 0;
ReadGIFData(FImage.FImageData, BI, FInterlaced, GIFLoadCorrupted,
FImage.FBitsPerPixel, Bits, Corrupt, @FOwner.DoProgress);
FCorrupted := FCorrupted or Corrupt;
Stream.WriteBuffer(Bits^, BI.biSizeImage);
finally
//GlobalFreePtr(Bits); // Not in LCL
FreeMem(Bits);
end;
Stream.Position := 0;
end;
function ColorItemTwiceInColorMap(Index: Integer; ColorMap: TGIFColorTable): Boolean;
var
I: Integer;
begin
Result := False;
I := 0;
while (I < ColorMap.Count) and not Result do
begin
if (I = Index) then
begin
Inc(I);
end
else
begin
Result := (ItemToRGB(ColorMap.Colors[Index]) = ItemToRGB(ColorMap.Colors[I]));
end;
Inc(I);
end;
end;
procedure TJvGIFFrame.LoadFromStream(Stream: TStream);
var
ImageDesc: TImageDescriptor;
I, Offset, TransIndex: Integer;
begin
FImage.FImageData := TMemoryStream.Create;
try
ImageDesc := Default(TImageDescriptor);
ReadImageStream(Stream, FImage.FImageData, ImageDesc, FInterlaced,
FLocalColors, FCorrupted, FImage.FBitsPerPixel, FImage.FColorMap);
if FCorrupted and not GIFLoadCorrupted then
GifError(SReadError);
FImage.FImageData.Position := 0;
with ImageDesc do
begin
if ImageHeight = 0 then
ImageHeight := FOwner.FScreenHeight;
if ImageWidth = 0 then
ImageWidth := FOwner.FScreenWidth;
FTopLeft := Point(ImageLeftPos, ImageTopPos);
FImage.FSize := Point(ImageWidth, ImageHeight);
FImage.FPackedFields := PackedFields;
end;
if not FLocalColors then
FImage.FColorMap := FOwner.FImage.FColorMap;
FAnimateInterval := 0;
if FExtensions <> nil then
begin
for I := 0 to FExtensions.Count - 1 do
with TExtension(FExtensions[I]) do
if FExtType = etGraphic then
begin
if (FExtRec.GCE.PackedFields and GCE_TRANSPARENT) <> 0 then
begin
TransIndex := FExtRec.GCE.TransparentColorIndex;
if FImage.FColorMap.Count > TransIndex then
begin
// Mantis 2135: Ensure that the transparent color does not appear
// twice in the palette or the second color index would end up
// being transparent as well
Offset := -1;
while ColorItemTwiceInColorMap(TransIndex, FImage.FColorMap) do
begin
if FImage.FColorMap.Colors[TransIndex].Blue = 0 then
Offset := 1
else
if FImage.FColorMap.Colors[TransIndex].Blue = 255 then
Offset := -1;
Inc(FImage.FColorMap.Colors[TransIndex].Blue, Offset);
end;
FTransparentColor := ItemToRGB(FImage.FColorMap.Colors[TransIndex]);
end;
end
else
FTransparentColor := clNone;
FAnimateInterval := Max(FExtRec.GCE.DelayTime * 10, FAnimateInterval);
FDisposal := TDisposalMethod((FExtRec.GCE.PackedFields and GCE_DISPOSAL_METHOD) shr 2);
end;
end;
except
FImage.FImageData.Free;
FImage.FImageData := nil;
raise;
end;
end;
procedure TJvGIFFrame.Draw(ACanvas: TCanvas; const ARect: TRect;
Transparent: Boolean);
begin
if (FTransparentColor <> clNone) and Transparent then
begin
StretchBitmapRectTransparent(ACanvas, ARect.Left, ARect.Top, ARect.Right - ARect.Left,
ARect.Bottom - ARect.Top, Bounds(0, 0, Bitmap.Width, Bitmap.Height), Bitmap,
FTransparentColor);
end
else
ACanvas.StretchDraw(ARect, Bitmap);
end;
//=== { TJvGIFImage } ========================================================
constructor TJvGIFImage.Create;
begin
inherited Create;
NewImage;
FTransparent := true;
end;
destructor TJvGIFImage.Destroy;
begin
OnChange := nil;
FImage.Release;
ClearItems;
FItems.Free;
inherited Destroy;
end;
procedure TJvGIFImage.Clear;
begin
Assign(nil);
end;
procedure TJvGIFImage.ClearItems;
begin
if FItems <> nil then
while FItems.Count > 0 do
begin
TObject(FItems[0]).Free;
FItems.Delete(0);
end;
end;
procedure TJvGIFImage.Assign(Source: TPersistent);
var
I: Integer;
AFrame: TJvGIFFrame;
begin
if Source = nil then
begin
NewImage;
Changed(Self);
end
else
if (Source is TJvGIFImage) and (Source <> Self) then
begin
FImage.Release;
FImage := TJvGIFImage(Source).FImage;
FImage.Reference;
FVersion := TJvGIFImage(Source).FVersion;
FBackgroundColor := TJvGIFImage(Source).FBackgroundColor;
FRepeatCount := TJvGIFImage(Source).FRepeatCount;
FLooping := TJvGIFImage(Source).FLooping;
FCorrupted := TJvGIFImage(Source).FCorrupted;
if FItems = nil then
FItems := TList.Create
else
ClearItems;
with TJvGIFImage(Source) do
begin
for I := 0 to FItems.Count - 1 do
begin
AFrame := TJvGIFFrame.Create(Self);
try
AFrame.FImage.FBitsPerPixel :=
TJvGIFFrame(FItems[I]).FImage.FBitsPerPixel;
AFrame.Assign(TJvGIFFrame(FItems[I]));
AFrame.FLocalColors := TJvGIFFrame(FItems[I]).FLocalColors;
Self.FItems.Add(AFrame);
except
AFrame.Free;
raise;
end;
end;
Self.FScreenWidth := FScreenWidth;
Self.FScreenHeight := FScreenHeight;
end;
FFrameIndex := TJvGIFImage(Source).FFrameIndex;
Changed(Self);
end
else
if Source is TJvGIFFrame then
begin
NewImage;
with TJvGIFFrame(Source).FOwner.FImage do
begin
FImage.FAspectRatio := FAspectRatio;
FImage.FBitsPerPixel := FBitsPerPixel;
FImage.FColorResBits := FColorResBits;
Move(FColorMap, FImage.FColorMap, SizeOf(FColorMap));
end;
FFrameIndex := FItems.Add(TJvGIFFrame.Create(Self));
TJvGIFFrame(FItems[FFrameIndex]).Assign(Source);
if FVersion = gvUnknown then
FVersion := gv87a;
Changed(Self);
end
else
if Source is TBitmap then
begin
NewImage;
AddFrame(TBitmap(Source));
Changed(Self);
end
(****************** NOT CONVERTED
else
if Source is TJvAni then
begin
NewImage;
FBackgroundColor := clWindow;
with TJvAni(Source) do
begin
for I := 0 to FrameCount - 1 do
begin
AddFrame(TIcon(Icons[I]));
Self.Frames[I].FAnimateInterval := Longint(Frames[I].Rate * 100) div 6;
if Frames[I].Rate = 0 then
Self.Frames[I].FAnimateInterval := 100;
end;
end;
Changed(Self);
end
*************************)
else
inherited Assign(Source);
end;
procedure TJvGIFImage.AssignTo(Dest: TPersistent);
begin
if Dest is TJvGIFImage then
Dest.Assign(Self)
else
if Dest is TGraphic then
begin
if Empty then
Dest.Assign(nil)
else
if FFrameIndex >= 0 then
TJvGIFFrame(FItems[FFrameIndex]).AssignTo(Dest)
else
Dest.Assign(Bitmap);
end
else
inherited AssignTo(Dest);
end;
procedure TJvGIFImage.Draw(ACanvas: TCanvas; const ARect: TRect);
begin
if FFrameIndex >= 0 then
TJvGIFFrame(FItems[FFrameIndex]).Draw(ACanvas, ARect, Self.Transparent);
end;
function TJvGIFImage.GetBackgroundColor: TColor;
begin
Result := FBackgroundColor;
end;
procedure TJvGIFImage.SetBackgroundColor(Value: TColor);
begin
if Value <> FBackgroundColor then
begin
FBackgroundColor := Value;
Changed(Self);
end;
end;
procedure TJvGIFImage.SetLooping(Value: Boolean);
begin
if Value <> FLooping then
begin
FLooping := Value;
Changed(Self);
end;
end;
procedure TJvGIFImage.SetRepeatCount(Value: Word);
begin
if Min(Value, MAX_LOOP_COUNT) <> FRepeatCount then
begin
FRepeatCount := Min(Value, MAX_LOOP_COUNT);
Changed(Self);
end;
end;
function TJvGIFImage.GetPixelFormat: TPixelFormat;
var
I: Integer;
begin
Result := pfDevice;
if not Empty then
begin
Result := ColorsToPixelFormat(FImage.FColorMap.Count);
for I := 0 to FItems.Count - 1 do
begin
if (Frames[I].FImage.FImageData = nil) or
(Frames[I].FImage.FImageData.Size = 0) then
begin
if Assigned(Frames[I].FBitmap) then
Result := TPixelFormat(Max(Ord(Result),
Ord(GetBitmapPixelFormat(Frames[I].FBitmap))))
else
Result := TPixelFormat(Max(Ord(Result), Ord(pfDevice)));
end
else
if Frames[I].FLocalColors then
Result := TPixelFormat(Max(Ord(Result),
Ord(ColorsToPixelFormat(Frames[I].FImage.FColorMap.Count))));
end;
end;
end;
function TJvGIFImage.GetCorrupted: Boolean;
var
I: Integer;
begin
Result := FCorrupted;
if not Result then
for I := 0 to FItems.Count - 1 do
if Frames[I].Corrupted then
begin
Result := True;
Exit;
end;
end;
function TJvGIFImage.GetTransparentColor: TColor;
begin
if (FItems.Count > 0) and (FFrameIndex >= 0) then
Result := TJvGIFFrame(FItems[FFrameIndex]).FTransparentColor
else
Result := clNone;
end;
function TJvGIFImage.GetCount: Integer;
begin
Result := FItems.Count;
end;
function TJvGIFImage.GetFrame(Index: Integer): TJvGIFFrame;
begin
Result := TJvGIFFrame(FItems[Index]);
end;
procedure TJvGIFImage.SetFrameIndex(Value: Integer);
begin
Value := Min(FItems.Count - 1, Max(-1, Value));
if FFrameIndex <> Value then
begin
FFrameIndex := Value;
PaletteModified := True;
Changed(Self);
end;
end;
function TJvGIFImage.Equals(Graphic: TGraphic): Boolean;
begin
Result := (Graphic is TJvGIFImage) and
(FImage = TJvGIFImage(Graphic).FImage);
end;
function TJvGIFImage.GetBitmap: TBitmap;
var
Bmp: TBitmap;
begin
if FItems.Count > 0 then
begin
if (FFrameIndex >= 0) and (FFrameIndex < FItems.Count) then
Result := TJvGIFFrame(FItems[FFrameIndex]).Bitmap
else
Result := TJvGIFFrame(FItems[0]).Bitmap
end
else
begin
FFrameIndex := 0;
Bmp := TBitmap.Create;
try
Bmp.Handle := 0;
Assign(Bmp);
Result := TJvGIFFrame(FItems[FFrameIndex]).Bitmap;
finally
Bmp.Free;
end;
end;
end;
function TJvGIFImage.GetGlobalColorCount: Integer;
begin
Result := FImage.FColorMap.Count;
end;
function TJvGIFImage.GetEmpty: Boolean;
var
I: Integer;
begin
I := Max(FFrameIndex, 0);
Result := (FItems.Count = 0) or
((TJvGIFFrame(FItems[I]).FBitmap = nil) and
((TJvGIFFrame(FItems[I]).FImage.FImageData = nil) or
(TJvGIFFrame(FItems[I]).FImage.FImageData.Size = 0)));
end;
function TJvGIFImage.GetPalette: HPALETTE;
begin
if FItems.Count > 0 then
Result := Bitmap.Palette
else
Result := 0;
end;
function TJvGIFImage.GetTransparent: Boolean;
var
I: Integer;
begin
if FTransparent then
for I := 0 to FItems.Count - 1 do
if Frames[I].TransparentColor <> clNone then
begin
Result := True;
Exit;
end;
Result := FTransparent;
end;
function TJvGIFImage.GetHeight: Integer;
begin
if not Empty and (FFrameIndex >= 0) and (FFrameIndex < Count) then
Result := TJvGIFFrame(FItems[FFrameIndex]).Bitmap.Height
else
Result := 0;
end;
function TJvGIFImage.GetWidth: Integer;
begin
if not Empty and (FFrameIndex >= 0) and (FFrameIndex < Count) then
Result := TJvGIFFrame(FItems[FFrameIndex]).Bitmap.Width
else
Result := 0;
end;
function TJvGIFImage.GetScreenWidth: Integer;
begin
if Empty then
Result := 0
else
Result := FScreenWidth;
end;
function TJvGIFImage.GetScreenHeight: Integer;
begin
if Empty then
Result := 0
else
Result := FScreenHeight;
end;
(*
procedure TJvGIFImage.LoadFromClipboardFormat(AFormat: Word; AData: THandle;
APalette: HPALETTE);
var
Bmp: TBitmap;
Stream: TMemoryStream;
Size: Longint;
Buffer: Pointer;
Data: THandle;
begin
{ !! check for gif clipboard Data, mime type image/gif }
Data := GetClipboardData(CF_JVGIF);
if Data <> 0 then
begin
Buffer := GlobalLock(Data);
try
Stream := TMemoryStream.Create;
try
Stream.Write(Buffer^, GlobalSize(Data));
Stream.Position := 0;
Stream.Read(Size, SizeOf(Size));
ReadStream(Size, Stream, False);
if Count > 0 then
begin
FFrameIndex := 0;
AData := GetClipboardData(CF_BITMAP);
if AData <> 0 then
begin
Frames[0].NewBitmap;
Frames[0].FBitmap.LoadFromClipboardFormat(CF_BITMAP, AData, APalette);
end;
end;
finally
Stream.Free;
end;
finally
GlobalUnlock(Data);
end;
end
else
begin
Bmp := TBitmap.Create;
try
Bmp.LoadFromClipboardFormat(AFormat, AData, APalette);
Assign(Bmp);
finally
Bmp.Free;
end;
end;
end;
*)
procedure TJvGIFImage.LoadFromStream(Stream: TStream);
begin
ReadStream(Stream.Size - Stream.Position, Stream, True);
end;
procedure TJvGIFImage.LoadFromResourceName(Instance: THandle; const ResName: string);
var
Stream: TStream;
ResType: TResourceType;
begin
ResType := GetResourceType;
Stream := TResourceStream.Create(Instance, ResName, ResType);
try
ReadStream(Stream.Size - Stream.Position, Stream, True);
finally
Stream.Free;
end;
end;
procedure TJvGIFImage.LoadFromResourceID(Instance: THandle; ResID: Integer);
var
ResType: TResourceType;
Stream: TStream;
begin
ResType := GetResourceType;
Stream := TResourceStream.CreateFromID(Instance, ResID, ResType);
try
ReadStream(Stream.Size - Stream.Position, Stream, True);
finally
Stream.Free;
end;
end;
procedure TJvGIFImage.UpdateScreenSize;
var
I: Integer;
begin
FScreenWidth := 0;
FScreenHeight := 0;
for I := 0 to FItems.Count - 1 do
if Frames[I] <> nil then
begin
FScreenWidth := Max(FScreenWidth, Frames[I].Width +
Frames[I].FTopLeft.X);
FScreenHeight := Max(FScreenHeight, Frames[I].Height +
Frames[I].FTopLeft.Y);
end;
end;
function TJvGIFImage.AddFrame(Value: TGraphic): Integer;
begin
FFrameIndex := FItems.Add(TJvGIFFrame.Create(Self));
TJvGIFFrame(FItems[FFrameIndex]).Assign(Value);
if FVersion = gvUnknown then
FVersion := gv87a;
if FItems.Count > 1 then
FVersion := gv89a;
Result := FFrameIndex;
end;
procedure TJvGIFImage.DeleteFrame(Index: Integer);
begin
Frames[Index].Free;
FItems.Delete(Index);
UpdateScreenSize;
if FFrameIndex >= FItems.Count then
Dec(FFrameIndex);
Changed(Self);
end;
procedure TJvGIFImage.MoveFrame(CurIndex, NewIndex: Integer);
begin
FItems.Move(CurIndex, NewIndex);
FFrameIndex := NewIndex;
Changed(Self);
end;
procedure TJvGIFImage.NewImage;
begin
if FImage <> nil then
FImage.Release;
FImage := TGIFData.Create;
FImage.Reference;
if FItems = nil then
FItems := TList.Create;
ClearItems;
FCorrupted := False;
FFrameIndex := -1;
FBackgroundColor := clNone;
FRepeatCount := 1;
FLooping := False;
FVersion := gvUnknown;
end;
procedure TJvGIFImage.UniqueImage;
var
Temp: TGIFData;
begin
if FImage = nil then
NewImage
else
if FImage.RefCount > 1 then
begin
Temp := TGIFData.Create;
with Temp do
try
FComment.Assign(FImage.FComment);
FAspectRatio := FImage.FAspectRatio;
FBitsPerPixel := FImage.FBitsPerPixel;
FColorResBits := FImage.FColorResBits;
FColorMap := FImage.FColorMap;
except
Temp.Free;
raise;
end;
FImage.Release;
FImage := Temp;
FImage.Reference;
end;
end;
function TJvGIFImage.GetComment: TStrings;
begin
Result := FImage.FComment;
end;
procedure TJvGIFImage.SetComment(Value: TStrings);
begin
UniqueImage;
FImage.FComment.Assign(Value);
end;
procedure TJvGIFImage.DecodeAllFrames;
var
FrameNo, I: Integer;
begin
for FrameNo := 0 to FItems.Count - 1 do
try
TJvGIFFrame(FItems[FrameNo]).GetBitmap;
except
on EAbort do
begin { OnProgress can raise EAbort to cancel image load }
for I := FItems.Count - 1 downto FrameNo do
begin
TObject(FItems[I]).Free;
FItems.Delete(I);
end;
FCorrupted := True;
Break;
end;
else
raise;
end;
end;
procedure TJvGIFImage.EncodeFrames(ReverseDecode: Boolean);
var
FrameNo: Integer;
begin
for FrameNo := 0 to FItems.Count - 1 do
with TJvGIFFrame(FItems[FrameNo]) do
begin
if (FImage.FImageData = nil) or (FImage.FImageData.Size = 0) then
begin
FImage.FImageData.Free;
FImage.FImageData := nil;
EncodeRasterData;
if ReverseDecode and (FBitmap.Palette = 0) then
begin
FBitmap.Free;
FBitmap := nil;
try
GetBitmap;
except
on EAbort do
; { OnProgress can raise EAbort to cancel encoding }
else
raise;
end;
end;
end;
UpdateExtensions;
end;
end;
procedure TJvGIFImage.EncodeAllFrames;
begin
EncodeFrames(True);
end;
procedure TJvGIFImage.ReadData(Stream: TStream);
var
Size: Longint = 0;
begin
Stream.Read(Size, SizeOf(Size));
ReadStream(Size, Stream, True);
end;
procedure TJvGIFImage.ReadSignature(Stream: TStream);
var
I: TGIFVersion;
S: TGifSignature;
begin
FVersion := gvUnknown;
S := Default(TGifSignature);
Stream.Read(S[0], 3);
if not CompareMem(@GIFSignature[0], @S[0], 3) then
GifError(RsEGIFVersion);
Stream.Read(S[0], 3);
for I := Low(TGIFVersion) to High(TGIFVersion) do
if CompareMem(@S[0], @GIFVersionStr[I][0], 3) then
begin
FVersion := I;
Break;
end;
if FVersion = gvUnknown then
GifError(RsEGIFVersion);
end;
procedure TJvGIFImage.ReadStream(Size: Longint; Stream: TStream;
ForceDecode: Boolean);
var
SeparatorChar: AnsiChar;
NewItem: TJvGIFFrame;
Extensions: TList;
ScreenDesc: TScreenDescriptor;
Data: TMemoryStream;
procedure ReadScreenDescriptor(Stream: TStream);
begin
Stream.Read(ScreenDesc, SizeOf(ScreenDesc));
FScreenWidth := ScreenDesc.ScreenWidth;
FScreenHeight := ScreenDesc.ScreenHeight;
with FImage do
begin
FAspectRatio := ScreenDesc.AspectRatio;
FBitsPerPixel := 1 + (ScreenDesc.PackedFields and
LSD_COLOR_TABLE_SIZE);
FColorResBits := 1 + (ScreenDesc.PackedFields and
LSD_COLOR_RESOLUTION) shr 4;
end;
end;
procedure ReadGlobalColorMap(Stream: TStream);
begin
if (ScreenDesc.PackedFields and LSD_GLOBAL_COLOR_TABLE) <> 0 then
with FImage.FColorMap do
begin
Count := 1 shl FImage.FBitsPerPixel;
Stream.Read(Colors[0], Count * SizeOf(TGIFColorItem));
if Count > ScreenDesc.BackgroundColorIndex then
FBackgroundColor := ItemToRGB(Colors[ScreenDesc.BackgroundColorIndex]);
end;
end;
function ReadDataBlock(Stream: TStream): TStringList;
var
BlockSize: Byte = 0;
S: AnsiString = '';
begin
Result := TStringList.Create;
try
repeat
Stream.Read(BlockSize, SizeOf(Byte));
if BlockSize <> 0 then
begin
SetLength(S, BlockSize);
Stream.Read(S[1], BlockSize);
Result.Add(string(S));
end;
until (BlockSize = 0) or (Stream.Position >= Stream.Size);
except
Result.Free;
raise;
end;
end;
function ReadExtension(Stream: TStream): TExtension;
var
ExtensionLabel: Byte = 0;
begin
Result := TExtension.Create;
try
Stream.Read(ExtensionLabel, SizeOf(Byte));
with Result do
begin
if ExtensionLabel = ExtLabels[etGraphic] then
begin
{ graphic control extension }
FExtType := etGraphic;
Stream.Read(FExtRec.GCE, SizeOf(TGraphicControlExtension));
end
else
if ExtensionLabel = ExtLabels[etComment] then
begin
{ comment extension }
FExtType := etComment;
FData := ReadDataBlock(Stream);
end
else
if ExtensionLabel = ExtLabels[etPlainText] then
begin
{ plain text extension }
FExtType := etPlainText;
Stream.Read(FExtRec.PTE, SizeOf(TPlainTextExtension));
FData := ReadDataBlock(Stream);
end
else
if ExtensionLabel = ExtLabels[etApplication] then
begin
{ application extension }
FExtType := etApplication;
Stream.Read(FExtRec.APPE, SizeOf(TAppExtension));
FData := ReadDataBlock(Stream);
end
else
GifError(Format(RsEUnrecognizedGIFExt, [ExtensionLabel]));
end;
except
Result.Free;
raise;
end;
end;
function ReadSeparator(Stream: TStream): AnsiChar;
begin
Result := #0;
while (Stream.Size > Stream.Position) and (Result = #0) do
Stream.Read(Result, SizeOf(Byte));
end;
function ReadExtensionBlock(Stream: TStream; var SeparatorChar: AnsiChar): TList;
var
NewExt: TExtension;
begin
Result := nil;
try
while SeparatorChar = CHR_EXT_INTRODUCER do
begin
NewExt := ReadExtension(Stream);
if NewExt.FExtType = etPlainText then
begin
{ plain text data blocks are not supported,
clear all previous readed extensions }
FreeExtensions(Result);
Result := nil;
end;
if NewExt.FExtType in [etPlainText, etApplication] then
begin
{ check for loop extension }
if NewExt.IsLoopExtension then
begin
FLooping := True;
FRepeatCount := Min(MakeWord(Byte(NewExt.FData[0][2]),
Byte(NewExt.FData[0][3])), MAX_LOOP_COUNT);
end;
{ not supported yet, must be ignored }
NewExt.Free;
end
else
begin
if Result = nil then
Result := TList.Create;
Result.Add(NewExt);
end;
if Stream.Size > Stream.Position then
SeparatorChar := ReadSeparator(Stream)
else
SeparatorChar := CHR_TRAILER;
end;
if (Result <> nil) and (Result.Count = 0) then
begin
Result.Free;
Result := nil;
end;
except
if Result <> nil then
Result.Free;
raise;
end;
end;
var
I: Integer;
Ext: TExtension;
begin
NewImage;
with FImage do
begin
if Size > 0 then
begin
Data := TMemoryStream.Create;
try
TMemoryStream(Data).SetSize(Size);
Stream.ReadBuffer(Data.Memory^, Size);
Data.Position := 0;
ReadSignature(Data);
ReadScreenDescriptor(Data);
ReadGlobalColorMap(Data);
SeparatorChar := ReadSeparator(Data);
while not (SeparatorChar in [CHR_TRAILER, #0]) and not (Data.Position >= Data.Size) do
begin
Extensions := ReadExtensionBlock(Data, SeparatorChar);
if SeparatorChar = CHR_IMAGE_SEPARATOR then
try
NewItem := TJvGIFFrame.Create(Self);
try
if FImage.FColorMap.Count > 0 then
NewItem.FImage.FBitsPerPixel := ColorsToBits(FImage.FColorMap.Count);
NewItem.FExtensions := Extensions;
Extensions := nil;
NewItem.LoadFromStream(Data);
FItems.Add(NewItem);
except
NewItem.Free;
raise;
end;
if not (Data.Position >= Data.Size) then
SeparatorChar := ReadSeparator(Data)
else
SeparatorChar := CHR_TRAILER;
if not (SeparatorChar in [CHR_EXT_INTRODUCER, CHR_IMAGE_SEPARATOR, CHR_TRAILER]) then
begin
SeparatorChar := #0;
{GifError(RsEGIFDecodeError);}
end;
except
FreeExtensions(Extensions);
raise;
end
else
if (FComment.Count = 0) and (Extensions <> nil) then
begin
try
{ trailig extensions }
for I := 0 to Extensions.Count - 1 do
begin
Ext := TExtension(Extensions[I]);
if (Ext <> nil) and (Ext.FExtType = etComment) then
begin
if FComment.Count > 0 then
FComment.Add(CrLf + CrLf);
FComment.AddStrings(Ext.FData);
end;
end;
finally
FreeExtensions(Extensions);
end;
end
else
if not (SeparatorChar in [CHR_TRAILER, #0]) then
GifError(SReadError);
end;
finally
Data.Free;
end;
end;
end;
if Count > 0 then
begin
FFrameIndex := 0;
if ForceDecode then
try
GetBitmap; { force bitmap creation }
except
Frames[0].Free;
FItems.Delete(0);
raise;
end;
end;
PaletteModified := True;
Changed(Self);
end;
(*
procedure TJvGIFImage.SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
var APalette: HPALETTE);
var
Stream: TMemoryStream;
Data: THandle;
Buffer: Pointer;
I: Integer;
begin
{ !! check for gif clipboard format, mime type image/gif }
if FItems.Count = 0 then
Exit;
Frames[0].Bitmap.SaveToClipboardFormat(AFormat, AData, APalette);
for I := 0 to FItems.Count - 1 do
with Frames[I] do
begin
if (FImage.FImageData = nil) or (FImage.FImageData.Size = 0) then
Exit;
end;
Stream := TMemoryStream.Create;
try
WriteStream(Stream, True);
Stream.Position := 0;
Data := GlobalAlloc(GMEM_MOVEABLE, Stream.Size);
try
if Data <> 0 then
begin
Buffer := GlobalLock(Data);
try
Stream.Read(Buffer^, Stream.Size);
SetClipboardData(CF_JVGIF, Data);
finally
GlobalUnlock(Data);
end;
end;
except
GlobalFree(Data);
raise;
end;
finally
Stream.Free;
end;
end; *)
procedure TJvGIFImage.WriteData(Stream: TStream);
begin
WriteStream(Stream, True);
end;
procedure TJvGIFImage.SetHeight(Value: Integer);
begin
GifError(RsEChangeGIFSize);
end;
procedure TJvGIFImage.SetTransparent(Value: Boolean);
begin
if FTransparent = Value then
exit;
FTransparent := Value;
Changed(nil);
end;
procedure TJvGIFImage.SetWidth(Value: Integer);
begin
GifError(RsEChangeGIFSize);
end;
procedure TJvGIFImage.WriteStream(Stream: TStream; WriteSize: Boolean);
var
Separator: Byte;
Temp: Byte;
FrameNo: Integer;
Frame: TJvGIFFrame;
Mem: TMemoryStream;
Size: Longint;
StrList: TStringList;
procedure WriteSignature(Stream: TStream);
var
Header: TGIFHeader;
begin
Header.Signature := GIFSignature;
Move(GIFVersionStr[FVersion][0], Header.Version[0], 3);
Stream.Write(Header, SizeOf(TGIFHeader));
end;
procedure WriteScreenDescriptor(Stream: TStream);
var
ColorResBits: Byte;
ScreenDesc: TScreenDescriptor;
I: Integer;
begin
UpdateScreenSize;
with ScreenDesc do
begin
ScreenWidth := Self.FScreenWidth;
ScreenHeight := Self.FScreenHeight;
AspectRatio := FImage.FAspectRatio;
PackedFields := 0;
BackgroundColorIndex := 0;
if FImage.FColorMap.Count > 0 then
begin
PackedFields := PackedFields or LSD_GLOBAL_COLOR_TABLE;
ColorResBits := ColorsToBits(FImage.FColorMap.Count);
if FBackgroundColor <> clNone then
for I := 0 to FImage.FColorMap.Count - 1 do
if ColorToRGB(FBackgroundColor) =
ItemToRGB(FImage.FColorMap.Colors[I]) then
begin
BackgroundColorIndex := I;
Break;
end;
PackedFields := PackedFields + ((ColorResBits - 1) shl 4) +
(FImage.FBitsPerPixel - 1);
end;
end;
Stream.Write(ScreenDesc, SizeOf(ScreenDesc));
end;
procedure WriteDataBlock(Stream: TStream; Data: TStrings);
var
I: Integer;
S: AnsiString;
BlockSize: Byte;
begin
for I := 0 to Data.Count - 1 do
begin
S := AnsiString(Data[I]);
BlockSize := Min(Length(S), 255);
if BlockSize > 0 then
begin
Stream.Write(BlockSize, SizeOf(Byte));
Stream.Write(S[1], BlockSize);
end;
end;
BlockSize := 0;
Stream.Write(BlockSize, SizeOf(Byte));
end;
procedure WriteExtensionBlock(Stream: TStream; Extensions: TList);
var
I: Integer;
Ext: TExtension;
ExtensionLabel: Byte;
SeparateChar: Byte;
begin
SeparateChar := Byte(CHR_EXT_INTRODUCER);
for I := 0 to Extensions.Count - 1 do
begin
Ext := TExtension(Extensions[I]);
if Ext <> nil then
begin
Stream.Write(SeparateChar, SizeOf(Byte));
ExtensionLabel := ExtLabels[Ext.FExtType];
Stream.Write(ExtensionLabel, SizeOf(Byte));
case Ext.FExtType of
etGraphic:
begin
Stream.Write(Ext.FExtRec.GCE, SizeOf(TGraphicControlExtension));
end;
etComment:
WriteDataBlock(Stream, Ext.FData);
etPlainText:
begin
Stream.Write(Ext.FExtRec.PTE, SizeOf(TPlainTextExtension));
WriteDataBlock(Stream, Ext.FData);
end;
etApplication:
begin
Stream.Write(Ext.FExtRec.APPE, SizeOf(TAppExtension));
WriteDataBlock(Stream, Ext.FData);
end;
end;
end;
end;
end;
begin
if FItems.Count = 0 then
GifError(RsENoGIFData);
EncodeFrames(False);
Mem := TMemoryStream.Create;
try
if FImage.FComment.Count > 0 then
FVersion := gv89a;
WriteSignature(Mem);
WriteScreenDescriptor(Mem);
if FImage.FColorMap.Count > 0 then
with FImage.FColorMap do
Mem.Write(Colors[0], Count * SizeOf(TGIFColorItem));
if FLooping and (FItems.Count > 1) then
begin
{ write looping extension }
Separator := Byte(CHR_EXT_INTRODUCER);
Mem.Write(Separator, SizeOf(Byte));
Temp := ExtLabels[etApplication];
Mem.Write(Temp, SizeOf(Byte));
Temp := SizeOf(TAppExtension) - SizeOf(Byte);
Mem.Write(Temp, SizeOf(Byte));
Mem.Write(LoopExtNS[1], Temp);
StrList := TStringList.Create;
try
StrList.Add(Char(AE_LOOPING) + Char(Low(FRepeatCount)) +
Char(High(FRepeatCount)));
WriteDataBlock(Mem, StrList);
finally
StrList.Free;
end;
end;
Separator := Byte(CHR_IMAGE_SEPARATOR);
for FrameNo := 0 to FItems.Count - 1 do
begin
Frame := TJvGIFFrame(FItems[FrameNo]);
if Frame.FExtensions <> nil then
WriteExtensionBlock(Mem, Frame.FExtensions);
Mem.Write(Separator, SizeOf(Byte));
Frame.WriteImageDescriptor(Mem);
Frame.WriteLocalColorMap(Mem);
Frame.WriteRasterData(Mem);
end;
if FImage.FComment.Count > 0 then
begin
Separator := Byte(CHR_EXT_INTRODUCER);
Mem.Write(Separator, SizeOf(Byte));
Temp := ExtLabels[etComment];
Mem.Write(Temp, SizeOf(Byte));
WriteDataBlock(Mem, FImage.FComment);
end;
Separator := Byte(CHR_TRAILER);
Mem.Write(Separator, SizeOf(Byte));
Size := Mem.Size;
if WriteSize then
Stream.Write(Size, SizeOf(Size));
Stream.Write(Mem.Memory^, Size);
finally
Mem.Free;
end;
end;
procedure TJvGIFImage.Grayscale(ForceEncoding: Boolean);
var
I: Integer;
begin
if FItems.Count = 0 then
GifError(RsENoGIFData);
for I := 0 to FItems.Count - 1 do
Frames[I].GrayscaleImage(ForceEncoding);
if FBackgroundColor <> clNone then
begin
if FImage.FColorMap.Count > 0 then
begin
I := FindColorIndex(FImage.FColorMap, FBackgroundColor);
GrayColorTable(FImage.FColorMap);
if I >= 0 then
FBackgroundColor := ItemToRGB(FImage.FColorMap.Colors[I])
else
FBackgroundColor := GrayColor(FBackgroundColor);
end
else
FBackgroundColor := GrayColor(FBackgroundColor);
end;
PaletteModified := True;
Changed(Self);
end;
procedure TJvGIFImage.SaveToStream(Stream: TStream);
begin
WriteStream(Stream, False);
end;
procedure TJvGIFImage.DoProgress(Stage: TProgressStage; PercentDone: Byte;
const Msg: string);
begin
Progress(Self, Stage, PercentDone, False, Rect(0, 0, 0, 0), Msg);
end;
procedure Init;
begin
CF_JVGIF := RegisterClipboardFormat('JvGIF Image');
{$IFDEF COMPILER7_UP}
GroupDescendentsWith(TJvGIFFrame, TControl);
GroupDescendentsWith(TJvGIFImage, TControl);
{$ENDIF COMPILER7_UP}
RegisterClasses([TJvGIFFrame, TJvGIFImage]);
{$IFDEF USE_JV_GIF}
TPicture.RegisterFileFormat('gif', RsGIFImage, TJvGIFImage);
{$ELSE}
TPicture.RegisterFileFormat('', '', TJvGIFImage); // register for loading but do not show in FileDialog
{$ENDIF USE_JV_GIF}
TPicture.RegisterClipboardFormat(CF_JVGIF, TJvGIFImage);
(********** NOT CONVERTED ***
RegisterGraphicSignature('GIF', 0, TJvGIFImage);
****************************)
end;
initialization
Init;
finalization
TPicture.UnRegisterGraphicClass(TJvGIFImage);
end.