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.
|