【问题标题】:Cannot draw GIF on dynamically created TBitmap(s)无法在动态创建的 TBitmap(s) 上绘制 GIF
【发布时间】:2015-08-15 16:40:07
【问题描述】:

我想提取 GIF 图像的帧。下面的代码有效,但这不是我需要的。我需要将提取的帧保存在一系列位图中。

procedure TForm1.Button2Click(Sender: TObject);
var
  GIF: TGIFImage;
  Bitmap: TBitmap;
  I: Integer;
  GR: TGIFRenderer;
  R: TRect;
begin
  GIF := TGIFImage.Create;
  TRY
    GIF.LoadFromFile('c:\1.gif');
    Bitmap := TBitmap.Create;       <------------ one single object, reused
    Bitmap.SetSize(GIF.Width, GIF.Height);
    GR := TGIFRenderer.Create(GIF);
    try
      for I := 0 to GIF.Images.Count - 1 do
       begin    
         GR.Draw(Bitmap.Canvas, Bitmap.Canvas.ClipRect);

         Self.Canvas.Draw(0, 0, Bitmap);

         GR.NextFrame;
       end;
    finally
      GR.Free;
    end;
  finally
    GIF.Free;
    //Bitmap.Free;
  end;
end;

所以我为每一帧动态地创建一个位图。但这行不通。它只会在所有位图中显示相同/第一帧!

procedure TForm1.Button2Click(Sender: TObject);
var
  GIF: TGIFImage;
  Bitmap: TBitmap;
  I: Integer;
  GR: TGIFRenderer;
  R: TRect;
begin
  GIF := TGIFImage.Create;
  TRY
    GIF.LoadFromFile('c:\1.gif');
    GR := TGIFRenderer.Create(GIF);
    try
      for I := 0 to GIF.Images.Count - 1 do
       begin
         Bitmap := TBitmap.Create;       <----- multiple bitmaps, one for each frame
         Bitmap.SetSize(GIF.Width, GIF.Height);

         GR.Draw(Bitmap.Canvas, Bitmap.Canvas.ClipRect);

         Self.Canvas.Draw(0, 0, Bitmap);

         GR.NextFrame;
       end;
    finally
      GR.Free;
    end;

    {TODO: store bitmaps in a TObjectList for later use 
    List.Add(Bitmap);  }     
  finally
    GIF.Free;
  end;
end;

上面的代码有什么问题?也许 TGIFRenderer 只绘制帧之间的差异?


TLama/jachguate 的更新:

TLama 说代码不起作用,因为我没有释放位图。我不想释放位图。我以后需要它们。这是一些(演示级)代码。

VAR List: TObjectList;    {used and freed somwhere else}

procedure TForm1.Button2Click(Sender: TObject);
var
  GIF: TGIFImage;
  UniqueBMP: TBitmap;
  I: Integer;
  GR: TGIFRenderer;
  R: TRect;
begin
  List:= TObjectList.Create;
  GIF := TGIFImage.Create;
  TRY
    GIF.LoadFromFile('c:\1.gif');

    GR := TGIFRenderer.Create(GIF);
    try
      for I := 0 to GIF.Images.Count - 1 do
       begin
         UniqueBMP := TBitmap.Create;
         UniqueBMP.SetSize(GIF.Width, GIF.Height);

         if GIF.Images[I].Empty then Break;

         GR.Draw(UniqueBMP.Canvas, UniqueBMP.Canvas.ClipRect);

         Self.Canvas.Draw(0, 0, UniqueBMP);
         Sleep(50);

         List.Add(UniqueBMP);

         GR.NextFrame;
       end;
    finally
      GR.Free;
    end;
  finally
    GIF.Free;
  end;
end;


procedure TForm1.btnFreeClick(Sender: TObject);
begin 
  FreeAndNil(List);
end;

【问题讨论】:

  • 我知道!请忽略它!这只是一个演示。这与我们当前的问题无关:)
  • 不起作用说明什么,但我怀疑帧交错。我的猜测是渲染器完全按原样绘制帧(为什么不会,无论如何),并且由于您始终为每个帧使用一个新位图,您可以获得(视觉上)几乎空的位图,因为(视觉上)其余部分必须被渲染由底层框架。换句话说,您需要使用一个位图并让渲染器为您绘制图层。但是,这只是我的猜测。
  • @TLama - 我也这么认为,但我使用的 GIF 图像非常动画。帧之间有很多差异。我应该能够在接下来的帧中看到一些东西......
  • 你能把那张图片上传到某个地方(例如 imgur.com)并附上你的问题的链接吗?除了渲染隔行扫描的帧,我找不到任何其他合理的原因,为什么这两个代码示例的结果可能不同。
  • 这对我来说似乎是一个错误:TGIFRenderer 在 FCanvas 严格的私有字段中缓存了对 Canvas 的引用。然后,TCustomGIFRenderer.Draw 方法以:if (FCanvas &lt;&gt; ACanvas) 开头,但是碰巧当 Bitmap 被释放时,新的 Bitmap 在相同的地址创建,然后,渲染器 ResetInitialize 方法没有被调用,所以动画工作,多亏了这个错误。

标签: delphi


【解决方案1】:

TCustomGIFRenderer.Draw 检查要在其上渲染的画布。如果它与上次渲染时记住的不同(并且它不同,因为您正在为每一帧创建新的位图),则调用 TCustomGIFRenderer.Reset 方法,正如其名称所解释的那样,它会重置帧索引到 0。这就是为什么你总是只渲染第一帧。

【讨论】:

  • 感谢 TLama 的解释。不用感谢 Embarcadero 的“出色”用户手册。
  • 不客气!是的,这应该在文档中提到或以不同的方式更好地实现。
【解决方案2】:

基于 TLama 解决方案的工作代码(请投票不是我的帖子):

procedure TForm1.Button2Click(Sender: TObject);
var
  GIF: TGIFImage;
  TempBMP, UniqueBMP: TBitmap;
  I: Integer;
  GR: TGIFRenderer;
  R: TRect;
begin
  GIF := TGIFImage.Create;
  TRY
    GIF.LoadFromFile('c:\1.gif');

    TempBMP := TBitmap.Create;       <------- SOLUTION
    TempBMP.SetSize(GIF.Width, GIF.Height);

    GR := TGIFRenderer.Create(GIF);
    try
      for I := 0 to GIF.Images.Count - 1 do
       begin
         UniqueBMP := TBitmap.Create;      <------- SOLUTION
         UniqueBMP.SetSize(GIF.Width, GIF.Height);

         if GIF.Images[I].Empty then Break;

         GR.Draw(TempBMP.Canvas, TempBMP.Canvas.ClipRect);

         UniqueBMP.Assign(TempBMP);                <------- SOLUTION
         Self.Canvas.Draw(0, 0, UniqueBMP);
         Sleep(50);

         GR.NextFrame;
       end;
    finally
      GR.Free;
    end;
  finally
    GIF.Free;
    TempBMP.Free;
  end;
end;

【讨论】:

  • 这个解决方案很好。要让渲染器绘制当前索引的框架,您必须使用 GR.Draw 方法调用相同的画布和相同的矩形。一旦这些参数中的任何一个与第一个 GR.Draw 方法调用不同,帧索引就会重置为第一帧。
猜你喜欢
  • 2023-03-29
  • 2020-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-03
相关资源
最近更新 更多