Statistics
| Branch: | Tag: | Revision:

root / Imaging / ImagingGif.pas @ 0:95bd93c28625

History | View | Annotate | Download (30.8 kB)

1
{
2
  $Id: ImagingGif.pas 111 2007-12-02 23:25:44Z galfar $
3
  Vampyre Imaging Library
4
  by Marek Mauder 
5
  http://imaginglib.sourceforge.net
6
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
11
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.
15
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.
25
26
  For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
27
}
28
29
{ This unit contains image format loader/saver for GIF images.}
30
unit ImagingGif;
31
32
{$I ImagingOptions.inc}
33
34
interface
35
36
uses
37
  SysUtils, Classes, Imaging, ImagingTypes, ImagingUtility;
38
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.}
48
  TGIFFileFormat = class(TImageFileFormat)
49
  private
50
    function InterlaceStep(Y, Height: Integer; var Pass: Integer): Integer;
51
    procedure LZWDecompress(const IO: TIOFunctions; Handle: TImagingHandle;
52
      Width, Height: Integer; Interlaced: Boolean; Data: Pointer);
53
    procedure LZWCompress(const IO: TIOFunctions; Handle: TImagingHandle;
54
      Width, Height, BitCount: Integer; Interlaced: Boolean; Data: Pointer);
55
  protected
56
    function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
57
      OnlyFirstLevel: Boolean): Boolean; override;
58
    function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
59
      Index: LongInt): Boolean; override;
60
    procedure ConvertToSupported(var Image: TImageData;
61
      const Info: TImageFormatInfo); override;
62
  public
63
    constructor Create; override;
64
    function TestFormat(Handle: TImagingHandle): Boolean; override;
65
  end;
66
67
implementation
68
69
const
70
  SGIFFormatName = 'Graphics Interchange Format';
71
  SGIFMasks      = '*.gif';
72
  GIFSupportedFormats: TImageFormats = [ifIndex8];
73
74
type
75
  TGIFVersion = (gv87, gv89);
76
  TDisposalMethod = (dmUndefined, dmLeave, dmRestoreBackground,
77
    dmRestorePrevious, dmReserved4, dmReserved5, dmReserved6, dmReserved7);
78
79
const
80
  GIFSignature: TChar3 = 'GIF';
81
  GIFVersions: array[TGIFVersion] of TChar3 = ('87a', '89a');
82
83
  // Masks for accessing fields in PackedFields of TGIFHeader
84
  GIFGlobalColorTable = $80;
85
  GIFColorResolution  = $70;
86
  GIFColorTableSorted = $08;
87
  GIFColorTableSize   = $07;
88
89
  // Masks for accessing fields in PackedFields of TImageDescriptor
90
  GIFLocalColorTable  = $80;
91
  GIFInterlaced       = $40;
92
  GIFLocalTableSorted = $20;
93
94
  // Block identifiers
95
  GIFPlainText: Byte               = $01;
96
  GIFGraphicControlExtension: Byte = $F9;
97
  GIFCommentExtension: Byte        = $FE;
98
  GIFApplicationExtension: Byte    = $FF;
99
  GIFImageDescriptor: Byte         = Ord(',');
100
  GIFExtensionIntroducer: Byte     = Ord('!');
101
  GIFTrailer: Byte                 = Ord(';');
102
  GIFBlockTerminator: Byte         = $00;
103
104
  // Masks for accessing fields in PackedFields of TGraphicControlExtension
105
  GIFTransparent    = $01;
106
  GIFUserInput      = $02;
107
  GIFDisposalMethod = $1C;
108
109
type
110
  TGIFHeader = packed record
111
    // File header part
112
    Signature: TChar3;  // Header Signature (always "GIF")
113
    Version: TChar3;    // GIF format version("87a" or "89a")
114
    // Logical Screen Descriptor part
115
    ScreenWidth: Word;  // Width of Display Screen in Pixels
116
    ScreenHeight: Word; // Height of Display Screen in Pixels
117
    PackedFields: Byte; // Screen and color map information
118
    BackgroundColorIndex: Byte; // Background color index (in global color table)
119
    AspectRatio: Byte;  // Pixel aspect ratio, ratio = (AspectRatio + 15) / 64
120
  end;
121
122
  TImageDescriptor = packed record
123
    //Separator: Byte; // leave that out since we always read one bye ahead
124
    Left: Word;        // X position of image with respect to logical screen
125
    Top: Word;         // Y position
126
    Width: Word;
127
    Height: Word;
128
    PackedFields: Byte;
129
  end;
130
131
const
132
  // GIF extension labels
133
  GIFExtTypeGraphic     = $F9;
134
  GIFExtTypePlainText   = $01;
135
  GIFExtTypeApplication = $FF;
136
  GIFExtTypeComment     = $FE;
137
138
type
139
  TGraphicControlExtension = packed record
