ImagingGif.pas 31 KB

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