2 Vampyre Imaging Library
4 http://imaginglib.sourceforge.net
6 The contents of this file are used with permission, subject to the Mozilla
7 Public License Version 1.1 (the "License"); you may not use this file except
8 in compliance with the License. You may obtain a copy of the License at
9 http://www.mozilla.org/MPL/MPL-1.1.html
11 Software distributed under the License is distributed on an "AS IS" basis,
12 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
13 the specific language governing rights and limitations under the License.
15 Alternatively, the contents of this file may be used under the terms of the
16 GNU Lesser General Public License (the "LGPL License"), in which case the
17 provisions of the LGPL License are applicable instead of those above.
18 If you wish to allow use of your version of this file only under the terms
19 of the LGPL License and not to allow others to use your version of this file
20 under the MPL, indicate your decision by deleting the provisions above and
21 replace them with the notice and other provisions required by the LGPL
22 License. If you do not delete the provisions above, a recipient may use
23 your version of this file under either the MPL or the LGPL License.
25 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
28 { This unit contains image format loader/saver for GIF images.}
31 {$I ImagingOptions.inc}
36 SysUtils
, Classes
, Imaging
, ImagingTypes
, ImagingIO
, ImagingUtility
;
39 { GIF (Graphics Interchange Format) loader/saver class. GIF was
40 (and is still used) popular format for storing images supporting
41 multiple images per file and single color transparency.
42 Pixel format is 8 bit indexed where each image frame can have
43 its own color palette. GIF uses lossless LZW compression
44 (patent expired few years ago).
45 Imaging can load and save all GIFs with all frames and supports
46 transparency. Imaging can load just raw ifIndex8 frames or
47 also animate them in ifA8R8G8B8 format. See ImagingGIFLoadAnimated option.}
48 TGIFFileFormat
= class(TImageFileFormat
)
50 FLoadAnimated
: LongBool;
51 function InterlaceStep(Y
, Height
: Integer; var Pass
: Integer): Integer;
52 procedure LZWDecompress(Stream
: TStream
; Handle
: TImagingHandle
;
53 Width
, Height
: Integer; Interlaced
: Boolean; Data
: Pointer);
54 procedure LZWCompress(const IO
: TIOFunctions
; Handle
: TImagingHandle
;
55 Width
, Height
, BitCount
: Integer; Interlaced
: Boolean; Data
: Pointer);
57 procedure Define
; override;
58 function LoadData(Handle
: TImagingHandle
; var Images
: TDynImageDataArray
;
59 OnlyFirstLevel
: Boolean): Boolean; override;
60 function SaveData(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
61 Index
: LongInt): Boolean; override;
62 procedure ConvertToSupported(var Image
: TImageData
;
63 const Info
: TImageFormatInfo
); override;
65 function TestFormat(Handle
: TImagingHandle
): Boolean; override;
67 property LoadAnimated
: LongBool read FLoadAnimated write FLoadAnimated
;
73 SGIFFormatName
= 'Graphics Interchange Format';
75 GIFSupportedFormats
: TImageFormats
= [ifIndex8
];
76 GIFDefaultLoadAnimated
= True;
79 TGIFVersion
= (gv87
, gv89
);
80 TDisposalMethod
= (dmNoRemoval
, dmLeave
, dmRestoreBackground
,
81 dmRestorePrevious
, dmReserved4
, dmReserved5
, dmReserved6
, dmReserved7
);
84 GIFSignature
: TChar3
= 'GIF';
85 GIFVersions
: array[TGIFVersion
] of TChar3
= ('87a', '89a');
88 // Masks for accessing fields in PackedFields of TGIFHeader
89 GIFGlobalColorTable
= $80;
90 GIFColorResolution
= $70;
91 GIFColorTableSorted
= $08;
92 GIFColorTableSize
= $07;
94 // Masks for accessing fields in PackedFields of TImageDescriptor
95 GIFLocalColorTable
= $80;
97 GIFLocalTableSorted
= $20;
100 GIFPlainText
: Byte = $01;
101 GIFGraphicControlExtension
: Byte = $F9;
102 GIFCommentExtension
: Byte = $FE;
103 GIFApplicationExtension
: Byte = $FF;
104 GIFImageDescriptor
: Byte = Ord(',');
105 GIFExtensionIntroducer
: Byte = Ord('!');
106 GIFTrailer
: Byte = Ord(';');
107 GIFBlockTerminator
: Byte = $00;
109 // Masks for accessing fields in PackedFields of TGraphicControlExtension
110 GIFTransparent
= $01;
112 GIFDisposalMethod
= $1C;
115 // Netscape sub block types
116 GIFAppLoopExtension
= 1;
117 GIFAppBufferExtension
= 2;
120 TGIFHeader
= packed record
122 Signature
: TChar3
; // Header Signature (always "GIF")
123 Version
: TChar3
; // GIF format version("87a" or "89a")
124 // Logical Screen Descriptor part
125 ScreenWidth
: Word; // Width of Display Screen in Pixels
126 ScreenHeight
: Word; // Height of Display Screen in Pixels
127 PackedFields
: Byte; // Screen and color map information
128 BackgroundColorIndex
: Byte; // Background color index (in global color table)
129 AspectRatio
: Byte; // Pixel aspect ratio, ratio = (AspectRatio + 15) / 64
132 TImageDescriptor
= packed record
133 //Separator: Byte; // leave that out since we always read one bye ahead
134 Left
: Word; // X position of image with respect to logical screen
135 Top
: Word; // Y position
142 // GIF extension labels
143 GIFExtTypeGraphic
= $F9;
144 GIFExtTypePlainText
= $01;
145 GIFExtTypeApplication
= $FF;
146 GIFExtTypeComment
= $FE;
149 TGraphicControlExtension
= packed record
153 TransparentColorIndex
: Byte;
158 TGIFIdentifierCode
= array[0..7] of AnsiChar;
159 TGIFAuthenticationCode
= array[0..2] of AnsiChar;
160 TGIFApplicationRec
= packed record
161 Identifier
: TGIFIdentifierCode
;
162 Authentication
: TGIFAuthenticationCode
;
166 CodeTableSize
= 4096;
167 HashTableSize
= 17777;
170 TReadContext
= record
173 Buf
: array [0..255 + 4] of Byte;
177 PReadContext
= ^TReadContext
;
179 TWriteContext
= record
182 Buf
: array [0..255 + 4] of Byte;
184 PWriteContext
= ^TWriteContext
;
186 TOutputContext
= record
191 BitsPerPixel
: Integer;
196 CurrLineData
: Pointer;
204 PImageDict
= ^TImageDict
;
206 PIntCodeTable
= ^TIntCodeTable
;
207 TIntCodeTable
= array [0..CodeTableSize
- 1] of Word;
209 TDictTable
= array [0..CodeTableSize
- 1] of TImageDict
;
210 PDictTable
= ^TDictTable
;
213 SGIFDecodingError
= 'Error when decoding GIF LZW data';
216 TGIFFileFormat implementation
219 procedure TGIFFileFormat
.Define
;
222 FName
:= SGIFFormatName
;
223 FFeatures
:= [ffLoad
, ffSave
, ffMultiImage
];
224 FSupportedFormats
:= GIFSupportedFormats
;
225 FLoadAnimated
:= GIFDefaultLoadAnimated
;
228 RegisterOption(ImagingGIFLoadAnimated
, @FLoadAnimated
);
231 function TGIFFileFormat
.InterlaceStep(Y
, Height
: Integer; var Pass
: Integer): Integer;
242 if Result
>= Height
then
248 if Result
< Height
then
255 if Result
< Height
then
266 { GIF LZW decompresion code is from JVCL JvGIF.pas unit.}
267 procedure TGIFFileFormat
.LZWDecompress(Stream
: TStream
; Handle
: TImagingHandle
; Width
, Height
: Integer;
268 Interlaced
: Boolean; Data
: Pointer);
271 MaxCode
, BitMask
, InitCodeSize
: Integer;
272 ClearCode
, EndingCode
, FirstFreeCode
, FreeCode
: Word;
273 I
, OutCount
, Code
: Integer;
274 CurCode
, OldCode
, InCode
, FinalChar
: Word;
275 Prefix
, Suffix
, OutCode
: PIntCodeTable
;
276 ReadCtxt
: TReadContext
;
277 OutCtxt
: TOutputContext
;
280 function ReadCode(var Context
: TReadContext
): Integer;
285 BytesToLose
: Integer;
287 while (Context
.Inx
+ Context
.CodeSize
> Context
.Size
) and
288 (Stream
.Position
< Stream
.Size
) do
290 // Not enough bits in buffer - refill it - Not very efficient, but infrequently called
291 BytesToLose
:= Context
.Inx
shr 3;
292 // Note biggest Code Size is 12 bits. And this can at worst span 3 Bytes
293 Move(Context
.Buf
[Word(BytesToLose
)], Context
.Buf
[0], 3);
294 Context
.Inx
:= Context
.Inx
and 7;
295 Context
.Size
:= Context
.Size
- (BytesToLose
shl 3);
296 Stream
.Read(Bytes
, 1);
298 Stream
.Read(Context
.Buf
[Word(Context
.Size
shr 3)], Bytes
);
299 Context
.Size
:= Context
.Size
+ (Bytes
shl 3);
301 ByteIndex
:= Context
.Inx
shr 3;
302 RawCode
:= Context
.Buf
[Word(ByteIndex
)] +
303 (Word(Context
.Buf
[Word(ByteIndex
+ 1)]) shl 8);
304 if Context
.CodeSize
> 8 then
305 RawCode
:= RawCode
+ (Integer(Context
.Buf
[ByteIndex
+ 2]) shl 16);
306 RawCode
:= RawCode
shr (Context
.Inx
and 7);
307 Context
.Inx
:= Context
.Inx
+ Byte(Context
.CodeSize
);
308 Result
:= RawCode
and Context
.ReadMask
;
311 procedure Output(Value
: Byte; var Context
: TOutputContext
);
315 if Context
.Y
>= Context
.H
then
318 // Only ifIndex8 supported
319 P
:= @PByteArray(Context
.CurrLineData
)[Context
.X
];
322 {case Context.BitsPerPixel of
325 P := @PByteArray(Context.CurrLineData)[Context.X shr 3];
326 if (Context.X and $07) <> 0 then
327 P^ := P^ or Word(Value shl (7 - (Word(Context.X and 7))))
329 P^ := Byte(Value shl 7);
333 P := @PByteArray(Context.CurrLineData)[Context.X shr 1];
334 if (Context.X and 1) <> 0 then
337 P^ := Byte(Value shl 4);
341 P := @PByteArray(Context.CurrLineData)[Context.X];
347 if Context
.X
< Context
.W
then
350 if Context
.Interlace
then
351 Context
.Y
:= InterlaceStep(Context
.Y
, Context
.H
, Context
.Pass
)
355 Context
.CurrLineData
:= @PByteArray(Context
.Data
)[Context
.Y
* Context
.LineIdent
];
363 GetMem(Prefix
, SizeOf(TIntCodeTable
));
364 GetMem(Suffix
, SizeOf(TIntCodeTable
));
365 GetMem(OutCode
, SizeOf(TIntCodeTable
) + SizeOf(Word));
367 Stream
.Read(MinCodeSize
, 1);
368 if (MinCodeSize
< 2) or (MinCodeSize
> 9) then
369 RaiseImaging(SGIFDecodingError
, []);
370 // Initial read context
373 ReadCtxt
.CodeSize
:= MinCodeSize
+ 1;
374 ReadCtxt
.ReadMask
:= (1 shl ReadCtxt
.CodeSize
) - 1;
375 // Initialise pixel-output context
381 OutCtxt
.BitsPerPixel
:= MinCodeSize
;
382 OutCtxt
.Interlace
:= Interlaced
;
383 OutCtxt
.LineIdent
:= Width
;
384 OutCtxt
.Data
:= Data
;
385 OutCtxt
.CurrLineData
:= Data
;
386 BitMask
:= (1 shl OutCtxt
.BitsPerPixel
) - 1;
387 // 2 ^ MinCodeSize accounts for all colours in file
388 ClearCode
:= 1 shl MinCodeSize
;
389 EndingCode
:= ClearCode
+ 1;
390 FreeCode
:= ClearCode
+ 2;
391 FirstFreeCode
:= FreeCode
;
392 // 2^ (MinCodeSize + 1) includes clear and eoi Code and space too
393 InitCodeSize
:= ReadCtxt
.CodeSize
;
394 MaxCode
:= 1 shl ReadCtxt
.CodeSize
;
395 Code
:= ReadCode(ReadCtxt
);
396 while (Code
<> EndingCode
) and (Code
<> $FFFF) and
397 (OutCtxt
.Y
< OutCtxt
.H
) do
399 if Code
= ClearCode
then
401 ReadCtxt
.CodeSize
:= InitCodeSize
;
402 MaxCode
:= 1 shl ReadCtxt
.CodeSize
;
403 ReadCtxt
.ReadMask
:= MaxCode
- 1;
404 FreeCode
:= FirstFreeCode
;
405 Code
:= ReadCode(ReadCtxt
);
410 FinalChar
:= (CurCode
and BitMask
);
411 Output(Byte(FinalChar
), OutCtxt
);
418 if CurCode
>= FreeCode
then
421 OutCode
^[OutCount
] := FinalChar
;
424 while CurCode
> BitMask
do
426 if OutCount
> CodeTableSize
then
427 RaiseImaging(SGIFDecodingError
, []);
428 OutCode
^[OutCount
] := Suffix
^[CurCode
];
430 CurCode
:= Prefix
^[CurCode
];
433 FinalChar
:= CurCode
and BitMask
;
434 OutCode
^[OutCount
] := FinalChar
;
436 for I
:= OutCount
- 1 downto 0 do
437 Output(Byte(OutCode
^[I
]), OutCtxt
);
440 if not TableFull
then
442 Prefix
^[FreeCode
] := OldCode
;
443 Suffix
^[FreeCode
] := FinalChar
;
444 // Advance to next free slot
446 if FreeCode
>= MaxCode
then
448 if ReadCtxt
.CodeSize
< 12 then
450 Inc(ReadCtxt
.CodeSize
);
451 MaxCode
:= MaxCode
shl 1;
452 ReadCtxt
.ReadMask
:= (1 shl ReadCtxt
.CodeSize
) - 1;
460 Code
:= ReadCode(ReadCtxt
);
463 RaiseImaging(SGIFDecodingError
, []);
471 { GIF LZW compresion code is from JVCL JvGIF.pas unit.}
472 procedure TGIFFileFormat
.LZWCompress(const IO
: TIOFunctions
; Handle
: TImagingHandle
; Width
, Height
, BitCount
: Integer;
473 Interlaced
: Boolean; Data
: Pointer);
476 MinCodeSize
, Col
: Byte;
477 InitCodeSize
, X
, Y
: Integer;
479 MaxCode
: Integer; { 1 shl CodeSize }
480 ClearCode
, EndingCode
, LastCode
, Tail
: Integer;
481 I
, HashValue
: Integer;
486 WriteCtxt
: TWriteContext
;
488 function InitHash(P
: Integer): Integer;
490 Result
:= (P
+ 3) * 301;
493 procedure WriteCode(Code
: Integer; var Context
: TWriteContext
);
498 BufIndex
:= Context
.Inx
shr 3;
499 Code
:= Code
shl (Context
.Inx
and 7);
500 Context
.Buf
[BufIndex
] := Context
.Buf
[BufIndex
] or Byte(Code
);
501 Context
.Buf
[BufIndex
+ 1] := Byte(Code
shr 8);
502 Context
.Buf
[BufIndex
+ 2] := Byte(Code
shr 16);
503 Context
.Inx
:= Context
.Inx
+ Context
.CodeSize
;
504 if Context
.Inx
>= 255 * 8 then
506 // Flush out full buffer
508 IO
.Write(Handle
, @Bytes
, 1);
509 IO
.Write(Handle
, @Context
.Buf
, Bytes
);
510 Move(Context
.Buf
[255], Context
.Buf
[0], 2);
511 FillChar(Context
.Buf
[2], 255, 0);
512 Context
.Inx
:= Context
.Inx
- (255 * 8);
516 procedure FlushCode(var Context
: TWriteContext
);
520 Bytes
:= (Context
.Inx
+ 7) shr 3;
523 IO
.Write(Handle
, @Bytes
, 1);
524 IO
.Write(Handle
, @Context
.Buf
, Bytes
);
526 // Data block terminator - a block of zero Size
528 IO
.Write(Handle
, @Bytes
, 1);
536 HashTable
:= TList
.Create
;
537 GetMem(Dict
, SizeOf(TDictTable
));
539 for I
:= 0 to HashTableSize
- 1 do
542 // Initialise encoder variables
543 InitCodeSize
:= BitCount
+ 1;
544 if InitCodeSize
= 2 then
546 MinCodeSize
:= InitCodeSize
- 1;
547 IO
.Write(Handle
, @MinCodeSize
, 1);
548 ClearCode
:= 1 shl MinCodeSize
;
549 EndingCode
:= ClearCode
+ 1;
550 LastCode
:= EndingCode
;
551 MaxCode
:= 1 shl InitCodeSize
;
553 // Setup write context
555 WriteCtxt
.CodeSize
:= InitCodeSize
;
556 FillChar(WriteCtxt
.Buf
, SizeOf(WriteCtxt
.Buf
), 0);
557 WriteCode(ClearCode
, WriteCtxt
);
563 PData
:= @PByteArray(Data
)[Y
* LineIdent
];
564 for X
:= 0 to Width
- 1 do
566 // Only ifIndex8 support
571 PData
:= @PByteArray(PData
)[1];
577 Col := PData^ and $0F;
578 PData := @PByteArray(PData)[1];
588 PData := @PByteArray(PData)[1];
591 Col := (PData^ shr (7 - (X and $07))) and $01;
595 if LenString
= 1 then
598 HashValue
:= InitHash(Col
);
602 HashValue
:= HashValue
* (Col
+ LenString
+ 4);
603 I
:= HashValue
mod HashTableSize
;
604 HashValue
:= HashValue
mod HashTableSize
;
605 while (HashTable
[I
] <> nil) and
606 ((PImageDict(HashTable
[I
])^.Tail
<> Tail
) or
607 (PImageDict(HashTable
[I
])^.Col
<> Col
)) do
610 if I
>= HashTableSize
then
613 if HashTable
[I
] <> nil then // Found in the strings table
614 Tail
:= PImageDict(HashTable
[I
])^.Index
618 WriteCode(Tail
, WriteCtxt
);
620 HashTable
[I
] := @Dict
^[LastCode
];
621 PImageDict(HashTable
[I
])^.Index
:= LastCode
;
622 PImageDict(HashTable
[I
])^.Tail
:= Tail
;
623 PImageDict(HashTable
[I
])^.Col
:= Col
;
625 HashValue
:= InitHash(Col
);
627 if LastCode
>= MaxCode
then
629 // Next Code will be written longer
630 MaxCode
:= MaxCode
shl 1;
631 Inc(WriteCtxt
.CodeSize
);
634 if LastCode
>= CodeTableSize
- 2 then
637 WriteCode(Tail
, WriteCtxt
);
638 WriteCode(ClearCode
, WriteCtxt
);
640 LastCode
:= EndingCode
;
641 WriteCtxt
.CodeSize
:= InitCodeSize
;
642 MaxCode
:= 1 shl InitCodeSize
;
643 for I
:= 0 to HashTableSize
- 1 do
650 Y
:= InterlaceStep(Y
, Height
, Pass
)
654 WriteCode(Tail
, WriteCtxt
);
655 WriteCode(EndingCode
, WriteCtxt
);
656 FlushCode(WriteCtxt
);
663 function TGIFFileFormat
.LoadData(Handle
: TImagingHandle
;
664 var Images
: TDynImageDataArray
; OnlyFirstLevel
: Boolean): Boolean;
668 Width
, Height
: Integer;
669 Disposal
: TDisposalMethod
;
670 HasTransparency
: Boolean;
671 HasLocalPal
: Boolean;
677 HasGlobalPal
: Boolean;
678 GlobalPalLength
: Integer;
679 GlobalPal
: TPalette32Size256
;
680 ScreenWidth
, ScreenHeight
, I
, CachedIndex
: Integer;
682 HasGraphicExt
: Boolean;
683 GraphicExt
: TGraphicControlExtension
;
684 FrameInfos
: array of TFrameInfo
;
686 CachedFrame
: TImageData
;
687 AnimFrames
: TDynImageDataArray
;
689 function ReadBlockID
: Byte;
691 Result
:= GIFTrailer
;
692 if GetIO
.Read(Handle
, @Result
, SizeOf(Result
)) < SizeOf(Result
) then
693 Result
:= GIFTrailer
;
696 procedure ReadExtensions
;
698 BlockSize
, BlockType
, ExtType
: Byte;
699 AppRec
: TGIFApplicationRec
;
706 // Read block sizes and skip them
707 Read(Handle
, @BlockSize
, SizeOf(BlockSize
));
708 Seek(Handle
, BlockSize
, smFromCurrent
);
713 HasGraphicExt
:= False;
716 // Read extensions until image descriptor is found. Only graphic extension
717 // is stored now (for transparency), others are skipped.
718 while BlockID
= GIFExtensionIntroducer
do
721 Read(Handle
, @ExtType
, SizeOf(ExtType
));
723 while ExtType
in [GIFGraphicControlExtension
, GIFCommentExtension
, GIFApplicationExtension
, GIFPlainText
] do
725 if ExtType
= GIFGraphicControlExtension
then
727 HasGraphicExt
:= True;
728 Read(Handle
, @GraphicExt
, SizeOf(GraphicExt
));
730 else if (ExtType
= GIFApplicationExtension
) and not AppRead
then
732 Read(Handle
, @BlockSize
, SizeOf(BlockSize
));
733 if BlockSize
>= SizeOf(AppRec
) then
735 Read(Handle
, @AppRec
, SizeOf(AppRec
));
736 if ((AppRec
.Identifier
= 'NETSCAPE') and (AppRec
.Authentication
= '2.0')) or
737 ((AppRec
.Identifier
= 'ANIMEXTS') and (AppRec
.Authentication
= '1.0')) then
739 Read(Handle
, @BlockSize
, SizeOf(BlockSize
));
740 while BlockSize
<> 0 do
742 BlockType
:= ReadBlockID
;
747 if (BlockSize
>= SizeOf(LoopCount
)) then
750 Read(Handle
, @LoopCount
, SizeOf(LoopCount
));
751 Dec(BlockSize
, SizeOf(LoopCount
));
752 if LoopCount
> 0 then
753 Inc(LoopCount
); // Netscape extension is really "repeats" not "loops"
754 FMetadata
.SetMetaItem(SMetaAnimationLoops
, LoopCount
);
756 GIFAppBufferExtension
:
758 Dec(BlockSize
, SizeOf(Word));
759 Seek(Handle
, SizeOf(Word), smFromCurrent
);
768 // Revert all bytes reading
769 Seek(Handle
, - SizeOf(AppRec
) - SizeOf(BlockSize
), smFromCurrent
);
775 Seek(Handle
, - BlockSize
- SizeOf(BlockSize
), smFromCurrent
);
779 else if ExtType
in [GIFCommentExtension
, GIFApplicationExtension
, GIFPlainText
] then
781 // Read block sizes and skip them
782 Read(Handle
, @BlockSize
, SizeOf(BlockSize
));
783 Seek(Handle
, BlockSize
, smFromCurrent
);
786 // Read ID of following block
787 BlockID
:= ReadBlockID
;
793 procedure CopyLZWData(Dest
: TStream
);
795 CodeSize
, BlockSize
: Byte;
797 Buff
: array[Byte] of Byte;
799 InputSize
:= ImagingIO
.GetInputSize(GetIO
, Handle
);
800 // Copy codesize to stream
801 GetIO
.Read(Handle
, @CodeSize
, 1);
802 Dest
.Write(CodeSize
, 1);
804 // Read and write data blocks, last is block term value of 0
805 GetIO
.Read(Handle
, @BlockSize
, 1);
806 Dest
.Write(BlockSize
, 1);
807 if BlockSize
> 0 then
809 GetIO
.Read(Handle
, @Buff
[0], BlockSize
);
810 Dest
.Write(Buff
[0], BlockSize
);
812 until (BlockSize
= 0) or (GetIO
.Tell(Handle
) >= InputSize
);
817 ImageDesc
: TImageDescriptor
;
819 I
, Idx
, LocalPalLength
: Integer;
820 LocalPal
: TPalette32Size256
;
821 LZWStream
: TMemoryStream
;
823 procedure RemoveBadFrame
;
825 FreeImage(Images
[Idx
]);
826 SetLength(Images
, Length(Images
) - 1);
830 Idx
:= Length(Images
);
831 SetLength(Images
, Idx
+ 1);
832 SetLength(FrameInfos
, Idx
+ 1);
833 FillChar(LocalPal
, SizeOf(LocalPal
), 0);
837 // Read and parse image descriptor
838 Read(Handle
, @ImageDesc
, SizeOf(ImageDesc
));
839 FrameInfos
[Idx
].HasLocalPal
:= (ImageDesc
.PackedFields
and GIFLocalColorTable
) = GIFLocalColorTable
;
840 Interlaced
:= (ImageDesc
.PackedFields
and GIFInterlaced
) = GIFInterlaced
;
841 LocalPalLength
:= ImageDesc
.PackedFields
and GIFColorTableSize
;
842 LocalPalLength
:= 1 shl (LocalPalLength
+ 1); // Total pal length is 2^(n+1)
844 // From Mozilla source
845 if (ImageDesc
.Width
= 0) or (ImageDesc
.Width
> Header
.ScreenWidth
) then
846 ImageDesc
.Width
:= Header
.ScreenWidth
;
847 if (ImageDesc
.Height
= 0) or (ImageDesc
.Height
> Header
.ScreenHeight
) then
848 ImageDesc
.Height
:= Header
.ScreenHeight
;
850 FrameInfos
[Idx
].Left
:= ImageDesc
.Left
;
851 FrameInfos
[Idx
].Top
:= ImageDesc
.Top
;
852 FrameInfos
[Idx
].Width
:= ImageDesc
.Width
;
853 FrameInfos
[Idx
].Height
:= ImageDesc
.Height
;
854 FrameInfos
[Idx
].BackIndex
:= Header
.BackgroundColorIndex
;
856 // Create new image for this frame which would be later pasted onto logical screen
857 NewImage(ImageDesc
.Width
, ImageDesc
.Height
, ifIndex8
, Images
[Idx
]);
859 // Load local palette if there is any
860 if FrameInfos
[Idx
].HasLocalPal
then
861 for I
:= 0 to LocalPalLength
- 1 do
863 LocalPal
[I
].A
:= 255;
864 Read(Handle
, @LocalPal
[I
].R
, SizeOf(LocalPal
[I
].R
));
865 Read(Handle
, @LocalPal
[I
].G
, SizeOf(LocalPal
[I
].G
));
866 Read(Handle
, @LocalPal
[I
].B
, SizeOf(LocalPal
[I
].B
));
869 // Use local pal if present or global pal if present or create
870 // default pal if neither of them is present
871 if FrameInfos
[Idx
].HasLocalPal
then
872 Move(LocalPal
, Images
[Idx
].Palette
^, SizeOf(LocalPal
))
873 else if HasGlobalPal
then
874 Move(GlobalPal
, Images
[Idx
].Palette
^, SizeOf(GlobalPal
))
876 FillCustomPalette(Images
[Idx
].Palette
, GlobalPalLength
, 3, 3, 2);
878 if (ImageDesc
.Left
<= Header
.ScreenWidth
+ 1) and (ImageDesc
.Top
<= Header
.ScreenHeight
+ 1) then
880 // Resize the screen if needed to fit the frame
881 ScreenWidth
:= Max(ScreenWidth
, ImageDesc
.Width
+ ImageDesc
.Left
);
882 ScreenHeight
:= Max(ScreenHeight
, ImageDesc
.Height
+ ImageDesc
.Top
);
886 // Remove frame outside logical screen
891 // If Grahic Control Extension is present make use of it
892 if HasGraphicExt
then
894 FrameInfos
[Idx
].HasTransparency
:= (GraphicExt
.PackedFields
and GIFTransparent
) = GIFTransparent
;
895 FrameInfos
[Idx
].Disposal
:= TDisposalMethod((GraphicExt
.PackedFields
and GIFDisposalMethod
) shr 2);
896 if FrameInfos
[Idx
].HasTransparency
then
898 FrameInfos
[Idx
].TransIndex
:= GraphicExt
.TransparentColorIndex
;
899 Images
[Idx
].Palette
[FrameInfos
[Idx
].TransIndex
].A
:= 0;
901 FMetadata
.SetMetaItem(SMetaFrameDelay
, Integer(GraphicExt
.DelayTime
* 10), Idx
);
904 FrameInfos
[Idx
].HasTransparency
:= False;
906 LZWStream
:= TMemoryStream
.Create
;
909 // Copy LZW data to temp stream, needed for correct decompression
910 CopyLZWData(LZWStream
);
911 LZWStream
.Position
:= 0;
912 // Data decompression finally
913 LZWDecompress(LZWStream
, Handle
, ImageDesc
.Width
, ImageDesc
.Height
, Interlaced
, Images
[Idx
].Bits
);
924 procedure CopyFrameTransparent32(const Image
, Frame
: TImageData
; Left
, Top
: Integer);
932 // Copy all pixels from frame to log screen but ignore the transparent ones
933 for Y
:= 0 to Frame
.Height
- 1 do
935 Dst
:= @PColor32RecArray(Image
.Bits
)[(Top
+ Y
) * Image
.Width
+ Left
];
936 for X
:= 0 to Frame
.Width
- 1 do
938 if (Frame
.Palette
[Src
^].A
<> 0) then
939 Dst
^ := Frame
.Palette
[Src
^].Color
;
946 procedure AnimateFrame(Index
: Integer; var AnimFrame
: TImageData
);
948 I
, First
, Last
: Integer;
952 // We may need to use raw frame 0 to n to correctly animate n-th frame
954 First
:= Max(0, Last
);
955 // See if we can use last animate frame as a basis for this one
956 // (so we don't have to use previous raw frames).
957 UseCache
:= TestImage(CachedFrame
) and (CachedIndex
= Index
- 1) and (CachedIndex
>= 0) and
958 (FrameInfos
[CachedIndex
].Disposal
<> dmRestorePrevious
);
960 // Reuse or release cache
962 CloneImage(CachedFrame
, AnimFrame
)
964 FreeImage(CachedFrame
);
966 // Default color for clearing of the screen
967 BGColor
:= Images
[Index
].Palette
[FrameInfos
[Index
].BackIndex
].Color
;
969 // Now prepare logical screen for drawing of raw frame at Index.
970 // We may need to use all previous raw frames to get the screen
971 // to proper state (according to their disposal methods).
975 if FrameInfos
[Index
].HasTransparency
then
976 BGColor
:= Images
[Index
].Palette
[FrameInfos
[Index
].TransIndex
].Color
;
977 // Clear whole screen
978 FillMemoryLongWord(AnimFrame
.Bits
, AnimFrame
.Size
, BGColor
);
980 // Try to maximize First so we don't have to use all 0 to n raw frames
983 if (ScreenWidth
= Images
[First
].Width
) and (ScreenHeight
= Images
[First
].Height
) then
985 if (FrameInfos
[First
].Disposal
= dmRestoreBackground
) and (First
< Last
) then
991 for I
:= First
to Last
- 1 do
993 case FrameInfos
[I
].Disposal
of
994 dmNoRemoval
, dmLeave
:
996 // Copy previous raw frame onto screen
997 CopyFrameTransparent32(AnimFrame
, Images
[I
], FrameInfos
[I
].Left
, FrameInfos
[I
].Top
);
1002 // Restore background color
1003 FillRect(AnimFrame
, FrameInfos
[I
].Left
, FrameInfos
[I
].Top
,
1004 FrameInfos
[I
].Width
, FrameInfos
[I
].Height
, @BGColor
);
1006 dmRestorePrevious
: ; // Do nothing - previous state is already on screen
1010 else if FrameInfos
[CachedIndex
].Disposal
= dmRestoreBackground
then
1012 // We have our cached result but also need to restore
1013 // background in a place of cached frame
1014 if FrameInfos
[CachedIndex
].HasTransparency
then
1015 BGColor
:= Images
[CachedIndex
].Palette
[FrameInfos
[CachedIndex
].TransIndex
].Color
;
1016 FillRect(AnimFrame
, FrameInfos
[CachedIndex
].Left
, FrameInfos
[CachedIndex
].Top
,
1017 FrameInfos
[CachedIndex
].Width
, FrameInfos
[CachedIndex
].Height
, @BGColor
);
1020 // Copy current raw frame to prepared screen
1021 CopyFrameTransparent32(AnimFrame
, Images
[Index
], FrameInfos
[Index
].Left
, FrameInfos
[Index
].Top
);
1023 // Cache animated result
1024 CloneImage(AnimFrame
, CachedFrame
);
1025 CachedIndex
:= Index
;
1031 SetLength(Images
, 0);
1032 FillChar(GlobalPal
, SizeOf(GlobalPal
), 0);
1037 Read(Handle
, @Header
, SizeOf(Header
));
1038 ScreenWidth
:= Header
.ScreenWidth
;
1039 ScreenHeight
:= Header
.ScreenHeight
;
1040 HasGlobalPal
:= Header
.PackedFields
and GIFGlobalColorTable
= GIFGlobalColorTable
; // Bit 7
1041 GlobalPalLength
:= Header
.PackedFields
and GIFColorTableSize
; // Bits 0-2
1042 GlobalPalLength
:= 1 shl (GlobalPalLength
+ 1); // Total pal length is 2^(n+1)
1044 // Read global palette from file if present
1045 if HasGlobalPal
then
1047 for I
:= 0 to GlobalPalLength
- 1 do
1049 GlobalPal
[I
].A
:= 255;
1050 Read(Handle
, @GlobalPal
[I
].R
, SizeOf(GlobalPal
[I
].R
));
1051 Read(Handle
, @GlobalPal
[I
].G
, SizeOf(GlobalPal
[I
].G
));
1052 Read(Handle
, @GlobalPal
[I
].B
, SizeOf(GlobalPal
[I
].B
));
1056 // Read ID of the first block
1057 BlockID
:= ReadBlockID
;
1059 // Now read all data blocks in the file until file trailer is reached
1060 while BlockID
<> GIFTrailer
do
1062 // Read blocks until we find the one of known type
1063 while not (BlockID
in [GIFTrailer
, GIFExtensionIntroducer
, GIFImageDescriptor
]) do
1064 BlockID
:= ReadBlockID
;
1065 // Read supported and skip unsupported extensions
1067 // If image frame is found read it
1068 if BlockID
= GIFImageDescriptor
then
1070 // Read next block's ID
1071 BlockID
:= ReadBlockID
;
1072 // If block ID is unknown set it to end-of-GIF marker
1073 if not (BlockID
in [GIFExtensionIntroducer
, GIFTrailer
, GIFImageDescriptor
]) then
1074 BlockID
:= GIFTrailer
;
1077 if FLoadAnimated
then
1079 // Aniated frames will be stored in AnimFrames
1080 SetLength(AnimFrames
, Length(Images
));
1081 InitImage(CachedFrame
);
1084 for I
:= 0 to High(Images
) do
1086 // Create new logical screen
1087 NewImage(ScreenWidth
, ScreenHeight
, ifA8R8G8B8
, AnimFrames
[I
]);
1088 // Animate frames to current log screen
1089 AnimateFrame(I
, AnimFrames
[I
]);
1092 // Now release raw 8bit frames and put animated 32bit ones
1094 FreeImage(CachedFrame
);
1095 for I
:= 0 to High(AnimFrames
) do
1097 FreeImage(Images
[I
]);
1098 Images
[I
] := AnimFrames
[I
];
1106 function TGIFFileFormat
.SaveData(Handle
: TImagingHandle
;
1107 const Images
: TDynImageDataArray
; Index
: Integer): Boolean;
1110 ImageDesc
: TImageDescriptor
;
1111 ImageToSave
: TImageData
;
1112 MustBeFreed
: Boolean;
1114 GraphicExt
: TGraphicControlExtension
;
1116 procedure FindMaxDimensions(var MaxWidth
, MaxHeight
: Word);
1120 MaxWidth
:= Images
[FFirstIdx
].Width
;
1121 MaxHeight
:= Images
[FFirstIdx
].Height
;
1123 for I
:= FFirstIdx
+ 1 to FLastIdx
do
1125 MaxWidth
:= Iff(Images
[I
].Width
> MaxWidth
, Images
[I
].Width
, MaxWidth
);
1126 MaxHeight
:= Iff(Images
[I
].Height
> MaxWidth
, Images
[I
].Height
, MaxHeight
);
1130 procedure SetFrameDelay(Idx
: Integer; var Ext
: TGraphicControlExtension
);
1132 if FMetadata
.HasMetaItemForSaving(SMetaFrameDelay
, Idx
) then
1133 Ext
.DelayTime
:= FMetadata
.MetaItemsForSavingMulti
[SMetaFrameDelay
, Idx
] div 10
1135 Ext
.DelayTime
:= GIFDefaultDelay
;
1138 procedure SaveGlobalMetadata
;
1140 AppExt
: TGIFApplicationRec
;
1141 BlockSize
, LoopExtId
: Byte;
1144 if FMetadata
.HasMetaItemForSaving(SMetaAnimationLoops
) then
1147 FillChar(AppExt
, SizeOf(AppExt
), 0);
1148 AppExt
.Identifier
:= 'NETSCAPE';
1149 AppExt
.Authentication
:= '2.0';
1150 Repeats
:= FMetadata
.MetaItemsForSaving
[SMetaAnimationLoops
];
1153 LoopExtId
:= GIFAppLoopExtension
;
1155 Write(Handle
, @GIFExtensionIntroducer
, SizeOf(GIFExtensionIntroducer
));
1156 Write(Handle
, @GIFApplicationExtension
, SizeOf(GIFApplicationExtension
));
1158 Write(Handle
, @BlockSize
, SizeOf(BlockSize
));
1159 Write(Handle
, @AppExt
, SizeOf(AppExt
));
1161 Write(Handle
, @BlockSize
, SizeOf(BlockSize
));
1162 Write(Handle
, @LoopExtId
, SizeOf(LoopExtId
));
1163 Write(Handle
, @Repeats
, SizeOf(Repeats
));
1164 Write(Handle
, @GIFBlockTerminator
, SizeOf(GIFBlockTerminator
));
1169 // Fill header with data, select size of largest image in array as
1170 // logical screen size
1171 FillChar(Header
, Sizeof(Header
), 0);
1172 Header
.Signature
:= GIFSignature
;
1173 Header
.Version
:= GIFVersions
[gv89
];
1174 FindMaxDimensions(Header
.ScreenWidth
, Header
.ScreenHeight
);
1175 Header
.PackedFields
:= GIFColorResolution
; // Color resolution is 256
1176 GetIO
.Write(Handle
, @Header
, SizeOf(Header
));
1178 // Prepare default GC extension with delay
1179 FillChar(GraphicExt
, Sizeof(GraphicExt
), 0);
1180 GraphicExt
.DelayTime
:= GIFDefaultDelay
;
1181 GraphicExt
.BlockSize
:= 4;
1185 for I
:= FFirstIdx
to FLastIdx
do
1187 if MakeCompatible(Images
[I
], ImageToSave
, MustBeFreed
) then
1188 with GetIO
, ImageToSave
do
1190 // Write Graphic Control Extension with default delay
1191 Write(Handle
, @GIFExtensionIntroducer
, SizeOf(GIFExtensionIntroducer
));
1192 Write(Handle
, @GIFGraphicControlExtension
, SizeOf(GIFGraphicControlExtension
));
1193 SetFrameDelay(I
, GraphicExt
);
1194 Write(Handle
, @GraphicExt
, SizeOf(GraphicExt
));
1195 // Write frame marker and fill and write image descriptor for this frame
1196 Write(Handle
, @GIFImageDescriptor
, SizeOf(GIFImageDescriptor
));
1197 FillChar(ImageDesc
, Sizeof(ImageDesc
), 0);
1198 ImageDesc
.Width
:= Width
;
1199 ImageDesc
.Height
:= Height
;
1200 ImageDesc
.PackedFields
:= GIFLocalColorTable
or GIFColorTableSize
; // Use lccal color table with 256 entries
1201 Write(Handle
, @ImageDesc
, SizeOf(ImageDesc
));
1203 // Write local color table for each frame
1204 for J
:= 0 to 255 do
1206 Write(Handle
, @Palette
[J
].R
, SizeOf(Palette
[J
].R
));
1207 Write(Handle
, @Palette
[J
].G
, SizeOf(Palette
[J
].G
));
1208 Write(Handle
, @Palette
[J
].B
, SizeOf(Palette
[J
].B
));
1211 // Finally compress image data
1212 LZWCompress(GetIO
, Handle
, Width
, Height
, 8, False, Bits
);
1216 FreeImage(ImageToSave
);
1220 GetIO
.Write(Handle
, @GIFTrailer
, SizeOf(GIFTrailer
));
1224 procedure TGIFFileFormat
.ConvertToSupported(var Image
: TImageData
;
1225 const Info
: TImageFormatInfo
);
1227 ConvertImage(Image
, ifIndex8
);
1230 function TGIFFileFormat
.TestFormat(Handle
: TImagingHandle
): Boolean;
1236 if Handle
<> nil then
1238 ReadCount
:= GetIO
.Read(Handle
, @Header
, SizeOf(Header
));
1239 GetIO
.Seek(Handle
, -ReadCount
, smFromCurrent
);
1240 Result
:= (ReadCount
>= SizeOf(Header
)) and
1241 (Header
.Signature
= GIFSignature
) and
1242 ((Header
.Version
= GIFVersions
[gv87
]) or (Header
.Version
= GIFVersions
[gv89
]));
1247 RegisterImageFileFormat(TGIFFileFormat
);
1252 -- TODOS ----------------------------------------------------
1255 -- 0.77 Changes/Bug Fixes -----------------------------------
1256 - Fixed crash when resaving GIF with animation metadata.
1257 - Writes frame delays of GIF animations from metadata.
1258 - Reads and writes looping of GIF animations stored into/from metadata.
1260 -- 0.26.5 Changes/Bug Fixes ---------------------------------
1261 - Reads frame delays from GIF animations into metadata.
1263 -- 0.26.3 Changes/Bug Fixes ---------------------------------
1264 - Fixed bug - loading of GIF with NETSCAPE app extensions
1265 failed with Delphi 2009.
1267 -- 0.26.1 Changes/Bug Fixes ---------------------------------
1268 - GIF loading and animation mostly rewritten, based on
1269 modification by Sergey Galezdinov (ExtraGIF in Extras/Contrib).
1271 -- 0.25.0 Changes/Bug Fixes ---------------------------------
1272 - Fixed loading of some rare GIFs, problems with LZW
1275 -- 0.24.3 Changes/Bug Fixes ---------------------------------
1276 - Better solution to transparency for some GIFs. Background not
1277 transparent by default.
1279 -- 0.24.1 Changes/Bug Fixes ---------------------------------
1280 - Made backround color transparent by default (alpha = 0).
1282 -- 0.23 Changes/Bug Fixes -----------------------------------
1283 - Fixed other loading bugs (local pal size, transparency).
1285 - Fixed bug when loading multiframe GIFs and implemented few animation
1286 features (disposal methods, ...).
1287 - Loading of GIFs working.
1288 - Unit created with initial stuff!