Repo for the search and displace ingest module that takes odf, docx and pdf and transforms it into .md to be used with search and displace operations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1291 lines
40 KiB

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