140
    BlockSize: Byte;
141
    PackedFields: Byte;
142
    DelayTime: Word;
143
    TransparentColorIndex: Byte;
144
    Terminator: Byte;
145
  end;
146
147
const
148
  CodeTableSize = 4096;
149
  HashTableSize = 17777;
150
  
151
type
152
  TReadContext = record
153
    Inx: Integer;
154
    Size: Integer;
155
    Buf: array [0..255 + 4] of Byte;
156
    CodeSize: Integer;
157
    ReadMask: Integer;
158
  end;
159
  PReadContext = ^TReadContext;
160
161
  TWriteContext = record
162
    Inx: Integer;
163
    CodeSize: Integer;
164
    Buf: array [0..255 + 4] of Byte;
165
  end;
166
  PWriteContext = ^TWriteContext;
167
168
  TOutputContext = record
169
    W: Integer;
170
    H: Integer;
171
    X: Integer;
172
    Y: Integer;
173
    BitsPerPixel: Integer;
174
    Pass: Integer;
175
    Interlace: Boolean;
176
    LineIdent: Integer;
177
    Data: Pointer;
178
    CurrLineData: Pointer;
179
  end;
180
181
  TImageDict = record
182
    Tail: Word;
183
    Index: Word;
184
    Col: Byte;
185
  end;
186
  PImageDict = ^TImageDict;
187
188
  PIntCodeTable = ^TIntCodeTable;
189
  TIntCodeTable = array [0..CodeTableSize - 1] of Word;
190
191
  TDictTable = array [0..CodeTableSize - 1] of TImageDict;
192
  PDictTable = ^TDictTable;
193
194
resourcestring
195
  SGIFDecodingError = 'Error when decoding GIF LZW data';
196
197
{
198
  TGIFFileFormat implementation
199
}
200
201
constructor TGIFFileFormat.Create;
202
begin
203
  inherited Create;
204
  FName := SGIFFormatName;
205
  FCanLoad := True;
206
  FCanSave := True;
207
  FIsMultiImageFormat := True;
208
  FSupportedFormats := GIFSupportedFormats;
209
210
  AddMasks(SGIFMasks);
211
end;
212
213
function TGIFFileFormat.InterlaceStep(Y, Height: Integer; var Pass: Integer): Integer;
214
begin
215
  Result := Y;
216
  case Pass of
217
    0, 1:
218
      Inc(Result, 8);
219
    2:
220
      Inc(Result, 4);
221
    3:
222
      Inc(Result, 2);
223
  end;
224
  if Result >= Height then
225
  begin
226
    if Pass = 0 then
227
    begin
228
      Pass := 1;
229
      Result := 4;
230
      if Result < Height then
231
        Exit;
232
    end;
233
    if Pass = 1 then
234
    begin
235
      Pass := 2;
236
      Result := 2;
237
      if Result < Height then
238
        Exit;
239
    end;
240
    if Pass = 2 then
241
    begin
242
      Pass := 3;
243
      Result := 1;
244
    end;
245
  end;
246
end;
247
248
{ GIF LZW decompresion code is from JVCL JvGIF.pas unit.}
249
procedure TGIFFileFormat.LZWDecompress(const IO: TIOFunctions; Handle: TImagingHandle; Width, Height: Integer;
250
  Interlaced: Boolean; Data: Pointer);
251
var
252
  MinCodeSize: Byte;
253
  MaxCode, BitMask, InitCodeSize: Integer;
254
  ClearCode, EndingCode, FirstFreeCode, FreeCode: Word;
255
  I, OutCount, Code: Integer;
256
  CurCode, OldCode, InCode, FinalChar: Word;
257
  Prefix, Suffix, OutCode: PIntCodeTable;
258
  ReadCtxt: TReadContext;
259
  OutCtxt: TOutputContext;
260
  TableFull: Boolean;
261
262
  function ReadCode(var Context: TReadContext): Integer;
263
  var
264
    RawCode: Integer;
265
    ByteIndex: Integer;
266
    Bytes: Byte;
267
    BytesToLose: Integer;
268
  begin
269
    while Context.Inx + Context.CodeSize > Context.Size do
270
    begin
271
      // Not enough bits in buffer - refill it - Not very efficient, but infrequently called
272
      BytesToLose := Context.Inx shr 3;
273
      // Note biggest Code Size is 12 bits. And this can at worst span 3 Bytes
274
      Move(Context.Buf[Word(BytesToLose)], Context.Buf[0], 3);
275
      Context.Inx := Context.Inx and 7;
276
      Context.Size := Context.Size - (BytesToLose shl 3);
277
      IO.Read(Handle, @Bytes, 1);
278
      if Bytes > 0 then
279
        IO.Read(Handle, @Context.Buf[Word(Context.Size shr 3)], Bytes);
280
      Context.Size := Context.Size + (Bytes shl 3);
