GifImage.pas
上传用户:daoqigc
上传日期:2021-04-20
资源大小:2795k
文件大小:363k
源码类别:

RichEdit

开发平台:

Delphi

  1.     end else
  2.     begin
  3.       // Create new bitmap and copy
  4.       NewBitmap;
  5.       FBitmap.Assign(TBitmap(Source));
  6.     end;
  7.     // Allocate new buffer
  8.     NewImage;
  9.     Image.Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressConverting);
  10.     try
  11. {$ifdef VER9x}
  12.       // This shouldn't happen, but better safe...
  13.       if (FBitmap.Palette = 0) then
  14.         PixelFormat := pf24bit;
  15. {$endif}
  16.       if (not(PixelFormat in [pf1bit, pf4bit, pf8bit, pf24bit])) then
  17.         PixelFormat := pf24bit;
  18.       DIBSource := TDIBReader.Create(FBitmap, PixelFormat);
  19.       try
  20.         // Copy pixels
  21.         case (PixelFormat) of
  22.           pf8bit: Import8Bit(Fdata);
  23.           pf4bit: Import4Bit(Fdata);
  24.           pf1bit: Import1Bit(Fdata);
  25.         else
  26. //        Error(sUnsupportedBitmap);
  27.           Import24Bit(Fdata);
  28.         end;
  29.       finally
  30.         DIBSource.Free;
  31.       end;
  32. {$ifdef VER10_PLUS}
  33.       // Add mask for transparent bitmaps
  34.       if (TBitmap(Source).Transparent) then
  35.         AddMaskOnly(TBitmap(Source).MaskHandle);
  36. {$endif}
  37.     finally
  38.       if ExceptObject = nil then
  39.         i := 100
  40.       else
  41.         i := 0;
  42.       Image.Progress(Self, psEnding, i, Image.PaletteModified, Rect(0,0,0,0), sProgressConverting);
  43.     end;
  44.   end else
  45.   //
  46.   // TGraphic import
  47.   //
  48.   if (Source is TGraphic) then
  49.   begin
  50.     // Zap existing colormap, extensions and bitmap
  51.     Clear;
  52.     if (TGraphic(Source).Empty) then
  53.       exit;
  54.     ABitmap := TBitmap.Create;
  55.     try
  56.       // Import TIcon and TMetafile by drawing them onto a bitmap...
  57.       // ...and then importing the bitmap recursively
  58.       if (Source is TIcon) or (Source is TMetafile) then
  59.       begin
  60.         try
  61.           ImportViaDraw(ABitmap, TGraphic(Source))
  62.         except
  63.           // If import via TCanvas.Draw fails (which it shouldn't), we try the
  64.           // Assign mechanism instead
  65.           ABitmap.Assign(Source);
  66.         end;
  67.       end else
  68.         try
  69.           ABitmap.Assign(Source);
  70.         except
  71.           // If automatic conversion to bitmap fails, we try and draw the
  72.           // graphic on the bitmap instead
  73.           ImportViaDraw(ABitmap, TGraphic(Source));
  74.         end;
  75.       // Convert the bitmap to a GIF frame recursively
  76.       Assign(ABitmap);
  77.     finally
  78.       ABitmap.Free;
  79.     end;
  80.     // Import transparency mask
  81.     if (Source is TIcon) then
  82.       AddIconMask(TIcon(Source));
  83.     if (Source is TMetaFile) then
  84.       AddMetafileMask(TMetaFile(Source));
  85.   end else
  86.   //
  87.   // TPicture import
  88.   //
  89.   if (Source is TPicture) then
  90.   begin
  91.     // Recursively import TGraphic
  92.     Assign(TPicture(Source).Graphic);
  93.   end else
  94.     // Unsupported format - fall back to Source.AssignTo
  95.     inherited Assign(Source);
  96. end;
  97. // Copied from D3 graphics.pas
  98. // Fixed by Brian Lowe of Acro Technology Inc. 30Jan98
  99. function TransparentStretchBlt(DstDC: HDC; DstX, DstY, DstW, DstH: Integer;
  100.   SrcDC: HDC; SrcX, SrcY, SrcW, SrcH: Integer; MaskDC: HDC; MaskX,
  101.   MaskY: Integer): Boolean;
  102. const
  103.   ROP_DstCopy = $00AA0029;
  104. var
  105.   MemDC ,
  106.   OrMaskDC : HDC;
  107.   MemBmp ,
  108.   OrMaskBmp : HBITMAP;
  109.   Save ,
  110.   OrMaskSave : THandle;
  111.   crText, crBack : TColorRef;
  112.   SavePal : HPALETTE;
  113. begin
  114.   Result := True;
  115.   if (Win32Platform = VER_PLATFORM_WIN32_NT) and (SrcW = DstW) and (SrcH = DstH) then
  116.   begin
  117.     MemBmp := GDICheck(CreateCompatibleBitmap(SrcDC, 1, 1));
  118.     MemBmp := SelectObject(MaskDC, MemBmp);
  119.     try
  120.       MaskBlt(DstDC, DstX, DstY, DstW, DstH, SrcDC, SrcX, SrcY, MemBmp, MaskX,
  121.         MaskY, MakeRop4(ROP_DstCopy, SrcCopy));
  122.     finally
  123.       MemBmp := SelectObject(MaskDC, MemBmp);
  124.       DeleteObject(MemBmp);
  125.     end;
  126.     Exit;
  127.   end;
  128.   SavePal := 0;
  129.   MemDC := GDICheck(CreateCompatibleDC(DstDC));
  130.   try
  131.     { Color bitmap for combining OR mask with source bitmap }
  132.     MemBmp := GDICheck(CreateCompatibleBitmap(DstDC, SrcW, SrcH));
  133.     try
  134.       Save := SelectObject(MemDC, MemBmp);
  135.       try
  136.         { This bitmap needs the size of the source but DC of the dest }
  137.         OrMaskDC := GDICheck(CreateCompatibleDC(DstDC));
  138.         try
  139.           { Need a monochrome bitmap for OR mask!! }
  140.           OrMaskBmp := GDICheck(CreateBitmap(SrcW, SrcH, 1, 1, nil));
  141.           try
  142.             OrMaskSave := SelectObject(OrMaskDC, OrMaskBmp);
  143.             try
  144.               // OrMask := 1
  145.               // Original: BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, OrMaskDC, SrcX, SrcY, WHITENESS);
  146.               // Replacement, but not needed: PatBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, WHITENESS);
  147.               // OrMask := OrMask XOR Mask
  148.               // Not needed: BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, MaskDC, SrcX, SrcY, SrcInvert);
  149.               // OrMask := NOT Mask
  150.               BitBlt(OrMaskDC, SrcX, SrcY, SrcW, SrcH, MaskDC, SrcX, SrcY, NotSrcCopy);
  151.               // Retrieve source palette (with dummy select)
  152.               SavePal := SelectPalette(SrcDC, SystemPalette16, False);
  153.               // Restore source palette
  154.               SelectPalette(SrcDC, SavePal, False);
  155.               // Select source palette into memory buffer
  156.               if SavePal <> 0 then
  157.                 SavePal := SelectPalette(MemDC, SavePal, True)
  158.               else
  159.                 SavePal := SelectPalette(MemDC, SystemPalette16, True);
  160.               RealizePalette(MemDC);
  161.               // Mem := OrMask
  162.               BitBlt(MemDC, SrcX, SrcY, SrcW, SrcH, OrMaskDC, SrcX, SrcY, SrcCopy);
  163.               // Mem := Mem AND Src
  164. {$IFNDEF GIF_TESTMASK} // Define GIF_TESTMASK if you want to know what it does...
  165.               BitBlt(MemDC, SrcX, SrcY, SrcW, SrcH, SrcDC, SrcX, SrcY, SrcAnd);
  166. {$ELSE}
  167.               StretchBlt(DstDC, DstX, DstY, DstW DIV 2, DstH, MemDC, SrcX, SrcY, SrcW, SrcH, SrcCopy);
  168.               StretchBlt(DstDC, DstX+DstW DIV 2, DstY, DstW DIV 2, DstH, SrcDC, SrcX, SrcY, SrcW, SrcH, SrcCopy);
  169.               exit;
  170. {$ENDIF}
  171.             finally
  172.               if (OrMaskSave <> 0) then
  173.                 SelectObject(OrMaskDC, OrMaskSave);
  174.             end;
  175.           finally
  176.             DeleteObject(OrMaskBmp);
  177.           end;
  178.         finally
  179.           DeleteDC(OrMaskDC);
  180.         end;
  181.         crText := SetTextColor(DstDC, $00000000);
  182.         crBack := SetBkColor(DstDC, $00FFFFFF);
  183.         { All color rendering is done at 1X (no stretching),
  184.           then final 2 masks are stretched to dest DC }
  185.         // Neat trick!
  186.         // Dst := Dst AND Mask
  187.         StretchBlt(DstDC, DstX, DstY, DstW, DstH, MaskDC, SrcX, SrcY, SrcW, SrcH, SrcAnd);
  188.         // Dst := Dst OR Mem
  189.         StretchBlt(DstDC, DstX, DstY, DstW, DstH, MemDC, SrcX, SrcY, SrcW, SrcH, SrcPaint);
  190.         SetTextColor(DstDC, crText);
  191.         SetTextColor(DstDC, crBack);
  192.       finally
  193.         if (Save <> 0) then
  194.           SelectObject(MemDC, Save);
  195.       end;
  196.     finally
  197.       DeleteObject(MemBmp);
  198.     end;
  199.   finally
  200.     if (SavePal <> 0) then
  201.       SelectPalette(MemDC, SavePal, False);
  202.     DeleteDC(MemDC);
  203.   end;
  204. end;
  205. procedure TGIFSubImage.Draw(ACanvas: TCanvas; const Rect: TRect;
  206.   DoTransparent, DoTile: boolean);
  207. begin
  208.   if (DoTile) then
  209.     StretchDraw(ACanvas, Rect, DoTransparent, DoTile)
  210.   else
  211.     StretchDraw(ACanvas, ScaleRect(Rect), DoTransparent, DoTile);
  212. end;
  213. type
  214.   // Dummy class used to gain access to protected method TCanvas.Changed
  215.   TChangableCanvas = class(TCanvas)
  216.   end;
  217. procedure TGIFSubImage.StretchDraw(ACanvas: TCanvas; const Rect: TRect;
  218.   DoTransparent, DoTile: boolean);
  219. var
  220.   MaskDC : HDC;
  221.   Save : THandle;
  222.   Tile : TRect;
  223. {$ifdef DEBUG_DRAWPERFORMANCE}
  224.   ImageCount ,
  225.   TimeStart ,
  226.   TimeStop : DWORD;
  227. {$endif}
  228. begin
  229. {$ifdef DEBUG_DRAWPERFORMANCE}
  230.   TimeStart := timeGetTime;
  231.   ImageCount := 0;
  232. {$endif}
  233.   if (DoTransparent) and (Transparent) and (HasMask) then
  234.   begin
  235.     // Draw transparent using mask
  236.     Save := 0;
  237.     MaskDC := 0;
  238.     try
  239.       MaskDC := GDICheck(CreateCompatibleDC(0));
  240.       Save := SelectObject(MaskDC, FMask);
  241.       if (DoTile) then
  242.       begin
  243.         Tile.Left := Rect.Left+Left;
  244.         Tile.Right := Tile.Left + Width;
  245.         while (Tile.Left < Rect.Right) do
  246.         begin
  247.           Tile.Top := Rect.Top+Top;
  248.           Tile.Bottom := Tile.Top + Height;
  249.           while (Tile.Top < Rect.Bottom) do
  250.           begin
  251.             TransparentStretchBlt(ACanvas.Handle, Tile.Left, Tile.Top, Width, Height,
  252.               Bitmap.Canvas.Handle, 0, 0, Width, Height, MaskDC, 0, 0);
  253.             Tile.Top := Tile.Top + Image.Height;
  254.             Tile.Bottom := Tile.Bottom + Image.Height;
  255. {$ifdef DEBUG_DRAWPERFORMANCE}
  256.             inc(ImageCount);
  257. {$endif}
  258.           end;
  259.           Tile.Left := Tile.Left + Image.Width;
  260.           Tile.Right := Tile.Right + Image.Width;
  261.         end;
  262.       end else
  263.         TransparentStretchBlt(ACanvas.Handle, Rect.Left, Rect.Top,
  264.           Rect.Right - Rect.Left, Rect.Bottom - Rect.Top,
  265.           Bitmap.Canvas.Handle, 0, 0, Width, Height, MaskDC, 0, 0);
  266.       // Since we are not using any of the TCanvas functions (only handle)
  267.       // we need to fire the TCanvas.Changed method "manually".
  268.       TChangableCanvas(ACanvas).Changed;
  269.     finally
  270.       if (Save <> 0) then
  271.         SelectObject(MaskDC, Save);
  272.       if (MaskDC <> 0) then
  273.         DeleteDC(MaskDC);
  274.     end;
  275.   end else
  276.   begin
  277.     if (DoTile) then
  278.     begin
  279.       Tile.Left := Rect.Left+Left;
  280.       Tile.Right := Tile.Left + Width;
  281.       while (Tile.Left < Rect.Right) do
  282.       begin
  283.         Tile.Top := Rect.Top+Top;
  284.         Tile.Bottom := Tile.Top + Height;
  285.         while (Tile.Top < Rect.Bottom) do
  286.         begin
  287.           ACanvas.StretchDraw(Tile, Bitmap);
  288.           Tile.Top := Tile.Top + Image.Height;
  289.           Tile.Bottom := Tile.Bottom + Image.Height;
  290. {$ifdef DEBUG_DRAWPERFORMANCE}
  291.           inc(ImageCount);
  292. {$endif}
  293.         end;
  294.         Tile.Left := Tile.Left + Image.Width;
  295.         Tile.Right := Tile.Right + Image.Width;
  296.       end;
  297.     end else
  298.       ACanvas.StretchDraw(Rect, Bitmap);
  299.   end;
  300. {$ifdef DEBUG_DRAWPERFORMANCE}
  301.   if (GetAsyncKeyState(VK_CONTROL) <> 0) then
  302.   begin
  303.     TimeStop := timeGetTime;
  304.     ShowMessage(format('Draw %d images in %d mS, Rate %d images/mS (%d images/S)',
  305.       [ImageCount, TimeStop-TimeStart,
  306.       ImageCount DIV (TimeStop-TimeStart+1),
  307.       MulDiv(ImageCount, 1000, TimeStop-TimeStart+1)]));
  308.   end;
  309. {$endif}
  310. end;
  311. // Given a destination rect (DestRect) calculates the
  312. // area covered by this sub image
  313. function TGIFSubImage.ScaleRect(DestRect: TRect): TRect;
  314. var
  315.   HeightMul ,
  316.   HeightDiv : integer;
  317.   WidthMul ,
  318.   WidthDiv : integer;
  319. begin
  320.   HeightDiv := Image.Height;
  321.   HeightMul := DestRect.Bottom-DestRect.Top;
  322.   WidthDiv := Image.Width;
  323.   WidthMul := DestRect.Right-DestRect.Left;
  324.   Result.Left := DestRect.Left + muldiv(Left, WidthMul, WidthDiv);
  325.   Result.Top := DestRect.Top + muldiv(Top, HeightMul, HeightDiv);
  326.   Result.Right := DestRect.Left + muldiv(Left+Width, WidthMul, WidthDiv);
  327.   Result.Bottom := DestRect.Top + muldiv(Top+Height, HeightMul, HeightDiv);
  328. end;
  329. procedure TGIFSubImage.Crop;
  330. var
  331.   TransparentColorIndex : byte;
  332.   CropLeft ,
  333.   CropTop ,
  334.   CropRight ,
  335.   CropBottom : integer;
  336.   WasTransparent : boolean;
  337.   i : integer;
  338.   NewSize : integer;
  339.   NewData : PChar;
  340.   NewWidth ,
  341.   NewHeight : integer;
  342.   pSource ,
  343.   pDest : PChar;
  344. begin
  345.   if (Empty) or (not Transparent) then
  346.     exit;
  347.   TransparentColorIndex := GraphicControlExtension.TransparentColorIndex;
  348.   CropLeft := 0;
  349.   CropRight := Width - 1;
  350.   CropTop := 0;
  351.   CropBottom := Height - 1;
  352.   // Find left edge
  353.   WasTransparent := True;
  354.   while (CropLeft <= CropRight) and (WasTransparent) do
  355.   begin
  356.     for i := CropTop to CropBottom do
  357.       if (Pixels[CropLeft, i] <> TransparentColorIndex) then
  358.       begin
  359.         WasTransparent := False;
  360.         break;
  361.       end;
  362.     if (WasTransparent) then
  363.       inc(CropLeft);
  364.   end;
  365.   // Find right edge
  366.   WasTransparent := True;
  367.   while (CropLeft <= CropRight) and (WasTransparent) do
  368.   begin
  369.     for i := CropTop to CropBottom do
  370.       if (pixels[CropRight, i] <> TransparentColorIndex) then
  371.       begin
  372.         WasTransparent := False;
  373.         break;
  374.       end;
  375.     if (WasTransparent) then
  376.       dec(CropRight);
  377.   end;
  378.   if (CropLeft <= CropRight) then
  379.   begin
  380.     // Find top edge
  381.     WasTransparent := True;
  382.     while (CropTop <= CropBottom) and (WasTransparent) do
  383.     begin
  384.       for i := CropLeft to CropRight do
  385.         if (pixels[i, CropTop] <> TransparentColorIndex) then
  386.         begin
  387.           WasTransparent := False;
  388.           break;
  389.         end;
  390.       if (WasTransparent) then
  391.         inc(CropTop);
  392.     end;
  393.     // Find bottom edge
  394.     WasTransparent := True;
  395.     while (CropTop <= CropBottom) and (WasTransparent) do
  396.     begin
  397.       for i := CropLeft to CropRight do
  398.         if (pixels[i, CropBottom] <> TransparentColorIndex) then
  399.         begin
  400.           WasTransparent := False;
  401.           break;
  402.         end;
  403.       if (WasTransparent) then
  404.         dec(CropBottom);
  405.     end;
  406.   end;
  407.   if (CropLeft > CropRight) or (CropTop > CropBottom) then
  408.   begin
  409.     // Cropped to nothing - frame is invisible
  410.     Clear;
  411.   end else
  412.   begin
  413.     // Crop frame - move data
  414.     NewWidth := CropRight - CropLeft + 1;
  415.     Newheight := CropBottom - CropTop + 1;
  416.     NewSize := NewWidth * NewHeight;
  417.     GetMem(NewData, NewSize);
  418.     pSource := PChar(integer(FData) + CropTop * Width + CropLeft);
  419.     pDest := NewData;
  420.     for i := 0 to NewHeight-1 do
  421.     begin
  422.       Move(pSource^, pDest^, NewWidth);
  423.       inc(pSource, Width);
  424.       inc(pDest, NewWidth);
  425.     end;
  426.     FreeImage;
  427.     FData := NewData;
  428.     FDataSize := NewSize;
  429.     inc(FImageDescriptor.Left, CropLeft);
  430.     inc(FImageDescriptor.Top, CropTop);
  431.     FImageDescriptor.Width := NewWidth;
  432.     FImageDescriptor.Height := NewHeight;
  433.     FreeBitmap;
  434.     FreeMask
  435.   end;
  436. end;
  437. procedure TGIFSubImage.Merge(Previous: TGIFSubImage);
  438. var
  439.   SourceIndex ,
  440.   DestIndex : byte;
  441.   SourceTransparent : boolean;
  442.   NeedTransparentColorIndex: boolean;
  443.   PreviousRect ,
  444.   ThisRect ,
  445.   MergeRect : TRect;
  446.   PreviousY ,
  447.   X ,
  448.   Y : integer;
  449.   pSource ,
  450.   pDest : PChar;
  451.   pSourceMap ,
  452.   pDestMap : PColorMap;
  453.   GCE : TGIFGraphicControlExtension;
  454.   function CanMakeTransparent: boolean;
  455.   begin
  456.     // Is there a local color map...
  457.     if (ColorMap.Count > 0) then
  458.       // ...and is there room in it?
  459.       Result := (ColorMap.Count < 256)
  460.     // Is there a global color map...
  461.     else if (Image.GlobalColorMap.Count > 0) then
  462.       // ...and is there room in it?
  463.       Result := (Image.GlobalColorMap.Count < 256)
  464.     else
  465.       Result := False;
  466.   end;
  467.   function GetTransparentColorIndex: byte;
  468.   var
  469.     i : integer;
  470.   begin
  471.     if (ColorMap.Count > 0) then
  472.     begin
  473.       // Get the transparent color from the local color map
  474.       Result := ColorMap.Add(TColor(0));
  475.     end else
  476.     begin
  477.       // Are any other frames using the global color map for transparency
  478.       for i := 0 to Image.Images.Count-1 do
  479.         if (Image.Images[i] <> self) and (Image.Images[i].Transparent) and
  480.           (Image.Images[i].ColorMap.Count = 0) then
  481.         begin
  482.           // Use the same transparency color as the other frame
  483.           Result := Image.Images[i].GraphicControlExtension.TransparentColorIndex;
  484.           exit;
  485.         end;
  486.       // Get the transparent color from the global color map
  487.       Result := Image.GlobalColorMap.Add(TColor(0));
  488.     end;
  489.   end;
  490. begin
  491.   // Determine if it is possible to merge this frame
  492.   if (Empty) or (Previous = nil) or (Previous.Empty) or
  493.     ((Previous.GraphicControlExtension <> nil) and
  494.      (Previous.GraphicControlExtension.Disposal in [dmBackground, dmPrevious])) then
  495.     exit;
  496.   PreviousRect := Previous.BoundsRect;
  497.   ThisRect := BoundsRect;
  498.   // Cannot merge unless the frames intersect
  499.   if (not IntersectRect(MergeRect, PreviousRect, ThisRect)) then
  500.     exit;
  501.   // If the frame isn't already transparent, determine
  502.   // if it is possible to make it so
  503.   if (Transparent) then
  504.   begin
  505.     DestIndex := GraphicControlExtension.TransparentColorIndex;
  506.     NeedTransparentColorIndex := False;
  507.   end else
  508.   begin
  509.     if (not CanMakeTransparent) then
  510.       exit;
  511.     DestIndex := 0; // To avoid compiler warning
  512.     NeedTransparentColorIndex := True;
  513.   end;
  514.   SourceTransparent := Previous.Transparent;
  515.   if (SourceTransparent) then
  516.     SourceIndex := Previous.GraphicControlExtension.TransparentColorIndex
  517.   else
  518.     SourceIndex := 0; // To avoid compiler warning
  519.   PreviousY := MergeRect.Top - Previous.Top;
  520.   pSourceMap := Previous.ActiveColorMap.Data;
  521.   pDestMap := ActiveColorMap.Data;
  522.   for Y := MergeRect.Top - Top to MergeRect.Bottom - Top-1 do
  523.   begin
  524.     pSource := PChar(integer(Previous.Scanline[PreviousY]) + MergeRect.Left - Previous.Left);
  525.     pDest := PChar(integer(Scanline[Y]) + MergeRect.Left - Left);
  526.     for X := MergeRect.Left to MergeRect.Right-1 do
  527.     begin
  528.       // Ignore pixels if either this frame's or the previous frame's pixel is transparent
  529.       if (
  530.             not(
  531.               ((not NeedTransparentColorIndex) and (pDest^ = char(DestIndex))) or
  532.               ((SourceTransparent) and (pSource^ = char(SourceIndex)))
  533.             )
  534.           ) and (
  535.             // Replace same colored pixels with transparency
  536.             ((pDestMap = pSourceMap) and (pDest^ = pSource^)) or
  537.             (CompareMem(@(pDestMap^[ord(pDest^)]), @(pSourceMap^[ord(pSource^)]), sizeof(TGIFColor)))
  538.           ) then
  539.       begin
  540.         if (NeedTransparentColorIndex) then
  541.         begin
  542.           NeedTransparentColorIndex := False;
  543.           DestIndex := GetTransparentColorIndex;
  544.         end;
  545.         pDest^ := char(DestIndex);
  546.       end;
  547.       inc(pDest);
  548.       inc(pSource);
  549.     end;
  550.     inc(PreviousY);
  551.   end;
  552.   (*
  553.   ** Create a GCE if the frame wasn't already transparent and any
  554.   ** pixels were made transparent
  555.   *)
  556.   if (not Transparent) and (not NeedTransparentColorIndex) then
  557.   begin
  558.     if (GraphicControlExtension = nil) then
  559.     begin
  560.       GCE := TGIFGraphicControlExtension.Create(self);
  561.       Extensions.Add(GCE);
  562.     end else
  563.       GCE := GraphicControlExtension;
  564.     GCE.Transparent := True;
  565.     GCE.TransparentColorIndex := DestIndex;
  566.   end;
  567.   FreeBitmap;
  568.   FreeMask
  569. end;
  570. ////////////////////////////////////////////////////////////////////////////////
  571. //
  572. // TGIFTrailer
  573. //
  574. ////////////////////////////////////////////////////////////////////////////////
  575. procedure TGIFTrailer.SaveToStream(Stream: TStream);
  576. begin
  577.   WriteByte(Stream, bsTrailer);
  578. end;
  579. procedure TGIFTrailer.LoadFromStream(Stream: TStream);
  580. var
  581.   b : BYTE;
  582. begin
  583.   if (Stream.Read(b, 1) <> 1) then
  584.     exit;
  585.   if (b <> bsTrailer) then
  586.     Warning(gsWarning, sBadTrailer);
  587. end;
  588. ////////////////////////////////////////////////////////////////////////////////
  589. //
  590. // TGIFExtension registration database
  591. //
  592. ////////////////////////////////////////////////////////////////////////////////
  593. type
  594.   TExtensionLeadIn = packed record
  595.     Introducer: byte;      { always $21 }
  596.     ExtensionLabel: byte;
  597.   end;
  598.   PExtRec = ^TExtRec;
  599.   TExtRec = record
  600.     ExtClass: TGIFExtensionClass;
  601.     ExtLabel: BYTE;
  602.   end;
  603.   TExtensionList = class(TList)
  604.   public
  605.     constructor Create;
  606.     destructor Destroy; override;
  607.     procedure Add(eLabel: BYTE; eClass: TGIFExtensionClass);
  608.     function FindExt(eLabel: BYTE): TGIFExtensionClass;
  609.     procedure Remove(eClass: TGIFExtensionClass);
  610.   end;
  611. constructor TExtensionList.Create;
  612. begin
  613.   inherited Create;
  614.   Add(bsPlainTextExtension, TGIFTextExtension);
  615.   Add(bsGraphicControlExtension, TGIFGraphicControlExtension);
  616.   Add(bsCommentExtension, TGIFCommentExtension);
  617.   Add(bsApplicationExtension, TGIFApplicationExtension);
  618. end;
  619. destructor TExtensionList.Destroy;
  620. var
  621.   I: Integer;
  622. begin
  623.   for I := 0 to Count-1 do
  624.     Dispose(PExtRec(Items[I]));
  625.   inherited Destroy;
  626. end;
  627. procedure TExtensionList.Add(eLabel: BYTE; eClass: TGIFExtensionClass);
  628. var
  629.   NewRec: PExtRec;
  630. begin
  631.   New(NewRec);
  632.   with NewRec^ do
  633.   begin
  634.     ExtLabel := eLabel;
  635.     ExtClass := eClass;
  636.   end;
  637.   inherited Add(NewRec);
  638. end;
  639. function TExtensionList.FindExt(eLabel: BYTE): TGIFExtensionClass;
  640. var
  641.   I: Integer;
  642. begin
  643.   for I := Count-1 downto 0 do
  644.     with PExtRec(Items[I])^ do
  645.       if ExtLabel = eLabel then
  646.       begin
  647.         Result := ExtClass;
  648.         Exit;
  649.       end;
  650.   Result := nil;
  651. end;
  652. procedure TExtensionList.Remove(eClass: TGIFExtensionClass);
  653. var
  654.   I: Integer;
  655.   P: PExtRec;
  656. begin
  657.   for I := Count-1 downto 0 do
  658.   begin
  659.     P := PExtRec(Items[I]);
  660.     if P^.ExtClass.InheritsFrom(eClass) then
  661.     begin
  662.       Dispose(P);
  663.       Delete(I);
  664.     end;
  665.   end;
  666. end;
  667. var
  668.   ExtensionList: TExtensionList = nil;
  669. function GetExtensionList: TExtensionList;
  670. begin
  671.   if (ExtensionList = nil) then
  672.     ExtensionList := TExtensionList.Create;
  673.   Result := ExtensionList;
  674. end;
  675. ////////////////////////////////////////////////////////////////////////////////
  676. //
  677. // TGIFExtension
  678. //
  679. ////////////////////////////////////////////////////////////////////////////////
  680. function TGIFExtension.GetVersion: TGIFVersion;
  681. begin
  682.   Result := gv89a;
  683. end;
  684. class procedure TGIFExtension.RegisterExtension(eLabel: BYTE; eClass: TGIFExtensionClass);
  685. begin
  686.   GetExtensionList.Add(eLabel, eClass);
  687. end;
  688. class function TGIFExtension.FindExtension(Stream: TStream): TGIFExtensionClass;
  689. var
  690.   eLabel : BYTE;
  691.   SubClass : TGIFExtensionClass;
  692.   Pos : LongInt;
  693. begin
  694.   Pos := Stream.Position;
  695.   if (Stream.Read(eLabel, 1) <> 1) then
  696.   begin
  697.     Result := nil;
  698.     exit;
  699.   end;
  700.   Result := GetExtensionList.FindExt(eLabel);
  701.   while (Result <> nil) do
  702.   begin
  703.     SubClass := Result.FindSubExtension(Stream);
  704.     if (SubClass = Result) then
  705.       break;
  706.     Result := SubClass;
  707.   end;
  708.   Stream.Position := Pos;
  709. end;
  710. class function TGIFExtension.FindSubExtension(Stream: TStream): TGIFExtensionClass;
  711. begin
  712.   Result := self;
  713. end;
  714. constructor TGIFExtension.Create(ASubImage: TGIFSubImage);
  715. begin
  716.   inherited Create(ASubImage.Image);
  717.   FSubImage := ASubImage;
  718. end;
  719. destructor TGIFExtension.Destroy;
  720. begin
  721.   if (FSubImage <> nil) then
  722.     FSubImage.Extensions.Remove(self);
  723.   inherited Destroy;
  724. end;
  725. procedure TGIFExtension.SaveToStream(Stream: TStream);
  726. var
  727.   ExtensionLeadIn : TExtensionLeadIn;
  728. begin
  729.   ExtensionLeadIn.Introducer := bsExtensionIntroducer;
  730.   ExtensionLeadIn.ExtensionLabel := ExtensionType;
  731.   Stream.Write(ExtensionLeadIn, sizeof(ExtensionLeadIn));
  732. end;
  733. function TGIFExtension.DoReadFromStream(Stream: TStream): TGIFExtensionType;
  734. var
  735.   ExtensionLeadIn : TExtensionLeadIn;
  736. begin
  737.   ReadCheck(Stream, ExtensionLeadIn, sizeof(ExtensionLeadIn));
  738.   if (ExtensionLeadIn.Introducer <> bsExtensionIntroducer) then
  739.     Error(sBadExtensionLabel);
  740.   Result := ExtensionLeadIn.ExtensionLabel;
  741. end;
  742. procedure TGIFExtension.LoadFromStream(Stream: TStream);
  743. begin
  744.   // Seek past lead-in
  745.   // Stream.Seek(sizeof(TExtensionLeadIn), soFromCurrent);
  746.   if (DoReadFromStream(Stream) <> ExtensionType) then
  747.     Error(sBadExtensionInstance);
  748. end;
  749. ////////////////////////////////////////////////////////////////////////////////
  750. //
  751. // TGIFGraphicControlExtension
  752. //
  753. ////////////////////////////////////////////////////////////////////////////////
  754. const
  755.   { Extension flag bit masks }
  756.   efInputFlag = $02; { 00000010 }
  757.   efDisposal = $1C; { 00011100 }
  758.   efTransparent = $01; { 00000001 }
  759.   efReserved = $E0; { 11100000 }
  760. constructor TGIFGraphicControlExtension.Create(ASubImage: TGIFSubImage);
  761. begin
  762.   inherited Create(ASubImage);
  763.   FGCExtension.BlockSize := 4;
  764.   FGCExtension.PackedFields := $00;
  765.   FGCExtension.DelayTime := 0;
  766.   FGCExtension.TransparentColorIndex := 0;
  767.   FGCExtension.Terminator := 0;
  768.   if (ASubImage.FGCE = nil) then
  769.     ASubImage.FGCE := self;
  770. end;
  771. destructor TGIFGraphicControlExtension.Destroy;
  772. begin
  773.   // Clear transparent flag in sub image
  774.   if (Transparent) then
  775.     SubImage.FTransparent := False;
  776.   if (SubImage.FGCE = self) then
  777.     SubImage.FGCE := nil;
  778.   inherited Destroy;
  779. end;
  780. function TGIFGraphicControlExtension.GetExtensionType: TGIFExtensionType;
  781. begin
  782.   Result := bsGraphicControlExtension;
  783. end;
  784. function TGIFGraphicControlExtension.GetTransparent: boolean;
  785. begin
  786.   Result := (FGCExtension.PackedFields AND efTransparent) <> 0;
  787. end;
  788. procedure TGIFGraphicControlExtension.SetTransparent(Value: boolean);
  789. begin
  790.   // Set transparent flag in sub image
  791.   SubImage.FTransparent := Value;
  792.   if (Value) then
  793.     FGCExtension.PackedFields := FGCExtension.PackedFields OR efTransparent
  794.   else
  795.     FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efTransparent);
  796. end;
  797. function TGIFGraphicControlExtension.GetTransparentColor: TColor;
  798. begin
  799.   Result := SubImage.ActiveColorMap[TransparentColorIndex];
  800. end;
  801. procedure TGIFGraphicControlExtension.SetTransparentColor(Color: TColor);
  802. begin
  803.   FGCExtension.TransparentColorIndex := Subimage.ActiveColorMap.AddUnique(Color);
  804. end;
  805. function TGIFGraphicControlExtension.GetTransparentColorIndex: BYTE;
  806. begin
  807.   Result := FGCExtension.TransparentColorIndex;
  808. end;
  809. procedure TGIFGraphicControlExtension.SetTransparentColorIndex(Value: BYTE);
  810. begin
  811.   if ((Value >= SubImage.ActiveColorMap.Count) and (SubImage.ActiveColorMap.Count > 0)) then
  812.   begin
  813.     Warning(gsWarning, sBadColorIndex);
  814.     Value := 0;
  815.   end;
  816.   FGCExtension.TransparentColorIndex := Value;
  817. end;
  818. function TGIFGraphicControlExtension.GetDelay: WORD;
  819. begin
  820.   Result := FGCExtension.DelayTime;
  821. end;
  822. procedure TGIFGraphicControlExtension.SetDelay(Value: WORD);
  823. begin
  824.   FGCExtension.DelayTime := Value;
  825. end;
  826. function TGIFGraphicControlExtension.GetUserInput: boolean;
  827. begin
  828.   Result := (FGCExtension.PackedFields AND efInputFlag) <> 0;
  829. end;
  830. procedure TGIFGraphicControlExtension.SetUserInput(Value: boolean);
  831. begin
  832.   if (Value) then
  833.     FGCExtension.PackedFields := FGCExtension.PackedFields OR efInputFlag
  834.   else
  835.     FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efInputFlag);
  836. end;
  837. function TGIFGraphicControlExtension.GetDisposal: TDisposalMethod;
  838. begin
  839.   Result := TDisposalMethod((FGCExtension.PackedFields AND efDisposal) SHR 2);
  840. end;
  841. procedure TGIFGraphicControlExtension.SetDisposal(Value: TDisposalMethod);
  842. begin
  843.   FGCExtension.PackedFields := FGCExtension.PackedFields AND NOT(efDisposal)
  844.     OR ((ord(Value) SHL 2) AND efDisposal);
  845. end;
  846. procedure TGIFGraphicControlExtension.SaveToStream(Stream: TStream);
  847. begin
  848.   inherited SaveToStream(Stream);
  849.   Stream.Write(FGCExtension, sizeof(FGCExtension));
  850. end;
  851. procedure TGIFGraphicControlExtension.LoadFromStream(Stream: TStream);
  852. begin
  853.   inherited LoadFromStream(Stream);
  854.   if (Stream.Read(FGCExtension, sizeof(FGCExtension)) <> sizeof(FGCExtension)) then
  855.   begin
  856.     Warning(gsWarning, sOutOfData);
  857.     exit;
  858.   end;
  859.   // Set transparent flag in sub image
  860.   if (Transparent) then
  861.     SubImage.FTransparent := True;
  862. end;
  863. ////////////////////////////////////////////////////////////////////////////////
  864. //
  865. // TGIFTextExtension
  866. //
  867. ////////////////////////////////////////////////////////////////////////////////
  868. constructor TGIFTextExtension.Create(ASubImage: TGIFSubImage);
  869. begin
  870.   inherited Create(ASubImage);
  871.   FText := TStringList.Create;
  872.   FPlainTextExtension.BlockSize := 12;
  873.   FPlainTextExtension.Left := 0;
  874.   FPlainTextExtension.Top := 0;
  875.   FPlainTextExtension.Width := 0;
  876.   FPlainTextExtension.Height := 0;
  877.   FPlainTextExtension.CellWidth := 0;
  878.   FPlainTextExtension.CellHeight := 0;
  879.   FPlainTextExtension.TextFGColorIndex := 0;
  880.   FPlainTextExtension.TextBGColorIndex := 0;
  881. end;
  882. destructor TGIFTextExtension.Destroy;
  883. begin
  884.   FText.Free;
  885.   inherited Destroy;
  886. end;
  887. function TGIFTextExtension.GetExtensionType: TGIFExtensionType;
  888. begin
  889.   Result := bsPlainTextExtension;
  890. end;
  891. function TGIFTextExtension.GetForegroundColor: TColor;
  892. begin
  893.   Result := SubImage.ColorMap[ForegroundColorIndex];
  894. end;
  895. procedure TGIFTextExtension.SetForegroundColor(Color: TColor);
  896. begin
  897.   ForegroundColorIndex := SubImage.ActiveColorMap.AddUnique(Color);
  898. end;
  899. function TGIFTextExtension.GetBackgroundColor: TColor;
  900. begin
  901.   Result := SubImage.ActiveColorMap[BackgroundColorIndex];
  902. end;
  903. procedure TGIFTextExtension.SetBackgroundColor(Color: TColor);
  904. begin
  905.   BackgroundColorIndex := SubImage.ColorMap.AddUnique(Color);
  906. end;
  907. function TGIFTextExtension.GetBounds(Index: integer): WORD;
  908. begin
  909.   case (Index) of
  910.     1: Result := FPlainTextExtension.Left;
  911.     2: Result := FPlainTextExtension.Top;
  912.     3: Result := FPlainTextExtension.Width;
  913.     4: Result := FPlainTextExtension.Height;
  914.   else
  915.     Result := 0; // To avoid compiler warnings
  916.   end;
  917. end;
  918. procedure TGIFTextExtension.SetBounds(Index: integer; Value: WORD);
  919. begin
  920.   case (Index) of
  921.     1: FPlainTextExtension.Left := Value;
  922.     2: FPlainTextExtension.Top := Value;
  923.     3: FPlainTextExtension.Width := Value;
  924.     4: FPlainTextExtension.Height := Value;
  925.   end;
  926. end;
  927. function TGIFTextExtension.GetCharWidthHeight(Index: integer): BYTE;
  928. begin
  929.   case (Index) of
  930.     1: Result := FPlainTextExtension.CellWidth;
  931.     2: Result := FPlainTextExtension.CellHeight;
  932.   else
  933.     Result := 0; // To avoid compiler warnings
  934.   end;
  935. end;
  936. procedure TGIFTextExtension.SetCharWidthHeight(Index: integer; Value: BYTE);
  937. begin
  938.   case (Index) of
  939.     1: FPlainTextExtension.CellWidth := Value;
  940.     2: FPlainTextExtension.CellHeight := Value;
  941.   end;
  942. end;
  943. function TGIFTextExtension.GetColorIndex(Index: integer): BYTE;
  944. begin
  945.   case (Index) of
  946.     1: Result := FPlainTextExtension.TextFGColorIndex;
  947.     2: Result := FPlainTextExtension.TextBGColorIndex;
  948.   else
  949.     Result := 0; // To avoid compiler warnings
  950.   end;
  951. end;
  952. procedure TGIFTextExtension.SetColorIndex(Index: integer; Value: BYTE);
  953. begin
  954.   case (Index) of
  955.     1: FPlainTextExtension.TextFGColorIndex := Value;
  956.     2: FPlainTextExtension.TextBGColorIndex := Value;
  957.   end;
  958. end;
  959. procedure TGIFTextExtension.SaveToStream(Stream: TStream);
  960. begin
  961.   inherited SaveToStream(Stream);
  962.   Stream.Write(FPlainTextExtension, sizeof(FPlainTextExtension));
  963.   WriteStrings(Stream, FText);
  964. end;
  965. procedure TGIFTextExtension.LoadFromStream(Stream: TStream);
  966. begin
  967.   inherited LoadFromStream(Stream);
  968.   ReadCheck(Stream, FPlainTextExtension, sizeof(FPlainTextExtension));
  969.   ReadStrings(Stream, FText);
  970. end;
  971. ////////////////////////////////////////////////////////////////////////////////
  972. //
  973. // TGIFCommentExtension
  974. //
  975. ////////////////////////////////////////////////////////////////////////////////
  976. constructor TGIFCommentExtension.Create(ASubImage: TGIFSubImage);
  977. begin
  978.   inherited Create(ASubImage);
  979.   FText := TStringList.Create;
  980. end;
  981. destructor TGIFCommentExtension.Destroy;
  982. begin
  983.   FText.Free;
  984.   inherited Destroy;
  985. end;
  986. function TGIFCommentExtension.GetExtensionType: TGIFExtensionType;
  987. begin
  988.   Result := bsCommentExtension;
  989. end;
  990. procedure TGIFCommentExtension.SaveToStream(Stream: TStream);
  991. begin
  992.   inherited SaveToStream(Stream);
  993.   WriteStrings(Stream, FText);
  994. end;
  995. procedure TGIFCommentExtension.LoadFromStream(Stream: TStream);
  996. begin
  997.   inherited LoadFromStream(Stream);
  998.   ReadStrings(Stream, FText);
  999. end;
  1000. ////////////////////////////////////////////////////////////////////////////////
  1001. //
  1002. // TGIFApplicationExtension registration database
  1003. //
  1004. ////////////////////////////////////////////////////////////////////////////////
  1005. type
  1006.   PAppExtRec = ^TAppExtRec;
  1007.   TAppExtRec = record
  1008.     AppClass: TGIFAppExtensionClass;
  1009.     Ident: TGIFApplicationRec;
  1010.   end;
  1011.   TAppExtensionList = class(TList)
  1012.   public
  1013.     constructor Create;
  1014.     destructor Destroy; override;
  1015.     procedure Add(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass);
  1016.     function FindExt(eIdent: TGIFApplicationRec): TGIFAppExtensionClass;
  1017.     procedure Remove(eClass: TGIFAppExtensionClass);
  1018.   end;
  1019. constructor TAppExtensionList.Create;
  1020. const
  1021.   NSLoopIdent: array[0..1] of TGIFApplicationRec =
  1022.     ((Identifier: 'NETSCAPE'; Authentication: '2.0'),
  1023.      (Identifier: 'ANIMEXTS'; Authentication: '1.0'));
  1024. begin
  1025.   inherited Create;
  1026.   Add(NSLoopIdent[0], TGIFAppExtNSLoop);
  1027.   Add(NSLoopIdent[1], TGIFAppExtNSLoop);
  1028. end;
  1029. destructor TAppExtensionList.Destroy;
  1030. var
  1031.   I: Integer;
  1032. begin
  1033.   for I := 0 to Count-1 do
  1034.     Dispose(PAppExtRec(Items[I]));
  1035.   inherited Destroy;
  1036. end;
  1037. procedure TAppExtensionList.Add(eIdent: TGIFApplicationRec; eClass: TGIFAppExtensionClass);
  1038. var
  1039.   NewRec: PAppExtRec;
  1040. begin
  1041.   New(NewRec);
  1042.   NewRec^.Ident := eIdent;
  1043.   NewRec^.AppClass := eClass;
  1044.   inherited Add(NewRec);
  1045. end;
  1046. function TAppExtensionList.FindExt(eIdent: TGIFApplicationRec): TGIFAppExtensionClass;
  1047. var
  1048.   I: Integer;
  1049. begin
  1050.   for I := Count-1 downto 0 do
  1051.     with PAppExtRec(Items[I])^ do
  1052.       if CompareMem(@Ident, @eIdent, sizeof(TGIFApplicationRec)) then
  1053.       begin
  1054.         Result := AppClass;
  1055.         Exit;
  1056.       end;
  1057.   Result := nil;
  1058. end;
  1059. procedure TAppExtensionList.Remove(eClass: TGIFAppExtensionClass);
  1060. var
  1061.   I: Integer;
  1062.   P: PAppExtRec;
  1063. begin
  1064.   for I := Count-1 downto 0 do
  1065.   begin
  1066.     P := PAppExtRec(Items[I]);
  1067.     if P^.AppClass.InheritsFrom(eClass) then
  1068.     begin
  1069.       Dispose(P);
  1070.       Delete(I);
  1071.     end;
  1072.   end;
  1073. end;
  1074. var
  1075.   AppExtensionList: TAppExtensionList = nil;
  1076. function GetAppExtensionList: TAppExtensionList;
  1077. begin
  1078.   if (AppExtensionList = nil) then
  1079.     AppExtensionList := TAppExtensionList.Create;
  1080.   Result := AppExtensionList;
  1081. end;
  1082. class procedure TGIFApplicationExtension.RegisterExtension(eIdent: TGIFApplicationRec;
  1083.   eClass: TGIFAppExtensionClass);
  1084. begin
  1085.   GetAppExtensionList.Add(eIdent, eClass);
  1086. end;
  1087. class function TGIFApplicationExtension.FindSubExtension(Stream: TStream): TGIFExtensionClass;
  1088. var
  1089.   eIdent : TGIFApplicationRec;
  1090.   OldPos : longInt;
  1091.   Size : BYTE;
  1092. begin
  1093.   OldPos := Stream.Position;
  1094.   Result := nil;
  1095.   if (Stream.Read(Size, 1) <> 1) then
  1096.     exit;
  1097.   // Some old Adobe export filters mistakenly uses a value of 10
  1098.   if (Size = 10) then
  1099.   begin
  1100.     { TODO -oanme -cImprovement : replace with seek or read and check contents = 'Adobe' }
  1101.     if (Stream.Read(eIdent, 10) <> 10) then
  1102.       exit;
  1103.     Result := TGIFUnknownAppExtension;
  1104.     exit;
  1105.   end else
  1106.   if (Size <> sizeof(TGIFApplicationRec)) or
  1107.     (Stream.Read(eIdent, sizeof(eIdent)) <> sizeof(eIdent)) then
  1108.   begin
  1109.     Stream.Position := OldPos;
  1110.     Result := inherited FindSubExtension(Stream);
  1111.   end else
  1112.   begin
  1113.     Result := GetAppExtensionList.FindExt(eIdent);
  1114.     if (Result = nil) then
  1115.       Result := TGIFUnknownAppExtension;
  1116.   end;
  1117. end;
  1118. ////////////////////////////////////////////////////////////////////////////////
  1119. //
  1120. // TGIFApplicationExtension
  1121. //
  1122. ////////////////////////////////////////////////////////////////////////////////
  1123. constructor TGIFApplicationExtension.Create(ASubImage: TGIFSubImage);
  1124. begin
  1125.   inherited Create(ASubImage);
  1126.   FillChar(FIdent, sizeof(FIdent), 0);
  1127. end;
  1128. destructor TGIFApplicationExtension.Destroy;
  1129. begin
  1130.   inherited Destroy;
  1131. end;
  1132. function TGIFApplicationExtension.GetExtensionType: TGIFExtensionType;
  1133. begin
  1134.   Result := bsApplicationExtension;
  1135. end;
  1136. function TGIFApplicationExtension.GetAuthentication: string;
  1137. begin
  1138.   Result := FIdent.Authentication;
  1139. end;
  1140. procedure TGIFApplicationExtension.SetAuthentication(const Value: string);
  1141. begin
  1142.   if (Length(Value) < sizeof(TGIFAuthenticationCode)) then
  1143.     FillChar(FIdent.Authentication, sizeof(TGIFAuthenticationCode), 32);
  1144.   StrLCopy(@(FIdent.Authentication[0]), PChar(Value), sizeof(TGIFAuthenticationCode));
  1145. end;
  1146. function TGIFApplicationExtension.GetIdentifier: string;
  1147. begin
  1148.   Result := FIdent.Identifier;
  1149. end;
  1150. procedure TGIFApplicationExtension.SetIdentifier(const Value: string);
  1151. begin
  1152.   if (Length(Value) < sizeof(TGIFIdentifierCode)) then
  1153.     FillChar(FIdent.Identifier, sizeof(TGIFIdentifierCode), 32);
  1154.   StrLCopy(@(FIdent.Identifier[0]), PChar(Value), sizeof(TGIFIdentifierCode));
  1155. end;
  1156. procedure TGIFApplicationExtension.SaveToStream(Stream: TStream);
  1157. begin
  1158.   inherited SaveToStream(Stream);
  1159.   WriteByte(Stream, sizeof(FIdent)); // Block size
  1160.   Stream.Write(FIdent, sizeof(FIdent));
  1161.   SaveData(Stream);
  1162. end;
  1163. procedure TGIFApplicationExtension.LoadFromStream(Stream: TStream);
  1164. var
  1165.   i : integer;
  1166. begin
  1167.   inherited LoadFromStream(Stream);
  1168.   i := ReadByte(Stream);
  1169.   // Some old Adobe export filters mistakenly uses a value of 10
  1170.   if (i = 10) then
  1171.     FillChar(FIdent, sizeOf(FIdent), 0)
  1172.   else
  1173.     if (i < 11) then
  1174.       Error(sBadBlockSize);
  1175.   ReadCheck(Stream, FIdent, sizeof(FIdent));
  1176.   Dec(i, sizeof(FIdent));
  1177.   // Ignore extra data
  1178.   Stream.Seek(i, soFromCurrent);
  1179.   // ***FIXME***
  1180.   // If self class is TGIFApplicationExtension, this will cause an "abstract
  1181.   // error".
  1182.   // TGIFApplicationExtension.LoadData should read and ignore rest of block.
  1183.   LoadData(Stream);
  1184. end;
  1185. ////////////////////////////////////////////////////////////////////////////////
  1186. //
  1187. // TGIFUnknownAppExtension
  1188. //
  1189. ////////////////////////////////////////////////////////////////////////////////
  1190. constructor TGIFBlock.Create(ASize: integer);
  1191. begin
  1192.   inherited Create;
  1193.   FSize := ASize;
  1194.   GetMem(FData, FSize);
  1195.   FillChar(FData^, FSize, 0);
  1196. end;
  1197. destructor TGIFBlock.Destroy;
  1198. begin
  1199.   FreeMem(FData);
  1200.   inherited Destroy;
  1201. end;
  1202. procedure TGIFBlock.SaveToStream(Stream: TStream);
  1203. begin
  1204.   Stream.Write(FSize, 1);
  1205.   Stream.Write(FData^, FSize);
  1206. end;
  1207. procedure TGIFBlock.LoadFromStream(Stream: TStream);
  1208. begin
  1209.   ReadCheck(Stream, FData^, FSize);
  1210. end;
  1211. constructor TGIFUnknownAppExtension.Create(ASubImage: TGIFSubImage);
  1212. begin
  1213.   inherited Create(ASubImage);
  1214.   FBlocks := TList.Create;
  1215. end;
  1216. destructor TGIFUnknownAppExtension.Destroy;
  1217. var
  1218.   i : integer;
  1219. begin
  1220.   for i := 0 to FBlocks.Count-1 do
  1221.     TGIFBlock(FBlocks[i]).Free;
  1222.   FBlocks.Free;
  1223.   inherited Destroy;
  1224. end;
  1225. procedure TGIFUnknownAppExtension.SaveData(Stream: TStream);
  1226. var
  1227.   i : integer;
  1228. begin
  1229.   for i := 0 to FBlocks.Count-1 do
  1230.     TGIFBlock(FBlocks[i]).SaveToStream(Stream);
  1231.   // Terminating zero
  1232.   WriteByte(Stream, 0);
  1233. end;
  1234. procedure TGIFUnknownAppExtension.LoadData(Stream: TStream);
  1235. var
  1236.   b : BYTE;
  1237.   Block : TGIFBlock;
  1238.   i : integer;
  1239. begin
  1240.   // Zap old blocks
  1241.   for i := 0 to FBlocks.Count-1 do
  1242.     TGIFBlock(FBlocks[i]).Free;
  1243.   FBlocks.Clear;
  1244.   // Read blocks
  1245.   if (Stream.Read(b, 1) <> 1) then
  1246.     exit;
  1247.   while (b <> 0) do
  1248.   begin
  1249.     Block := TGIFBlock.Create(b);
  1250.     try
  1251.       Block.LoadFromStream(Stream);
  1252.     except
  1253.       Block.Free;
  1254.       raise;
  1255.     end;
  1256.     FBlocks.Add(Block);
  1257.     if (Stream.Read(b, 1) <> 1) then
  1258.       exit;
  1259.   end;
  1260. end;
  1261. ////////////////////////////////////////////////////////////////////////////////
  1262. //
  1263. //                      TGIFAppExtNSLoop
  1264. //
  1265. ////////////////////////////////////////////////////////////////////////////////
  1266. const
  1267.   // Netscape sub block types
  1268.   nbLoopExtension = 1;
  1269.   nbBufferExtension = 2;
  1270. constructor TGIFAppExtNSLoop.Create(ASubImage: TGIFSubImage);
  1271. const
  1272.   NSLoopIdent: TGIFApplicationRec = (Identifier: 'NETSCAPE'; Authentication: '2.0');
  1273. begin
  1274.   inherited Create(ASubImage);
  1275.   FIdent := NSLoopIdent;
  1276. end;
  1277. procedure TGIFAppExtNSLoop.SaveData(Stream: TStream);
  1278. begin
  1279.   // Write loop count
  1280.   WriteByte(Stream, 1 + sizeof(FLoops)); // Size of block
  1281.   WriteByte(Stream, nbLoopExtension); // Identify sub block as looping extension data
  1282.   Stream.Write(FLoops, sizeof(FLoops)); // Loop count
  1283.   // Write buffer size if specified
  1284.   if (FBufferSize > 0) then
  1285.   begin
  1286.     WriteByte(Stream, 1 + sizeof(FBufferSize)); // Size of block
  1287.     WriteByte(Stream, nbBufferExtension); // Identify sub block as buffer size data
  1288.     Stream.Write(FBufferSize, sizeof(FBufferSize)); // Buffer size
  1289.   end;
  1290.   WriteByte(Stream, 0); // Terminating zero
  1291. end;
  1292. procedure TGIFAppExtNSLoop.LoadData(Stream: TStream);
  1293. var
  1294.   BlockSize : integer;
  1295.   BlockType : integer;
  1296. begin
  1297.   // Read size of first block or terminating zero
  1298.   BlockSize := ReadByte(Stream);
  1299.   while (BlockSize <> 0) do
  1300.   begin
  1301.     BlockType := ReadByte(Stream);
  1302.     dec(BlockSize);
  1303.     case (BlockType AND $07) of
  1304.       nbLoopExtension:
  1305.         begin
  1306.           if (BlockSize < sizeof(FLoops)) then
  1307.             Error(sInvalidData);
  1308.           // Read loop count
  1309.           ReadCheck(Stream, FLoops, sizeof(FLoops));
  1310.           dec(BlockSize, sizeof(FLoops));
  1311.         end;
  1312.       nbBufferExtension:
  1313.         begin
  1314.           if (BlockSize < sizeof(FBufferSize)) then
  1315.             Error(sInvalidData);
  1316.           // Read buffer size
  1317.           ReadCheck(Stream, FBufferSize, sizeof(FBufferSize));
  1318.           dec(BlockSize, sizeof(FBufferSize));
  1319.         end;
  1320.     end;
  1321.     // Skip/ignore unread data
  1322.     if (BlockSize > 0) then
  1323.       Stream.Seek(BlockSize, soFromCurrent);
  1324.     // Read size of next block or terminating zero
  1325.     BlockSize := ReadByte(Stream);
  1326.   end;
  1327. end;
  1328. ////////////////////////////////////////////////////////////////////////////////
  1329. //
  1330. // TGIFImageList
  1331. //
  1332. ////////////////////////////////////////////////////////////////////////////////
  1333. function TGIFImageList.GetImage(Index: Integer): TGIFSubImage;
  1334. begin
  1335.   Result := TGIFSubImage(Items[Index]);
  1336. end;
  1337. procedure TGIFImageList.SetImage(Index: Integer; SubImage: TGIFSubImage);
  1338. begin
  1339.   Items[Index] := SubImage;
  1340. end;
  1341. procedure TGIFImageList.LoadFromStream(Stream: TStream; Parent: TObject);
  1342. var
  1343.   b : BYTE;
  1344.   SubImage : TGIFSubImage;
  1345. begin
  1346.   // Peek ahead to determine block type
  1347.   repeat
  1348.     if (Stream.Read(b, 1) <> 1) then
  1349.       exit;
  1350.   until (b <> 0); // Ignore 0 padding (non-compliant)
  1351.   while (b <> bsTrailer) do
  1352.   begin
  1353.     Stream.Seek(-1, soFromCurrent);
  1354.     if (b in [bsExtensionIntroducer, bsImageDescriptor]) then
  1355.     begin
  1356.       SubImage := TGIFSubImage.Create(Parent as TGIFImage);
  1357.       try
  1358.         SubImage.LoadFromStream(Stream);
  1359.         Add(SubImage);
  1360.         Image.Progress(Self, psRunning, MulDiv(Stream.Position, 100, Stream.Size),
  1361.           GIFImageRenderOnLoad, Rect(0,0,0,0), sProgressLoading);
  1362.       except
  1363.         SubImage.Free;
  1364.         raise;
  1365.       end;
  1366.     end else
  1367.     begin
  1368.       Warning(gsWarning, sBadBlock);
  1369.       break;
  1370.     end;
  1371.     repeat
  1372.       if (Stream.Read(b, 1) <> 1) then
  1373.         exit;
  1374.     until (b <> 0); // Ignore 0 padding (non-compliant)
  1375.   end;
  1376.   Stream.Seek(-1, soFromCurrent);
  1377. end;
  1378. procedure TGIFImageList.SaveToStream(Stream: TStream);
  1379. var
  1380.   i : integer;
  1381. begin
  1382.   for i := 0 to Count-1 do
  1383.   begin
  1384.     TGIFItem(Items[i]).SaveToStream(Stream);
  1385.     Image.Progress(Self, psRunning, MulDiv((i+1), 100, Count), False, Rect(0,0,0,0), sProgressSaving);
  1386.   end;
  1387. end;
  1388. ////////////////////////////////////////////////////////////////////////////////
  1389. //
  1390. // TGIFPainter
  1391. //
  1392. ////////////////////////////////////////////////////////////////////////////////
  1393. constructor TGIFPainter.CreateRef(Painter: PGIFPainter; AImage: TGIFImage;
  1394.   ACanvas: TCanvas; ARect: TRect; Options: TGIFDrawOptions);
  1395. begin
  1396.   Create(AImage, ACanvas, ARect, Options);
  1397.   PainterRef := Painter;
  1398.   if (PainterRef <> nil) then
  1399.     PainterRef^ := self;
  1400. end;
  1401. constructor TGIFPainter.Create(AImage: TGIFImage; ACanvas: TCanvas; ARect: TRect;
  1402.   Options: TGIFDrawOptions);
  1403. var
  1404.   i : integer;
  1405.   BackgroundColor : TColor;
  1406.   Disposals : set of TDisposalMethod;
  1407. begin
  1408.   inherited Create(True);
  1409.   FreeOnTerminate := True;
  1410.   Onterminate := DoOnTerminate;
  1411.   FImage := AImage;
  1412.   FCanvas := ACanvas;
  1413.   FRect := ARect;
  1414.   FActiveImage := -1;
  1415.   FDrawOptions := Options;
  1416.   FStarted := False;
  1417.   BackupBuffer := nil;
  1418.   FrameBuffer := nil;
  1419.   Background := nil;
  1420.   FEventHandle := 0;
  1421.   // This should be a parameter, but I think I've got enough of them already...
  1422.   FAnimationSpeed := FImage.AnimationSpeed;
  1423.   // An event handle is used for animation delays
  1424.   if (FDrawOptions >= [goAnimate, goAsync]) and (FImage.Images.Count > 1) and
  1425.     (FAnimationSpeed >= 0) then
  1426.     FEventHandle := CreateEvent(nil, False, False, nil);
  1427.   // Preprocessing of extensions to determine if we need frame buffers
  1428.   Disposals := [];
  1429.   if (FImage.DrawBackgroundColor = clNone) then
  1430.   begin
  1431.     if (FImage.GlobalColorMap.Count > 0) then
  1432.       BackgroundColor := FImage.BackgroundColor
  1433.     else
  1434.       BackgroundColor := ColorToRGB(clWindow);
  1435.   end else
  1436.     BackgroundColor := ColorToRGB(FImage.DrawBackgroundColor);
  1437.   // Need background buffer to clear on loop
  1438.   if (goClearOnLoop in FDrawOptions) then
  1439.     Include(Disposals, dmBackground);
  1440.   for i := 0 to FImage.Images.Count-1 do
  1441.     if (FImage.Images[i].GraphicControlExtension <> nil) then
  1442.       with (FImage.Images[i].GraphicControlExtension) do
  1443.         Include(Disposals, Disposal);
  1444.   // Need background buffer to draw transparent on background
  1445.   if (dmBackground in Disposals) and (goTransparent in FDrawOptions) then
  1446.   begin
  1447.     Background := TBitmap.Create;
  1448.     Background.Height := FRect.Bottom-FRect.Top;
  1449.     Background.Width := FRect.Right-FRect.Left;
  1450.     // Copy background immediately
  1451.     Background.Canvas.CopyMode := cmSrcCopy;
  1452.     Background.Canvas.CopyRect(Background.Canvas.ClipRect, FCanvas, FRect);
  1453.   end;
  1454.   // Need frame- and backup buffer to restore to previous and background
  1455.   if ((Disposals * [dmPrevious, dmBackground]) <> []) then
  1456.   begin
  1457.     BackupBuffer := TBitmap.Create;
  1458.     BackupBuffer.Height := FRect.Bottom-FRect.Top;
  1459.     BackupBuffer.Width := FRect.Right-FRect.Left;
  1460.     BackupBuffer.Canvas.CopyMode := cmSrcCopy;
  1461.     BackupBuffer.Canvas.Brush.Color := BackgroundColor;
  1462.     BackupBuffer.Canvas.Brush.Style := bsSolid;
  1463. {$IFDEF DEBUG}
  1464.     BackupBuffer.Canvas.Brush.Color := clBlack;
  1465.     BackupBuffer.Canvas.Brush.Style := bsDiagCross;
  1466. {$ENDIF}
  1467.     // Step 1: Copy destination to backup buffer
  1468.     //         Always executed before first frame and only once.
  1469.     BackupBuffer.Canvas.CopyRect(BackupBuffer.Canvas.ClipRect, FCanvas, FRect);
  1470.     FrameBuffer := TBitmap.Create;
  1471.     FrameBuffer.Height := FRect.Bottom-FRect.Top;
  1472.     FrameBuffer.Width := FRect.Right-FRect.Left;
  1473.     FrameBuffer.Canvas.CopyMode := cmSrcCopy;
  1474.     FrameBuffer.Canvas.Brush.Color := BackgroundColor;
  1475.     FrameBuffer.Canvas.Brush.Style := bsSolid;
  1476. {$IFDEF DEBUG}
  1477.     FrameBuffer.Canvas.Brush.Color := clBlack;
  1478.     FrameBuffer.Canvas.Brush.Style := bsDiagCross;
  1479. {$ENDIF}
  1480.   end;
  1481. end;
  1482. destructor TGIFPainter.Destroy;
  1483. begin
  1484.   // OnTerminate isn't called if we are running in main thread, so we must call
  1485.   // it manually
  1486.   if not(goAsync in DrawOptions) then
  1487.     DoOnTerminate(self);
  1488.   // Reraise any exptions that were eaten in the Execute method
  1489.   if (ExceptObject <> nil) then
  1490.     raise ExceptObject at ExceptAddress;
  1491.   inherited Destroy;
  1492. end;
  1493. procedure TGIFPainter.SetAnimationSpeed(Value: integer);
  1494. begin
  1495.   if (Value < 0) then
  1496.     Value := 0
  1497.   else if (Value > 1000) then
  1498.     Value := 1000;
  1499.   if (Value <> FAnimationSpeed) then
  1500.   begin
  1501.     FAnimationSpeed := Value;
  1502.     // Signal WaitForSingleObject delay to abort
  1503.     if (FEventHandle <> 0) then
  1504.       SetEvent(FEventHandle)
  1505.     else
  1506.       DoRestart := True;
  1507.   end;
  1508. end;
  1509. procedure TGIFPainter.SetActiveImage(const Value: integer);
  1510. begin
  1511.   if (Value >= 0) and (Value < FImage.Images.Count) then
  1512.     FActiveImage := Value;
  1513. end;
  1514. // Conditional Synchronize
  1515. procedure TGIFPainter.DoSynchronize(Method: TThreadMethod);
  1516. begin
  1517.   if (Terminated) then
  1518.     exit;
  1519.   if (goAsync in FDrawOptions) then
  1520.     // Execute Synchronized if requested...
  1521.     Synchronize(Method)
  1522.   else
  1523.     // ...Otherwise just execute in current thread (probably main thread)
  1524.     Method;
  1525. end;
  1526. // Delete frame buffers - Executed in main thread
  1527. procedure TGIFPainter.DoOnTerminate(Sender: TObject);
  1528. begin
  1529.   // It shouldn't really be nescessary to protect PainterRef in this manner
  1530.   // since we are running in the main thread at this point, but I'm a little
  1531.   // paranoid about the way PainterRef is being used...
  1532.   with Image.Painters.LockList do
  1533.     try
  1534.       // Zap pointer to self and remove from painter list
  1535.       if (PainterRef <> nil) and (PainterRef^ = self) then
  1536.         PainterRef^ := nil;
  1537.     finally
  1538.       Image.Painters.UnLockList;
  1539.     end;
  1540.   Image.Painters.Remove(self);
  1541.   FImage := nil;
  1542.   // Free buffers
  1543.   if (BackupBuffer <> nil) then
  1544.     BackupBuffer.Free;
  1545.   if (FrameBuffer <> nil) then
  1546.     FrameBuffer.Free;
  1547.   if (Background <> nil) then
  1548.     Background.Free;
  1549.   // Delete event handle
  1550.   if (FEventHandle <> 0) then
  1551.     CloseHandle(FEventHandle);
  1552. end;
  1553. // Event "dispatcher" - Executed in main thread
  1554. procedure TGIFPainter.DoEvent;
  1555. begin
  1556.   if (Assigned(FEvent)) then
  1557.     FEvent(self);
  1558. end;
  1559. // Non-buffered paint - Executed in main thread
  1560. procedure TGIFPainter.DoPaint;
  1561. begin
  1562.   FImage.Images[ActiveImage].Draw(FCanvas, FRect, (goTransparent in FDrawOptions),
  1563.     (goTile in FDrawOptions));
  1564.   FStarted := True;
  1565. end;
  1566. // Buffered paint - Executed in main thread
  1567. procedure TGIFPainter.DoPaintFrame;
  1568. var
  1569.   DrawDestination : TCanvas;
  1570.   DrawRect : TRect;
  1571.   DoStep2 ,
  1572.   DoStep3 ,
  1573.   DoStep5 ,
  1574.   DoStep6 : boolean;
  1575.   SavePal ,
  1576.   SourcePal : HPALETTE;
  1577.   procedure ClearBackup;
  1578.   var
  1579.     r ,
  1580.     Tile : TRect;
  1581.     FrameTop ,
  1582.     FrameHeight : integer;
  1583.     ImageWidth ,
  1584.     ImageHeight : integer;
  1585.   begin
  1586.     if (goTransparent in FDrawOptions) then
  1587.     begin
  1588.       // If the frame is transparent, we must remove it by copying the
  1589.       // background buffer over it
  1590.       if (goTile in FDrawOptions) then
  1591.       begin
  1592.         FrameTop := FImage.Images[ActiveImage].Top;
  1593.         FrameHeight := FImage.Images[ActiveImage].Height;
  1594.         ImageWidth := FImage.Width;
  1595.         ImageHeight := FImage.Height;
  1596.         Tile.Left := FRect.Left + FImage.Images[ActiveImage].Left;
  1597.         Tile.Right := Tile.Left + FImage.Images[ActiveImage].Width;
  1598.         while (Tile.Left < FRect.Right) do
  1599.         begin
  1600.           Tile.Top := FRect.Top + FrameTop;
  1601.           Tile.Bottom := Tile.Top + FrameHeight;
  1602.           while (Tile.Top < FRect.Bottom) do
  1603.           begin
  1604.             BackupBuffer.Canvas.CopyRect(Tile, Background.Canvas, Tile);
  1605.             Tile.Top := Tile.Top + ImageHeight;
  1606.             Tile.Bottom := Tile.Bottom + ImageHeight;
  1607.           end;
  1608.           Tile.Left := Tile.Left + ImageWidth;
  1609.           Tile.Right := Tile.Right + ImageWidth;
  1610.         end;
  1611.       end else
  1612.       begin
  1613.         r := FImage.Images[ActiveImage].ScaleRect(BackupBuffer.Canvas.ClipRect);
  1614.         BackupBuffer.Canvas.CopyRect(r, Background.Canvas, r)
  1615.       end;
  1616.     end else
  1617.     begin
  1618.       // If the frame isn't transparent, we just clear the area covered by
  1619.       // it to the background color.
  1620.       // Tile the background unless the frame covers all of the image
  1621.       if (goTile in FDrawOptions) and
  1622.         ((FImage.Width <> FImage.Images[ActiveImage].Width) and
  1623.          (FImage.height <> FImage.Images[ActiveImage].Height)) then
  1624.       begin
  1625.         FrameTop := FImage.Images[ActiveImage].Top;
  1626.         FrameHeight := FImage.Images[ActiveImage].Height;
  1627.         ImageWidth := FImage.Width;
  1628.         ImageHeight := FImage.Height;
  1629.         // ***FIXME*** I don't think this does any difference
  1630.         BackupBuffer.Canvas.Brush.Color := FImage.DrawBackgroundColor;
  1631.         Tile.Left := FRect.Left + FImage.Images[ActiveImage].Left;
  1632.         Tile.Right := Tile.Left + FImage.Images[ActiveImage].Width;
  1633.         while (Tile.Left < FRect.Right) do
  1634.         begin
  1635.           Tile.Top := FRect.Top + FrameTop;
  1636.           Tile.Bottom := Tile.Top + FrameHeight;
  1637.           while (Tile.Top < FRect.Bottom) do
  1638.           begin
  1639.             BackupBuffer.Canvas.FillRect(Tile);
  1640.             Tile.Top := Tile.Top + ImageHeight;
  1641.             Tile.Bottom := Tile.Bottom + ImageHeight;
  1642.           end;
  1643.           Tile.Left := Tile.Left + ImageWidth;
  1644.           Tile.Right := Tile.Right + ImageWidth;
  1645.         end;
  1646.       end else
  1647.         BackupBuffer.Canvas.FillRect(FImage.Images[ActiveImage].ScaleRect(FRect));
  1648.     end;
  1649.   end;
  1650. begin
  1651.   if (goValidateCanvas in FDrawOptions) then
  1652.     if (GetObjectType(ValidateDC) <> OBJ_DC) then
  1653.     begin
  1654.       Terminate;
  1655.       exit;
  1656.     end;
  1657.   DrawDestination := nil;
  1658.   DoStep2 := (goClearOnLoop in FDrawOptions) and (FActiveImage = 0);
  1659.   DoStep3 := False;
  1660.   DoStep5 := False;
  1661.   DoStep6 := False;
  1662. {
  1663. Disposal mode algorithm:
  1664. Step 1: Copy destination to backup buffer
  1665.         Always executed before first frame and only once.
  1666.         Done in constructor.
  1667. Step 2: Clear previous frame (implementation is same as step 6)
  1668.         Done implicitly by implementation.
  1669.         Only done explicitly on first frame if goClearOnLoop option is set.
  1670. Step 3: Copy backup buffer to frame buffer
  1671. Step 4: Draw frame
  1672. Step 5: Copy buffer to destination
  1673. Step 6: Clear frame from backup buffer
  1674. +------------+------------------+---------------------+------------------------+
  1675. |New    Old |  dmNone          |  dmBackground       |  dmPrevious            |
  1676. +------------+------------------+---------------------+------------------------+
  1677. |dmNone      |                  |                     |                        |
  1678. |            |4. Paint on backup|4. Paint on backup   |4. Paint on backup      |
  1679. |            |5. Restore        |5. Restore           |5. Restore              |
  1680. +------------+------------------+---------------------+------------------------+
  1681. |dmBackground|                  |                     |                        |
  1682. |            |4. Paint on backup|4. Paint on backup   |4. Paint on backup      |
  1683. |            |5. Restore        |5. Restore           |5. Restore              |
  1684. |            |6. Clear backup   |6. Clear backup      |6. Clear backup         |
  1685. +------------+------------------+---------------------+------------------------+
  1686. |dmPrevious  |                  |                     |                        |
  1687. |            |                  |3. Copy backup to buf|3. Copy backup to buf   |
  1688. |            |4. Paint on dest  |4. Paint on buf      |4. Paint on buf         |
  1689. |            |                  |5. Copy buf to dest  |5. Copy buf to dest     |
  1690. +------------+------------------+---------------------+------------------------+
  1691. }
  1692.   case (Disposal) of
  1693.     dmNone, dmNoDisposal:
  1694.       begin
  1695.         DrawDestination := BackupBuffer.Canvas;
  1696.         DrawRect := BackupBuffer.Canvas.ClipRect;
  1697.         DoStep5 := True;
  1698.       end;
  1699.     dmBackground:
  1700.       begin
  1701.         DrawDestination := BackupBuffer.Canvas;
  1702.         DrawRect := BackupBuffer.Canvas.ClipRect;
  1703.         DoStep5 := True;
  1704.         DoStep6 := True;
  1705.       end;
  1706.     dmPrevious:
  1707.       case (OldDisposal) of
  1708.         dmNone, dmNoDisposal:
  1709.         begin
  1710.           DrawDestination := FCanvas;
  1711.           DrawRect := FRect;
  1712.         end;
  1713.         dmBackground, dmPrevious:
  1714.         begin
  1715.           DrawDestination := FrameBuffer.Canvas;
  1716.           DrawRect := FrameBuffer.Canvas.ClipRect;
  1717.           DoStep3 := True;
  1718.           DoStep5 := True;
  1719.         end;
  1720.       end;
  1721.   end;
  1722.   // Find source palette
  1723.   SourcePal := FImage.Images[ActiveImage].Palette;
  1724.   if (SourcePal = 0) then
  1725.     SourcePal := SystemPalette16; // This should never happen
  1726.   SavePal := SelectPalette(DrawDestination.Handle, SourcePal, False);
  1727.   RealizePalette(DrawDestination.Handle);
  1728.   // Step 2: Clear previous frame
  1729.   if (DoStep2) then
  1730.    ClearBackup;
  1731.   // Step 3: Copy backup buffer to frame buffer
  1732.   if (DoStep3) then
  1733.     FrameBuffer.Canvas.CopyRect(FrameBuffer.Canvas.ClipRect,
  1734.       BackupBuffer.Canvas, BackupBuffer.Canvas.ClipRect);
  1735.   // Step 4: Draw frame
  1736.   if (DrawDestination <> nil) then
  1737.     FImage.Images[ActiveImage].Draw(DrawDestination, DrawRect,
  1738.       (goTransparent in FDrawOptions), (goTile in FDrawOptions));
  1739.   // Step 5: Copy buffer to destination
  1740.   if (DoStep5) then
  1741.   begin
  1742.     FCanvas.CopyMode := cmSrcCopy;
  1743.     FCanvas.CopyRect(FRect, DrawDestination, DrawRect);
  1744.   end;
  1745.   if (SavePal <> 0) then
  1746.     SelectPalette(DrawDestination.Handle, SavePal, False);
  1747.   // Step 6: Clear frame from backup buffer
  1748.   if (DoStep6) then
  1749.    ClearBackup;
  1750.   FStarted := True;
  1751. end;
  1752. // Prefetch bitmap
  1753. // Used to force the GIF image to be rendered as a bitmap
  1754. {$ifdef SERIALIZE_RENDER}
  1755. procedure TGIFPainter.PrefetchBitmap;
  1756. begin
  1757.   // Touch current bitmap to force bitmap to be rendered
  1758.   if not((FImage.Images[ActiveImage].Empty) or (FImage.Images[ActiveImage].HasBitmap)) then
  1759.     FImage.Images[ActiveImage].Bitmap;
  1760. end;
  1761. {$endif}
  1762. // Main thread execution loop - This is where it all happens...
  1763. procedure TGIFPainter.Execute;
  1764. var
  1765.   i : integer;
  1766.   LoopCount ,
  1767.   LoopPoint : integer;
  1768.   Looping : boolean;
  1769.   Ext : TGIFExtension;
  1770.   Msg : TMsg;
  1771.   Delay ,
  1772.   OldDelay ,
  1773.   DelayUsed : longInt;
  1774.   DelayStart ,
  1775.   NewDelayStart : DWORD;
  1776.   procedure FireEvent(Event: TNotifyEvent);
  1777.   begin
  1778.     if not(Assigned(Event)) then
  1779.       exit;
  1780.     FEvent := Event;
  1781.     try
  1782.       DoSynchronize(DoEvent);
  1783.     finally
  1784.       FEvent := nil;
  1785.     end;
  1786.   end;
  1787. begin
  1788. {
  1789.   Disposal:
  1790.     dmNone: Same as dmNodisposal
  1791.     dmNoDisposal: Do not dispose
  1792.     dmBackground: Clear with background color *)
  1793.     dmPrevious: Previous image
  1794.     *) Note: Background color should either be a BROWSER SPECIFIED Background
  1795.        color (DrawBackgroundColor) or the background image if any frames are
  1796.        transparent.
  1797. }
  1798.   try
  1799.     try
  1800.       if (goValidateCanvas in FDrawOptions) then
  1801.         ValidateDC := FCanvas.Handle;
  1802.       DoRestart := True;
  1803.       // Loop to restart paint
  1804.       while (DoRestart) and not(Terminated) do
  1805.       begin
  1806.         FActiveImage := 0;
  1807.         // Fire OnStartPaint event
  1808.         // Note: ActiveImage may be altered by the event handler
  1809.         FireEvent(FOnStartPaint);
  1810.         FStarted := False;
  1811.         DoRestart := False;
  1812.         LoopCount := 1;
  1813.         LoopPoint := FActiveImage;
  1814.         Looping := False;
  1815.         if (goAsync in DrawOptions) then
  1816.           Delay := 0
  1817.         else
  1818.           Delay := 1; // Dummy to process messages
  1819.         OldDisposal := dmNoDisposal;
  1820.         // Fetch delay start time
  1821.         DelayStart := timeGetTime;
  1822.         OldDelay := 0;
  1823.         // Loop to loop - duh!
  1824.         while ((LoopCount <> 0) or (goLoopContinously in DrawOptions)) and
  1825.           not(Terminated or DoRestart) do
  1826.         begin
  1827.           FActiveImage := LoopPoint;
  1828.           // Fire OnLoopPaint event
  1829.           // Note: ActiveImage may be altered by the event handler
  1830.           if (FStarted) then
  1831.             FireEvent(FOnLoop);
  1832.           // Loop to animate
  1833.           while (ActiveImage < FImage.Images.Count) and not(Terminated or DoRestart) do
  1834.           begin
  1835.             // Ignore empty images
  1836.             if (FImage.Images[ActiveImage].Empty) then
  1837.               break;
  1838.             // Delay from previous image
  1839.             if (Delay > 0) then
  1840.             begin
  1841.               // Prefetch frame bitmap
  1842. {$ifdef SERIALIZE_RENDER}
  1843.               DoSynchronize(PrefetchBitmap);
  1844. {$else}
  1845.               FImage.Images[ActiveImage].Bitmap;
  1846. {$endif}
  1847.               // Calculate inter frame delay
  1848.               NewDelayStart := timeGetTime;
  1849.               if (FAnimationSpeed > 0) then
  1850.               begin
  1851.                 // Calculate number of mS used in prefetch and display
  1852.                 try
  1853.                   DelayUsed := integer(NewDelayStart-DelayStart)-OldDelay;
  1854.                   // Prevent feedback oscillations caused by over/undercompensation.
  1855.                   DelayUsed := DelayUsed DIV 2;
  1856.                   // Convert delay value to mS and...
  1857.                   // ...Adjust for time already spent converting GIF to bitmap and...
  1858.                   // ...Adjust for Animation Speed factor.
  1859.                   Delay := MulDiv(Delay * GIFDelayExp - DelayUsed, 100, FAnimationSpeed);
  1860.                   OldDelay := Delay;
  1861.                 except
  1862.                   Delay := GIFMaximumDelay * GIFDelayExp;
  1863.                   OldDelay := 0;
  1864.                 end;
  1865.               end else
  1866.               begin
  1867.                 if (goAsync in DrawOptions) then
  1868.                   Delay := longInt(INFINITE)
  1869.                 else
  1870.                   Delay := GIFMaximumDelay * GIFDelayExp;
  1871.               end;
  1872.               // Fetch delay start time
  1873.               DelayStart := NewDelayStart;
  1874.               // Sleep in one chunk if we are running in a thread
  1875.               if (goAsync in DrawOptions) then
  1876.               begin
  1877.                 // Use of WaitForSingleObject allows TGIFPainter.Stop to wake us up
  1878.                 if (Delay > 0) or (FAnimationSpeed = 0) then
  1879.                 begin
  1880.                   if (WaitForSingleObject(FEventHandle, DWORD(Delay)) <> WAIT_TIMEOUT) then
  1881.                   begin
  1882.                     // Don't use interframe delay feedback adjustment if delay
  1883.                     // were prematurely aborted (e.g. because the animation
  1884.                     // speed were changed)
  1885.                     OldDelay := 0;
  1886.                     DelayStart := longInt(timeGetTime);
  1887.                   end;
  1888.                 end;
  1889.               end else
  1890.               begin
  1891.                 if (Delay <= 0) then
  1892.                   Delay := 1;
  1893.                 // Fetch start time
  1894.                 NewDelayStart := timeGetTime;
  1895.                 // If we are not running in a thread we Sleep in small chunks
  1896.                 // and give the user a chance to abort
  1897.                 while (Delay > 0) and not(Terminated or DoRestart) do
  1898.                 begin
  1899.                   if (Delay < 100) then
  1900.                     Sleep(Delay)
  1901.                   else
  1902.                     Sleep(100);
  1903.                   // Calculate number of mS delayed in this chunk
  1904.                   DelayUsed := integer(timeGetTime - NewDelayStart);
  1905.                   dec(Delay, DelayUsed);
  1906.                   // Reset start time for chunk
  1907.                   NewDelaySTart := timeGetTime;
  1908.                   // Application.ProcessMessages wannabe
  1909.                   while (not(Terminated or DoRestart)) and
  1910.                     (PeekMessage(Msg, 0, 0, 0, PM_REMOVE)) do
  1911.                   begin
  1912.                     if (Msg.Message <> WM_QUIT) then
  1913.                     begin
  1914.                       TranslateMessage(Msg);
  1915.                       DispatchMessage(Msg);
  1916.                     end else
  1917.                     begin
  1918.                       // Put WM_QUIT back in queue and get out of here fast
  1919.                       PostQuitMessage(Msg.WParam);
  1920.                       Terminate;
  1921.                     end;
  1922.                   end;
  1923.                 end;
  1924.               end;
  1925.             end else
  1926.               Sleep(0); // Yield
  1927.             if (Terminated) then
  1928.               break;
  1929.             // Fire OnPaint event
  1930.             // Note: ActiveImage may be altered by the event handler
  1931.             FireEvent(FOnPaint);
  1932.             if (Terminated) then
  1933.               break;
  1934.             // Pre-draw processing of extensions
  1935.             Disposal := dmNoDisposal;
  1936.             for i := 0 to FImage.Images[ActiveImage].Extensions.Count-1 do
  1937.             begin
  1938.               Ext := FImage.Images[ActiveImage].Extensions[i];
  1939.               if (Ext is TGIFAppExtNSLoop) then
  1940.               begin
  1941.                 // Recursive loops not supported (or defined)
  1942.                 if (Looping) then
  1943.                   continue;
  1944.                 Looping := True;
  1945.                 LoopCount := TGIFAppExtNSLoop(Ext).Loops;
  1946.                 if ((LoopCount = 0) or (goLoopContinously in DrawOptions)) and
  1947.                   (goAsync in DrawOptions) then
  1948.                   LoopCount := -1; // Infinite if running in separate thread
  1949. {$IFNDEF STRICT_MOZILLA}
  1950.                 // Loop from this image and on
  1951.                 // Note: This is not standard behavior
  1952.                 LoopPoint := ActiveImage;
  1953. {$ENDIF}
  1954.               end else
  1955.               if (Ext is TGIFGraphicControlExtension) then
  1956.                 Disposal := TGIFGraphicControlExtension(Ext).Disposal;
  1957.             end;
  1958.             // Paint the image
  1959.             if (BackupBuffer <> nil) then
  1960.               DoSynchronize(DoPaintFrame)
  1961.             else
  1962.               DoSynchronize(DoPaint);
  1963.             OldDisposal := Disposal;
  1964.             if (Terminated) then
  1965.               break;
  1966.             Delay := GIFDefaultDelay; // Default delay
  1967.             // Post-draw processing of extensions
  1968.             if (FImage.Images[ActiveImage].GraphicControlExtension <> nil) then
  1969.               if (FImage.Images[ActiveImage].GraphicControlExtension.Delay > 0) then
  1970.               begin
  1971.                 Delay := FImage.Images[ActiveImage].GraphicControlExtension.Delay;
  1972.                 // Enforce minimum animation delay in compliance with Mozilla
  1973.                 if (Delay < GIFMinimumDelay) then
  1974.                   Delay := GIFMinimumDelay;
  1975.                 // Do not delay more than 10 seconds if running in main thread
  1976.                 if (Delay > GIFMaximumDelay) and not(goAsync in DrawOptions) then
  1977.                   Delay := GIFMaximumDelay; // Max 10 seconds
  1978.               end;
  1979.             // Fire OnAfterPaint event
  1980.             // Note: ActiveImage may be altered by the event handler
  1981.             i := FActiveImage;
  1982.             FireEvent(FOnAfterPaint);
  1983.             if (Terminated) then
  1984.               break;
  1985.             // Don't increment frame counter if event handler modified
  1986.             // current frame
  1987.             if (FActiveImage = i) then
  1988.               Inc(FActiveImage);
  1989.             // Nothing more to do unless we are animating
  1990.             if not(goAnimate in DrawOptions) then
  1991.               break;
  1992.           end;
  1993.           if (LoopCount > 0) then
  1994.             Dec(LoopCount);
  1995.           if ([goAnimate, goLoop] * DrawOptions <> [goAnimate, goLoop]) then
  1996.             break;
  1997.         end;
  1998.       end;
  1999.       FActiveImage := -1;
  2000.       // Fire OnEndPaint event
  2001.       FireEvent(FOnEndPaint);
  2002.     finally
  2003.       // If we are running in the main thread we will have to zap our self
  2004.       if not(goAsync in DrawOptions) then
  2005.         Free;
  2006.     end;
  2007.   except
  2008.     on E: Exception do
  2009.     begin
  2010.       // Eat exception and terminate thread...
  2011.       // If we allow the exception to abort the thread at this point, the
  2012.       // application will hang since the thread destructor will never be called
  2013.       // and the application will wait forever for the thread to die!
  2014.       Terminate;
  2015.       // Clone exception
  2016.       ExceptObject := E.Create(E.Message);
  2017.       ExceptAddress := ExceptAddr;
  2018.     end;
  2019.   end;
  2020. end;
  2021. procedure TGIFPainter.Start;
  2022. begin
  2023.   if (goAsync in FDrawOptions) then
  2024.     Resume;
  2025. end;
  2026. procedure TGIFPainter.Stop;
  2027. begin
  2028.   Terminate;
  2029.   if (goAsync in FDrawOptions) then
  2030.   begin
  2031.     // Signal WaitForSingleObject delay to abort
  2032.     if (FEventHandle <> 0) then
  2033.       SetEvent(FEventHandle);
  2034.     Priority := tpNormal;
  2035.     if (Suspended) then
  2036.       Resume; // Must be running before we can terminate
  2037.   end;
  2038. end;
  2039. procedure TGIFPainter.Restart;
  2040. begin
  2041.   DoRestart := True;
  2042.   if (Suspended) and (goAsync in FDrawOptions) then
  2043.     Resume; // Must be running before we can terminate
  2044. end;
  2045. ////////////////////////////////////////////////////////////////////////////////
  2046. //
  2047. // TColorMapOptimizer
  2048. //
  2049. ////////////////////////////////////////////////////////////////////////////////
  2050. // Used by TGIFImage to optimize local color maps to a single global color map.
  2051. // The following algorithm is used:
  2052. // 1) Build a histogram for each image
  2053. // 2) Merge histograms
  2054. // 3) Sum equal colors and adjust max # of colors
  2055. // 4) Map entries > max to entries <= 256
  2056. // 5) Build new color map
  2057. // 6) Map images to new color map
  2058. ////////////////////////////////////////////////////////////////////////////////
  2059. type
  2060.   POptimizeEntry = ^TOptimizeEntry;
  2061.   TColorRec = record
  2062.   case byte of
  2063.     0: (Value: integer);
  2064.     1: (Color: TGIFColor);
  2065.     2: (SameAs: POptimizeEntry); // Used if TOptimizeEntry.Count = 0
  2066.   end;
  2067.   TOptimizeEntry = record
  2068.     Count : integer; // Usage count
  2069.     OldIndex : integer; // Color OldIndex
  2070.     NewIndex : integer; // NewIndex color OldIndex
  2071.     Color : TColorRec; // Color value
  2072.   end;
  2073.   TOptimizeEntries = array[0..255] of TOptimizeEntry;
  2074.   POptimizeEntries = ^TOptimizeEntries;
  2075.   THistogram = class(TObject)
  2076.   private
  2077.     PHistogram : POptimizeEntries;
  2078.     FCount : integer;
  2079.     FColorMap : TGIFColorMap;
  2080.     FList : TList;
  2081.     FImages : TList;
  2082.   public
  2083.     constructor Create(AColorMap: TGIFColorMap);
  2084.     destructor Destroy; override;
  2085.     function ProcessSubImage(Image: TGIFSubImage): boolean;
  2086.     function Prune: integer;
  2087.     procedure MapImages(UseTransparency: boolean; NewTransparentColorIndex: byte);
  2088.     property Count: integer read FCount;
  2089.     property ColorMap: TGIFColorMap read FColorMap;
  2090.     property List: TList read FList;
  2091.   end;
  2092.   TColorMapOptimizer = class(TObject)
  2093.   private
  2094.     FImage : TGIFImage;
  2095.     FHistogramList : TList;
  2096.     FHistogram : TList;
  2097.     FColorMap : TColorMap;
  2098.     FFinalCount : integer;
  2099.     FUseTransparency : boolean;
  2100.     FNewTransparentColorIndex: byte;
  2101.   protected
  2102.     procedure ProcessImage;
  2103.     procedure MergeColors;
  2104.     procedure MapColors;
  2105.     procedure ReplaceColorMaps;
  2106.   public
  2107.     constructor Create(AImage: TGIFImage);
  2108.     destructor Destroy; override;
  2109.     procedure Optimize;
  2110.   end;
  2111. function CompareColor(Item1, Item2: Pointer): integer;
  2112. begin
  2113.   Result := POptimizeEntry(Item2)^.Color.Value - POptimizeEntry(Item1)^.Color.Value;
  2114. end;
  2115. function CompareCount(Item1, Item2: Pointer): integer;
  2116. begin
  2117.   Result := POptimizeEntry(Item2)^.Count - POptimizeEntry(Item1)^.Count;
  2118. end;
  2119. constructor THistogram.Create(AColorMap: TGIFColorMap);
  2120. var
  2121.   i : integer;
  2122. begin
  2123.   inherited Create;
  2124.   FCount := AColorMap.Count;
  2125.   FColorMap := AColorMap;
  2126.   FImages := TList.Create;
  2127.   // Allocate memory for histogram
  2128.   GetMem(PHistogram, FCount * sizeof(TOptimizeEntry));
  2129.   FList := TList.Create;
  2130.   FList.Capacity := FCount;
  2131.   // Move data to histogram and initialize
  2132.   for i := 0 to FCount-1 do
  2133.     with PHistogram^[i] do
  2134.     begin
  2135.       FList.Add(@PHistogram^[i]);
  2136.       OldIndex := i;
  2137.       Count := 0;
  2138.       Color.Value := 0;
  2139.       Color.Color := AColorMap.Data^[i];
  2140.       NewIndex := 256; // Used to signal unmapped
  2141.     end;
  2142. end;
  2143. destructor THistogram.Destroy;
  2144. begin
  2145.   FImages.Free;
  2146.   FList.Free;
  2147.   FreeMem(PHistogram);
  2148.   inherited Destroy;
  2149. end;
  2150. //: Build a color histogram
  2151. function THistogram.ProcessSubImage(Image: TGIFSubImage): boolean;
  2152. var
  2153.   Size : integer;
  2154.   Pixel : PChar;
  2155.   IsTransparent ,
  2156.   WasTransparent : boolean;
  2157.   OldTransparentColorIndex: byte;
  2158. begin
  2159.   Result := False;
  2160.   if (Image.Empty) then
  2161.     exit;
  2162.   FImages.Add(Image);
  2163.   Pixel := Image.data;
  2164.   Size := Image.Width * Image.Height;
  2165.   IsTransparent := Image.Transparent;
  2166.   if (IsTransparent) then
  2167.     OldTransparentColorIndex := Image.GraphicControlExtension.TransparentColorIndex
  2168.   else
  2169.     OldTransparentColorIndex := 0; // To avoid compiler warning
  2170.   WasTransparent := False;
  2171.   (*
  2172.   ** Sum up usage count for each color
  2173.   *)
  2174.   while (Size > 0) do
  2175.   begin
  2176.     // Ignore transparent pixels
  2177.     if (not IsTransparent) or (ord(Pixel^) <> OldTransparentColorIndex) then
  2178.     begin
  2179.       // Check for invalid color index
  2180.       if (ord(Pixel^) >= FCount) then
  2181.       begin
  2182.         Pixel^ := #0; // ***FIXME*** Isn't this an error condition?
  2183.         Image.Warning(gsWarning, sInvalidColor);
  2184.       end;
  2185.       with PHistogram^[ord(Pixel^)] do
  2186.       begin
  2187.         // Stop if any color reaches the max count
  2188.         if (Count = high(integer)) then
  2189.           break;
  2190.         inc(Count);
  2191.       end;
  2192.     end else
  2193.       WasTransparent := WasTransparent or IsTransparent;
  2194.     inc(Pixel);
  2195.     dec(Size);
  2196.   end;
  2197.   (*
  2198.   ** Clear frames transparency flag if the frame claimed to
  2199.   ** be transparent, but wasn't
  2200.   *)
  2201.   if (IsTransparent and not WasTransparent) then
  2202.   begin
  2203.     Image.GraphicControlExtension.TransparentColorIndex := 0;
  2204.     Image.GraphicControlExtension.Transparent := False;
  2205.   end;
  2206.   Result := WasTransparent;
  2207. end;
  2208. //: Removed unused color entries from the histogram
  2209. function THistogram.Prune: integer;
  2210. var
  2211.   i, j : integer;
  2212. begin
  2213.   (*
  2214.   **  Sort by usage count
  2215.   *)
  2216.   FList.Sort(CompareCount);
  2217.   (*
  2218.   **  Determine number of used colors
  2219.   *)
  2220.   for i := 0 to FCount-1 do
  2221.     // Find first unused color entry
  2222.     if (POptimizeEntry(FList[i])^.Count = 0) then
  2223.     begin
  2224.       // Zap unused colors
  2225.       for j := i to FCount-1 do
  2226.         POptimizeEntry(FList[j])^.Count := -1; // Use -1 to signal unused entry
  2227.       // Remove unused entries
  2228.       FCount := i;
  2229.       FList.Count := FCount;
  2230.       break;
  2231.     end;
  2232.   Result := FCount;
  2233. end;
  2234. //: Convert images from old color map to new color map
  2235. procedure THistogram.MapImages(UseTransparency: boolean; NewTransparentColorIndex: byte);
  2236. var
  2237.   i : integer;
  2238.   Size : integer;
  2239.   Pixel : PChar;
  2240.   ReverseMap : array[byte] of byte;
  2241.   IsTransparent : boolean;
  2242.   OldTransparentColorIndex: byte;
  2243. begin
  2244.   (*
  2245.   ** Build NewIndex map
  2246.   *)
  2247.   for i := 0 to List.Count-1 do
  2248.     ReverseMap[POptimizeEntry(List[i])^.OldIndex] := POptimizeEntry(List[i])^.NewIndex;
  2249.   (*
  2250.   **  Reorder all images using this color map
  2251.   *)
  2252.   for i := 0 to FImages.Count-1 do
  2253.     with TGIFSubImage(FImages[i]) do
  2254.     begin
  2255.       Pixel := Data;
  2256.       Size := Width * Height;
  2257.       // Determine frame transparency
  2258.       IsTransparent := (Transparent) and (UseTransparency);
  2259.       if (IsTransparent) then
  2260.       begin
  2261.         OldTransparentColorIndex := GraphicControlExtension.TransparentColorIndex;
  2262.         // Map transparent color
  2263.         GraphicControlExtension.TransparentColorIndex := NewTransparentColorIndex;
  2264.       end else
  2265.         OldTransparentColorIndex := 0; // To avoid compiler warning
  2266.       // Map all pixels to new color map
  2267.       while (Size > 0) do
  2268.       begin
  2269.         // Map transparent pixels to the new transparent color index and...
  2270.         if (IsTransparent) and (ord(Pixel^) = OldTransparentColorIndex) then
  2271.           Pixel^ := char(NewTransparentColorIndex)
  2272.         else
  2273.           // ... all other pixels to their new color index
  2274.           Pixel^ := char(ReverseMap[ord(Pixel^)]);
  2275.         dec(size);
  2276.         inc(Pixel);
  2277.       end;
  2278.     end;
  2279. end;
  2280. constructor TColorMapOptimizer.Create(AImage: TGIFImage);
  2281. begin
  2282.   inherited Create;
  2283.   FImage := AImage;
  2284.   FHistogramList := TList.Create;
  2285.   FHistogram := TList.Create;
  2286. end;
  2287. destructor TColorMapOptimizer.Destroy;
  2288. var
  2289.   i : integer;
  2290. begin
  2291.   FHistogram.Free;
  2292.   for i := FHistogramList.Count-1 downto 0 do
  2293.     THistogram(FHistogramList[i]).Free;
  2294.   FHistogramList.Free;
  2295.   inherited Destroy;
  2296. end;
  2297. procedure TColorMapOptimizer.ProcessImage;
  2298. var
  2299.   Hist : THistogram;
  2300.   i : integer;
  2301.   ProcessedImage : boolean;
  2302. begin
  2303.   FUseTransparency := False;
  2304.   (*
  2305.   ** First process images using global color map
  2306.   *)
  2307.   if (FImage.GlobalColorMap.Count > 0) then
  2308.   begin
  2309.     Hist := THistogram.Create(FImage.GlobalColorMap);
  2310.     ProcessedImage := False;
  2311.     // Process all images that are using the global color map
  2312.     for i := 0 to FImage.Images.Count-1 do
  2313.       if (FImage.Images[i].ColorMap.Count = 0) and (not FImage.Images[i].Empty) then
  2314.       begin
  2315.         ProcessedImage := True;
  2316.       // Note: Do not change order of statements. Shortcircuit evaluation not desired!
  2317.         FUseTransparency := Hist.ProcessSubImage(FImage.Images[i]) or FUseTransparency;
  2318.       end;
  2319.     // Keep the histogram if any images used the global color map...
  2320.     if (ProcessedImage) then
  2321.       FHistogramList.Add(Hist)
  2322.     else // ... otherwise delete it
  2323.       Hist.Free;
  2324.   end;
  2325.   (*
  2326.   ** Next process images that have a local color map
  2327.   *)
  2328.   for i := 0 to FImage.Images.Count-1 do
  2329.     if (FImage.Images[i].ColorMap.Count > 0) and (not FImage.Images[i].Empty) then
  2330.     begin
  2331.       Hist := THistogram.Create(FImage.Images[i].ColorMap);
  2332.       FHistogramList.Add(Hist);
  2333.       // Note: Do not change order of statements. Shortcircuit evaluation not desired!
  2334.       FUseTransparency := Hist.ProcessSubImage(FImage.Images[i]) or FUseTransparency;
  2335.     end;
  2336. end;
  2337. procedure TColorMapOptimizer.MergeColors;
  2338. var
  2339.   Entry, SameEntry : POptimizeEntry;
  2340.   i : integer;
  2341. begin
  2342.   (*
  2343.   **  Sort by color value
  2344.   *)
  2345.   FHistogram.Sort(CompareColor);
  2346.   (*
  2347.   **  Merge same colors
  2348.   *)
  2349.   SameEntry := POptimizeEntry(FHistogram[0]);
  2350.   for i := 1 to FHistogram.Count-1 do
  2351.   begin
  2352.     Entry := POptimizeEntry(FHistogram[i]);
  2353.     ASSERT(Entry^.Count > 0, 'Unused entry exported from THistogram');
  2354.     if (Entry^.Color.Value = SameEntry^.Color.Value) then
  2355.     begin
  2356.       // Transfer usage count to first entry
  2357.       inc(SameEntry^.Count, Entry^.Count);
  2358.       Entry^.Count := 0; // Use 0 to signal merged entry
  2359.       Entry^.Color.SameAs := SameEntry; // Point to master
  2360.     end else
  2361.       SameEntry := Entry;
  2362.   end;
  2363. end;
  2364. procedure TColorMapOptimizer.MapColors;
  2365. var
  2366.   i, j : integer;
  2367.   Delta, BestDelta : integer;
  2368.   BestIndex : integer;
  2369.   MaxColors : integer;
  2370. begin
  2371.   (*
  2372.   **  Sort by usage count
  2373.   *)
  2374.   FHistogram.Sort(CompareCount);
  2375.   (*
  2376.   ** Handle transparency
  2377.   *)
  2378.   if (FUseTransparency) then
  2379.     MaxColors := 255
  2380.   else
  2381.     MaxColors := 256;
  2382.   (*
  2383.   **  Determine number of colors used (max 256)
  2384.   *)
  2385.   FFinalCount := FHistogram.Count;
  2386.   for i := 0 to FFinalCount-1 do
  2387.     if (i >= MaxColors) or (POptimizeEntry(FHistogram[i])^.Count = 0) then
  2388.     begin
  2389.       FFinalCount := i;
  2390.       break;
  2391.     end;
  2392.   (*
  2393.   **  Build color map and reverse map for final entries
  2394.   *)
  2395.   for i := 0 to FFinalCount-1 do
  2396.   begin
  2397.     POptimizeEntry(FHistogram[i])^.NewIndex := i;
  2398.     FColorMap[i] := POptimizeEntry(FHistogram[i])^.Color.Color;
  2399.   end;
  2400.   (*
  2401.   **  Map colors > 256 to colors <= 256 and build NewIndex color map
  2402.   *)
  2403.   for i := FFinalCount to FHistogram.Count-1 do
  2404.     with POptimizeEntry(FHistogram[i])^ do
  2405.     begin
  2406.       // Entries with a usage count of -1 is unused
  2407.       ASSERT(Count <> -1, 'Internal error: Unused entry exported');
  2408.       // Entries with a usage count of 0 has been merged with another entry
  2409.       if (Count = 0) then
  2410.       begin
  2411.         // Use mapping of master entry
  2412.         ASSERT(Color.SameAs.NewIndex < 256, 'Internal error: Mapping to unmapped color');
  2413.         NewIndex := Color.SameAs.NewIndex;
  2414.       end else
  2415.       begin
  2416.         // Search for entry with nearest color value
  2417.         BestIndex := 0;
  2418.         BestDelta := 255*3;
  2419.         for j := 0 to FFinalCount-1 do
  2420.         begin
  2421.           Delta := ABS((POptimizeEntry(FHistogram[j])^.Color.Color.Red - Color.Color.Red) +
  2422.             (POptimizeEntry(FHistogram[j])^.Color.Color.Green - Color.Color.Green) +
  2423.             (POptimizeEntry(FHistogram[j])^.Color.Color.Blue - Color.Color.Blue));
  2424.           if (Delta < BestDelta) then
  2425.           begin
  2426.             BestDelta := Delta;
  2427.             BestIndex := j;
  2428.           end;
  2429.         end;
  2430.         NewIndex := POptimizeEntry(FHistogram[BestIndex])^.NewIndex;;
  2431.       end;
  2432.     end;
  2433.   (*
  2434.   ** Add transparency color to new color map
  2435.   *)
  2436.   if (FUseTransparency) then
  2437.   begin
  2438.     FNewTransparentColorIndex := FFinalCount;
  2439.     FColorMap[FFinalCount].Red := 0;
  2440.     FColorMap[FFinalCount].Green := 0;
  2441.     FColorMap[FFinalCount].Blue := 0;
  2442.     inc(FFinalCount);
  2443.   end;
  2444. end;
  2445. procedure TColorMapOptimizer.ReplaceColorMaps;
  2446. var
  2447.   i : integer;
  2448. begin
  2449.   // Zap all local color maps
  2450.   for i := 0 to FImage.Images.Count-1 do
  2451.     if (FImage.Images[i].ColorMap <> nil) then
  2452.       FImage.Images[i].ColorMap.Clear;
  2453.   // Store optimized global color map
  2454.   FImage.GlobalColorMap.ImportColorMap(FColorMap, FFinalCount);
  2455.   FImage.GlobalColorMap.Optimized := True;
  2456. end;
  2457. procedure TColorMapOptimizer.Optimize;
  2458. var
  2459.   Total : integer;
  2460.   i, j : integer;
  2461. begin
  2462.   // Stop all painters during optimize...
  2463.   FImage.PaintStop;
  2464.   // ...and prevent any new from starting while we are doing our thing
  2465.   FImage.Painters.LockList;
  2466.   try
  2467.     (*
  2468.     **  Process all sub images
  2469.     *)
  2470.     ProcessImage;
  2471.     // Prune histograms and calculate total number of colors
  2472.     Total := 0;
  2473.     for i := 0 to FHistogramList.Count-1 do
  2474.       inc(Total, THistogram(FHistogramList[i]).Prune);
  2475.     // Allocate global histogram
  2476.     FHistogram.Clear;
  2477.     FHistogram.Capacity := Total;
  2478.     // Move data pointers from local histograms to global histogram
  2479.     for i := 0 to FHistogramList.Count-1 do
  2480.       with THistogram(FHistogramList[i]) do
  2481.         for j := 0 to Count-1 do
  2482.         begin
  2483.           ASSERT(POptimizeEntry(List[j])^.Count > 0, 'Unused entry exported from THistogram');
  2484.           FHistogram.Add(List[j]);
  2485.         end;
  2486.     (*
  2487.     **  Merge same colors
  2488.     *)
  2489.     MergeColors;
  2490.     (*
  2491.     **  Build color map and NewIndex map for final entries
  2492.     *)
  2493.     MapColors;
  2494.     (*
  2495.     **  Replace local colormaps with global color map
  2496.     *)
  2497.     ReplaceColorMaps;
  2498.     (*
  2499.     **  Process images for each color map
  2500.     *)
  2501.     for i := 0 to FHistogramList.Count-1 do
  2502.       THistogram(FHistogramList[i]).MapImages(FUseTransparency, FNewTransparentColorIndex);
  2503.     (*
  2504.     **  Delete the frame's old bitmaps and palettes
  2505.     *)
  2506.     for i := 0 to FImage.Images.Count-1 do
  2507.     begin
  2508.       FImage.Images[i].HasBitmap := False;
  2509.       FImage.Images[i].Palette := 0;
  2510.     end;
  2511.   finally
  2512.     FImage.Painters.UnlockList;
  2513.   end;
  2514. end;
  2515. ////////////////////////////////////////////////////////////////////////////////
  2516. //
  2517. // TGIFImage
  2518. //
  2519. ////////////////////////////////////////////////////////////////////////////////
  2520. constructor TGIFImage.Create;
  2521. begin
  2522.   inherited Create;
  2523.   FImages := TGIFImageList.Create(self);
  2524.   FHeader := TGIFHeader.Create(self);
  2525.   FPainters := TThreadList.Create;
  2526.   FGlobalPalette := 0;
  2527.   // Load defaults
  2528.   FDrawOptions := GIFImageDefaultDrawOptions;
  2529.   ColorReduction := GIFImageDefaultColorReduction;
  2530.   FReductionBits := GIFImageDefaultColorReductionBits;
  2531.   FDitherMode := GIFImageDefaultDitherMode;
  2532.   FCompression := GIFImageDefaultCompression;
  2533.   FThreadPriority := GIFImageDefaultThreadPriority;
  2534.   FAnimationSpeed := GIFImageDefaultAnimationSpeed;
  2535.   FDrawBackgroundColor := clNone;
  2536.   IsDrawing := False;
  2537.   IsInsideGetPalette := False;
  2538.   NewImage;
  2539. end;
  2540. destructor TGIFImage.Destroy;
  2541. var
  2542.   i : integer;
  2543. begin
  2544.   PaintStop;
  2545.   with FPainters.LockList do
  2546.     try
  2547.       for i := Count-1 downto 0 do
  2548.         TGIFPainter(Items[i]).FImage := nil;
  2549.     finally
  2550.       FPainters.UnLockList;
  2551.     end;
  2552.   Clear;
  2553.   FPainters.Free;
  2554.   FImages.Free;
  2555.   FHeader.Free;
  2556.   inherited Destroy;
  2557. end;
  2558. procedure TGIFImage.Clear;
  2559. begin
  2560.   PaintStop;
  2561.   FreeBitmap;
  2562.   FImages.Clear;
  2563.   FHeader.ColorMap.Clear;
  2564.   FHeader.Height := 0;
  2565.   FHeader.Width := 0;
  2566.   FHeader.Prepare;
  2567.   Palette := 0;
  2568. end;
  2569. procedure TGIFImage.NewImage;
  2570. begin
  2571.   Clear;
  2572. end;
  2573. function TGIFImage.GetVersion: TGIFVersion;
  2574. var
  2575.   v : TGIFVersion;
  2576.   i : integer;
  2577. begin
  2578.   Result := gvUnknown;
  2579.   for i := 0 to FImages.Count-1 do
  2580.   begin
  2581.     v := FImages[i].Version;
  2582.     if (v > Result) then
  2583.       Result := v;
  2584.     if (v >= high(TGIFVersion)) then
  2585.       break;
  2586.   end;
  2587. end;
  2588. function TGIFImage.GetColorResolution: integer;
  2589. var
  2590.   i : integer;
  2591. begin
  2592.   Result := FHeader.ColorResolution;
  2593.   for i := 0 to FImages.Count-1 do
  2594.     if (FImages[i].ColorResolution > Result) then
  2595.       Result := FImages[i].ColorResolution;
  2596. end;
  2597. function TGIFImage.GetBitsPerPixel: integer;
  2598. var
  2599.   i : integer;
  2600. begin
  2601.   Result := FHeader.BitsPerPixel;
  2602.   for i := 0 to FImages.Count-1 do
  2603.     if (FImages[i].BitsPerPixel > Result) then
  2604.       Result := FImages[i].BitsPerPixel;
  2605. end;
  2606. function TGIFImage.GetBackgroundColorIndex: BYTE;
  2607. begin
  2608.   Result := FHeader.BackgroundColorIndex;
  2609. end;
  2610. procedure TGIFImage.SetBackgroundColorIndex(const Value: BYTE);
  2611. begin
  2612.   FHeader.BackgroundColorIndex := Value;
  2613. end;
  2614. function TGIFImage.GetBackgroundColor: TColor;
  2615. begin
  2616.   Result := FHeader.BackgroundColor;
  2617. end;
  2618. procedure TGIFImage.SetBackgroundColor(const Value: TColor);
  2619. begin
  2620.   FHeader.BackgroundColor := Value;
  2621. end;
  2622. function TGIFImage.GetAspectRatio: BYTE;
  2623. begin
  2624.   Result := FHeader.AspectRatio;
  2625. end;
  2626. procedure TGIFImage.SetAspectRatio(const Value: BYTE);
  2627. begin
  2628.   FHeader.AspectRatio := Value;
  2629. end;
  2630. procedure TGIFImage.SetDrawOptions(Value: TGIFDrawOptions);
  2631. begin
  2632.   if (FDrawOptions = Value) then
  2633.     exit;
  2634.   if (DrawPainter <> nil) then
  2635.     DrawPainter.Stop;
  2636.   FDrawOptions := Value;
  2637.   // Zap all bitmaps
  2638.   Pack;
  2639.   Changed(self);
  2640. end;
  2641. function TGIFImage.GetAnimate: Boolean;
  2642. begin
  2643.   Result:= goAnimate in DrawOptions;
  2644. end;
  2645. procedure TGIFImage.SetAnimate(const Value: Boolean);
  2646. begin
  2647.   if Value then
  2648.     DrawOptions:= DrawOptions + [goAnimate]
  2649.   else
  2650.     DrawOptions:= DrawOptions - [goAnimate];
  2651. end;
  2652. procedure TGIFImage.SetAnimationSpeed(Value: integer);
  2653. begin
  2654.   if (Value < 0) then
  2655.     Value := 0
  2656.   else if (Value > 1000) then
  2657.     Value := 1000;
  2658.   if (Value <> FAnimationSpeed) then
  2659.   begin
  2660.     FAnimationSpeed := Value;
  2661.     // Use the FPainters threadlist to protect FDrawPainter from being modified
  2662.     // by the thread while we mess with it
  2663.     with FPainters.LockList do
  2664.       try
  2665.         if (FDrawPainter <> nil) then
  2666.           FDrawPainter.AnimationSpeed := FAnimationSpeed;
  2667.       finally
  2668.         // Release the lock on FPainters to let paint thread kill itself
  2669.         FPainters.UnLockList;
  2670.       end;
  2671.   end;
  2672. end;
  2673. procedure TGIFImage.SetReductionBits(Value: integer);
  2674. begin
  2675.   if (Value < 3) or (Value > 8) then
  2676.     Error(sInvalidBitSize);
  2677.   FReductionBits := Value;
  2678. end;
  2679. procedure TGIFImage.OptimizeColorMap;
  2680. var
  2681.   ColorMapOptimizer : TColorMapOptimizer;
  2682. begin
  2683.   ColorMapOptimizer := TColorMapOptimizer.Create(self);
  2684.   try
  2685.     ColorMapOptimizer.Optimize;
  2686.   finally
  2687.     ColorMapOptimizer.Free;
  2688.   end;
  2689. end;
  2690. procedure TGIFImage.Optimize(Options: TGIFOptimizeOptions;
  2691.   ColorReduction: TColorReduction; DitherMode: TDitherMode;
  2692.   ReductionBits: integer);
  2693. var
  2694.   i ,
  2695.   j : integer;
  2696.   Delay : integer;
  2697.   GCE : TGIFGraphicControlExtension;
  2698.   ThisRect ,
  2699.   NextRect ,
  2700.   MergeRect : TRect;
  2701.   Prog ,
  2702.   MaxProg : integer;
  2703.   function Scan(Buf: PChar; Value: Byte; Count: integer): boolean; assembler;
  2704.   asm
  2705.     PUSH EDI
  2706.     MOV EDI, Buf
  2707.     MOV ECX, Count
  2708.     MOV AL, Value
  2709.     REPNE SCASB
  2710.     MOV EAX, False
  2711.     JNE @@1
  2712.     MOV EAX, True
  2713. @@1:POP EDI
  2714.   end;
  2715. begin
  2716.   if (Empty) then
  2717.     exit;
  2718.   // Stop all painters during optimize...
  2719.   PaintStop;
  2720.   // ...and prevent any new from starting while we are doing our thing
  2721.   FPainters.LockList;
  2722.   try
  2723.     Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressOptimizing);
  2724.     try
  2725.       Prog := 0;
  2726.       MaxProg := Images.Count*6;
  2727.       // Sort color map by usage and remove unused entries
  2728.       if (ooColorMap in Options) then
  2729.       begin
  2730.         // Optimize global color map
  2731.         if (GlobalColorMap.Count > 0) then
  2732.           GlobalColorMap.Optimize;
  2733.         // Optimize local color maps
  2734.         for i := 0 to Images.Count-1 do
  2735.         begin
  2736.           inc(Prog);
  2737.           if (Images[i].ColorMap.Count > 0) then
  2738.           begin
  2739.             Images[i].ColorMap.Optimize;
  2740.             Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
  2741.               Rect(0,0,0,0), sProgressOptimizing);
  2742.           end;
  2743.         end;
  2744.       end;
  2745.       // Remove passive elements, pass 1
  2746.       if (ooCleanup in Options) then
  2747.       begin
  2748.         // Check for transparency flag without any transparent pixels
  2749.         for i := 0 to Images.Count-1 do
  2750.         begin
  2751.           inc(Prog);
  2752.           if (Images[i].Transparent) then
  2753.           begin
  2754.             if not(Scan(Images[i].Data,
  2755.                         Images[i].GraphicControlExtension.TransparentColorIndex,
  2756.                         Images[i].DataSize)) then
  2757.             begin
  2758.               Images[i].GraphicControlExtension.Transparent := False;
  2759.               Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
  2760.                 Rect(0,0,0,0), sProgressOptimizing);
  2761.             end;
  2762.           end;
  2763.         end;
  2764.         // Change redundant disposal modes
  2765.         for i := 0 to Images.Count-2 do
  2766.         begin
  2767.           inc(Prog);
  2768.           if (Images[i].GraphicControlExtension <> nil) and
  2769.             (Images[i].GraphicControlExtension.Disposal in [dmPrevious, dmBackground]) and
  2770.             (not Images[i+1].Transparent) then
  2771.           begin
  2772.             ThisRect := Images[i].BoundsRect;
  2773.             NextRect := Images[i+1].BoundsRect;
  2774.             if (not IntersectRect(MergeRect, ThisRect, NextRect)) then
  2775.               continue;
  2776.             // If the next frame completely covers the current frame,
  2777.             // change the disposal mode to dmNone
  2778.             if (EqualRect(MergeRect, NextRect)) then
  2779.               Images[i].GraphicControlExtension.Disposal := dmNone;
  2780.             Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
  2781.               Rect(0,0,0,0), sProgressOptimizing);
  2782.           end;
  2783.         end;
  2784.       end else
  2785.         inc(Prog, 2*Images.Count);
  2786.       // Merge layers of equal pixels (remove redundant pixels)
  2787.       if (ooMerge in Options) then
  2788.       begin
  2789.         // Merge from last to first to avoid intefering with merge
  2790.         for i := Images.Count-1 downto 1 do
  2791.         begin
  2792.           inc(Prog);
  2793.           j := i-1;
  2794.           // If the "previous" frames uses dmPrevious disposal mode, we must
  2795.           // instead merge with the frame before the previous
  2796.           while (j > 0) and
  2797.             ((Images[j].GraphicControlExtension <> nil) and
  2798.              (Images[j].GraphicControlExtension.Disposal = dmPrevious)) do
  2799.             dec(j);
  2800.           // Merge
  2801.           Images[i].Merge(Images[j]);
  2802.           Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
  2803.             Rect(0,0,0,0), sProgressOptimizing);
  2804.         end;
  2805.       end else
  2806.         inc(Prog, Images.Count);
  2807.       // Crop transparent areas
  2808.       if (ooCrop in Options) then
  2809.       begin
  2810.         for i := Images.Count-1 downto 0 do
  2811.         begin
  2812.           inc(Prog);
  2813.           if (not Images[i].Empty) and (Images[i].Transparent) then
  2814.           begin
  2815.             // Remember frames delay in case frame is deleted
  2816.             Delay := Images[i].GraphicControlExtension.Delay;
  2817.             // Crop
  2818.             Images[i].Crop;
  2819.             // If the frame was completely transparent we remove it
  2820.             if (Images[i].Empty) then
  2821.             begin
  2822.               // Transfer delay to previous frame in case frame was deleted
  2823.               if (i > 0) and (Images[i-1].Transparent) then
  2824.                 Images[i-1].GraphicControlExtension.Delay :=
  2825.                   Images[i-1].GraphicControlExtension.Delay + Delay;
  2826.               Images.Delete(i);
  2827.             end;
  2828.             Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
  2829.               Rect(0,0,0,0), sProgressOptimizing);
  2830.           end;
  2831.         end;
  2832.       end else
  2833.         inc(Prog, Images.Count);
  2834.       // Remove passive elements, pass 2
  2835.       inc(Prog, Images.Count);
  2836.       if (ooCleanup in Options) then
  2837.       begin
  2838.         for i := Images.Count-1 downto 0 do
  2839.         begin
  2840.           // Remove comments and application extensions
  2841.           for j := Images[i].Extensions.Count-1 downto 0 do
  2842.             if (Images[i].Extensions[j] is TGIFCommentExtension) or
  2843.               (Images[i].Extensions[j] is TGIFTextExtension) or
  2844.               (Images[i].Extensions[j] is TGIFUnknownAppExtension) or
  2845.               ((Images[i].Extensions[j] is TGIFAppExtNSLoop) and
  2846.                ((i > 0) or (Images.Count = 1))) then
  2847.               Images[i].Extensions.Delete(j);
  2848.           if (Images[i].GraphicControlExtension <> nil) then
  2849.           begin
  2850.             GCE := Images[i].GraphicControlExtension;
  2851.             // Zap GCE if all of the following are true:
  2852.             // * No delay or only one image
  2853.             // * Not transparent
  2854.             // * No prompt
  2855.             // * No disposal or only one image
  2856.             if ((GCE.Delay = 0) or (Images.Count = 1)) and
  2857.               (not GCE.Transparent) and
  2858.               (not GCE.UserInput) and
  2859.               ((GCE.Disposal in [dmNone, dmNoDisposal]) or (Images.Count = 1)) then
  2860.             begin
  2861.               GCE.Free;
  2862.             end;
  2863.           end;
  2864.           // Zap frame if it has become empty
  2865.           if (Images[i].Empty) and (Images[i].Extensions.Count = 0) then
  2866.             Images[i].Free;
  2867.         end;
  2868.         Progress(Self, psRunning, MulDiv(Prog, 100, MaxProg), False,
  2869.           Rect(0,0,0,0), sProgressOptimizing);
  2870.       end else
  2871.       // Reduce color depth
  2872.       if (ooReduceColors in Options) then
  2873.       begin
  2874.         if (ColorReduction = rmPalette) then
  2875.           Error(sInvalidReduction);
  2876.         { TODO -oanme -cFeature : Implement ooReduceColors option. }
  2877.         // Not implemented!
  2878.       end;
  2879.     finally
  2880.       if ExceptObject = nil then
  2881.         i := 100
  2882.       else
  2883.         i := 0;
  2884.       Progress(Self, psEnding, i, False, Rect(0,0,0,0), sProgressOptimizing);
  2885.     end;
  2886.   finally
  2887.     FPainters.UnlockList;
  2888.   end;
  2889. end;
  2890. procedure TGIFImage.Pack;
  2891. var
  2892.   i : integer;
  2893. begin
  2894.   // Zap bitmaps and palettes
  2895.   FreeBitmap;
  2896.   Palette := 0;
  2897.   for i := 0 to FImages.Count-1 do
  2898.   begin
  2899.     FImages[i].Bitmap := nil;
  2900.     FImages[i].Palette := 0;
  2901.   end;
  2902.   // Only pack if no global colormap and a single image
  2903.   if (FHeader.ColorMap.Count > 0) or (FImages.Count <> 1) then
  2904.     exit;
  2905.   // Copy local colormap to global
  2906.   FHeader.ColorMap.Assign(FImages[0].ColorMap);
  2907.   // Zap local colormap
  2908.   FImages[0].ColorMap.Clear;
  2909. end;
  2910. procedure TGIFImage.SaveToStream(Stream: TStream);
  2911. var
  2912.   n : Integer;
  2913. begin
  2914.   Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressSaving);
  2915.   try
  2916.     // Write header
  2917.     FHeader.SaveToStream(Stream);
  2918.     // Write images
  2919.     FImages.SaveToStream(Stream);
  2920.     // Write trailer
  2921.     with TGIFTrailer.Create(self) do
  2922.       try
  2923.         SaveToStream(Stream);
  2924.       finally
  2925.         Free;
  2926.       end;
  2927.   finally
  2928.     if ExceptObject = nil then
  2929.       n := 100
  2930.     else
  2931.       n := 0;
  2932.     Progress(Self, psEnding, n, True, Rect(0,0,0,0), sProgressSaving);
  2933.   end;
  2934. end;
  2935. procedure TGIFImage.LoadFromStream(Stream: TStream);
  2936. var
  2937.   n : Integer;
  2938.   Position : integer;
  2939. begin
  2940.   Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressLoading);
  2941.   try
  2942.     // Zap old image
  2943.     Clear;
  2944.     Position := Stream.Position;
  2945.     try
  2946.       // Read header
  2947.       FHeader.LoadFromStream(Stream);
  2948.       // Read images
  2949.       FImages.LoadFromStream(Stream, self);
  2950.       // Read trailer
  2951.       with TGIFTrailer.Create(self) do
  2952.         try
  2953.           LoadFromStream(Stream);
  2954.         finally
  2955.           Free;
  2956.         end;
  2957.     except
  2958.       // Restore stream position in case of error.
  2959.       // Not required, but "a nice thing to do"
  2960.       Stream.Position := Position;
  2961.       raise;
  2962.     end;
  2963.   finally
  2964.     if ExceptObject = nil then
  2965.       n := 100
  2966.     else
  2967.       n := 0;
  2968.     Progress(Self, psEnding, n, True, Rect(0,0,0,0), sProgressLoading);
  2969.   end;
  2970. end;
  2971. procedure TGIFImage.LoadFromResourceName(Instance: THandle; const ResName: String);
  2972. var
  2973.   Stream: TCustomMemoryStream;
  2974. begin
  2975.   Stream := TResourceStream.Create(Instance, ResName, RT_RCDATA);
  2976.   try
  2977.     LoadFromStream(Stream);
  2978.   finally
  2979.     Stream.Free;
  2980.   end;
  2981. end;
  2982. function TGIFImage.GetBitmap: TBitmap;
  2983. begin
  2984.   if not(Empty) then
  2985.   begin
  2986.     Result := FBitmap;
  2987.     if (Result <> nil) then
  2988.       exit;
  2989.     FBitmap := TBitmap.Create;
  2990.     Result := FBitmap;
  2991.     FBitmap.OnChange := Changed;
  2992.     // Use first image as default
  2993.     if (Images.Count > 0) then
  2994.     begin
  2995.       if (Images[0].Width = Width) and (Images[0].Height = Height) then
  2996.       begin
  2997.         // Use first image as it has same dimensions
  2998.         FBitmap.Assign(Images[0].Bitmap);
  2999.       end else
  3000.       begin
  3001.         // Draw first image on bitmap
  3002.         FBitmap.Palette := CopyPalette(Palette);
  3003.         FBitmap.Height := Height;
  3004.         FBitmap.Width := Width;
  3005.         Images[0].Draw(FBitmap.Canvas, FBitmap.Canvas.ClipRect, False, False);
  3006.       end;
  3007.     end;
  3008.   end else
  3009.     Result := nil
  3010. end;
  3011. // Create a new (empty) bitmap
  3012. function TGIFImage.NewBitmap: TBitmap;
  3013. begin
  3014.   Result := FBitmap;
  3015.   if (Result <> nil) then
  3016.     exit;
  3017.   FBitmap := TBitmap.Create;
  3018.   Result := FBitmap;
  3019.   FBitmap.OnChange := Changed;
  3020.   // Draw first image on bitmap
  3021.   FBitmap.Palette := CopyPalette(Palette);
  3022.   FBitmap.Height := Height;
  3023.   FBitmap.Width := Width;
  3024. end;
  3025. procedure TGIFImage.FreeBitmap;
  3026. begin
  3027.   if (DrawPainter <> nil) then
  3028.     DrawPainter.Stop;
  3029.   if (FBitmap <> nil) then
  3030.   begin
  3031.     FBitmap.Free;
  3032.     FBitmap := nil;
  3033.   end;
  3034. end;
  3035. function TGIFImage.Add(Source: TPersistent): integer;
  3036. var
  3037.   Image : TGIFSubImage;
  3038. begin
  3039.   Image := nil; // To avoid compiler warning - not needed.
  3040.   if (Source is TGraphic) then
  3041.   begin
  3042.     Image := TGIFSubImage.Create(self);
  3043.     try
  3044.       Image.Assign(Source);
  3045.       // ***FIXME*** Documentation should explain the inconsistency here:
  3046.       // TGIFimage does not take ownership of Source after TGIFImage.Add() and
  3047.       // therefore does not delete Source.
  3048.     except
  3049.       Image.Free;
  3050.       raise;
  3051.     end;
  3052.   end else
  3053.   if (Source is TGIFSubImage) then
  3054.     Image := TGIFSubImage(Source)
  3055.   else
  3056.     Error(sUnsupportedClass);
  3057.   Result := FImages.Add(Image);
  3058.   FreeBitmap;
  3059.   Changed(self);
  3060. end;
  3061. function TGIFImage.GetEmpty: Boolean;
  3062. begin
  3063.   Result := (FImages.Count = 0);
  3064. end;
  3065. function TGIFImage.GetHeight: Integer;
  3066. begin
  3067.   Result := FHeader.Height;
  3068. end;
  3069. function TGIFImage.GetWidth: Integer;
  3070. begin
  3071.   Result := FHeader.Width;
  3072. end;
  3073. function TGIFImage.GetIsTransparent: Boolean;
  3074. var
  3075.   i : integer;
  3076. begin
  3077.   Result := False;
  3078.   for i := 0 to Images.Count-1 do
  3079.     if (Images[i].GraphicControlExtension <> nil) and
  3080.       (Images[i].GraphicControlExtension.Transparent) then
  3081.     begin
  3082.       Result := True;
  3083.       exit;
  3084.     end;
  3085. end;
  3086. function TGIFImage.Equals(Graphic: TGraphic): Boolean;
  3087. begin
  3088.   Result := (Graphic = self);
  3089. end;
  3090. function TGIFImage.GetPalette: HPALETTE;
  3091. begin
  3092.   // Check for recursion
  3093.   // (TGIFImage.GetPalette->TGIFSubImage.GetPalette->TGIFImage.GetPalette etc...)
  3094.   if (IsInsideGetPalette) then
  3095.     Error(sNoColorTable);
  3096.   IsInsideGetPalette := True;
  3097.   try
  3098.     Result := 0;
  3099.     if (FBitmap <> nil) and (FBitmap.Palette <> 0) then
  3100.       // Use bitmaps own palette if possible
  3101.       Result := FBitmap.Palette
  3102.     else if (FGlobalPalette <> 0) then
  3103.       // Or a previously exported global palette
  3104.       Result := FGlobalPalette
  3105.     else if (DoDither) then
  3106.     begin
  3107.       // or create a new dither palette
  3108.       FGlobalPalette := WebPalette;
  3109.       Result := FGlobalPalette;
  3110.     end else
  3111.     if (FHeader.ColorMap.Count > 0) then
  3112.     begin
  3113.       // or create a new if first time
  3114.       FGlobalPalette := FHeader.ColorMap.ExportPalette;
  3115.       Result := FGlobalPalette;
  3116.     end else
  3117.     if (FImages.Count > 0) then
  3118.       // This can cause a recursion if no global palette exist and image[0]
  3119.       // hasn't got one either. Checked by the IsInsideGetPalette semaphor.
  3120.       Result := FImages[0].Palette;
  3121.   finally
  3122.     IsInsideGetPalette := False;
  3123.   end;
  3124. end;
  3125. procedure TGIFImage.SetPalette(Value: HPalette);
  3126. var
  3127.   NeedNewBitmap : boolean;
  3128. begin
  3129.   if (Value <> FGlobalPalette) then
  3130.   begin
  3131.     // Zap old palette
  3132.     if (FGlobalPalette <> 0) then
  3133.       DeleteObject(FGlobalPalette);
  3134.     // Zap bitmap unless new palette is same as bitmaps own
  3135.     NeedNewBitmap := (FBitmap <> nil) and (Value <> FBitmap.Palette);
  3136.     // Use new palette
  3137.     FGlobalPalette := Value;
  3138.     if (NeedNewBitmap) then
  3139.     begin
  3140.       // Need to create new bitmap and repaint
  3141.       FreeBitmap;
  3142.       PaletteModified := True;
  3143.       Changed(Self);
  3144.     end;
  3145.   end;
  3146. end;
  3147. // Obsolete
  3148. // procedure TGIFImage.Changed(Sender: TObject);
  3149. // begin
  3150. //  inherited Changed(Sender);
  3151. // end;
  3152. procedure TGIFImage.SetHeight(Value: Integer);
  3153. var
  3154.   i : integer;
  3155. begin
  3156.   for i := 0 to Images.Count-1 do
  3157.     if (Images[i].Top + Images[i].Height > Value) then
  3158.       Error(sBadHeight);
  3159.   if (Value <> Header.Height) then
  3160.   begin
  3161.     Header.Height := Value;
  3162.     FreeBitmap;
  3163.     Changed(self);
  3164.   end;
  3165. end;
  3166. procedure TGIFImage.SetWidth(Value: Integer);
  3167. var
  3168.   i : integer;
  3169. begin
  3170.   for i := 0 to Images.Count-1 do
  3171.     if (Images[i].Left + Images[i].Width > Value) then
  3172.       Error(sBadWidth);
  3173.   if (Value <> Header.Width) then
  3174.   begin
  3175.     Header.Width := Value;
  3176.     FreeBitmap;
  3177.     Changed(self);
  3178.   end;
  3179. end;
  3180. procedure TGIFImage.WriteData(Stream: TStream);
  3181. begin
  3182.   if (GIFImageOptimizeOnStream) then
  3183.     Optimize([ooCrop, ooMerge, ooCleanup, ooColorMap, ooReduceColors], rmNone, dmNearest, 8);
  3184.   inherited WriteData(Stream);
  3185. end;
  3186. procedure TGIFImage.AssignTo(Dest: TPersistent);
  3187. begin
  3188.   if (Dest is TBitmap) then
  3189.     Dest.Assign(Bitmap)
  3190.   else
  3191.     inherited AssignTo(Dest);
  3192. end;
  3193. { TODO 1 -oanme -cImprovement : Better handling of TGIFImage.Assign(Empty TBitmap). }
  3194. procedure TGIFImage.Assign(Source: TPersistent);
  3195. var
  3196.   i : integer;
  3197.   Image : TGIFSubImage;
  3198. begin
  3199.   if (Source = self) then
  3200.     exit;
  3201.   if (Source = nil) then
  3202.   begin
  3203.     Clear;
  3204.   end else
  3205.   //
  3206.   // TGIFImage import
  3207.   //
  3208.   if (Source is TGIFImage) then
  3209.   begin
  3210.     Clear;
  3211.     // Temporarily copy event handlers to be able to generate progress events
  3212.     // during the copy and handle copy errors
  3213.     OnProgress := TGIFImage(Source).OnProgress;
  3214.     try
  3215.       FOnWarning := TGIFImage(Source).OnWarning;
  3216.       Progress(Self, psStarting, 0, False, Rect(0,0,0,0), sProgressCopying);
  3217.       try
  3218.         FHeader.Assign(TGIFImage(Source).Header);
  3219.         FThreadPriority := TGIFImage(Source).ThreadPriority;
  3220.         FDrawBackgroundColor := TGIFImage(Source).DrawBackgroundColor;
  3221.         FDrawOptions := TGIFImage(Source).DrawOptions;
  3222.         FColorReduction := TGIFImage(Source).ColorReduction;
  3223.         FDitherMode := TGIFImage(Source).DitherMode;
  3224.         FOnWarning:= TGIFImage(Source).FOnWarning;
  3225.         FOnStartPaint:= TGIFImage(Source).FOnStartPaint;
  3226.         FOnPaint:= TGIFImage(Source).FOnPaint;
  3227.         FOnEndPaint:= TGIFImage(Source).FOnEndPaint;
  3228.         FOnAfterPaint:= TGIFImage(Source).FOnAfterPaint;
  3229.         FOnLoop:= TGIFImage(Source).FOnLoop;
  3230.         for i := 0 to TGIFImage(Source).Images.Count-1 do
  3231.         begin
  3232.           Image := TGIFSubImage.Create(self);
  3233.           Image.Assign(TGIFImage(Source).Images[i]);
  3234.           Add(Image);
  3235.           Progress(Self, psRunning, MulDiv((i+1), 100, TGIFImage(Source).Images.Count),
  3236.             False, Rect(0,0,0,0), sProgressCopying);
  3237.         end;
  3238.       finally
  3239.         if ExceptObject = nil then
  3240.           i := 100
  3241.         else
  3242.           i := 0;
  3243.         Progress(Self, psEnding, i, False, Rect(0,0,0,0), sProgressCopying);
  3244.       end;
  3245.     finally
  3246.       // Reset event handlers
  3247.       FOnWarning := nil;
  3248.       OnProgress := nil;
  3249.     end;
  3250.   end else
  3251.   //
  3252.   // Import via TGIFSubImage.Assign
  3253.   //
  3254.   begin
  3255.     Clear;
  3256.     Image := TGIFSubImage.Create(self);
  3257.     try
  3258.       Image.Assign(Source);
  3259.       Add(Image);
  3260.     except
  3261.       on E: EConvertError do
  3262.       begin
  3263.         Image.Free;
  3264.         // Unsupported format - fall back to Source.AssignTo
  3265.         inherited Assign(Source);
  3266.       end;
  3267.     else
  3268.       // Unknown conversion error
  3269.       Image.Free;
  3270.       raise;
  3271.     end;
  3272.   end;
  3273. end;
  3274. procedure TGIFImage.LoadFromClipboardFormat(AFormat: Word; AData: THandle;
  3275.   APalette: HPALETTE);
  3276. {$IFDEF REGISTER_TGIFIMAGE}
  3277. var
  3278.   Size : Longint;
  3279.   Buffer : Pointer;
  3280.   Stream : TMemoryStream;
  3281.   Bmp : TBitmap;
  3282. {$ENDIF}
  3283. begin
  3284. {$IFDEF REGISTER_TGIFIMAGE}
  3285.   if (AData = 0) then
  3286.     AData := GetClipboardData(AFormat);
  3287.   if (AData <> 0) and (AFormat = CF_GIF) then
  3288.   begin
  3289.     // Get size and pointer to data
  3290.     Size := GlobalSize(AData);
  3291.     Buffer := GlobalLock(AData);
  3292.     try
  3293.       Stream := TMemoryStream.Create;
  3294.       try
  3295.         // Copy data to a stream
  3296.         Stream.SetSize(Size);
  3297.         Move(Buffer^, Stream.Memory^, Size);
  3298.         // Load GIF from stream
  3299.         LoadFromStream(Stream);
  3300.       finally
  3301.         Stream.Free;
  3302.       end;
  3303.     finally
  3304.       GlobalUnlock(AData);
  3305.     end;
  3306.   end else
  3307.   if (AData <> 0) and (AFormat = CF_BITMAP) then
  3308.   begin
  3309.     // No GIF on clipboard - try loading a bitmap instead
  3310.     Bmp := TBitmap.Create;
  3311.     try
  3312.       Bmp.LoadFromClipboardFormat(AFormat, AData, APalette);
  3313.       Assign(Bmp);
  3314.     finally
  3315.       Bmp.Free;
  3316.     end;
  3317.   end else
  3318.     Error(sUnknownClipboardFormat);
  3319. {$ELSE}
  3320.   Error(sGIFToClipboard);
  3321. {$ENDIF}
  3322. end;
  3323. procedure TGIFImage.SaveToClipboardFormat(var AFormat: Word; var AData: THandle;
  3324.   var APalette: HPALETTE);
  3325. {$IFDEF REGISTER_TGIFIMAGE}
  3326. var
  3327.   Stream : TMemoryStream;
  3328.   Data : THandle;
  3329.   Buffer : Pointer;
  3330. {$ENDIF}
  3331. begin
  3332. {$IFDEF REGISTER_TGIFIMAGE}
  3333.   if (Empty) then
  3334.     exit;
  3335.   // First store a bitmap version on the clipboard...
  3336.   Bitmap.SaveToClipboardFormat(AFormat, AData, APalette);
  3337.   // ...then store a GIF
  3338.   Stream := TMemoryStream.Create;
  3339.   try
  3340.     // Save the GIF to a memory stream
  3341.     SaveToStream(Stream);
  3342.     Stream.Position := 0;
  3343.     // Allocate some memory for the GIF data
  3344.     Data := GlobalAlloc(HeapAllocFlags, Stream.Size);
  3345.     try
  3346.       if (Data <> 0) then
  3347.       begin
  3348.         Buffer := GlobalLock(Data);
  3349.         try
  3350.           // Copy GIF data from stream memory to clipboard memory
  3351.           Move(Stream.Memory^, Buffer^, Stream.Size);
  3352.         finally
  3353.           GlobalUnlock(Data);
  3354.         end;
  3355.         // Transfer data to clipboard
  3356.         if (SetClipboardData(CF_GIF, Data) = 0) then
  3357.           Error(sFailedPaste);
  3358.       end;
  3359.     except
  3360.       GlobalFree(Data);
  3361.       raise;
  3362.     end;
  3363.   finally
  3364.     Stream.Free;
  3365.   end;
  3366. {$ELSE}
  3367.   Error(sGIFToClipboard);
  3368. {$ENDIF}
  3369. end;
  3370. function TGIFImage.GetColorMap: TGIFColorMap;
  3371. begin
  3372.   Result := FHeader.ColorMap;
  3373. end;
  3374. function TGIFImage.GetDoDither: boolean;
  3375. begin
  3376.   Result := (goDither in DrawOptions) and
  3377.     (((goAutoDither in DrawOptions) and DoAutoDither) or
  3378.       not(goAutoDither in DrawOptions));
  3379. end;
  3380. {$IFDEF VER9x}
  3381. procedure TGIFImage.Progress(Sender: TObject; Stage: TProgressStage;
  3382.   PercentDone: Byte; RedrawNow: Boolean; const R: TRect; const Msg: string);
  3383. begin
  3384.   if Assigned(FOnProgress) then
  3385.     FOnProgress(Sender, Stage, PercentDone, RedrawNow, R, Msg);
  3386. end;
  3387. {$ENDIF}
  3388. procedure TGIFImage.StopDraw;
  3389. var
  3390.   Msg : TMsg;
  3391.   ThreadWindow : HWND;
  3392. begin
  3393.   repeat
  3394.     // Use the FPainters threadlist to protect FDrawPainter from being modified
  3395.     // by the thread while we mess with it
  3396.     with FPainters.LockList do
  3397.       try
  3398.         if (FDrawPainter = nil) then
  3399.           break;
  3400.         // Tell thread to terminate
  3401.         FDrawPainter.Stop;
  3402.         // No need to wait for "thread" to terminate if running in main thread
  3403.         if not(goAsync in FDrawPainter.DrawOptions) then
  3404.           break;
  3405.       finally
  3406.         // Release the lock on FPainters to let paint thread kill itself
  3407.         FPainters.UnLockList;
  3408.       end;
  3409. {$IFDEF VER14_PLUS}
  3410.     if (GetCurrentThreadID = MainThreadID) then
  3411.       while CheckSynchronize do {loop};
  3412. {$ELSE}
  3413.     // Process Messages to make Synchronize work
  3414.     // (Instead of Application.ProcessMessages)
  3415.     ThreadWindow := FindWindow('TThreadWindow', nil);
  3416.     while PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) do
  3417.     begin
  3418.       if (Msg.Message <> WM_QUIT) then
  3419.       begin
  3420.         TranslateMessage(Msg);
  3421.         DispatchMessage(Msg);
  3422.       end else
  3423.       begin
  3424.         PostQuitMessage(Msg.WParam);
  3425.         exit;
  3426.       end;
  3427.     end;
  3428. {$ENDIF}
  3429.     Sleep(0); // Yield
  3430.   until (False);
  3431.   FreeBitmap;
  3432. end;
  3433. procedure TGIFImage.Draw(ACanvas: TCanvas; const Rect: TRect);
  3434. var
  3435.   Canvas : TCanvas;
  3436.   DestRect : TRect;
  3437.   Msg : TMsg;
  3438.   ThreadWindow : HWND;
  3439.   procedure DrawTile(Rect: TRect; Bitmap: TBitmap);
  3440.   var
  3441.     Tile : TRect;
  3442.   begin
  3443.     if (goTile in FDrawOptions) then
  3444.     begin
  3445.       // Note: This design does not handle transparency correctly!
  3446.       Tile.Left := Rect.Left;
  3447.       Tile.Right := Tile.Left + Width;
  3448.       while (Tile.Left < Rect.Right) do
  3449.       begin
  3450.         Tile.Top := Rect.Top;
  3451.         Tile.Bottom := Tile.Top + Height;
  3452.         while (Tile.Top < Rect.Bottom) do
  3453.         begin
  3454.           ACanvas.StretchDraw(Tile, Bitmap);
  3455.           Tile.Top := Tile.Top + Height;
  3456.           Tile.Bottom := Tile.Top + Height;
  3457.         end;
  3458.         Tile.Left := Tile.Left + Width;
  3459.         Tile.Right := Tile.Left + Width;
  3460.       end;
  3461.     end else
  3462.       ACanvas.StretchDraw(Rect, Bitmap);
  3463.   end;
  3464. begin
  3465.   // Prevent recursion(s(s(s)))
  3466.   if (IsDrawing) or (FImages.Count = 0) then
  3467.     exit;
  3468.   IsDrawing := True;
  3469.   try
  3470.     // Copy bitmap to canvas if we are already drawing
  3471.     // (or have drawn but are finished)
  3472.     if (FImages.Count = 1) or // Only one image
  3473.       (not (goAnimate in FDrawOptions)) then // Don't animate
  3474.     begin
  3475.       FImages[0].Draw(ACanvas, Rect, (goTransparent in FDrawOptions),
  3476.         (goTile in FDrawOptions));
  3477.       exit;
  3478.     end else
  3479.     if (FBitmap <> nil) and not(goDirectDraw in FDrawOptions) then
  3480.     begin
  3481.       DrawTile(Rect, Bitmap);
  3482.       exit;
  3483.     end;
  3484.     // Use the FPainters threadlist to protect FDrawPainter from being modified
  3485.     // by the thread while we mess with it
  3486.     with FPainters.LockList do
  3487.       try
  3488.       // If we are already painting on the canvas in goDirectDraw mode
  3489.       // and at the same location, just exit and let the painter do
  3490.       // its thing when it's ready
  3491.       if (FDrawPainter <> nil) and (FDrawPainter.Canvas = ACanvas) and
  3492.         EqualRect(FDrawPainter.Rect, Rect) then
  3493.         exit;
  3494.       // Kill the current paint thread
  3495.       StopDraw;
  3496.       if not(goDirectDraw in FDrawOptions) then
  3497.       begin
  3498.         // Create a bitmap to draw on
  3499.         NewBitmap;
  3500.         Canvas := FBitmap.Canvas;
  3501.         DestRect := Canvas.ClipRect;
  3502.         // Initialize bitmap canvas with background image
  3503.         Canvas.CopyRect(DestRect, ACanvas, Rect);
  3504.       end else
  3505.       begin
  3506.         Canvas := ACanvas;
  3507.         DestRect := Rect;
  3508.       end;
  3509.       // Create new paint thread
  3510.       InternalPaint(@FDrawPainter, Canvas, DestRect, FDrawOptions);
  3511.       if (FDrawPainter <> nil) then
  3512.       begin
  3513.         // Launch thread
  3514.         FDrawPainter.Start;
  3515.         if not(goDirectDraw in FDrawOptions) then
  3516.         begin
  3517. {$IFDEF VER14_PLUS}
  3518.           while (FDrawPainter <> nil) and (not FDrawPainter.Terminated) and
  3519.             (not FDrawPainter.Started) do
  3520.           begin
  3521.             if not CheckSynchronize then
  3522.               Sleep(0); // Yield
  3523.           end;
  3524. {$ELSE}
  3525.           ThreadWindow := FindWindow('TThreadWindow', nil);
  3526.           // Wait for thread to render first frame
  3527.           while (FDrawPainter <> nil) and (not FDrawPainter.Terminated) and
  3528.             (not FDrawPainter.Started) do
  3529.             // Process Messages to make Synchronize work
  3530.             // (Instead of Application.ProcessMessages)
  3531.             if PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) then
  3532.             begin
  3533.               if (Msg.Message <> WM_QUIT) then
  3534.               begin
  3535.                 TranslateMessage(Msg);
  3536.                 DispatchMessage(Msg);
  3537.               end else
  3538.               begin
  3539.                 PostQuitMessage(Msg.WParam);
  3540.                 exit;
  3541.               end;
  3542.             end else
  3543.               Sleep(0); // Yield
  3544. {$ENDIF}
  3545.           // Draw frame to destination
  3546.           DrawTile(Rect, Bitmap);
  3547.         end;
  3548.       end;
  3549.     finally
  3550.       FPainters.UnLockList;
  3551.     end;
  3552.   finally
  3553.     IsDrawing := False;
  3554.   end;
  3555. end;
  3556. // Internal pain(t) routine used by Draw()
  3557. function TGIFImage.InternalPaint(Painter: PGifPainter; ACanvas: TCanvas;
  3558.   const Rect: TRect; Options: TGIFDrawOptions): TGIFPainter;
  3559. begin
  3560.   if (Empty) or (Rect.Left >= Rect.Right) or (Rect.Top >= Rect.Bottom) then
  3561.   begin
  3562.     Result := nil;
  3563.     if (Painter <> nil) then
  3564.       Painter^ := Result;
  3565.     exit;
  3566.   end;
  3567.   // Draw in main thread if only one image
  3568.   if (Images.Count = 1) then
  3569.     Options := Options - [goAsync, goAnimate];
  3570.   Result := TGIFPainter.CreateRef(Painter, self, ACanvas, Rect, Options);
  3571.   FPainters.Add(Result);
  3572.   Result.OnStartPaint := FOnStartPaint;
  3573.   Result.OnPaint := FOnPaint;
  3574.   Result.OnAfterPaint := FOnAfterPaint;
  3575.   Result.OnLoop := FOnLoop;
  3576.   Result.OnEndPaint := FOnEndPaint;
  3577.   if not(goAsync in Options) then
  3578.   begin
  3579.     // Run in main thread
  3580.     Result.Execute;
  3581.     // Note: Painter threads executing in the main thread are freed upon exit
  3582.     // from the Execute method, so no need to do it here.
  3583.     Result := nil;
  3584.     if (Painter <> nil) then
  3585.       Painter^ := Result;
  3586.   end else
  3587.     Result.Priority := FThreadPriority;
  3588. end;
  3589. function TGIFImage.Paint(ACanvas: TCanvas; const Rect: TRect;
  3590.   Options: TGIFDrawOptions): TGIFPainter;
  3591. begin
  3592.   Result := InternalPaint(nil, ACanvas, Rect, Options);
  3593.   if (Result <> nil) then
  3594.     // Run in separate thread
  3595.     Result.Start;
  3596. end;
  3597. procedure TGIFImage.PaintStart;
  3598. var
  3599.   i : integer;
  3600. begin
  3601.   with FPainters.LockList do
  3602.     try
  3603.       for i := 0 to Count-1 do
  3604.         TGIFPainter(Items[i]).Start;
  3605.     finally
  3606.       FPainters.UnLockList;
  3607.     end;
  3608. end;
  3609. procedure TGIFImage.PaintStop;
  3610. var
  3611.   Ghosts : integer;
  3612.   i : integer;
  3613.   Msg : TMsg;
  3614.   ThreadWindow : HWND;
  3615.   procedure KillThreads;
  3616.   var
  3617.     i : integer;
  3618.   begin
  3619.     with FPainters.LockList do
  3620.       try
  3621.         for i := Count-1 downto 0 do
  3622.           if (goAsync in TGIFPainter(Items[i]).DrawOptions) then
  3623.           begin
  3624.             TerminateThread(TGIFPainter(Items[i]).Handle, 0);
  3625.             Delete(i);
  3626.           end;
  3627.       finally
  3628.         FPainters.UnLockList;
  3629.       end;
  3630.   end;
  3631. begin
  3632.   try
  3633.     // Loop until all have died
  3634.     repeat
  3635.       with FPainters.LockList do
  3636.         try
  3637.           if (Count = 0) then
  3638.             exit;
  3639.           // Signal painters to terminate
  3640.           // Painters will attempt to remove them self from the
  3641.           // painter list when they die
  3642.           Ghosts := Count;
  3643.           for i := Ghosts-1 downto 0 do
  3644.           begin
  3645.             if not(goAsync in TGIFPainter(Items[i]).DrawOptions) then
  3646.               dec(Ghosts);
  3647.             TGIFPainter(Items[i]).Stop;
  3648.           end;
  3649.         finally
  3650.           FPainters.UnLockList;
  3651.         end;
  3652.       // If all painters were synchronous, there's no purpose waiting for them
  3653.       // to terminate, because they are running in the main thread.
  3654.       if (Ghosts = 0) then
  3655.         exit;
  3656. {$IFDEF VER14_PLUS}
  3657.       if (GetCurrentThreadID = MainThreadID) then
  3658.         while CheckSynchronize do {loop};
  3659. {$ELSE}
  3660.       // Process Messages to make TThread.Synchronize work
  3661.       // (Instead of Application.ProcessMessages)
  3662.       ThreadWindow := FindWindow('TThreadWindow', nil);
  3663.       if (ThreadWindow = 0) then
  3664.       begin
  3665.         KillThreads;
  3666.         exit;
  3667.       end;
  3668.       while PeekMessage(Msg, ThreadWindow, CM_DESTROYWINDOW, CM_EXECPROC, PM_REMOVE) do
  3669.       begin
  3670.         if (Msg.Message <> WM_QUIT) then
  3671.         begin
  3672.           TranslateMessage(Msg);
  3673.           DispatchMessage(Msg);
  3674.         end else
  3675.         begin
  3676.           KillThreads;
  3677.           exit;
  3678.         end;
  3679.       end;
  3680. {$ENDIF}
  3681.       Sleep(0);
  3682.     until (False);
  3683.   finally
  3684.     FreeBitmap;
  3685.   end;
  3686. end;
  3687. procedure TGIFImage.PaintPause;
  3688. var
  3689.   i : integer;
  3690. begin
  3691.   with FPainters.LockList do
  3692.     try
  3693.       for i := 0 to Count-1 do
  3694.         TGIFPainter(Items[i]).Suspend;
  3695.     finally
  3696.       FPainters.UnLockList;
  3697.     end;
  3698. end;
  3699. procedure TGIFImage.PaintResume;
  3700. var
  3701.   i : integer;
  3702. begin
  3703.   // Implementation is currently same as PaintStart, but don't call PaintStart
  3704.   // in case its implementation changes
  3705.   with FPainters.LockList do
  3706.     try
  3707.       for i := 0 to Count-1 do
  3708.         TGIFPainter(Items[i]).Start;
  3709.     finally
  3710.       FPainters.UnLockList;
  3711.     end;
  3712. end;
  3713. procedure TGIFImage.PaintRestart;
  3714. var
  3715.   i : integer;
  3716. begin
  3717.   with FPainters.LockList do
  3718.     try
  3719.       for i := 0 to Count-1 do
  3720.         TGIFPainter(Items[i]).Restart;
  3721.     finally
  3722.       FPainters.UnLockList;
  3723.     end;
  3724. end;
  3725. procedure TGIFImage.Warning(Sender: TObject; Severity: TGIFSeverity; Message: string);
  3726. begin
  3727.   if (Assigned(FOnWarning)) then
  3728.     FOnWarning(Sender, Severity, Message);
  3729. end;
  3730. {$IFDEF VER12_PLUS}
  3731. type
  3732.   TDummyThread = class(TThread)
  3733.   protected
  3734.     procedure Execute; override;
  3735.   end;
  3736. procedure TDummyThread.Execute;
  3737. begin
  3738. end;
  3739. {$ENDIF}
  3740. var
  3741.   DesktopDC: HDC;
  3742. {$IFDEF VER12_PLUS}
  3743.   DummyThread: TThread;
  3744. {$ENDIF}
  3745. ////////////////////////////////////////////////////////////////////////////////
  3746. //
  3747. // Initialization
  3748. //
  3749. ////////////////////////////////////////////////////////////////////////////////
  3750. initialization
  3751. {$IFDEF REGISTER_TGIFIMAGE}
  3752.   TPicture.RegisterFileFormat('GIF', sGIFImageFile, TGIFImage);
  3753.   CF_GIF := RegisterClipboardFormat(PChar(sGIFImageFile));
  3754.   TPicture.RegisterClipboardFormat(CF_GIF, TGIFImage);
  3755. {$ENDIF}
  3756.   DesktopDC := GetDC(0);
  3757.   try
  3758.     PaletteDevice := (GetDeviceCaps(DesktopDC, BITSPIXEL) * GetDeviceCaps(DesktopDC, PLANES) <= 8);
  3759.     DoAutoDither := PaletteDevice;
  3760.   finally
  3761.     ReleaseDC(0, DesktopDC);
  3762.   end;
  3763. {$IFDEF VER9x}
  3764.   // Note: This doesn't return the same palette as the Delphi 3 system palette
  3765.   // since the true system palette contains 20 entries and the Delphi 3 system
  3766.   // palette only contains 16.
  3767.   // For our purpose this doesn't matter since we do not care about the actual
  3768.   // colors (or their number) in the palette.
  3769.   // Stock objects doesn't have to be deleted.
  3770.   SystemPalette16 := GetStockObject(DEFAULT_PALETTE);
  3771. {$ENDIF}
  3772. {$IFDEF VER12_PLUS}
  3773.   // Make sure that at least one thread always exist.
  3774.   // This is done to circumvent a race condition bug in Delphi 4.x and later:
  3775.   // When threads are deleted and created in rapid succesion, a situation might
  3776.   // arise where the thread window is deleted *after* the threads it controls
  3777.   // has been created. See the Delphi Bug Lists for more information.
  3778.   DummyThread := TDummyThread.Create(True);
  3779. {$ENDIF}
  3780. ////////////////////////////////////////////////////////////////////////////////
  3781. //
  3782. // Finalization
  3783. //
  3784. ////////////////////////////////////////////////////////////////////////////////
  3785. finalization
  3786.   ExtensionList.Free;
  3787.   AppExtensionList.Free;
  3788. {$IFNDEF VER9x}
  3789.   {$IFDEF REGISTER_TGIFIMAGE}
  3790.     TPicture.UnregisterGraphicClass(TGIFImage);
  3791.   {$ENDIF}
  3792.   {$IFDEF VER100}
  3793.   if (pf8BitBitmap <> nil) then
  3794.     pf8BitBitmap.Free;
  3795.   {$ENDIF}
  3796. {$ENDIF}
  3797. {$IFDEF VER12_PLUS}
  3798.   if (DummyThread <> nil) then
  3799.     DummyThread.Free;
  3800. {$ENDIF}
  3801. end.