GR32.ImageFormats.GIF.pas 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. unit GR32.ImageFormats.GIF;
  2. (* ***** BEGIN LICENSE BLOCK *****
  3. * Version: MPL 1.1 or LGPL 2.1 with linking exception
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. * http://www.mozilla.org/MPL/
  9. *
  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
  12. * for the specific language governing rights and limitations under the
  13. * License.
  14. *
  15. * Alternatively, the contents of this file may be used under the terms of the
  16. * Free Pascal modified version of the GNU Lesser General Public License
  17. * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
  18. * of this license are applicable instead of those above.
  19. * Please see the file LICENSE.txt for additional information concerning this
  20. * license.
  21. *
  22. * The Original Code is GIF support for Graphics32
  23. *
  24. * The Initial Developer of the Original Code is
  25. * Anders Melander <[email protected]>
  26. *
  27. * Portions created by the Initial Developer are Copyright (C) 2008-2022
  28. * the Initial Developer. All Rights Reserved.
  29. *
  30. * Contributor(s):
  31. *
  32. * ***** END LICENSE BLOCK ***** *)
  33. interface
  34. {$include GR32.inc}
  35. implementation
  36. uses
  37. {$ifdef FPC}
  38. Graphics,
  39. {$else FPC}
  40. Classes,
  41. Generics.Defaults,
  42. Generics.Collections,
  43. GIFImg,
  44. GIFConsts,
  45. GR32,
  46. {$endif FPC}
  47. GR32.ImageFormats,
  48. GR32.ImageFormats.TGraphic;
  49. {$ifdef FPC}
  50. resourcestring
  51. sGIFImageFile = 'GIF Image';
  52. {$endif FPC}
  53. //------------------------------------------------------------------------------
  54. //------------------------------------------------------------------------------
  55. //------------------------------------------------------------------------------
  56. type
  57. {$ifdef FPC}
  58. // FPC TGIFImage is read-only
  59. TImageFormatAdapterTGIFImage = TImageFormatReaderTGraphic;
  60. {$else FPC}
  61. TImageFormatAdapterTGIFImage = class(TImageFormatReaderWriterTGraphic)
  62. strict protected
  63. // IImageFormatWriter
  64. procedure SaveToStream(ASource: TCustomBitmap32; AStream: TStream); override;
  65. end;
  66. {$endif FPC}
  67. { TImageFormatAdapterTGIFImage }
  68. {$ifndef FPC}
  69. procedure TImageFormatAdapterTGIFImage.SaveToStream(ASource: TCustomBitmap32; AStream: TStream);
  70. type
  71. TColorPair = TPair<TColor32, Cardinal>;
  72. var
  73. Colors: TDictionary<TColor32, Cardinal>;
  74. Histogram: TArray<TColorPair>;
  75. ColorMap: TDictionary<TColor32, integer>;
  76. HasTransparency: boolean;
  77. i: integer;
  78. Color: TColor32;
  79. FirstColorIndex, LastColorIndex: integer;
  80. TransparetIndex: integer;
  81. ColorIndex: integer;
  82. GIF: TGIFImage;
  83. Frame: TGIFFrame;
  84. CommentExtension: TGIFCommentExtension;
  85. GraphicControlExtension: TGIFGraphicControlExtension;
  86. FramePixel: PByte;
  87. Count: Cardinal;
  88. const
  89. MaxColorTableSize = 256; // From 2 to 256
  90. MinAlpha = 64;
  91. begin
  92. // Build histogram...
  93. Colors := TDictionary<TColor32, Cardinal>.Create;
  94. try
  95. HasTransparency := False;
  96. for i := 0 to ASource.Height * ASource.Width - 1 do
  97. begin
  98. Color := ASource.Bits[i];
  99. if (AlphaComponent(Color) < MinAlpha) then
  100. begin
  101. HasTransparency := True;
  102. continue;
  103. end;
  104. Color := Color or $FF000000;
  105. if (Colors.TryGetValue(Color, Count)) then
  106. begin
  107. Inc(Count);
  108. Colors[Color] := Count;
  109. end else
  110. Colors.Add(Color, 1);
  111. end;
  112. Histogram := Colors.ToArray;
  113. finally
  114. Colors.Free;
  115. end;
  116. // ...and sort the histogram by count
  117. TArray.Sort<TColorPair>(Histogram, TComparer<TColorPair>.Construct(
  118. function(const A, B: TColorPair): integer
  119. begin
  120. Result := (B.Value - A.Value);
  121. end));
  122. ColorMap := TDictionary<TColor32, integer>.Create;
  123. try
  124. // Build color-to-index map
  125. for i := 0 to High(Histogram) do
  126. ColorMap.Add(Histogram[i].Key, i);
  127. GIF := TGIFImage.Create;
  128. try
  129. GIF.SetSize(ASource.Width, ASource.Height);
  130. // Each frame can contain up to 255 colors and a "transparent color".
  131. FirstColorIndex := 0;
  132. while (FirstColorIndex <= High(Histogram)) do
  133. begin
  134. LastColorIndex := FirstColorIndex + MaxColorTableSize - 2;
  135. // If first frame has no transparency then there's room for one more actual
  136. // color in the color map.
  137. if (FirstColorIndex = 0) and (not HasTransparency) then
  138. Inc(LastColorIndex);
  139. if (LastColorIndex > High(Histogram)) then
  140. LastColorIndex := High(Histogram);
  141. Frame := TGIFFrame.Create(GIF);
  142. Frame.Width := GIF.Width;
  143. Frame.Height := GIF.Height;
  144. // Add the colors of this frame to the frame color map
  145. for i := FirstColorIndex to LastColorIndex do
  146. Frame.ColorMap.Add(WinColor(Histogram[i].Key));
  147. if (FirstColorIndex = 0) then
  148. begin
  149. CommentExtension := TGIFCommentExtension.Create(Frame);
  150. CommentExtension.Text.Text := 'Generated by Graphics32 via TGIFImage for Delphi';
  151. end;
  152. // First frame has no transparency
  153. if (HasTransparency) or (FirstColorIndex > 0) then
  154. begin
  155. // Add transparent color.
  156. // The actual color doesn't matter. We just need the index of it.
  157. TransparetIndex := Frame.ColorMap.Add(WinColor(clFuchsia32));
  158. GraphicControlExtension := TGIFGraphicControlExtension.Create(Frame);
  159. GraphicControlExtension.Transparent := True;
  160. GraphicControlExtension.TransparentColorIndex := TransparetIndex;
  161. GraphicControlExtension.Disposal := dmNoDisposal;
  162. end else
  163. // Just default to 0. Pixel will be overwritten by later pixels
  164. TransparetIndex := 0;
  165. Frame.ColorMap.Optimized := True;
  166. FramePixel := Frame.Data;
  167. for i := 0 to ASource.Height * ASource.Width - 1 do
  168. begin
  169. Color := ASource.Bits[i];
  170. if (AlphaComponent(Color) >= MinAlpha) then
  171. begin
  172. ColorIndex := ColorMap[Color or $FF000000]; // Ignore alpha
  173. if (ColorIndex < FirstColorIndex) or (ColorIndex > LastColorIndex) then
  174. ColorIndex := TransparetIndex // Transparent in this frame
  175. else
  176. Dec(ColorIndex, FirstColorIndex);
  177. end else
  178. ColorIndex := TransparetIndex; // Alpha=0 -> Transparent
  179. FramePixel^ := ColorIndex;
  180. Inc(FramePixel);
  181. end;
  182. FirstColorIndex := LastColorIndex+1;
  183. end;
  184. GIF.Optimize([ooCrop]);
  185. GIF.SaveToStream(AStream);
  186. finally
  187. GIF.Free;
  188. end;
  189. finally
  190. ColorMap.Free;
  191. end;
  192. end;
  193. {$endif FPC}
  194. var
  195. ImageFormatHandle: integer = 0;
  196. initialization
  197. ImageFormatHandle := ImageFormatManager.RegisterImageFormat(
  198. TImageFormatAdapterTGIFImage.Create(TGIFImage, sGIFImageFile, ['gif']),
  199. ImageFormatPriorityNormal);
  200. finalization
  201. ImageFormatManager.UnregisterImageFormat(ImageFormatHandle);
  202. end.