Search code examples
delphidelphi-xe

TGifImage Transparency Issue


I am using TGifImage that is included with Delphi XE.

What I am trying to do is load a Gif from a File and and extract all the frames to a Bitmap.

This is what I did so far:

procedure ExtractGifFrames(FileName: string);
var
  Gif: TGifImage;
  Bmp: TBitmap;
  i: Integer;
begin
  Gif := TGifImage.Create;
  try
    Gif.LoadFromFile(FileName);

    Bmp := TBitmap.Create;
    try
      Bmp.SetSize(Gif.Width, Gif.Height);

      for i := 0 to Gif.Images.Count - 1 do
      begin
        if not Gif.Images[i].Empty then
        begin
          Bmp.Assign(Gif.Images[i]);
          Bmp.SaveToFile('C:\test\bitmap' + IntToStr(i) + '.bmp');
        end;
      end;
    finally
      Bmp.Free;
    end;
  finally
    Gif.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenPictureDialog1.Execute then
  begin
    ExtractGifFrames(OpenPictureDialog1.FileName);
  end;
end;

The problem I am facing is with some transparency issue with a lot of different Gifs, and also size problems.

Here are some example bitmaps that were saved using my code above:

enter image description here enter image description here

As you can see the results are not great, they have size and transparency issues.

I know the Gif Files themselves are not corrupt, because I can load them through my web browser and they display correctly without fault.

How can I load a Gif from File, assign each frame to Bitmap without losing any quality?


Solution

  • For older Delphi Versions (Pre 2009): Take a look at the code of GIFImage unit, you might want to check how TGIFPainter renders the images based on each Frame's Disposal method.

    I have wrote a small code utilizing TGIFPainter.OnAfterPaint event handler to save the active frame to BMP, and do all the "hard work".

    Note: GIFImage unit version 2.2 Release: 5 (23-MAY-1999)

    type
      TForm1 = class(TForm)
        Button1: TButton;
        ProgressBar1: TProgressBar;
        procedure Button1Click(Sender: TObject);
      public
        FBitmap: TBitmap;
        procedure AfterPaintGIF(Sender: TObject);
      end;
    
    ...
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      GIF: TGIFImage;
    begin
      GIF := TGIFImage.Create;
      FBitmap := TBitmap.Create;
      Button1.Enabled := False;
      try
        GIF.LoadFromFile('c:\test\test.gif');
        GIF.DrawOptions := GIF.DrawOptions - [goLoop, goLoopContinously, goAsync];
        GIF.AnimationSpeed := 1000; // Max - no delay
        FBitmap.Width := GIF.Width;
        FBitmap.Height := GIF.Height;
        GIF.OnAfterPaint := AfterPaintGIF;
    
        ProgressBar1.Max := Gif.Images.Count;
        ProgressBar1.Position := 0;
        ProgressBar1.Smooth := True;
        ProgressBar1.Step := 1;
    
        // Paint the GIF onto FBitmap, Let TGIFPainter do the painting logic
        // AfterPaintGIF will fire for each Frame
        GIF.Paint(FBitmap.Canvas, FBitmap.Canvas.ClipRect, GIF.DrawOptions);
        ShowMessage('Done!');
      finally
        FBitmap.Free;
        GIF.Free;
        Button1.Enabled := True;
      end;
    end;
    
    procedure TForm1.AfterPaintGIF(Sender: TObject);
    begin
      if not (Sender is TGIFPainter) then Exit;
      if not Assigned(FBitmap) then Exit;
      // The event will ignore Empty frames      
      FBitmap.Canvas.Lock;
      try
        FBitmap.SaveToFile(Format('%.2d.bmp', [TGIFPainter(Sender).ActiveImage]));
      finally
        FBitmap.Canvas.Unlock;
      end;
      ProgressBar1.StepIt;
    end;
    

    Note: No error handling to simplify the code.

    output bitmaps


    For newer Delphi Versions (2009+): With build-in GIFImg unit, you can do this quit easy with the use of TGIFRenderer (which completely replaced old TGIFPainter) e.g.:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      GIF: TGIFImage;
      Bitmap: TBitmap;
      I: Integer;
      GR: TGIFRenderer;
    begin
      GIF := TGIFImage.Create;      
      Bitmap := TBitmap.Create;
      try
        GIF.LoadFromFile('c:\test\test.gif');
        Bitmap.SetSize(GIF.Width, GIF.Height);
        GR := TGIFRenderer.Create(GIF);
        try
          for I := 0 to GIF.Images.Count - 1 do
          begin
            if GIF.Images[I].Empty then Break;
            GR.Draw(Bitmap.Canvas, Bitmap.Canvas.ClipRect);
            GR.NextFrame;
            Bitmap.SaveToFile(Format('%.2d.bmp', [I]));
          end;
        finally
          GR.Free;
        end;  
      finally
        GIF.Free;
        Bitmap.Free;
      end;
    end;
    

    Using GDI+:

    uses ..., GDIPAPI, GDIPOBJ, GDIPUTIL;
    
    procedure ExtractGifFrames(const FileName: string);
    var
      GPImage: TGPImage;
      encoderClsid: TGUID;
      BmpFrame: TBitmap;
      MemStream: TMemoryStream;
      FrameCount, FrameIndex: Integer;
    begin
      GPImage := TGPImage.Create(FileName);
      try
        if GPImage.GetLastStatus = Ok then
        begin
          GetEncoderClsid('image/bmp', encoderClsid);
          FrameCount := GPImage.GetFrameCount(GDIPAPI.FrameDimensionTime);
          for FrameIndex := 0 to FrameCount - 1 do
          begin
            GPImage.SelectActiveFrame(GDIPAPI.FrameDimensionTime, FrameIndex);
            MemStream := TMemoryStream.Create;
            try
              if GPImage.Save(TStreamAdapter.Create(MemStream), encoderClsid) = Ok then
              begin
                MemStream.Position := 0;
                BmpFrame := TBitmap.Create;
                try
                  BmpFrame.LoadFromStream(MemStream);
                  BmpFrame.SaveToFile(Format('%.2d.bmp', [FrameIndex]));
                finally
                  BmpFrame.Free;
                end;
              end;
            finally
              MemStream.Free;
            end;
          end;
        end;
      finally
        GPImage.Free;
      end;
    end;