281
    end;
282
    ByteIndex := Context.Inx shr 3;
283
    RawCode := Context.Buf[Word(ByteIndex)] +
284
      (Word(Context.Buf[Word(ByteIndex + 1)]) shl 8);
285
    if Context.CodeSize > 8 then
286
      RawCode := RawCode + (Longint(Context.Buf[ByteIndex + 2]) shl 16);
287
    RawCode := RawCode shr (Context.Inx and 7);
288
    Context.Inx := Context.Inx + Byte(Context.CodeSize);
289
    Result := RawCode and Context.ReadMask;
290
  end;
291
292
  procedure Output(Value: Byte; var Context: TOutputContext);
293
  var
294
    P: PByte;
295
  begin
296
    if Context.Y >= Context.H then
297
      Exit;
298
299
    // Only ifIndex8 supported
300
    P := @PByteArray(Context.CurrLineData)[Context.X];
301
    P^ := Value;
302
303
    {case Context.BitsPerPixel of
304
      1:
305
        begin
306
          P := @PByteArray(Context.CurrLineData)[Context.X shr 3];
307
          if (Context.X and $07) <> 0 then
308
            P^ := P^ or Word(Value shl (7 - (Word(Context.X and 7))))
309
          else
310
            P^ := Byte(Value shl 7);
311
        end;
312
      4:
313
        begin
314
          P := @PByteArray(Context.CurrLineData)[Context.X shr 1];
315
          if (Context.X and 1) <> 0 then
316
            P^ := P^ or Value
317
          else
318
            P^ := Byte(Value shl 4);
319
        end;
320
      8:
321
        begin
322
          P := @PByteArray(Context.CurrLineData)[Context.X];
323
          P^ := Value;
324
        end;
325
    end;}
326
    Inc(Context.X);
327
328
    if Context.X < Context.W then
329
      Exit;
330
    Context.X := 0;
331
    if Context.Interlace then
332
      Context.Y := InterlaceStep(Context.Y, Context.H, Context.Pass)
333
    else
334
      Inc(Context.Y);
335
336
    Context.CurrLineData := @PByteArray(Context.Data)[Context.Y * Context.LineIdent];
337
  end;
338
339
begin
340
  OutCount := 0;
341
  OldCode := 0;
342
  FinalChar := 0;
343
  TableFull := False;
344
  GetMem(Prefix, SizeOf(TIntCodeTable));
345
  GetMem(Suffix, SizeOf(TIntCodeTable));
346
  GetMem(OutCode, SizeOf(TIntCodeTable) + SizeOf(Word));
347
  try
348
    IO.Read(Handle, @MinCodeSize, 1);
349
    if (MinCodeSize < 2) or (MinCodeSize > 9) then
350
      RaiseImaging(SGIFDecodingError, []);
351
    // Initial read context
352
    ReadCtxt.Inx := 0;
353
    ReadCtxt.Size := 0;
354
    ReadCtxt.CodeSize := MinCodeSize + 1;
355
    ReadCtxt.ReadMask := (1 shl ReadCtxt.CodeSize) - 1;
356
    // Initialise pixel-output context
357
    OutCtxt.X := 0;
358
    OutCtxt.Y := 0;
359
    OutCtxt.Pass := 0;
360
    OutCtxt.W := Width;
361
    OutCtxt.H := Height;
362
    OutCtxt.BitsPerPixel := MinCodeSize;
363
    OutCtxt.Interlace := Interlaced;
364
    OutCtxt.LineIdent := Width;
365
    OutCtxt.Data := Data;
366
    OutCtxt.CurrLineData := Data;
367
    BitMask := (1 shl OutCtxt.BitsPerPixel) - 1;
368
    // 2 ^ MinCodeSize accounts for all colours in file
369
    ClearCode := 1 shl MinCodeSize;
370
    EndingCode := ClearCode + 1;
371
    FreeCode := ClearCode + 2;
372
    FirstFreeCode := FreeCode;
373
    // 2^ (MinCodeSize + 1) includes clear and eoi Code and space too
374
    InitCodeSize := ReadCtxt.CodeSize;
375
    MaxCode := 1 shl ReadCtxt.CodeSize;
376
    Code := ReadCode(ReadCtxt);
377
    while (Code <> EndingCode) and (Code <> $FFFF) and
378
      (OutCtxt.Y < OutCtxt.H) do
379
    begin
380
      if Code = ClearCode then
381
      begin
382
        ReadCtxt.CodeSize := InitCodeSize;
383
        MaxCode := 1 shl ReadCtxt.CodeSize;
384
        ReadCtxt.ReadMask := MaxCode - 1;
385
        FreeCode := FirstFreeCode;
386
        Code := ReadCode(ReadCtxt);
387
        CurCode := Code;
388
        OldCode := Code;
389
        if Code = $FFFF then
