Last active
April 20, 2020 11:45
-
-
Save NeilBostrom/8dde778f06e6f337ad73aea6e0152bbc to your computer and use it in GitHub Desktop.
How to read a GIF loop count correctly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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