CentrED/Imaging/ImagingGif.pas

989 lines
31 KiB
Plaintext

{
$Id: ImagingGif.pas 111 2007-12-02 23:25:44Z galfar $
Vampyre Imaging Library
by Marek Mauder
http://imaginglib.sourceforge.net
The contents of this file are used with permission, 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 express or implied. See the License for
the specific language governing rights and limitations under the License.
Alternatively, the contents of this file may be used under the terms of the
GNU Lesser General Public License (the "LGPL License"), in which case the
provisions of the LGPL License are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms
of the LGPL License and not to allow others to use your version of this file
under the MPL, indicate your decision by deleting the provisions above and
replace them with the notice and other provisions required by the LGPL
License. If you do not delete the provisions above, a recipient may use
your version of this file under either the MPL or the LGPL License.
For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
}
{ This unit contains image format loader/saver for GIF images.}
unit ImagingGif;
{$I ImagingOptions.inc}
interface
uses
SysUtils, Classes, Imaging, ImagingTypes, ImagingUtility;
type
{ GIF (Graphics Interchange Format) loader/saver class. GIF was
(and is still used) popular format for storing images supporting
multiple images per file and single color transparency.
Pixel format is 8 bit indexed where each image frame can have
its own color palette. GIF uses lossless LZW compression
(patent expired few years ago).
Imaging can load and save all GIFs with all frames and supports
transparency.}
TGIFFileFormat = class(TImageFileFormat)
private
function InterlaceStep(Y, Height: Integer; var Pass: Integer): Integer;
procedure LZWDecompress(const IO: TIOFunctions; Handle: TImagingHandle;
Width, Height: Integer; Interlaced: Boolean; Data: Pointer);
procedure LZWCompress(const IO: TIOFunctions; Handle: TImagingHandle;
Width, Height, BitCount: Integer; Interlaced: Boolean; Data: Pointer);
protected
function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
OnlyFirstLevel: Boolean): Boolean; override;
function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
Index: LongInt): Boolean; override;
procedure ConvertToSupported(var Image: TImageData;
const Info: TImageFormatInfo); override;
public
constructor Create; override;
function TestFormat(Handle: TImagingHandle): Boolean; override;
end;
implementation
const
SGIFFormatName = 'Graphics Interchange Format';
SGIFMasks = '*.gif';
GIFSupportedFormats: TImageFormats = [ifIndex8];
type
TGIFVersion = (gv87, gv89);
TDisposalMethod = (dmUndefined, dmLeave, dmRestoreBackground,
dmRestorePrevious, dmReserved4, dmReserved5, dmReserved6, dmReserved7);
const
GIFSignature: TChar3 = 'GIF';
GIFVersions: array[TGIFVersion] of TChar3 = ('87a', '89a');
// Masks for accessing fields in PackedFields of TGIFHeader
GIFGlobalColorTable = $80;
GIFColorResolution = $70;
GIFColorTableSorted = $08;
GIFColorTableSize = $07;
// Masks for accessing fields in PackedFields of TImageDescriptor
GIFLocalColorTable = $80;
GIFInterlaced = $40;
GIFLocalTableSorted = $20;
// Block identifiers
GIFPlainText: Byte = $01;
GIFGraphicControlExtension: Byte = $F9;
GIFCommentExtension: Byte = $FE;
GIFApplicationExtension: Byte = $FF;
GIFImageDescriptor: Byte = Ord(',');
GIFExtensionIntroducer: Byte = Ord('!');
GIFTrailer: Byte = Ord(';');
GIFBlockTerminator: Byte = $00;
// Masks for accessing fields in PackedFields of TGraphicControlExtension
GIFTransparent = $01;
GIFUserInput = $02;
GIFDisposalMethod = $1C;
type
TGIFHeader = packed record
// File header part
Signature: TChar3; // Header Signature (always "GIF")
Version: TChar3; // GIF format version("87a" or "89a")
// Logical Screen Descriptor part
ScreenWidth: Word; // Width of Display Screen in Pixels
ScreenHeight: Word; // Height of Display Screen in Pixels
PackedFields: Byte; // Screen and color map information
BackgroundColorIndex: Byte; // Background color index (in global color table)
AspectRatio: Byte; // Pixel aspect ratio, ratio = (AspectRatio + 15) / 64
end;
TImageDescriptor = packed record
//Separator: Byte; // leave that out since we always read one bye ahead
Left: Word; // X position of image with respect to logical screen
Top: Word; // Y position
Width: Word;
Height: Word;
PackedFields: Byte;
end;
const
// GIF extension labels
GIFExtTypeGraphic = $F9;
GIFExtTypePlainText = $01;
GIFExtTypeApplication = $FF;
GIFExtTypeComment = $FE;
type
TGraphicControlExtension = packed record
BlockSize: Byte;
PackedFields: Byte;
DelayTime: Word;
TransparentColorIndex: Byte;
Terminator: Byte;
end;
const
CodeTableSize = 4096;
HashTableSize = 17777;
type
TReadContext = record
Inx: Integer;
Size: Integer;
Buf: array [0..255 + 4] of Byte;
CodeSize: Integer;
ReadMask: Integer;
end;
PReadContext = ^TReadContext;
TWriteContext = record
Inx: Integer;
CodeSize: Integer;
Buf: array [0..255 + 4] of Byte;
end;
PWriteContext = ^TWriteContext;
TOutputContext = record
W: Integer;
H: Integer;
X: Integer;
Y: Integer;
BitsPerPixel: Integer;
Pass: Integer;
Interlace: Boolean;
LineIdent: Integer;
Data: Pointer;
CurrLineData: Pointer;
end;
TImageDict = record
Tail: Word;
Index: Word;
Col: Byte;
end;
PImageDict = ^TImageDict;
PIntCodeTable = ^TIntCodeTable;
TIntCodeTable = array [0..CodeTableSize - 1] of Word;
TDictTable = array [0..CodeTableSize - 1] of TImageDict;
PDictTable = ^TDictTable;
resourcestring
SGIFDecodingError = 'Error when decoding GIF LZW data';
{
TGIFFileFormat implementation
}
constructor TGIFFileFormat.Create;
begin
inherited Create;
FName := SGIFFormatName;
FCanLoad := True;
FCanSave := True;
FIsMultiImageFormat := True;
FSupportedFormats := GIFSupportedFormats;
AddMasks(SGIFMasks);
end;
function TGIFFileFormat.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;
{ GIF LZW decompresion code is from JVCL JvGIF.pas unit.}
procedure TGIFFileFormat.LZWDecompress(const IO: TIOFunctions; Handle: TImagingHandle; Width, Height: Integer;
Interlaced: Boolean; Data: Pointer);
var
MinCodeSize: Byte;
MaxCode, BitMask, InitCodeSize: Integer;
ClearCode, EndingCode, FirstFreeCode, FreeCode: Word;
I, OutCount, Code: Integer;
CurCode, OldCode, InCode, FinalChar: Word;
Prefix, Suffix, OutCode: PIntCodeTable;
ReadCtxt: TReadContext;
OutCtxt: TOutputContext;
TableFull: Boolean;
function ReadCode(var Context: TReadContext): Integer;
var
RawCode: Integer;
ByteIndex: Integer;
Bytes: Byte;
BytesToLose: Integer;
begin
while Context.Inx + Context.CodeSize > Context.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);
IO.Read(Handle, @Bytes, 1);
if Bytes > 0 then
IO.Read(Handle, @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;
// Only ifIndex8 supported
P := @PByteArray(Context.CurrLineData)[Context.X];
P^ := Value;
{case Context.BitsPerPixel of
1:
begin
P := @PByteArray(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 := @PByteArray(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 := @PByteArray(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 := @PByteArray(Context.Data)[Context.Y * Context.LineIdent];
end;
begin
OutCount := 0;
OldCode := 0;
FinalChar := 0;
TableFull := False;
GetMem(Prefix, SizeOf(TIntCodeTable));
GetMem(Suffix, SizeOf(TIntCodeTable));
GetMem(OutCode, SizeOf(TIntCodeTable) + SizeOf(Word));
try
IO.Read(Handle, @MinCodeSize, 1);
if (MinCodeSize < 2) or (MinCodeSize > 9) then
RaiseImaging(SGIFDecodingError, []);
// 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 := Width;
OutCtxt.H := Height;
OutCtxt.BitsPerPixel := MinCodeSize;
OutCtxt.Interlace := Interlaced;
OutCtxt.LineIdent := Width;
OutCtxt.Data := Data;
OutCtxt.CurrLineData := Data;
BitMask := (1 shl OutCtxt.BitsPerPixel) - 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(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(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 > CodeTableSize then
RaiseImaging(SGIFDecodingError, []);
OutCode^[OutCount] := Suffix^[CurCode];
Inc(OutCount);
CurCode := Prefix^[CurCode];
end;
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(ReadCtxt);
end;
if Code = $FFFF then
RaiseImaging(SGIFDecodingError, []);
finally
FreeMem(Prefix);
FreeMem(OutCode);
FreeMem(Suffix);
end;
end;
{ GIF LZW compresion code is from JVCL JvGIF.pas unit.}
procedure TGIFFileFormat.LZWCompress(const IO: TIOFunctions; Handle: TImagingHandle; Width, Height, BitCount: Integer;
Interlaced: Boolean; Data: Pointer);
var
LineIdent: Integer;
MinCodeSize, Col: Byte;
InitCodeSize, X, Y: Integer;
Pass: Integer;
MaxCode: Integer; { 1 shl CodeSize }
ClearCode, EndingCode, LastCode, Tail: Integer;
I, HashValue: Integer;
LenString: Word;
Dict: PDictTable;
HashTable: TList;
PData: PByte;
WriteCtxt: TWriteContext;
function InitHash(P: Integer): Integer;
begin
Result := (P + 3) * 301;
end;
procedure WriteCode(Code: Integer; var Context: TWriteContext);
var
BufIndex: Integer;
Bytes: Byte;
begin
BufIndex := Context.Inx shr 3;
Code := Code shl (Context.Inx and 7);
Context.Buf[BufIndex] := Context.Buf[BufIndex] or Byte(Code);
Context.Buf[BufIndex + 1] := Byte(Code shr 8);
Context.Buf[BufIndex + 2] := Byte(Code shr 16);
Context.Inx := Context.Inx + Context.CodeSize;
if Context.Inx >= 255 * 8 then
begin
// Flush out full buffer
Bytes := 255;
IO.Write(Handle, @Bytes, 1);
IO.Write(Handle, @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(var Context: TWriteContext);
var
Bytes: Byte;
begin
Bytes := (Context.Inx + 7) shr 3;
if Bytes > 0 then
begin
IO.Write(Handle, @Bytes, 1);
IO.Write(Handle, @Context.Buf, Bytes);
end;
// Data block terminator - a block of zero Size
Bytes := 0;
IO.Write(Handle, @Bytes, 1);
end;
begin
LineIdent := Width;
Tail := 0;
HashValue := 0;
Col := 0;
HashTable := TList.Create;
GetMem(Dict, SizeOf(TDictTable));
try
for I := 0 to HashTableSize - 1 do
HashTable.Add(nil);
// Initialise encoder variables
InitCodeSize := BitCount + 1;
if InitCodeSize = 2 then
Inc(InitCodeSize);
MinCodeSize := InitCodeSize - 1;
IO.Write(Handle, @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(ClearCode, WriteCtxt);
Y := 0;
Pass := 0;
while Y < Height do
begin
PData := @PByteArray(Data)[Y * LineIdent];
for X := 0 to Width - 1 do
begin
// Only ifIndex8 support
case BitCount of
8:
begin
Col := PData^;
PData := @PByteArray(PData)[1];
end;
{4:
begin
if X and 1 <> 0 then
begin
Col := PData^ and $0F;
PData := @PByteArray(PData)[1];
end
else
Col := PData^ shr 4;
end;
1:
begin
if X and 7 = 7 then
begin
Col := PData^ and 1;
PData := @PByteArray(PData)[1];
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 HashTableSize;
HashValue := HashValue mod HashTableSize;
while (HashTable[I] <> nil) and
((PImageDict(HashTable[I])^.Tail <> Tail) or
(PImageDict(HashTable[I])^.Col <> Col)) do
begin
Inc(I);
if I >= HashTableSize then
I := 0;
end;
if HashTable[I] <> nil then // Found in the strings table
Tail := PImageDict(HashTable[I])^.Index
else
begin
// Not found
WriteCode(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 >= CodeTableSize - 2 then
begin
// Reset tables
WriteCode(Tail, WriteCtxt);
WriteCode(ClearCode, WriteCtxt);
LenString := 0;
LastCode := EndingCode;
WriteCtxt.CodeSize := InitCodeSize;
MaxCode := 1 shl InitCodeSize;
for I := 0 to HashTableSize - 1 do
HashTable[I] := nil;
end;
end;
end;
end;
if Interlaced then
Y := InterlaceStep(Y, Height, Pass)
else
Inc(Y);
end;
WriteCode(Tail, WriteCtxt);
WriteCode(EndingCode, WriteCtxt);
FlushCode(WriteCtxt);
finally
HashTable.Free;
FreeMem(Dict);
end;
end;
function TGIFFileFormat.LoadData(Handle: TImagingHandle;
var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
var
Header: TGIFHeader;
HasGlobalPal: Boolean;
GlobalPalLength: Integer;
GlobalPal: TPalette32Size256;
I: Integer;
BlockID: Byte;
HasGraphicExt: Boolean;
GraphicExt: TGraphicControlExtension;
Disposals: array of TDisposalMethod;
function ReadBlockID: Byte;
begin
Result := GIFTrailer;
GetIO.Read(Handle, @Result, SizeOf(Result));
end;
procedure ReadExtensions;
var
BlockSize, ExtType: Byte;
begin
HasGraphicExt := False;
// Read extensions until image descriptor is found. Only graphic extension
// is stored now (for transparency), others are skipped.
while BlockID = GIFExtensionIntroducer do
with GetIO do
begin
Read(Handle, @ExtType, SizeOf(ExtType));
if ExtType = GIFGraphicControlExtension then
begin
HasGraphicExt := True;
Read(Handle, @GraphicExt, SizeOf(GraphicExt));
end
else if ExtType in [GIFCommentExtension, GIFApplicationExtension, GIFPlainText] then
repeat
// Read block sizes and skip them
Read(Handle, @BlockSize, SizeOf(BlockSize));
Seek(Handle, BlockSize, smFromCurrent);
until BlockSize = 0;
// Read ID of following block
BlockID := ReadBlockID;
end;
end;
procedure CopyFrameTransparent(const Image, Frame: TImageData; Left, Top, TransIndex: Integer);
var
X, Y: Integer;
Src, Dst: PByte;
begin
Src := Frame.Bits;
// Copy all pixels from frame to log screen but ignore the transparent ones
for Y := 0 to Frame.Height - 1 do
begin
Dst := @PByteArray(Image.Bits)[(Top + Y) * Image.Width + Left];
for X := 0 to Frame.Width - 1 do
begin
if Src^ <> TransIndex then
Dst^ := Src^;
Inc(Src);
Inc(Dst);
end;
end;
end;
procedure ReadFrame;
var
ImageDesc: TImageDescriptor;
HasLocalPal, Interlaced, HasTransparency: Boolean;
I, Idx, LocalPalLength, TransIndex: Integer;
LocalPal: TPalette32Size256;
BlockTerm: Byte;
Frame: TImageData;
begin
Idx := Length(Images);
SetLength(Images, Idx + 1);
FillChar(LocalPal, SizeOf(LocalPal), 0);
with GetIO do
begin
// Read and parse image descriptor
Read(Handle, @ImageDesc, SizeOf(ImageDesc));
HasLocalPal := (ImageDesc.PackedFields and GIFLocalColorTable) = GIFLocalColorTable;
Interlaced := (ImageDesc.PackedFields and GIFInterlaced) = GIFInterlaced;
LocalPalLength := ImageDesc.PackedFields and GIFColorTableSize;
LocalPalLength := 1 shl (LocalPalLength + 1); // Total pal length is 2^(n+1)
// Create new logical screen
NewImage(Header.ScreenWidth, Header.ScreenHeight, ifIndex8, Images[Idx]);
// Create new image for this frame which would be later pasted onto logical screen
InitImage(Frame);
NewImage(ImageDesc.Width, ImageDesc.Height, ifIndex8, Frame);
// Load local palette if there is any
if HasLocalPal then
for I := 0 to LocalPalLength - 1 do
begin
LocalPal[I].A := 255;
Read(Handle, @LocalPal[I].R, SizeOf(LocalPal[I].R));
Read(Handle, @LocalPal[I].G, SizeOf(LocalPal[I].G));
Read(Handle, @LocalPal[I].B, SizeOf(LocalPal[I].B));
end;
// Use local pal if present or global pal if present or create
// default pal if neither of them is present
if HasLocalPal then
Move(LocalPal, Images[Idx].Palette^, SizeOf(LocalPal))
else if HasGlobalPal then
Move(GlobalPal, Images[Idx].Palette^, SizeOf(GlobalPal))
else
FillCustomPalette(Images[Idx].Palette, GlobalPalLength, 3, 3, 2);
// Add default disposal method for this frame
SetLength(Disposals, Length(Disposals) + 1);
Disposals[High(Disposals)] := dmUndefined;
// If Grahic Control Extension is present make use of it
if HasGraphicExt then
begin
HasTransparency := (GraphicExt.PackedFields and GIFTransparent) = GIFTransparent;
Disposals[High(Disposals)] := TDisposalMethod((GraphicExt.PackedFields and GIFDisposalMethod) shr 2);
if HasTransparency then
Images[Idx].Palette[GraphicExt.TransparentColorIndex].A := 0;
end
else
HasTransparency := False;
if Idx >= 1 then
begin
// If previous frame had some special disposal method we take it into
// account now
case Disposals[Idx - 1] of
dmUndefined: ; // Do nothing
dmLeave:
begin
// Leave previous frame on log screen
CopyRect(Images[Idx - 1], 0, 0, Images[Idx].Width,
Images[Idx].Height, Images[Idx], 0, 0);
end;
dmRestoreBackground:
begin
// Clear log screen with background color
FillRect(Images[Idx], 0, 0, Images[Idx].Width, Images[Idx].Height,
@Header.BackgroundColorIndex);
end;
dmRestorePrevious:
if Idx >= 2 then
begin
// Set log screen to "previous of previous" frame
CopyRect(Images[Idx - 2], 0, 0, Images[Idx].Width,
Images[Idx].Height, Images[Idx], 0, 0);
end;
end;
end
else
begin
// First frame - just fill with background color
FillRect(Images[Idx], 0, 0, Images[Idx].Width, Images[Idx].Height,
@Header.BackgroundColorIndex);
end;
try
// Data decompression finally
LZWDecompress(GetIO, Handle, ImageDesc.Width, ImageDesc.Height, Interlaced, Frame.Bits);
Read(Handle, @BlockTerm, SizeOf(BlockTerm));
// Now copy frame to logical screen with skipping of transparent pixels (if enabled)
TransIndex := Iff(HasTransparency, GraphicExt.TransparentColorIndex, MaxInt);
CopyFrameTransparent(Images[Idx], Frame, ImageDesc.Left, ImageDesc.Top, TransIndex);
finally
FreeImage(Frame);
end;
end;
end;
begin
SetLength(Images, 0);
FillChar(GlobalPal, SizeOf(GlobalPal), 0);
with GetIO do
begin
// Read GIF header
Read(Handle, @Header, SizeOf(Header));
HasGlobalPal := Header.PackedFields and GIFGlobalColorTable = GIFGlobalColorTable; // Bit 7
GlobalPalLength := Header.PackedFields and GIFColorTableSize; // Bits 0-2
GlobalPalLength := 1 shl (GlobalPalLength + 1); // Total pal length is 2^(n+1)
// Read global palette from file if present
if HasGlobalPal then
begin
for I := 0 to GlobalPalLength - 1 do
begin
GlobalPal[I].A := 255;
Read(Handle, @GlobalPal[I].R, SizeOf(GlobalPal[I].R));
Read(Handle, @GlobalPal[I].G, SizeOf(GlobalPal[I].G));
Read(Handle, @GlobalPal[I].B, SizeOf(GlobalPal[I].B));
end;
GlobalPal[Header.BackgroundColorIndex].A := 0;
end;
// Read ID of the first block
BlockID := ReadBlockID;
// Now read all data blocks in the file until file trailer is reached
while BlockID <> GIFTrailer do
begin
// Read supported and skip unsupported extensions
ReadExtensions;
// If image frame is found read it
if BlockID = GIFImageDescriptor then
ReadFrame;
// Read next block's ID
BlockID := ReadBlockID;
// If block ID is unknown set it to end-of-GIF marker
if not (BlockID in [GIFExtensionIntroducer, GIFTrailer, GIFImageDescriptor]) then
BlockID := GIFTrailer;
end;
Result := True;
end;
end;
function TGIFFileFormat.SaveData(Handle: TImagingHandle;
const Images: TDynImageDataArray; Index: Integer): Boolean;
var
Header: TGIFHeader;
ImageDesc: TImageDescriptor;
ImageToSave: TImageData;
MustBeFreed: Boolean;
I, J: Integer;
GraphicExt: TGraphicControlExtension;
procedure FindMaxDimensions(var MaxWidth, MaxHeight: Word);
var
I: Integer;
begin
MaxWidth := Images[FFirstIdx].Width;
MaxHeight := Images[FFirstIdx].Height;
for I := FFirstIdx + 1 to FLastIdx do
begin
MaxWidth := Iff(Images[I].Width > MaxWidth, Images[I].Width, MaxWidth);
MaxHeight := Iff(Images[I].Height > MaxWidth, Images[I].Height, MaxHeight);
end;
end;
begin
// Fill header with data, select size of largest image in array as
// logical screen size
FillChar(Header, Sizeof(Header), 0);
Header.Signature := GIFSignature;
Header.Version := GIFVersions[gv89];
FindMaxDimensions(Header.ScreenWidth, Header.ScreenHeight);
Header.PackedFields := GIFColorResolution; // Color resolution is 256
GetIO.Write(Handle, @Header, SizeOf(Header));
// Prepare default GC extension with delay
FillChar(GraphicExt, Sizeof(GraphicExt), 0);
GraphicExt.DelayTime := 65;
GraphicExt.BlockSize := 4;
for I := FFirstIdx to FLastIdx do
begin
if MakeCompatible(Images[I], ImageToSave, MustBeFreed) then
with GetIO, ImageToSave do
try
// Write Graphic Control Extension with default delay
Write(Handle, @GIFExtensionIntroducer, SizeOf(GIFExtensionIntroducer));
Write(Handle, @GIFGraphicControlExtension, SizeOf(GIFGraphicControlExtension));
Write(Handle, @GraphicExt, SizeOf(GraphicExt));
// Write frame marker and fill and write image descriptor for this frame
Write(Handle, @GIFImageDescriptor, SizeOf(GIFImageDescriptor));
FillChar(ImageDesc, Sizeof(ImageDesc), 0);
ImageDesc.Width := Width;
ImageDesc.Height := Height;
ImageDesc.PackedFields := GIFLocalColorTable or GIFColorTableSize; // Use lccal color table with 256 entries
Write(Handle, @ImageDesc, SizeOf(ImageDesc));
// Write local color table for each frame
for J := 0 to 255 do
begin
Write(Handle, @Palette[J].R, SizeOf(Palette[J].R));
Write(Handle, @Palette[J].G, SizeOf(Palette[J].G));
Write(Handle, @Palette[J].B, SizeOf(Palette[J].B));
end;
// Fonally compress image data
LZWCompress(GetIO, Handle, Width, Height, 8, False, Bits);
finally
if MustBeFreed then
FreeImage(ImageToSave);
end;
end;
GetIO.Write(Handle, @GIFTrailer, SizeOf(GIFTrailer));
Result := True;
end;
procedure TGIFFileFormat.ConvertToSupported(var Image: TImageData;
const Info: TImageFormatInfo);
begin
ConvertImage(Image, ifIndex8);
end;
function TGIFFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
var
Header: TGIFHeader;
ReadCount: LongInt;
begin
Result := False;
if Handle <> nil then
begin
ReadCount := GetIO.Read(Handle, @Header, SizeOf(Header));
GetIO.Seek(Handle, -ReadCount, smFromCurrent);
Result := (ReadCount >= SizeOf(Header)) and
(Header.Signature = GIFSignature) and
((Header.Version = GIFVersions[gv87]) or (Header.Version = GIFVersions[gv89]));
end;
end;
initialization
RegisterImageFileFormat(TGIFFileFormat);
{
File Notes:
-- TODOS ----------------------------------------------------
- nothing now
-- 0.24.1 Changes/Bug Fixes ---------------------------------
- Made backround color transparent by default (alpha = 0).
-- 0.23 Changes/Bug Fixes -----------------------------------
- Fixed other loading bugs (local pal size, transparency).
- Added GIF saving.
- Fixed bug when loading multiframe GIFs and implemented few animation
features (disposal methods, ...).
- Loading of GIFs working.
- Unit created with initial stuff!
}
end.