390
          Break;
391
        FinalChar := (CurCode and BitMask);
392
        Output(Byte(FinalChar), OutCtxt);
393
        TableFull := False;
394
      end
395
      else
396
      begin
397
        CurCode := Code;
398
        InCode := Code;
399
        if CurCode >= FreeCode then
400
        begin
401
          CurCode := OldCode;
402
          OutCode^[OutCount] := FinalChar;
403
          Inc(OutCount);
404
        end;
405
        while CurCode > BitMask do
406
        begin
407
          if OutCount > CodeTableSize then
408
            RaiseImaging(SGIFDecodingError, []);
409
          OutCode^[OutCount] := Suffix^[CurCode];
410
          Inc(OutCount);
411
          CurCode := Prefix^[CurCode];
412
        end;
413
414
        FinalChar := CurCode and BitMask;
415
        OutCode^[OutCount] := FinalChar;
416
        Inc(OutCount);
417
        for I := OutCount - 1 downto 0 do
418
          Output(Byte(OutCode^[I]), OutCtxt);
419
        OutCount := 0;
420
        // Update dictionary
421
        if not TableFull then
422
        begin
423
          Prefix^[FreeCode] := OldCode;
424
          Suffix^[FreeCode] := FinalChar;
425
          // Advance to next free slot
426
          Inc(FreeCode);
427
          if FreeCode >= MaxCode then
428
          begin
429
            if ReadCtxt.CodeSize < 12 then
430
            begin
431
              Inc(ReadCtxt.CodeSize);
432
              MaxCode := MaxCode shl 1;
433
              ReadCtxt.ReadMask := (1 shl ReadCtxt.CodeSize) - 1;
434
            end
435
            else
436
              TableFull := True;
437
          end;
438
        end;
439
        OldCode := InCode;
440
      end;
441
      Code := ReadCode(ReadCtxt);
442
    end;
443
    if Code = $FFFF then
444
      RaiseImaging(SGIFDecodingError, []);
445
  finally
446
    FreeMem(Prefix);
447
    FreeMem(OutCode);
448
    FreeMem(Suffix);
449
  end;
450
end;
451
452
{ GIF LZW compresion code is from JVCL JvGIF.pas unit.}
453
procedure TGIFFileFormat.LZWCompress(const IO: TIOFunctions; Handle: TImagingHandle; Width, Height, BitCount: Integer;
454
    Interlaced: Boolean; Data: Pointer);
455
var
456
  LineIdent: Integer;
457
  MinCodeSize, Col: Byte;
458
  InitCodeSize, X, Y: Integer;
459
  Pass: Integer;
460
  MaxCode: Integer; { 1 shl CodeSize }
461
  ClearCode, EndingCode, LastCode, Tail: Integer;
462
  I, HashValue: Integer;
463
  LenString: Word;
464
  Dict: PDictTable;
465
  HashTable: TList;
466
  PData: PByte;
467
  WriteCtxt: TWriteContext;
468
469
  function InitHash(P: Integer): Integer;
470
  begin
471
    Result := (P + 3) * 301;
472
  end;
473
474
  procedure WriteCode(Code: Integer; var Context: TWriteContext);
475
  var
476
    BufIndex: Integer;
477
    Bytes: Byte;
478
  begin
479
    BufIndex := Context.Inx shr 3;
480
    Code := Code shl (Context.Inx and 7);
481
    Context.Buf[BufIndex] := Context.Buf[BufIndex] or Byte(Code);
482
    Context.Buf[BufIndex + 1] := Byte(Code shr 8);
483
    Context.Buf[BufIndex + 2] := Byte(Code shr 16);
484
    Context.Inx := Context.Inx + Context.CodeSize;
485
    if Context.Inx >= 255 * 8 then
486
    begin
487
      // Flush out full buffer
488
      Bytes := 255;
489
      IO.Write(Handle, @Bytes, 1);
490
      IO.Write(Handle, @Context.Buf, Bytes);
491
      Move(Context.Buf[255], Context.Buf[0], 2);
492
      FillChar(Context.Buf[2], 255, 0);
493
      Context.Inx := Context.Inx - (255 * 8);
494
    end;
495
  end;
496
497
  procedure FlushCode(var Context: TWriteContext);
498
  var
499
    Bytes: Byte;
500
  begin
501
    Bytes := (Context.Inx + 7) shr 3;
502
    if Bytes > 0 then
503
    begin
504
      IO.Write(Handle, @Bytes, 1);
505
      IO.Write(Handle, @Context.Buf, Bytes);
506
    end;
507
    // Data block terminator - a block of zero Size
508
    Bytes := 0;
509
    IO.Write(Handle, @Bytes, 1);
510
  end;
511
512
begin
513
  LineIdent := Width;
514
  Tail := 0;
515
  HashValue := 0;
516
  Col := 0;
517
  HashTable := TList.Create;
