DEADSOFTWARE

added Vampyre Imaging Library; now textures can be in various formats, including...
[d2df-sdl.git] / src / lib / vampimg / ImagingGif.pas
1 {
2 $Id: ImagingGif.pas 157 2009-02-15 14:24:58Z galfar $
3 Vampyre Imaging Library
4 by Marek Mauder
5 http://imaginglib.sourceforge.net
7 The contents of this file are used with permission, subject to the Mozilla
8 Public License Version 1.1 (the "License"); you may not use this file except
9 in compliance with the License. You may obtain a copy of the License at
10 http://www.mozilla.org/MPL/MPL-1.1.html
12 Software distributed under the License is distributed on an "AS IS" basis,
13 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14 the specific language governing rights and limitations under the License.
16 Alternatively, the contents of this file may be used under the terms of the
17 GNU Lesser General Public License (the "LGPL License"), in which case the
18 provisions of the LGPL License are applicable instead of those above.
19 If you wish to allow use of your version of this file only under the terms
20 of the LGPL License and not to allow others to use your version of this file
21 under the MPL, indicate your decision by deleting the provisions above and
22 replace them with the notice and other provisions required by the LGPL
23 License. If you do not delete the provisions above, a recipient may use
24 your version of this file under either the MPL or the LGPL License.
26 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
27 }
29 { This unit contains image format loader/saver for GIF images.}
30 unit ImagingGif;
32 {$I ImagingOptions.inc}
34 interface
36 uses
37 SysUtils, Classes, Imaging, ImagingTypes, ImagingIO, ImagingUtility;
39 type
40 { GIF (Graphics Interchange Format) loader/saver class. GIF was
41 (and is still used) popular format for storing images supporting
42 multiple images per file and single color transparency.
43 Pixel format is 8 bit indexed where each image frame can have
44 its own color palette. GIF uses lossless LZW compression
45 (patent expired few years ago).
46 Imaging can load and save all GIFs with all frames and supports
47 transparency. Imaging can load just raw ifIndex8 frames or
48 also animate them in ifA8R8G8B8 format. See ImagingGIFLoadAnimated option.}
49 TGIFFileFormat = class(TImageFileFormat)
50 private
51 FLoadAnimated: LongBool;
52 function InterlaceStep(Y, Height: Integer; var Pass: Integer): Integer;
53 procedure LZWDecompress(Stream: TStream; Handle: TImagingHandle;
54 Width, Height: Integer; Interlaced: Boolean; Data: Pointer);
55 procedure LZWCompress(const IO: TIOFunctions; Handle: TImagingHandle;
56 Width, Height, BitCount: Integer; Interlaced: Boolean; Data: Pointer);
57 protected
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;
64 public
65 constructor Create; override;
66 function TestFormat(Handle: TImagingHandle): Boolean; override;
67 published
68 property LoadAnimated: LongBool read FLoadAnimated write FLoadAnimated;
69 end;
71 implementation
73 const
74 SGIFFormatName = 'Graphics Interchange Format';
75 SGIFMasks = '*.gif';
76 GIFSupportedFormats: TImageFormats = [ifIndex8];
77 GIFDefaultLoadAnimated = True;
79 type
80 TGIFVersion = (gv87, gv89);
81 TDisposalMethod = (dmNoRemoval, dmLeave, dmRestoreBackground,
82 dmRestorePrevious, dmReserved4, dmReserved5, dmReserved6, dmReserved7);
84 const
85 GIFSignature: TChar3 = 'GIF';
86 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;
96 GIFInterlaced = $40;
97 GIFLocalTableSorted = $20;
99 // Block identifiers
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;
111 GIFUserInput = $02;
112 GIFDisposalMethod = $1C;
114 type
115 TGIFHeader = packed record
116 // File header part
117 Signature: TChar3; // Header Signature (always "GIF")
118 Version: TChar3; // GIF format version("87a" or "89a")
119 // Logical Screen Descriptor part
120 ScreenWidth: Word; // Width of Display Screen in Pixels
121 ScreenHeight: Word; // Height of Display Screen in Pixels
122 PackedFields: Byte; // Screen and color map information
123 BackgroundColorIndex: Byte; // Background color index (in global color table)
124 AspectRatio: Byte; // Pixel aspect ratio, ratio = (AspectRatio + 15) / 64
125 end;
127 TImageDescriptor = packed record
128 //Separator: Byte; // leave that out since we always read one bye ahead
129 Left: Word; // X position of image with respect to logical screen
130 Top: Word; // Y position
131 Width: Word;
132 Height: Word;
133 PackedFields: Byte;
134 end;
136 const
137 // GIF extension labels
138 GIFExtTypeGraphic = $F9;
139 GIFExtTypePlainText = $01;
140 GIFExtTypeApplication = $FF;
141 GIFExtTypeComment = $FE;
143 type
144 TGraphicControlExtension = packed record
145 BlockSize: Byte;
146 PackedFields: Byte;
147 DelayTime: Word;
148 TransparentColorIndex: Byte;
149 Terminator: Byte;
150 end;
152 const
153 // Netscape sub block types
154 GIFAppLoopExtension = 1;
155 GIFAppBufferExtension = 2;
157 type
158 TGIFIdentifierCode = array[0..7] of AnsiChar;
159 TGIFAuthenticationCode = array[0..2] of AnsiChar;
160 TGIFApplicationRec = packed record
161 Identifier: TGIFIdentifierCode;
162 Authentication: TGIFAuthenticationCode;
163 end;
165 const
166 CodeTableSize = 4096;
167 HashTableSize = 17777;
169 type
170 TReadContext = record
171 Inx: Integer;
172 Size: Integer;
173 Buf: array [0..255 + 4] of Byte;
174 CodeSize: Integer;
175 ReadMask: Integer;
176 end;
177 PReadContext = ^TReadContext;
179 TWriteContext = record
180 Inx: Integer;
181 CodeSize: Integer;
182 Buf: array [0..255 + 4] of Byte;
183 end;
184 PWriteContext = ^TWriteContext;
186 TOutputContext = record
187 W: Integer;
188 H: Integer;
189 X: Integer;
190 Y: Integer;
191 BitsPerPixel: Integer;
192 Pass: Integer;
193 Interlace: Boolean;
194 LineIdent: Integer;
195 Data: Pointer;
196 CurrLineData: Pointer;
197 end;
199 TImageDict = record
200 Tail: Word;
201 Index: Word;
202 Col: Byte;
203 end;
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;
212 resourcestring
213 SGIFDecodingError = 'Error when decoding GIF LZW data';
216 TGIFFileFormat implementation
219 constructor TGIFFileFormat.Create;
220 begin
221 inherited Create;
222 FName := SGIFFormatName;
223 FCanLoad := True;
224 FCanSave := True;
225 FIsMultiImageFormat := True;
226 FSupportedFormats := GIFSupportedFormats;
227 FLoadAnimated := GIFDefaultLoadAnimated;
229 AddMasks(SGIFMasks);
230 RegisterOption(ImagingGIFLoadAnimated, @FLoadAnimated);
231 end;
233 function TGIFFileFormat.InterlaceStep(Y, Height: Integer; var Pass: Integer): Integer;
234 begin
235 Result := Y;
236 case Pass of
237 0, 1:
238 Inc(Result, 8);
239 2:
240 Inc(Result, 4);
241 3:
242 Inc(Result, 2);
243 end;
244 if Result >= Height then
245 begin
246 if Pass = 0 then
247 begin
248 Pass := 1;
249 Result := 4;
250 if Result < Height then
251 Exit;
252 end;
253 if Pass = 1 then
254 begin
255 Pass := 2;
256 Result := 2;
257 if Result < Height then
258 Exit;
259 end;
260 if Pass = 2 then
261 begin
262 Pass := 3;
263 Result := 1;
264 end;
265 end;
266 end;
268 { GIF LZW decompresion code is from JVCL JvGIF.pas unit.}
269 procedure TGIFFileFormat.LZWDecompress(Stream: TStream; Handle: TImagingHandle; Width, Height: Integer;
270 Interlaced: Boolean; Data: Pointer);
271 var
272 MinCodeSize: Byte;
273 MaxCode, BitMask, InitCodeSize: Integer;
274 ClearCode, EndingCode, FirstFreeCode, FreeCode: Word;
275 I, OutCount, Code: Integer;
276 CurCode, OldCode, InCode, FinalChar: Word;
277 Prefix, Suffix, OutCode: PIntCodeTable;
278 ReadCtxt: TReadContext;
279 OutCtxt: TOutputContext;
280 TableFull: Boolean;
282 function ReadCode(var Context: TReadContext): Integer;
283 var
284 RawCode: Integer;
285 ByteIndex: Integer;
286 Bytes: Byte;
287 BytesToLose: Integer;
288 begin
289 while (Context.Inx + Context.CodeSize > Context.Size) and
290 (Stream.Position < Stream.Size) do
291 begin
292 // Not enough bits in buffer - refill it - Not very efficient, but infrequently called
293 BytesToLose := Context.Inx shr 3;
294 // Note biggest Code Size is 12 bits. And this can at worst span 3 Bytes
295 Move(Context.Buf[Word(BytesToLose)], Context.Buf[0], 3);
296 Context.Inx := Context.Inx and 7;
297 Context.Size := Context.Size - (BytesToLose shl 3);
298 Stream.Read(Bytes, 1);
299 if Bytes > 0 then
300 Stream.Read(Context.Buf[Word(Context.Size shr 3)], Bytes);
301 Context.Size := Context.Size + (Bytes shl 3);
302 end;
303 ByteIndex := Context.Inx shr 3;
304 RawCode := Context.Buf[Word(ByteIndex)] +
305 (Word(Context.Buf[Word(ByteIndex + 1)]) shl 8);
306 if Context.CodeSize > 8 then
307 RawCode := RawCode + (LongInt(Context.Buf[ByteIndex + 2]) shl 16);
308 RawCode := RawCode shr (Context.Inx and 7);
309 Context.Inx := Context.Inx + Byte(Context.CodeSize);
310 Result := RawCode and Context.ReadMask;
311 end;
313 procedure Output(Value: Byte; var Context: TOutputContext);
314 var
315 P: PByte;
316 begin
317 if Context.Y >= Context.H then
318 Exit;
320 // Only ifIndex8 supported
321 P := @PByteArray(Context.CurrLineData)[Context.X];
322 P^ := Value;
324 {case Context.BitsPerPixel of
325 1:
326 begin
327 P := @PByteArray(Context.CurrLineData)[Context.X shr 3];
328 if (Context.X and $07) <> 0 then
329 P^ := P^ or Word(Value shl (7 - (Word(Context.X and 7))))
330 else
331 P^ := Byte(Value shl 7);
332 end;
333 4:
334 begin
335 P := @PByteArray(Context.CurrLineData)[Context.X shr 1];
336 if (Context.X and 1) <> 0 then
337 P^ := P^ or Value
338 else
339 P^ := Byte(Value shl 4);
340 end;
341 8:
342 begin
343 P := @PByteArray(Context.CurrLineData)[Context.X];
344 P^ := Value;
345 end;
346 end;}
347 Inc(Context.X);
349 if Context.X < Context.W then
350 Exit;
351 Context.X := 0;
352 if Context.Interlace then
353 Context.Y := InterlaceStep(Context.Y, Context.H, Context.Pass)
354 else
355 Inc(Context.Y);
357 Context.CurrLineData := @PByteArray(Context.Data)[Context.Y * Context.LineIdent];
358 end;
360 begin
361 OutCount := 0;
362 OldCode := 0;
363 FinalChar := 0;
364 TableFull := False;
365 GetMem(Prefix, SizeOf(TIntCodeTable));
366 GetMem(Suffix, SizeOf(TIntCodeTable));
367 GetMem(OutCode, SizeOf(TIntCodeTable) + SizeOf(Word));
368 try
369 Stream.Read(MinCodeSize, 1);
370 if (MinCodeSize < 2) or (MinCodeSize > 9) then
371 RaiseImaging(SGIFDecodingError, []);
372 // Initial read context
373 ReadCtxt.Inx := 0;
374 ReadCtxt.Size := 0;
375 ReadCtxt.CodeSize := MinCodeSize + 1;
376 ReadCtxt.ReadMask := (1 shl ReadCtxt.CodeSize) - 1;
377 // Initialise pixel-output context
378 OutCtxt.X := 0;
379 OutCtxt.Y := 0;
380 OutCtxt.Pass := 0;
381 OutCtxt.W := Width;
382 OutCtxt.H := Height;
383 OutCtxt.BitsPerPixel := MinCodeSize;
384 OutCtxt.Interlace := Interlaced;
385 OutCtxt.LineIdent := Width;
386 OutCtxt.Data := Data;
387 OutCtxt.CurrLineData := Data;
388 BitMask := (1 shl OutCtxt.BitsPerPixel) - 1;
389 // 2 ^ MinCodeSize accounts for all colours in file
390 ClearCode := 1 shl MinCodeSize;
391 EndingCode := ClearCode + 1;
392 FreeCode := ClearCode + 2;
393 FirstFreeCode := FreeCode;
394 // 2^ (MinCodeSize + 1) includes clear and eoi Code and space too
395 InitCodeSize := ReadCtxt.CodeSize;
396 MaxCode := 1 shl ReadCtxt.CodeSize;
397 Code := ReadCode(ReadCtxt);
398 while (Code <> EndingCode) and (Code <> $FFFF) and
399 (OutCtxt.Y < OutCtxt.H) do
400 begin
401 if Code = ClearCode then
402 begin
403 ReadCtxt.CodeSize := InitCodeSize;
404 MaxCode := 1 shl ReadCtxt.CodeSize;
405 ReadCtxt.ReadMask := MaxCode - 1;
406 FreeCode := FirstFreeCode;
407 Code := ReadCode(ReadCtxt);
408 CurCode := Code;
409 OldCode := Code;
410 if Code = $FFFF then
411 Break;
412 FinalChar := (CurCode and BitMask);
413 Output(Byte(FinalChar), OutCtxt);
414 TableFull := False;
415 end
416 else
417 begin
418 CurCode := Code;
419 InCode := Code;
420 if CurCode >= FreeCode then
421 begin
422 CurCode := OldCode;
423 OutCode^[OutCount] := FinalChar;
424 Inc(OutCount);
425 end;
426 while CurCode > BitMask do
427 begin
428 if OutCount > CodeTableSize then
429 RaiseImaging(SGIFDecodingError, []);
430 OutCode^[OutCount] := Suffix^[CurCode];
431 Inc(OutCount);
432 CurCode := Prefix^[CurCode];
433 end;
435 FinalChar := CurCode and BitMask;
436 OutCode^[OutCount] := FinalChar;
437 Inc(OutCount);
438 for I := OutCount - 1 downto 0 do
439 Output(Byte(OutCode^[I]), OutCtxt);
440 OutCount := 0;
441 // Update dictionary
442 if not TableFull then
443 begin
444 Prefix^[FreeCode] := OldCode;
445 Suffix^[FreeCode] := FinalChar;
446 // Advance to next free slot
447 Inc(FreeCode);
448 if FreeCode >= MaxCode then
449 begin
450 if ReadCtxt.CodeSize < 12 then
451 begin
452 Inc(ReadCtxt.CodeSize);
453 MaxCode := MaxCode shl 1;
454 ReadCtxt.ReadMask := (1 shl ReadCtxt.CodeSize) - 1;
455 end
456 else
457 TableFull := True;
458 end;
459 end;
460 OldCode := InCode;
461 end;
462 Code := ReadCode(ReadCtxt);
463 end;
464 if Code = $FFFF then
465 RaiseImaging(SGIFDecodingError, []);
466 finally
467 FreeMem(Prefix);
468 FreeMem(OutCode);
469 FreeMem(Suffix);
470 end;
471 end;
473 { GIF LZW compresion code is from JVCL JvGIF.pas unit.}
474 procedure TGIFFileFormat.LZWCompress(const IO: TIOFunctions; Handle: TImagingHandle; Width, Height, BitCount: Integer;
475 Interlaced: Boolean; Data: Pointer);
476 var
477 LineIdent: Integer;
478 MinCodeSize, Col: Byte;
479 InitCodeSize, X, Y: Integer;
480 Pass: Integer;
481 MaxCode: Integer; { 1 shl CodeSize }
482 ClearCode, EndingCode, LastCode, Tail: Integer;
483 I, HashValue: Integer;
484 LenString: Word;
485 Dict: PDictTable;
486 HashTable: TList;
487 PData: PByte;
488 WriteCtxt: TWriteContext;
490 function InitHash(P: Integer): Integer;
491 begin
492 Result := (P + 3) * 301;
493 end;
495 procedure WriteCode(Code: Integer; var Context: TWriteContext);
496 var
497 BufIndex: Integer;
498 Bytes: Byte;
499 begin
500 BufIndex := Context.Inx shr 3;
501 Code := Code shl (Context.Inx and 7);
502 Context.Buf[BufIndex] := Context.Buf[BufIndex] or Byte(Code);
503 Context.Buf[BufIndex + 1] := Byte(Code shr 8);
504 Context.Buf[BufIndex + 2] := Byte(Code shr 16);
505 Context.Inx := Context.Inx + Context.CodeSize;
506 if Context.Inx >= 255 * 8 then
507 begin
508 // Flush out full buffer
509 Bytes := 255;
510 IO.Write(Handle, @Bytes, 1);
511 IO.Write(Handle, @Context.Buf, Bytes);
512 Move(Context.Buf[255], Context.Buf[0], 2);
513 FillChar(Context.Buf[2], 255, 0);
514 Context.Inx := Context.Inx - (255 * 8);
515 end;
516 end;
518 procedure FlushCode(var Context: TWriteContext);
519 var
520 Bytes: Byte;
521 begin
522 Bytes := (Context.Inx + 7) shr 3;
523 if Bytes > 0 then
524 begin
525 IO.Write(Handle, @Bytes, 1);
526 IO.Write(Handle, @Context.Buf, Bytes);
527 end;
528 // Data block terminator - a block of zero Size
529 Bytes := 0;
530 IO.Write(Handle, @Bytes, 1);
531 end;
533 begin
534 LineIdent := Width;
535 Tail := 0;
536 HashValue := 0;
537 Col := 0;
538 HashTable := TList.Create;
539 GetMem(Dict, SizeOf(TDictTable));
540 try
541 for I := 0 to HashTableSize - 1 do
542 HashTable.Add(nil);
544 // Initialise encoder variables
545 InitCodeSize := BitCount + 1;
546 if InitCodeSize = 2 then
547 Inc(InitCodeSize);
548 MinCodeSize := InitCodeSize - 1;
549 IO.Write(Handle, @MinCodeSize, 1);
550 ClearCode := 1 shl MinCodeSize;
551 EndingCode := ClearCode + 1;
552 LastCode := EndingCode;
553 MaxCode := 1 shl InitCodeSize;
554 LenString := 0;
555 // Setup write context
556 WriteCtxt.Inx := 0;
557 WriteCtxt.CodeSize := InitCodeSize;
558 FillChar(WriteCtxt.Buf, SizeOf(WriteCtxt.Buf), 0);
559 WriteCode(ClearCode, WriteCtxt);
560 Y := 0;
561 Pass := 0;
563 while Y < Height do
564 begin
565 PData := @PByteArray(Data)[Y * LineIdent];
566 for X := 0 to Width - 1 do
567 begin
568 // Only ifIndex8 support
569 case BitCount of
570 8:
571 begin
572 Col := PData^;
573 PData := @PByteArray(PData)[1];
574 end;
575 {4:
576 begin
577 if X and 1 <> 0 then
578 begin
579 Col := PData^ and $0F;
580 PData := @PByteArray(PData)[1];
581 end
582 else
583 Col := PData^ shr 4;
584 end;
585 1:
586 begin
587 if X and 7 = 7 then
588 begin
589 Col := PData^ and 1;
590 PData := @PByteArray(PData)[1];
591 end
592 else
593 Col := (PData^ shr (7 - (X and $07))) and $01;
594 end;}
595 end;
596 Inc(LenString);
597 if LenString = 1 then
598 begin
599 Tail := Col;
600 HashValue := InitHash(Col);
601 end
602 else
603 begin
604 HashValue := HashValue * (Col + LenString + 4);
605 I := HashValue mod HashTableSize;
606 HashValue := HashValue mod HashTableSize;
607 while (HashTable[I] <> nil) and
608 ((PImageDict(HashTable[I])^.Tail <> Tail) or
609 (PImageDict(HashTable[I])^.Col <> Col)) do
610 begin
611 Inc(I);
612 if I >= HashTableSize then
613 I := 0;
614 end;
615 if HashTable[I] <> nil then // Found in the strings table
616 Tail := PImageDict(HashTable[I])^.Index
617 else
618 begin
619 // Not found
620 WriteCode(Tail, WriteCtxt);
621 Inc(LastCode);
622 HashTable[I] := @Dict^[LastCode];
623 PImageDict(HashTable[I])^.Index := LastCode;
624 PImageDict(HashTable[I])^.Tail := Tail;
625 PImageDict(HashTable[I])^.Col := Col;
626 Tail := Col;
627 HashValue := InitHash(Col);
628 LenString := 1;
629 if LastCode >= MaxCode then
630 begin
631 // Next Code will be written longer
632 MaxCode := MaxCode shl 1;
633 Inc(WriteCtxt.CodeSize);
634 end
635 else
636 if LastCode >= CodeTableSize - 2 then
637 begin
638 // Reset tables
639 WriteCode(Tail, WriteCtxt);
640 WriteCode(ClearCode, WriteCtxt);
641 LenString := 0;
642 LastCode := EndingCode;
643 WriteCtxt.CodeSize := InitCodeSize;
644 MaxCode := 1 shl InitCodeSize;
645 for I := 0 to HashTableSize - 1 do
646 HashTable[I] := nil;
647 end;
648 end;
649 end;
650 end;
651 if Interlaced then
652 Y := InterlaceStep(Y, Height, Pass)
653 else
654 Inc(Y);
655 end;
656 WriteCode(Tail, WriteCtxt);
657 WriteCode(EndingCode, WriteCtxt);
658 FlushCode(WriteCtxt);
659 finally
660 HashTable.Free;
661 FreeMem(Dict);
662 end;
663 end;
665 function TGIFFileFormat.LoadData(Handle: TImagingHandle;
666 var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
667 type
668 TFrameInfo = record
669 Left, Top: Integer;
670 Width, Height: Integer;
671 Disposal: TDisposalMethod;
672 HasTransparency: Boolean;
673 HasLocalPal: Boolean;
674 TransIndex: Integer;
675 BackIndex: Integer;
676 end;
677 var
678 Header: TGIFHeader;
679 HasGlobalPal: Boolean;
680 GlobalPalLength: Integer;
681 GlobalPal: TPalette32Size256;
682 ScreenWidth, ScreenHeight, I, CachedIndex: Integer;
683 BlockID: Byte;
684 HasGraphicExt: Boolean;
685 GraphicExt: TGraphicControlExtension;
686 FrameInfos: array of TFrameInfo;
687 AppRead: Boolean;
688 CachedFrame: TImageData;
689 AnimFrames: TDynImageDataArray;
691 function ReadBlockID: Byte;
692 begin
693 Result := GIFTrailer;
694 if GetIO.Read(Handle, @Result, SizeOf(Result)) < SizeOf(Result) then
695 Result := GIFTrailer;
696 end;
698 procedure ReadExtensions;
699 var
700 BlockSize, BlockType, ExtType: Byte;
701 AppRec: TGIFApplicationRec;
702 LoopCount: SmallInt;
704 procedure SkipBytes;
705 begin
706 with GetIO do
707 repeat
708 // Read block sizes and skip them
709 Read(Handle, @BlockSize, SizeOf(BlockSize));
710 Seek(Handle, BlockSize, smFromCurrent);
711 until BlockSize = 0;
712 end;
714 begin
715 HasGraphicExt := False;
716 AppRead := False;
718 // Read extensions until image descriptor is found. Only graphic extension
719 // is stored now (for transparency), others are skipped.
720 while BlockID = GIFExtensionIntroducer do
721 with GetIO do
722 begin
723 Read(Handle, @ExtType, SizeOf(ExtType));
725 while ExtType in [GIFGraphicControlExtension, GIFCommentExtension, GIFApplicationExtension, GIFPlainText] do
726 begin
727 if ExtType = GIFGraphicControlExtension then
728 begin
729 HasGraphicExt := True;
730 Read(Handle, @GraphicExt, SizeOf(GraphicExt));
731 end
732 else if (ExtType = GIFApplicationExtension) and not AppRead then
733 begin
734 Read(Handle, @BlockSize, SizeOf(BlockSize));
735 if BlockSize >= SizeOf(AppRec) then
736 begin
737 Read(Handle, @AppRec, SizeOf(AppRec));
738 if (AppRec.Identifier = 'NETSCAPE') and (AppRec.Authentication = '2.0') then
739 begin
740 Read(Handle, @BlockSize, SizeOf(BlockSize));
741 while BlockSize <> 0 do
742 begin
743 BlockType := ReadBlockID;
744 Dec(BlockSize);
746 case BlockType of
747 GIFAppLoopExtension:
748 if (BlockSize >= SizeOf(LoopCount)) then
749 begin
750 // Read loop count
751 Read(Handle, @LoopCount, SizeOf(LoopCount));
752 Dec(BlockSize, SizeOf(LoopCount));
753 end;
754 GIFAppBufferExtension:
755 begin
756 Dec(BlockSize, SizeOf(Word));
757 Seek(Handle, SizeOf(Word), smFromCurrent);
758 end;
759 end;
760 end;
761 SkipBytes;
762 AppRead := True;
763 end
764 else
765 begin
766 // Revert all bytes reading
767 Seek(Handle, - SizeOf(AppRec) - SizeOf(BlockSize), smFromCurrent);
768 SkipBytes;
769 end;
770 end
771 else
772 begin
773 Seek(Handle, - BlockSize - SizeOf(BlockSize), smFromCurrent);
774 SkipBytes;
775 end;
776 end
777 else if ExtType in [GIFCommentExtension, GIFApplicationExtension, GIFPlainText] then
778 repeat
779 // Read block sizes and skip them
780 Read(Handle, @BlockSize, SizeOf(BlockSize));
781 Seek(Handle, BlockSize, smFromCurrent);
782 until BlockSize = 0;
784 // Read ID of following block
785 BlockID := ReadBlockID;
786 ExtType := BlockID;
787 end
788 end;
789 end;
791 procedure CopyLZWData(Dest: TStream);
792 var
793 CodeSize, BlockSize: Byte;
794 InputSize: Integer;
795 Buff: array[Byte] of Byte;
796 begin
797 InputSize := ImagingIO.GetInputSize(GetIO, Handle);
798 // Copy codesize to stream
799 GetIO.Read(Handle, @CodeSize, 1);
800 Dest.Write(CodeSize, 1);
801 repeat
802 // Read and write data blocks, last is block term value of 0
803 GetIO.Read(Handle, @BlockSize, 1);
804 Dest.Write(BlockSize, 1);
805 if BlockSize > 0 then
806 begin
807 GetIO.Read(Handle, @Buff[0], BlockSize);
808 Dest.Write(Buff[0], BlockSize);
809 end;
810 until (BlockSize = 0) or (GetIO.Tell(Handle) >= InputSize);
811 end;
813 procedure ReadFrame;
814 var
815 ImageDesc: TImageDescriptor;
816 Interlaced: Boolean;
817 I, Idx, LocalPalLength: Integer;
818 LocalPal: TPalette32Size256;
819 LZWStream: TMemoryStream;
821 procedure RemoveBadFrame;
822 begin
823 FreeImage(Images[Idx]);
824 SetLength(Images, Length(Images) - 1);
825 end;
827 begin
828 Idx := Length(Images);
829 SetLength(Images, Idx + 1);
830 SetLength(FrameInfos, Idx + 1);
831 FillChar(LocalPal, SizeOf(LocalPal), 0);
833 with GetIO do
834 begin
835 // Read and parse image descriptor
836 Read(Handle, @ImageDesc, SizeOf(ImageDesc));
837 FrameInfos[Idx].HasLocalPal := (ImageDesc.PackedFields and GIFLocalColorTable) = GIFLocalColorTable;
838 Interlaced := (ImageDesc.PackedFields and GIFInterlaced) = GIFInterlaced;
839 LocalPalLength := ImageDesc.PackedFields and GIFColorTableSize;
840 LocalPalLength := 1 shl (LocalPalLength + 1); // Total pal length is 2^(n+1)
842 // From Mozilla source
843 if (ImageDesc.Width = 0) or (ImageDesc.Width > Header.ScreenWidth) then
844 ImageDesc.Width := Header.ScreenWidth;
845 if (ImageDesc.Height = 0) or (ImageDesc.Height > Header.ScreenHeight) then
846 ImageDesc.Height := Header.ScreenHeight;
848 FrameInfos[Idx].Left := ImageDesc.Left;
849 FrameInfos[Idx].Top := ImageDesc.Top;
850 FrameInfos[Idx].Width := ImageDesc.Width;
851 FrameInfos[Idx].Height := ImageDesc.Height;
852 FrameInfos[Idx].BackIndex := Header.BackgroundColorIndex;
854 // Create new image for this frame which would be later pasted onto logical screen
855 NewImage(ImageDesc.Width, ImageDesc.Height, ifIndex8, Images[Idx]);
857 // Load local palette if there is any
858 if FrameInfos[Idx].HasLocalPal then
859 for I := 0 to LocalPalLength - 1 do
860 begin
861 LocalPal[I].A := 255;
862 Read(Handle, @LocalPal[I].R, SizeOf(LocalPal[I].R));
863 Read(Handle, @LocalPal[I].G, SizeOf(LocalPal[I].G));
864 Read(Handle, @LocalPal[I].B, SizeOf(LocalPal[I].B));
865 end;
867 // Use local pal if present or global pal if present or create
868 // default pal if neither of them is present
869 if FrameInfos[Idx].HasLocalPal then
870 Move(LocalPal, Images[Idx].Palette^, SizeOf(LocalPal))
871 else if HasGlobalPal then
872 Move(GlobalPal, Images[Idx].Palette^, SizeOf(GlobalPal))
873 else
874 FillCustomPalette(Images[Idx].Palette, GlobalPalLength, 3, 3, 2);
876 if (ImageDesc.Left <= Header.ScreenWidth + 1) and (ImageDesc.Top <= Header.ScreenHeight + 1) then
877 begin
878 // Resize the screen if needed to fit the frame
879 ScreenWidth := Max(ScreenWidth, ImageDesc.Width + ImageDesc.Left);
880 ScreenHeight := Max(ScreenHeight, ImageDesc.Height + ImageDesc.Top);
881 end
882 else
883 begin
884 // Remove frame outside logical screen
885 RemoveBadFrame;
886 Exit;
887 end;
889 // If Grahic Control Extension is present make use of it
890 if HasGraphicExt then
891 begin
892 FrameInfos[Idx].HasTransparency := (GraphicExt.PackedFields and GIFTransparent) = GIFTransparent;
893 FrameInfos[Idx].Disposal := TDisposalMethod((GraphicExt.PackedFields and GIFDisposalMethod) shr 2);
894 if FrameInfos[Idx].HasTransparency then
895 begin
896 FrameInfos[Idx].TransIndex := GraphicExt.TransparentColorIndex;
897 Images[Idx].Palette[FrameInfos[Idx].TransIndex].A := 0;
898 end;
899 end
900 else
901 FrameInfos[Idx].HasTransparency := False;
903 LZWStream := TMemoryStream.Create;
904 try
905 try
906 // Copy LZW data to temp stream, needed for correct decompression
907 CopyLZWData(LZWStream);
908 LZWStream.Position := 0;
909 // Data decompression finally
910 LZWDecompress(LZWStream, Handle, ImageDesc.Width, ImageDesc.Height, Interlaced, Images[Idx].Bits);
911 except
912 RemoveBadFrame;
913 Exit;
914 end;
915 finally
916 LZWStream.Free;
917 end;
918 end;
919 end;
921 procedure CopyFrameTransparent32(const Image, Frame: TImageData; Left, Top: Integer);
922 var
923 X, Y: Integer;
924 Src: PByte;
925 Dst: PColor32;
926 begin
927 Src := Frame.Bits;
929 // Copy all pixels from frame to log screen but ignore the transparent ones
930 for Y := 0 to Frame.Height - 1 do
931 begin
932 Dst := @PColor32RecArray(Image.Bits)[(Top + Y) * Image.Width + Left];
933 for X := 0 to Frame.Width - 1 do
934 begin
935 if (Frame.Palette[Src^].A <> 0) then
936 Dst^ := Frame.Palette[Src^].Color;
937 Inc(Src);
938 Inc(Dst);
939 end;
940 end;
941 end;
943 procedure AnimateFrame(Index: Integer; var AnimFrame: TImageData);
944 var
945 I, First, Last: Integer;
946 UseCache: Boolean;
947 BGColor: TColor32;
948 begin
949 // We may need to use raw frame 0 to n to correctly animate n-th frame
950 Last := Index;
951 First := Max(0, Last);
952 // See if we can use last animate frame as a basis for this one
953 // (so we don't have to use previous raw frames).
954 UseCache := TestImage(CachedFrame) and (CachedIndex = Index - 1) and (CachedIndex >= 0) and
955 (FrameInfos[CachedIndex].Disposal <> dmRestorePrevious);
957 // Reuse or release cache
958 if UseCache then
959 CloneImage(CachedFrame, AnimFrame)
960 else
961 FreeImage(CachedFrame);
963 // Default color for clearing of the screen
964 BGColor := Images[Index].Palette[FrameInfos[Index].BackIndex].Color;
966 // Now prepare logical screen for drawing of raw frame at Index.
967 // We may need to use all previous raw frames to get the screen
968 // to proper state (according to their disposal methods).
970 if not UseCache then
971 begin
972 if FrameInfos[Index].HasTransparency then
973 BGColor := Images[Index].Palette[FrameInfos[Index].TransIndex].Color;
974 // Clear whole screen
975 FillMemoryLongWord(AnimFrame.Bits, AnimFrame.Size, BGColor);
977 // Try to maximize First so we don't have to use all 0 to n raw frames
978 while First > 0 do
979 begin
980 if (ScreenWidth = Images[First].Width) and (ScreenHeight = Images[First].Height) then
981 begin
982 if (FrameInfos[First].Disposal = dmRestoreBackground) and (First < Last) then
983 Break;
984 end;
985 Dec(First);
986 end;
988 for I := First to Last - 1 do
989 begin
990 case FrameInfos[I].Disposal of
991 dmNoRemoval, dmLeave:
992 begin
993 // Copy previous raw frame onto screen
994 CopyFrameTransparent32(AnimFrame, Images[I], FrameInfos[I].Left, FrameInfos[I].Top);
995 end;
996 dmRestoreBackground:
997 if (I > First) then
998 begin
999 // Restore background color
1000 FillRect(AnimFrame, FrameInfos[I].Left, FrameInfos[I].Top,
1001 FrameInfos[I].Width, FrameInfos[I].Height, @BGColor);
1002 end;
1003 dmRestorePrevious: ; // Do nothing - previous state is already on screen
1004 end;
1005 end;
1006 end
1007 else if FrameInfos[CachedIndex].Disposal = dmRestoreBackground then
1008 begin
1009 // We have our cached result but also need to restore
1010 // background in a place of cached frame
1011 if FrameInfos[CachedIndex].HasTransparency then
1012 BGColor := Images[CachedIndex].Palette[FrameInfos[CachedIndex].TransIndex].Color;
1013 FillRect(AnimFrame, FrameInfos[CachedIndex].Left, FrameInfos[CachedIndex].Top,
1014 FrameInfos[CachedIndex].Width, FrameInfos[CachedIndex].Height, @BGColor);
1015 end;
1017 // Copy current raw frame to prepared screen
1018 CopyFrameTransparent32(AnimFrame, Images[Index], FrameInfos[Index].Left, FrameInfos[Index].Top);
1020 // Cache animated result
1021 CloneImage(AnimFrame, CachedFrame);
1022 CachedIndex := Index;
1023 end;
1025 begin
1026 AppRead := False;
1028 SetLength(Images, 0);
1029 FillChar(GlobalPal, SizeOf(GlobalPal), 0);
1031 with GetIO do
1032 begin
1033 // Read GIF header
1034 Read(Handle, @Header, SizeOf(Header));
1035 ScreenWidth := Header.ScreenWidth;
1036 ScreenHeight := Header.ScreenHeight;
1037 HasGlobalPal := Header.PackedFields and GIFGlobalColorTable = GIFGlobalColorTable; // Bit 7
1038 GlobalPalLength := Header.PackedFields and GIFColorTableSize; // Bits 0-2
1039 GlobalPalLength := 1 shl (GlobalPalLength + 1); // Total pal length is 2^(n+1)
1041 // Read global palette from file if present
1042 if HasGlobalPal then
1043 begin
1044 for I := 0 to GlobalPalLength - 1 do
1045 begin
1046 GlobalPal[I].A := 255;
1047 Read(Handle, @GlobalPal[I].R, SizeOf(GlobalPal[I].R));
1048 Read(Handle, @GlobalPal[I].G, SizeOf(GlobalPal[I].G));
1049 Read(Handle, @GlobalPal[I].B, SizeOf(GlobalPal[I].B));
1050 end;
1051 end;
1053 // Read ID of the first block
1054 BlockID := ReadBlockID;
1056 // Now read all data blocks in the file until file trailer is reached
1057 while BlockID <> GIFTrailer do
1058 begin
1059 // Read blocks until we find the one of known type
1060 while not (BlockID in [GIFTrailer, GIFExtensionIntroducer, GIFImageDescriptor]) do
1061 BlockID := ReadBlockID;
1062 // Read supported and skip unsupported extensions
1063 ReadExtensions;
1064 // If image frame is found read it
1065 if BlockID = GIFImageDescriptor then
1066 ReadFrame;
1067 // Read next block's ID
1068 BlockID := ReadBlockID;
1069 // If block ID is unknown set it to end-of-GIF marker
1070 if not (BlockID in [GIFExtensionIntroducer, GIFTrailer, GIFImageDescriptor]) then
1071 BlockID := GIFTrailer;
1072 end;
1074 if FLoadAnimated then
1075 begin
1076 // Aniated frames will be stored in AnimFrames
1077 SetLength(AnimFrames, Length(Images));
1078 InitImage(CachedFrame);
1079 CachedIndex := -1;
1081 for I := 0 to High(Images) do
1082 begin
1083 // Create new logical screen
1084 NewImage(ScreenWidth, ScreenHeight, ifA8R8G8B8, AnimFrames[I]);
1085 // Animate frames to current log screen
1086 AnimateFrame(I, AnimFrames[I]);
1087 end;
1089 // Now release raw 8bit frames and put animated 32bit ones
1090 // to output array
1091 FreeImage(CachedFrame);
1092 for I := 0 to High(AnimFrames) do
1093 begin
1094 FreeImage(Images[I]);
1095 Images[I] := AnimFrames[I];
1096 end;
1097 end;
1099 Result := True;
1100 end;
1101 end;
1103 function TGIFFileFormat.SaveData(Handle: TImagingHandle;
1104 const Images: TDynImageDataArray; Index: Integer): Boolean;
1105 var
1106 Header: TGIFHeader;
1107 ImageDesc: TImageDescriptor;
1108 ImageToSave: TImageData;
1109 MustBeFreed: Boolean;
1110 I, J: Integer;
1111 GraphicExt: TGraphicControlExtension;
1113 procedure FindMaxDimensions(var MaxWidth, MaxHeight: Word);
1114 var
1115 I: Integer;
1116 begin
1117 MaxWidth := Images[FFirstIdx].Width;
1118 MaxHeight := Images[FFirstIdx].Height;
1120 for I := FFirstIdx + 1 to FLastIdx do
1121 begin
1122 MaxWidth := Iff(Images[I].Width > MaxWidth, Images[I].Width, MaxWidth);
1123 MaxHeight := Iff(Images[I].Height > MaxWidth, Images[I].Height, MaxHeight);
1124 end;
1125 end;
1127 begin
1128 // Fill header with data, select size of largest image in array as
1129 // logical screen size
1130 FillChar(Header, Sizeof(Header), 0);
1131 Header.Signature := GIFSignature;
1132 Header.Version := GIFVersions[gv89];
1133 FindMaxDimensions(Header.ScreenWidth, Header.ScreenHeight);
1134 Header.PackedFields := GIFColorResolution; // Color resolution is 256
1135 GetIO.Write(Handle, @Header, SizeOf(Header));
1137 // Prepare default GC extension with delay
1138 FillChar(GraphicExt, Sizeof(GraphicExt), 0);
1139 GraphicExt.DelayTime := 65;
1140 GraphicExt.BlockSize := 4;
1142 for I := FFirstIdx to FLastIdx do
1143 begin
1144 if MakeCompatible(Images[I], ImageToSave, MustBeFreed) then
1145 with GetIO, ImageToSave do
1146 try
1147 // Write Graphic Control Extension with default delay
1148 Write(Handle, @GIFExtensionIntroducer, SizeOf(GIFExtensionIntroducer));
1149 Write(Handle, @GIFGraphicControlExtension, SizeOf(GIFGraphicControlExtension));
1150 Write(Handle, @GraphicExt, SizeOf(GraphicExt));
1151 // Write frame marker and fill and write image descriptor for this frame
1152 Write(Handle, @GIFImageDescriptor, SizeOf(GIFImageDescriptor));
1153 FillChar(ImageDesc, Sizeof(ImageDesc), 0);
1154 ImageDesc.Width := Width;
1155 ImageDesc.Height := Height;
1156 ImageDesc.PackedFields := GIFLocalColorTable or GIFColorTableSize; // Use lccal color table with 256 entries
1157 Write(Handle, @ImageDesc, SizeOf(ImageDesc));
1159 // Write local color table for each frame
1160 for J := 0 to 255 do
1161 begin
1162 Write(Handle, @Palette[J].R, SizeOf(Palette[J].R));
1163 Write(Handle, @Palette[J].G, SizeOf(Palette[J].G));
1164 Write(Handle, @Palette[J].B, SizeOf(Palette[J].B));
1165 end;
1167 // Fonally compress image data
1168 LZWCompress(GetIO, Handle, Width, Height, 8, False, Bits);
1170 finally
1171 if MustBeFreed then
1172 FreeImage(ImageToSave);
1173 end;
1174 end;
1176 GetIO.Write(Handle, @GIFTrailer, SizeOf(GIFTrailer));
1177 Result := True;
1178 end;
1180 procedure TGIFFileFormat.ConvertToSupported(var Image: TImageData;
1181 const Info: TImageFormatInfo);
1182 begin
1183 ConvertImage(Image, ifIndex8);
1184 end;
1186 function TGIFFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
1187 var
1188 Header: TGIFHeader;
1189 ReadCount: LongInt;
1190 begin
1191 Result := False;
1192 if Handle <> nil then
1193 begin
1194 ReadCount := GetIO.Read(Handle, @Header, SizeOf(Header));
1195 GetIO.Seek(Handle, -ReadCount, smFromCurrent);
1196 Result := (ReadCount >= SizeOf(Header)) and
1197 (Header.Signature = GIFSignature) and
1198 ((Header.Version = GIFVersions[gv87]) or (Header.Version = GIFVersions[gv89]));
1199 end;
1200 end;
1202 initialization
1203 RegisterImageFileFormat(TGIFFileFormat);
1206 File Notes:
1208 -- TODOS ----------------------------------------------------
1209 - nothing now
1211 -- 0.26.3 Changes/Bug Fixes ---------------------------------
1212 - Fixed bug - loading of GIF with NETSCAPE app extensions
1213 failed with Delphi 2009.
1215 -- 0.26.1 Changes/Bug Fixes ---------------------------------
1216 - GIF loading and animation mostly rewritten, based on
1217 modification by Sergey Galezdinov (ExtraGIF in Extras/Contrib).
1219 -- 0.25.0 Changes/Bug Fixes ---------------------------------
1220 - Fixed loading of some rare GIFs, problems with LZW
1221 decompression.
1223 -- 0.24.3 Changes/Bug Fixes ---------------------------------
1224 - Better solution to transparency for some GIFs. Background not
1225 transparent by default.
1227 -- 0.24.1 Changes/Bug Fixes ---------------------------------
1228 - Made backround color transparent by default (alpha = 0).
1230 -- 0.23 Changes/Bug Fixes -----------------------------------
1231 - Fixed other loading bugs (local pal size, transparency).
1232 - Added GIF saving.
1233 - Fixed bug when loading multiframe GIFs and implemented few animation
1234 features (disposal methods, ...).
1235 - Loading of GIFs working.
1236 - Unit created with initial stuff!
1239 end.