Delphi TGIFImage动画在某些GIF查看器中出现问题

11

我发现使用 Delphi 2009 的 TGIFImage 创建的动画 GIF 在某些 GIF 查看器中有时无法正确播放。问题在于动画过早重新启动。

考虑以下示例:

program GIFAnomaly;

{$APPTYPE CONSOLE}

uses
  Windows, Types, Classes, SysUtils, Graphics, GIFImg;

var
  g: TGIFImage;
  bm: TBitmap;

procedure MakeFrame(n: integer);
var
  x: Integer;
  y: Integer;
begin
  for x := 0 to 256 - 1 do
    for y := 0 to 256 - 1 do
      bm.Canvas.Pixels[x, y] := RGB((x + n) mod 255,
        (x + y - 2*n) mod 255, (x*y*n div 500) mod 255);
end;

var
  i: integer;

begin

  bm := TBitmap.Create;
  bm.SetSize(256, 256);

  g := TGIFImage.Create;
  g.Animate := true;
  for i := 0 to 499 do
  begin
    MakeFrame(i);
    TGIFGraphicControlExtension.Create(g.Add(bm)).Delay := 3;
    Writeln('Creating frame ', i+1, ' of 500.');
  end;
  TGIFAppExtNSLoop.Create(g.Images.Frames[0]).Loops := 0;

  g.SaveToFile('C:\Users\Andreas Rejbrand\Desktop\test.gif');


end.

(这是我能找到的一个展示问题的最简单的例子。)

输出是一个相当大的动画GIF。在Internet Explorer 11中,整个15秒的“电影”正常播放,但在Google Chrome中,“电影”在只有大约四秒后就会被重新启动。

这是为什么呢?

  1. 输出的GIF文件有问题吗?
  2. 如果是,我的上面的代码有问题还是GIFImg有问题?
  3. 如果不是,观看者的问题性质是什么?可用的观众中有多少遇到了这个问题?在GIF创建过程中有没有一种方式可以“避免”这个问题?

出于对SO用户的利益,上面的代码是一个最小的工作示例。当然,当我发现这个问题时,我并没有创建这些迷幻的图案。相反,我正在开发一个洛伦兹系统模拟器,并生成了这个GIF动画,在IE中播放,但在Chrome中没有:

显示问题的样本GIF动画

在Internet Explorer 11中,在重新启动动画之前,模型会旋转360度。在Google Chrome中,动画在仅有大约20度后就会被提前重新启动。

  • 洛伦兹图像在Internet Explorer 11.0.9600.17239、The GIMP 2.8.0、Opera 12.16中可以工作。
  • 洛伦兹图像在Google Chrome 36.0.1985.143 m、Firefox 26.0、27.0.1、31.0中无法工作

如果我在The GIMP中打开一个“问题”GIF,并让GIMP重新将其保存为动画GIF,则结果可以在任何查看器中正常工作。以下是经过GIMP处理的Lorenz动画版本:

已经经过GIMP处理的样本GIF动画

使用十六进制编辑器比较这两个文件,并使用维基百科文章作为参考,似乎例如“NETSCAPE”字符串在原始(未GIMP)版本中位置错误。有点奇怪的是,即使我设置了GIF图像的widthheight,逻辑屏幕描述符中对应的值也不在那里。


@LU RD:关于 Q 中的 GIF 怎么样? - Andreas Rejbrand
看起来就像你所描述的那样,是过早重启。 - LU RD
我还怀疑存在“颜色溢出”问题。 - Andreas Rejbrand
@LU RD:如果你在 Delphi XEn 中创建 GIF,你在哪个偏移量找到字符串“NETSCAPE”? - Andreas Rejbrand
我发现的唯一问题是,在D2009中未设置AnimationSpeed属性。因此,它可能具有随机值。我找到了一个带有D2009的平台并运行了测试。它可以使用15秒的动画序列。前48个字节:47 49 46 38 39 61 00 01 00 01 77 00 00 21 F9 04 00 03 00 00 00 21 FF 0B 4E 45 54 53 43 41 50 45 32 2E 30 03 01 00 00 00 2C 00 00 00 00 00 01 00 - LU RD
显示剩余12条评论
2个回答

6

这是TGIFImage的LZW编码器中的一个错误。

在极少情况下,LZW编码器会在LZW流的末尾输出一个额外的零字节。由于LZW结束块标记也是零字节,严格的GIF阅读器可能会因此而出错,或将其解释为GIF的结尾(尽管文件结尾标记为$3B)。

一些GIF阅读器之所以能够处理这个问题,可能是因为多年前存在这个问题的GIF很常见。显然,TGIFImage并不是唯一犯这个特定错误的库。

要修复这个问题,请对gifimg.pas进行以下修改(用*标记更改):

procedure TGIFWriter.FlushBuffer;
begin
  if (FNeedsFlush) then
  begin
    FBuffer[0] := Byte(FBufferCount-1); // Block size excluding the count
    Stream.WriteBuffer(FBuffer, FBufferCount);
    FBufferCount := 1; // Reserve first byte of buffer for length
    FNeedsFlush := False; // *** Add this ***
  end;
end;

这似乎解决了问题。非常感谢!回答非常好! - Andreas Rejbrand
但是如果您使用一个超过一次调用 FlushBuffer 的计数来调用 function TGIFWriter.Write(const Buffer; Count: Longint): Longint; 会发生什么呢?那将导致写入被阻塞。 - LU RD
2
据我所知,编码器每次只写入一个字节,因此这永远不会成为真正的问题。除此之外,我同意这很可疑。FNeedsFlush 应该在 TGIFWriter.Write 的内部循环中设置,而不是在外层循环之外。 - SpeedFreak

1
编辑:事实证明这不是答案,但我仍然保留它作为循环扩展的规则。
“NETSCAPE循环扩展”必须是第一个扩展名:
var
  Frame: TGIFFrame;
...
for i := 0 to 499 do
begin
  MakeFrame(i);
  Frame := g.Add(bm);
  if (i = 0) then
    TGIFAppExtNSLoop.Create(Frame).Loops := 0;
  TGIFGraphicControlExtension.Create(Frame).Delay := 3;
  Writeln('Creating frame ', i+1, ' of 500.');
end;

请参见:TGIFImage FAQ

除此之外,我认为您的GIF没有任何问题,但是您可以通过全局颜色表来减小其大小。


奇怪,这对我起作用了。我加载了GIF,从第0帧中删除了GCE(从而使循环扩展成为第一个),然后再次保存(全部使用TGIFImage)。我刚刚再试了一次,现在不起作用了。将进一步调查... - SpeedFreak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接