518
  GetMem(Dict, SizeOf(TDictTable));
519
  try
520
    for I := 0 to HashTableSize - 1 do
521
      HashTable.Add(nil);
522
523
    // Initialise encoder variables
524
    InitCodeSize := BitCount + 1;
525
    if InitCodeSize = 2 then
526
      Inc(InitCodeSize);
527
    MinCodeSize := InitCodeSize - 1;
528
    IO.Write(Handle, @MinCodeSize, 1);
529
    ClearCode := 1 shl MinCodeSize;
530
    EndingCode := ClearCode + 1;
531
    LastCode := EndingCode;
532
    MaxCode := 1 shl InitCodeSize;
533
    LenString := 0;
534
    // Setup write context
535
    WriteCtxt.Inx := 0;
536
    WriteCtxt.CodeSize := InitCodeSize;
537
    FillChar(WriteCtxt.Buf, SizeOf(WriteCtxt.Buf), 0);
538
    WriteCode(ClearCode, WriteCtxt);
539
    Y := 0;
540
    Pass := 0;
541
542
    while Y < Height do
543
    begin
544
      PData := @PByteArray(Data)[Y * LineIdent];
545
      for X := 0 to Width - 1 do
546
      begin
547
        // Only ifIndex8 support
548
        case BitCount of
549
          8:
550
            begin
551
              Col := PData^;
552
              PData := @PByteArray(PData)[1];
553
            end;
554
          {4:
555
            begin
556
              if X and 1 <> 0 then
557
              begin
558
                Col := PData^ and $0F;
559
                PData := @PByteArray(PData)[1];
560
              end
561
              else
562
                Col := PData^ shr 4;
563
            end;
564
          1:
565
            begin
566
              if X and 7 = 7 then
567
              begin
568
                Col := PData^ and 1;
569
                PData := @PByteArray(PData)[1];
570
              end
571
              else
572
                Col := (PData^ shr (7 - (X and $07))) and $01;
573
            end;}
574
        end;
575
        Inc(LenString);
576
        if LenString = 1 then
577
        begin
578
          Tail := Col;
579
          HashValue := InitHash(Col);
580
        end
581
        else
582
        begin
583
          HashValue := HashValue * (Col + LenString + 4);
584
          I := HashValue mod HashTableSize;
585
          HashValue := HashValue mod HashTableSize;
586
          while (HashTable[I] <> nil) and
587
            ((PImageDict(HashTable[I])^.Tail <> Tail) or
588
            (PImageDict(HashTable[I])^.Col <> Col)) do
589
          begin
590
            Inc(I);
591
            if I >= HashTableSize then
592
              I := 0;
593
          end;
594
          if HashTable[I] <> nil then // Found in the strings table
595
            Tail := PImageDict(HashTable[I])^.Index
596
          else
597
          begin
598
            // Not found
599
            WriteCode(Tail, WriteCtxt);
600
            Inc(LastCode);
601
            HashTable[I] := @Dict^[LastCode];
602
            PImageDict(HashTable[I])^.Index := LastCode;
603
            PImageDict(HashTable[I])^.Tail := Tail;
604
            PImageDict(HashTable[I])^.Col := Col;
605
            Tail := Col;
606
            HashValue := InitHash(Col);
607
            LenString := 1;
608
            if LastCode >= MaxCode then
609
            begin
610
              // Next Code will be written longer
611
              MaxCode := MaxCode shl 1;
612
              Inc(WriteCtxt.CodeSize);
613
            end
614
            else
615
            if LastCode >= CodeTableSize - 2 then
616
            begin
617
              // Reset tables
618
              WriteCode(Tail, WriteCtxt);
619
              WriteCode(ClearCode, WriteCtxt);
620
              LenString := 0;
621
              LastCode := EndingCode;
622
              WriteCtxt.CodeSize := InitCodeSize;
623
              MaxCode := 1 shl InitCodeSize;
624
              for I := 0 to HashTableSize - 1 do
625
                HashTable[I] := nil;
626
            end;
627
          end;
628
        end;
629
      end;
630
      if Interlaced then
631
        Y := InterlaceStep(Y, Height, Pass)
632
      else
633
        Inc(Y);
634
    end;
635
    WriteCode(Tail, WriteCtxt);
636
    WriteCode(EndingCode, WriteCtxt);
637
    FlushCode(WriteCtxt);
638
  finally
639
    HashTable.Free;
640
    FreeMem(Dict);
641
  end;
642
end;
643
644
function TGIFFileFormat.LoadData(Handle: TImagingHandle;
645
  var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
646
var
647
  Header: TGIFHeader;
648
  HasGlobalPal: Boolean;
649
  GlobalPalLength: Integer;
650
  GlobalPal: TPalette32Size256;
651
  I: Integer;
652
  BlockID: Byte;
653
  HasGraphicExt: Boolean;
654
  GraphicExt: TGraphicControlExtension;
655
  Disposals: array of TDisposalMethod;
656
657
  function ReadBlockID: Byte;
658
  begin
659
    Result := GIFTrailer;
660
    GetIO.Read(Handle, @Result, SizeOf(Result));
661
  end;
662
663
  procedure ReadExtensions;
664
  var
665
    BlockSize, ExtType: Byte;
666
  begin
667
    HasGraphicExt := False;
668
669
    // Read extensions until image descriptor is found. Only graphic extension
670
    // is stored now (for transparency), others are skipped.
671
    while BlockID = GIFExtensionIntroducer do
672
    with GetIO do
673
    begin
674
      Read(Handle, @ExtType, SizeOf(ExtType));
675
676
      if ExtType = GIFGraphicControlExtension then
677
      begin
678
        HasGraphicExt := True;
679
        Read(Handle, @GraphicExt, SizeOf(GraphicExt));
680
      end
681
      else if ExtType in [GIFCommentExtension, GIFApplicationExtension, GIFPlainText] then
682
      repeat
683
        // Read block sizes and skip them
684
        Read(Handle, @BlockSize, SizeOf(BlockSize));
685
        Seek(Handle, BlockSize, smFromCurrent);
686
      until BlockSize = 0;
687
688
      // Read ID of following block
689
      BlockID := ReadBlockID;
690
    end;
691
  end;
692
693
  procedure CopyFrameTransparent(const Image, Frame: TImageData; Left, Top, TransIndex: Integer);
694
  var
695
    X, Y: Integer;
696
    Src, Dst: PByte;
697
  begin
698
    Src := Frame.Bits;
699
700
    // Copy all pixels from frame to log screen but ignore the transparent ones 
701
    for Y := 0 to Frame.Height - 1 do
702
    begin
703
      Dst := @PByteArray(Image.Bits)[(Top + Y) * Image.Width + Left];
704
      for X := 0 to Frame.Width - 1 do
705
      begin
706
        if Src^ <> TransIndex then
707
          Dst^ := Src^;
708
        Inc(Src);
709
        Inc(Dst);
710
      end;
711
    end;
712
  end;
713
714
  procedure ReadFrame;
715
  var
716
    ImageDesc: TImageDescriptor;
717
    HasLocalPal, Interlaced, HasTransparency: Boolean;
718
    I, Idx, LocalPalLength, TransIndex: Integer;
719
    LocalPal: TPalette32Size256;
720
    BlockTerm: Byte;
721
    Frame: TImageData;
722
  begin
723
    Idx := Length(Images);
724
    SetLength(Images, Idx + 1);
725
    FillChar(LocalPal, SizeOf(LocalPal), 0);
726
    with GetIO do
727
    begin
728
      // Read and parse image descriptor
729
      Read(Handle, @ImageDesc, SizeOf(ImageDesc));
730
      HasLocalPal := (ImageDesc.PackedFields and GIFLocalColorTable) = GIFLocalColorTable;
731
      Interlaced := (ImageDesc.PackedFields and GIFInterlaced) = GIFInterlaced;
732
      LocalPalLength := ImageDesc.PackedFields and GIFColorTableSize;
733
      LocalPalLength := 1 shl (LocalPalLength + 1);   // Total pal length is 2^(n+1)
734
735
      // Create new logical screen
736
      NewImage(Header.ScreenWidth, Header.ScreenHeight, ifIndex8, Images[Idx]);
737
      // Create new image for this frame which would be later pasted onto logical screen
738
      InitImage(Frame);
739
      NewImage(ImageDesc.Width, ImageDesc.Height, ifIndex8, Frame);
740
741
      // Load local palette if there is any
742
      if HasLocalPal then
743
        for I := 0 to LocalPalLength - 1 do
744
        begin
745
          LocalPal[I].A := 255;
746
          Read(Handle, @LocalPal[I].R, SizeOf(LocalPal[I].R));
747
          Read(Handle, @LocalPal[I].G, SizeOf(LocalPal[I].G));
748
          Read(Handle, @LocalPal[I].B, SizeOf(LocalPal[I].B));
749
        end;
750
751
      // Use local pal if present or global pal if present or create
752
      // default pal if neither of them is present
753
      if HasLocalPal then
754
        Move(LocalPal, Images[Idx].Palette^, SizeOf(LocalPal))
755
      else if HasGlobalPal then
756
        Move(GlobalPal, Images[Idx].Palette^, SizeOf(GlobalPal))
757
      else
758
        FillCustomPalette(Images[Idx].Palette, GlobalPalLength, 3, 3, 2);
759
760
      // Add default disposal method for this frame
761
      SetLength(Disposals, Length(Disposals) + 1);
762
      Disposals[High(Disposals)] := dmUndefined;
763
764
      // If Grahic Control Extension is present make use of it
765
      if HasGraphicExt then
766
      begin
767
        HasTransparency := (GraphicExt.PackedFields and GIFTransparent) = GIFTransparent;
768
        Disposals[High(Disposals)] := TDisposalMethod((GraphicExt.PackedFields and GIFDisposalMethod) shr 2);
769
        if HasTransparency then
770
          Images[Idx].Palette[GraphicExt.TransparentColorIndex].A := 0;
771
      end
772
      else
773
        HasTransparency := False;
774
775
      if Idx >= 1 then
776
      begin
777
        // If previous frame had some special disposal method we take it into
778
        // account now
779
        case Disposals[Idx - 1] of
780
          dmUndefined: ; // Do nothing
781
          dmLeave:
782
            begin
783
              // Leave previous frame on log screen
784
              CopyRect(Images[Idx - 1], 0, 0, Images[Idx].Width,
785
                Images[Idx].Height, Images[Idx], 0, 0);
786
            end;
787
          dmRestoreBackground:
788
            begin
789
              // Clear log screen with background color
790
              FillRect(Images[Idx], 0, 0, Images[Idx].Width, Images[Idx].Height,
791
                @Header.BackgroundColorIndex);
792
            end;
793
          dmRestorePrevious:
794
            if Idx >= 2 then
795
            begin
796
              // Set log screen to "previous of previous" frame
797
              CopyRect(Images[Idx - 2], 0, 0, Images[Idx].Width,
798
                Images[Idx].Height, Images[Idx], 0, 0);
799
            end;
800
        end;
801
      end
802
      else
803
      begin
804
        // First frame - just fill with background color
805
        FillRect(Images[Idx], 0, 0, Images[Idx].Width, Images[Idx].Height,
806
          @Header.BackgroundColorIndex);
807
      end;
808
809
      try
810
        // Data decompression finally
811
        LZWDecompress(GetIO, Handle, ImageDesc.Width, ImageDesc.Height, Interlaced, Frame.Bits);
812
        Read(Handle, @BlockTerm, SizeOf(BlockTerm));
813
        // Now copy frame to logical screen with skipping of transparent pixels (if enabled)
814
        TransIndex := Iff(HasTransparency, GraphicExt.TransparentColorIndex, MaxInt);
815
        CopyFrameTransparent(Images[Idx], Frame, ImageDesc.Left, ImageDesc.Top, TransIndex);
816
      finally
817
        FreeImage(Frame);
818
      end;
819
    end;
820
  end;
821
822
begin
823
  SetLength(Images, 0);
824
  FillChar(GlobalPal, SizeOf(GlobalPal), 0);
825
  with GetIO do
826
  begin
827
    // Read GIF header
828
    Read(Handle, @Header, SizeOf(Header));
829
    HasGlobalPal := Header.PackedFields and GIFGlobalColorTable = GIFGlobalColorTable; // Bit 7
830
    GlobalPalLength := Header.PackedFields and GIFColorTableSize; // Bits 0-2
831
    GlobalPalLength := 1 shl (GlobalPalLength + 1);   // Total pal length is 2^(n+1)
832
833
    // Read global palette from file if present
834
    if HasGlobalPal then
835
    begin
836
      for I := 0 to GlobalPalLength - 1 do
837
      begin
838
        GlobalPal[I].A := 255;
839
        Read(Handle, @GlobalPal[I].R, SizeOf(GlobalPal[I].R));
840
        Read(Handle, @GlobalPal[I].G, SizeOf(GlobalPal[I].G));
841
        Read(Handle, @GlobalPal[I].B, SizeOf(GlobalPal[I].B));
842
      end;
843
      GlobalPal[Header.BackgroundColorIndex].A := 0;
844
    end;
845
846
    // Read ID of the first block
847
    BlockID := ReadBlockID;
848
849
    // Now read all data blocks in the file until file trailer is reached
850
    while BlockID <> GIFTrailer do
851
    begin
852
      // Read supported and skip unsupported extensions
853
      ReadExtensions;
854
      // If image frame is found read it
855
      if BlockID = GIFImageDescriptor then
856
        ReadFrame;
857
      // Read next block's ID
858
      BlockID := ReadBlockID;
859
      // If block ID is unknown set it to end-of-GIF marker
860
      if not (BlockID in [GIFExtensionIntroducer, GIFTrailer, GIFImageDescriptor]) then
861
        BlockID := GIFTrailer;
862
    end;
863
864
    Result := True;
865
  end;
866
end;
867
868
function TGIFFileFormat.SaveData(Handle: TImagingHandle;
869
  const Images: TDynImageDataArray; Index: Integer): Boolean;
870
var
871
  Header: TGIFHeader;
872
  ImageDesc: TImageDescriptor;
873
  ImageToSave: TImageData;
874
  MustBeFreed: Boolean;
875
  I, J: Integer;
876
  GraphicExt: TGraphicControlExtension;
877
878
  procedure FindMaxDimensions(var MaxWidth, MaxHeight: Word);
879
  var
880
    I: Integer;
881
  begin
882
    MaxWidth := Images[FFirstIdx].Width;
883
    MaxHeight := Images[FFirstIdx].Height;
884
885
    for I := FFirstIdx + 1 to FLastIdx do
886
    begin
887
      MaxWidth := Iff(Images[I].Width > MaxWidth, Images[I].Width, MaxWidth);
888
      MaxHeight := Iff(Images[I].Height > MaxWidth, Images[I].Height, MaxHeight);
889
    end;
890
  end;
891
892
begin
893
  // Fill header with data, select size of largest image in array as
894
  // logical screen size
895
  FillChar(Header, Sizeof(Header), 0);
896
  Header.Signature := GIFSignature;
897
  Header.Version := GIFVersions[gv89];
898
  FindMaxDimensions(Header.ScreenWidth, Header.ScreenHeight);
899
  Header.PackedFields := GIFColorResolution; // Color resolution is 256
900
  GetIO.Write(Handle, @Header, SizeOf(Header));
901
902
  // Prepare default GC extension with delay
903
  FillChar(GraphicExt, Sizeof(GraphicExt), 0);
904
  GraphicExt.DelayTime := 65;
905
  GraphicExt.BlockSize := 4;
906
907
  for I := FFirstIdx to FLastIdx do
908
  begin
909
    if MakeCompatible(Images[I], ImageToSave, MustBeFreed) then
910
    with GetIO, ImageToSave do
911
    try
912
      // Write Graphic Control Extension with default delay
913
      Write(Handle, @GIFExtensionIntroducer, SizeOf(GIFExtensionIntroducer));
914
      Write(Handle, @GIFGraphicControlExtension, SizeOf(GIFGraphicControlExtension));
915
      Write(Handle, @GraphicExt, SizeOf(GraphicExt));
916
      // Write frame marker and fill and write image descriptor for this frame
917
      Write(Handle, @GIFImageDescriptor, SizeOf(GIFImageDescriptor));
918
      FillChar(ImageDesc, Sizeof(ImageDesc), 0);
919
      ImageDesc.Width := Width;
920
      ImageDesc.Height := Height;
921
      ImageDesc.PackedFields := GIFLocalColorTable or GIFColorTableSize; // Use lccal color table with 256 entries
922
      Write(Handle, @ImageDesc, SizeOf(ImageDesc));
923
924
      // Write local color table for each frame
925
      for J := 0 to 255 do
926
      begin
927
        Write(Handle, @Palette[J].R, SizeOf(Palette[J].R));
928
        Write(Handle, @Palette[J].G, SizeOf(Palette[J].G));
929
        Write(Handle, @Palette[J].B, SizeOf(Palette[J].B));
930
      end;
931
932
      // Fonally compress image data 
933
      LZWCompress(GetIO, Handle, Width, Height, 8, False, Bits);
934
935
    finally
936
      if MustBeFreed then
937
        FreeImage(ImageToSave);
938
    end;
939
  end;
940
941
  GetIO.Write(Handle, @GIFTrailer, SizeOf(GIFTrailer));
942
  Result := True;
943
end;
944
945
procedure TGIFFileFormat.ConvertToSupported(var Image: TImageData;
946
  const Info: TImageFormatInfo);
947
begin
948
  ConvertImage(Image, ifIndex8);
949
end;
950
951
function TGIFFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
952
var
953
  Header: TGIFHeader;
954
  ReadCount: LongInt;
955
begin
956
  Result := False;
957
  if Handle <> nil then
958
  begin
959
    ReadCount := GetIO.Read(Handle, @Header, SizeOf(Header));
960
    GetIO.Seek(Handle, -ReadCount, smFromCurrent);
961
    Result := (ReadCount >= SizeOf(Header)) and
962
      (Header.Signature = GIFSignature) and
963
      ((Header.Version = GIFVersions[gv87]) or (Header.Version = GIFVersions[gv89]));
964
  end;
965
end;
966
967
initialization
968
  RegisterImageFileFormat(TGIFFileFormat);
969
970
{
971
  File Notes:
972
973
 -- TODOS ----------------------------------------------------
974
    - nothing now
975
976
  -- 0.24.1 Changes/Bug Fixes ---------------------------------
977
    - Made backround color transparent by default (alpha = 0).
978
979
  -- 0.23 Changes/Bug Fixes -----------------------------------
980
    - Fixed other loading bugs (local pal size, transparency).
981
    - Added GIF saving.
982
    - Fixed bug when loading multiframe GIFs and implemented few animation
983
      features (disposal methods, ...). 
984
    - Loading of GIFs working.
985
    - Unit created with initial stuff!
986
}
987
988
end.