Skip to content

Instantly share code, notes, and snippets.

@NeilBostrom
Last active April 20, 2020 11:45
Show Gist options
  • Save NeilBostrom/8dde778f06e6f337ad73aea6e0152bbc to your computer and use it in GitHub Desktop.
Save NeilBostrom/8dde778f06e6f337ad73aea6e0152bbc to your computer and use it in GitHub Desktop.
How to read a GIF loop count correctly
public class GifImageInfo
{
public int Width { get; set; }
public int Height { get; set; }
public bool IsAnimated { get; set; }
public bool IsLooped { get; set; }
public int? LoopCount { get; set; }
public TimeSpan? AnimationLength { get; set; }
public TimeSpan? TotalAnimationLength => IsLooped ? AnimationLength * LoopCount : AnimationLength;
}
public static class GifTools
{
public static GifImageInfo GetImageInfo(string path)
{
var info = new GifImageInfo();
using var fileStream = new FileStream(path, FileMode.Open);
using var image = Image.FromStream(fileStream);
info.Height = image.Height;
info.Width = image.Width;
if (image.RawFormat.Equals(ImageFormat.Gif))
{
if (ImageAnimator.CanAnimate(image))
{
FrameDimension frameDimension = new FrameDimension(image.FrameDimensionsList[0]);
int frameCount = image.GetFrameCount(frameDimension);
float totalDuration = 0;
var minimumFrameDelay = 1000.0 / 60;
for (int f = 0; f < frameCount; f++)
{
var delayPropertyBytes = image.GetPropertyItem(20736).Value;
var frameDelay = BitConverter.ToInt32(delayPropertyBytes, f * 4) * 10;
// Minimum delay is 16 ms. It's 1/60 sec i.e. 60 fps
totalDuration += frameDelay < minimumFrameDelay ? (int)minimumFrameDelay : frameDelay;
}
info.AnimationLength = TimeSpan.FromMilliseconds(totalDuration);
info.IsAnimated = true;
fileStream.Position = 0;
if (ContainsApplicationExtensionBlock(fileStream))
{
// Value 0 in "NETSCAPE2.0" means loop forever.
// Missing "NETSCAPE2.0" block means 0 repetitions = loop once.
// Value 1 in "NETSCAPE2.0" means 1 repetition = 2 loops.
// Value 2 in "NETSCAPE2.0" means 2 repetitions = 3 loops.
var loopValue = BitConverter.ToInt16(image.GetPropertyItem(20737).Value, 0);
info.IsLooped = true;
// Use null to say infinite looping
if (loopValue == 0)
info.LoopCount = null;
else
info.LoopCount = loopValue + 1;
}
}
}
return info;
}
private static bool ContainsApplicationExtensionBlock(Stream imageStream)
{
// http://www.vurdalakov.net/misc/gif/netscape-looping-application-extension
var buf2 = new byte[14];
buf2[0] = 33; //extension introducer
buf2[1] = 255; //application extension
buf2[2] = 11; //size of block
buf2[3] = 78; //N
buf2[4] = 69; //E
buf2[5] = 84; //T
buf2[6] = 83; //S
buf2[7] = 67; //C
buf2[8] = 65; //A
buf2[9] = 80; //P
buf2[10] = 69; //E
buf2[11] = 50; //2
buf2[12] = 46; //.
buf2[13] = 48; //0
return FindPosition(imageStream, buf2) > 0;
}
private static long FindPosition(Stream stream, byte[] byteSequence)
{
if (byteSequence.Length > stream.Length)
return -1;
byte[] buffer = new byte[byteSequence.Length];
using (BufferedStream bufStream = new BufferedStream(stream, byteSequence.Length))
{
int i;
while ((i = bufStream.Read(buffer, 0, byteSequence.Length)) == byteSequence.Length)
{
if (byteSequence.SequenceEqual(buffer))
return bufStream.Position - byteSequence.Length;
else
bufStream.Position -= byteSequence.Length - PadLeftSequence(buffer, byteSequence);
}
}
return -1;
}
private static int PadLeftSequence(byte[] bytes, byte[] seqBytes)
{
int i = 1;
while (i < bytes.Length)
{
int n = bytes.Length - i;
byte[] aux1 = new byte[n];
byte[] aux2 = new byte[n];
Array.Copy(bytes, i, aux1, 0, n);
Array.Copy(seqBytes, aux2, n);
if (aux1.SequenceEqual(aux2))
return i;
i++;
}
return i;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment