ImagingGif.pas 40 KB

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