aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.AnimatedGif
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio/Tango.AnimatedGif')
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/AnimationCache.cs151
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifApplicationExtension.cs50
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlock.cs28
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlockKind.cs10
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifColor.cs25
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifCommentExtension.cs37
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifDecoderException.cs16
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifExtension.cs32
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFile.cs86
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFrame.cs47
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifGraphicControlExtension.cs52
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHeader.cs38
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHelpers.cs110
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageData.cs27
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageDescriptor.cs43
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifLogicalScreenDescriptor.cs43
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifPlainTextExtension.cs68
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifTrailer.cs21
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/ImageAnimationController.cs192
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/ImageBehavior.cs966
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Properties/AssemblyInfo.cs10
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/Tango.AnimatedGif.csproj90
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml12
-rw-r--r--Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml.cs28
24 files changed, 2182 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/AnimationCache.cs b/Software/Visual_Studio/Tango.AnimatedGif/AnimationCache.cs
new file mode 100644
index 000000000..b9eaf189a
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/AnimationCache.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Imaging;
+
+namespace Tango.AnimatedGif
+{
+ static class AnimationCache
+ {
+ private class CacheKey
+ {
+ private readonly ImageSource _source;
+ private readonly RepeatBehavior _repeatBehavior;
+
+ public CacheKey(ImageSource source, RepeatBehavior repeatBehavior)
+ {
+ _source = source;
+ _repeatBehavior = repeatBehavior;
+ }
+
+ private bool Equals(CacheKey other)
+ {
+ return ImageEquals(_source, other._source)
+ && Equals(_repeatBehavior, other._repeatBehavior);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((CacheKey)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (ImageGetHashCode(_source) * 397) ^ _repeatBehavior.GetHashCode();
+ }
+ }
+
+ private static int ImageGetHashCode(ImageSource image)
+ {
+ if (image != null)
+ {
+ var uri = GetUri(image);
+ if (uri != null)
+ return uri.GetHashCode();
+ }
+ return 0;
+ }
+
+ private static bool ImageEquals(ImageSource x, ImageSource y)
+ {
+ if (Equals(x, y))
+ return true;
+ if ((x == null) != (y == null))
+ return false;
+ // They can't both be null or Equals would have returned true
+ // and if any is null, the previous would have detected it
+ // ReSharper disable PossibleNullReferenceException
+ if (x.GetType() != y.GetType())
+ return false;
+ // ReSharper restore PossibleNullReferenceException
+ var xUri = GetUri(x);
+ var yUri = GetUri(y);
+ return xUri != null && xUri == yUri;
+ }
+
+ private static Uri GetUri(ImageSource image)
+ {
+ var bmp = image as BitmapImage;
+ if (bmp != null && bmp.UriSource != null)
+ {
+ if (bmp.UriSource.IsAbsoluteUri)
+ return bmp.UriSource;
+ if (bmp.BaseUri != null)
+ return new Uri(bmp.BaseUri, bmp.UriSource);
+ }
+ var frame = image as BitmapFrame;
+ if (frame != null)
+ {
+ string s = frame.ToString();
+ if (s != frame.GetType().FullName)
+ {
+ Uri fUri;
+ if (Uri.TryCreate(s, UriKind.RelativeOrAbsolute, out fUri))
+ {
+ if (fUri.IsAbsoluteUri)
+ return fUri;
+ if (frame.BaseUri != null)
+ return new Uri(frame.BaseUri, fUri);
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ private static readonly Dictionary<CacheKey, ObjectAnimationUsingKeyFrames> _animationCache = new Dictionary<CacheKey, ObjectAnimationUsingKeyFrames>();
+ private static readonly Dictionary<CacheKey, int> _referenceCount = new Dictionary<CacheKey, int>();
+
+ public static void IncrementReferenceCount(ImageSource source, RepeatBehavior repeatBehavior)
+ {
+ var cacheKey = new CacheKey(source, repeatBehavior);
+ int count;
+ _referenceCount.TryGetValue(cacheKey, out count);
+ count++;
+ _referenceCount[cacheKey] = count;
+ }
+
+ public static void DecrementReferenceCount(ImageSource source, RepeatBehavior repeatBehavior)
+ {
+ var cacheKey = new CacheKey(source, repeatBehavior);
+ int count;
+ _referenceCount.TryGetValue(cacheKey, out count);
+ if (count > 0)
+ {
+ count--;
+ _referenceCount[cacheKey] = count;
+ }
+ if (count == 0)
+ {
+ _animationCache.Remove(cacheKey);
+ _referenceCount.Remove(cacheKey);
+ }
+ }
+
+ public static void AddAnimation(ImageSource source, RepeatBehavior repeatBehavior, ObjectAnimationUsingKeyFrames animation)
+ {
+ var key = new CacheKey(source, repeatBehavior);
+ _animationCache[key] = animation;
+ }
+
+ public static void RemoveAnimation(ImageSource source, RepeatBehavior repeatBehavior, ObjectAnimationUsingKeyFrames animation)
+ {
+ var key = new CacheKey(source, repeatBehavior);
+ _animationCache.Remove(key);
+ }
+
+ public static ObjectAnimationUsingKeyFrames GetAnimation(ImageSource source, RepeatBehavior repeatBehavior)
+ {
+ var key = new CacheKey(source, repeatBehavior);
+ ObjectAnimationUsingKeyFrames animation;
+ _animationCache.TryGetValue(key, out animation);
+ return animation;
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifApplicationExtension.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifApplicationExtension.cs
new file mode 100644
index 000000000..bbf8a5820
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifApplicationExtension.cs
@@ -0,0 +1,50 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ // label 0xFF
+ internal class GifApplicationExtension : GifExtension
+ {
+ internal const int ExtensionLabel = 0xFF;
+
+ public int BlockSize { get; private set; }
+ public string ApplicationIdentifier { get; private set; }
+ public byte[] AuthenticationCode { get; private set; }
+ public byte[] Data { get; private set; }
+
+ private GifApplicationExtension()
+ {
+ }
+
+ internal override GifBlockKind Kind
+ {
+ get { return GifBlockKind.SpecialPurpose; }
+ }
+
+ internal static GifApplicationExtension ReadApplication(Stream stream)
+ {
+ var ext = new GifApplicationExtension();
+ ext.Read(stream);
+ return ext;
+ }
+
+ private void Read(Stream stream)
+ {
+ // Note: at this point, the label (0xFF) has already been read
+
+ byte[] bytes = new byte[12];
+ stream.ReadAll(bytes, 0, bytes.Length);
+ BlockSize = bytes[0]; // should always be 11
+ if (BlockSize != 11)
+ throw GifHelpers.InvalidBlockSizeException("Application Extension", 11, BlockSize);
+
+ ApplicationIdentifier = Encoding.ASCII.GetString(bytes, 1, 8);
+ byte[] authCode = new byte[3];
+ Array.Copy(bytes, 9, authCode, 0, 3);
+ AuthenticationCode = authCode;
+ Data = GifHelpers.ReadDataBlocks(stream, false);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlock.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlock.cs
new file mode 100644
index 000000000..331e7bd8f
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlock.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal abstract class GifBlock
+ {
+ internal static GifBlock ReadBlock(Stream stream, IEnumerable<GifExtension> controlExtensions, bool metadataOnly)
+ {
+ int blockId = stream.ReadByte();
+ if (blockId < 0)
+ throw GifHelpers.UnexpectedEndOfStreamException();
+ switch (blockId)
+ {
+ case GifExtension.ExtensionIntroducer:
+ return GifExtension.ReadExtension(stream, controlExtensions, metadataOnly);
+ case GifFrame.ImageSeparator:
+ return GifFrame.ReadFrame(stream, controlExtensions, metadataOnly);
+ case GifTrailer.TrailerByte:
+ return GifTrailer.ReadTrailer();
+ default:
+ throw GifHelpers.UnknownBlockTypeException(blockId);
+ }
+ }
+
+ internal abstract GifBlockKind Kind { get; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlockKind.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlockKind.cs
new file mode 100644
index 000000000..eab7ea2c1
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlockKind.cs
@@ -0,0 +1,10 @@
+namespace Tango.AnimatedGif.Decoding
+{
+ internal enum GifBlockKind
+ {
+ Control,
+ GraphicRendering,
+ SpecialPurpose,
+ Other
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifColor.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifColor.cs
new file mode 100644
index 000000000..8c0461941
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifColor.cs
@@ -0,0 +1,25 @@
+namespace Tango.AnimatedGif.Decoding
+{
+ internal struct GifColor
+ {
+ private readonly byte _r;
+ private readonly byte _g;
+ private readonly byte _b;
+
+ internal GifColor(byte r, byte g, byte b)
+ {
+ _r = r;
+ _g = g;
+ _b = b;
+ }
+
+ public byte R { get { return _r; } }
+ public byte G { get { return _g; } }
+ public byte B { get { return _b; } }
+
+ public override string ToString()
+ {
+ return string.Format("#{0:x2}{1:x2}{2:x2}", _r, _g, _b);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifCommentExtension.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifCommentExtension.cs
new file mode 100644
index 000000000..cde458d0f
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifCommentExtension.cs
@@ -0,0 +1,37 @@
+using System.IO;
+using System.Text;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal class GifCommentExtension : GifExtension
+ {
+ internal const int ExtensionLabel = 0xFE;
+
+ public string Text { get; private set; }
+
+ private GifCommentExtension()
+ {
+ }
+
+ internal override GifBlockKind Kind
+ {
+ get { return GifBlockKind.SpecialPurpose; }
+ }
+
+ internal static GifCommentExtension ReadComment(Stream stream)
+ {
+ var comment = new GifCommentExtension();
+ comment.Read(stream);
+ return comment;
+ }
+
+ private void Read(Stream stream)
+ {
+ // Note: at this point, the label (0xFE) has already been read
+
+ var bytes = GifHelpers.ReadDataBlocks(stream, false);
+ if (bytes != null)
+ Text = Encoding.ASCII.GetString(bytes);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifDecoderException.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifDecoderException.cs
new file mode 100644
index 000000000..7ff8ef6e0
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifDecoderException.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ [Serializable]
+ internal class GifDecoderException : Exception
+ {
+ internal GifDecoderException() { }
+ internal GifDecoderException(string message) : base(message) { }
+ internal GifDecoderException(string message, Exception inner) : base(message, inner) { }
+ protected GifDecoderException(
+ System.Runtime.Serialization.SerializationInfo info,
+ System.Runtime.Serialization.StreamingContext context)
+ : base(info, context) { }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifExtension.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifExtension.cs
new file mode 100644
index 000000000..fda8909ec
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifExtension.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal abstract class GifExtension : GifBlock
+ {
+ internal const int ExtensionIntroducer = 0x21;
+
+ internal static GifExtension ReadExtension(Stream stream, IEnumerable<GifExtension> controlExtensions, bool metadataOnly)
+ {
+ // Note: at this point, the Extension Introducer (0x21) has already been read
+
+ int label = stream.ReadByte();
+ if (label < 0)
+ throw GifHelpers.UnexpectedEndOfStreamException();
+ switch (label)
+ {
+ case GifGraphicControlExtension.ExtensionLabel:
+ return GifGraphicControlExtension.ReadGraphicsControl(stream);
+ case GifCommentExtension.ExtensionLabel:
+ return GifCommentExtension.ReadComment(stream);
+ case GifPlainTextExtension.ExtensionLabel:
+ return GifPlainTextExtension.ReadPlainText(stream, controlExtensions, metadataOnly);
+ case GifApplicationExtension.ExtensionLabel:
+ return GifApplicationExtension.ReadApplication(stream);
+ default:
+ throw GifHelpers.UnknownExtensionTypeException(label);
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFile.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFile.cs
new file mode 100644
index 000000000..500ce12a8
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFile.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal class GifFile
+ {
+ public GifHeader Header { get; private set; }
+ public GifColor[] GlobalColorTable { get; set; }
+ public IList<GifFrame> Frames { get; set; }
+ public IList<GifExtension> Extensions { get; set; }
+ public ushort RepeatCount { get; set; }
+
+ private GifFile()
+ {
+ }
+
+ internal static GifFile ReadGifFile(Stream stream, bool metadataOnly)
+ {
+ var file = new GifFile();
+ file.Read(stream, metadataOnly);
+ return file;
+ }
+
+ private void Read(Stream stream, bool metadataOnly)
+ {
+ Header = GifHeader.ReadHeader(stream);
+
+ if (Header.LogicalScreenDescriptor.HasGlobalColorTable)
+ {
+ GlobalColorTable = GifHelpers.ReadColorTable(stream, Header.LogicalScreenDescriptor.GlobalColorTableSize);
+ }
+ ReadFrames(stream, metadataOnly);
+
+ var netscapeExtension =
+ Extensions
+ .OfType<GifApplicationExtension>()
+ .FirstOrDefault(GifHelpers.IsNetscapeExtension);
+
+ if (netscapeExtension != null)
+ RepeatCount = GifHelpers.GetRepeatCount(netscapeExtension);
+ else
+ RepeatCount = 1;
+ }
+
+ private void ReadFrames(Stream stream, bool metadataOnly)
+ {
+ List<GifFrame> frames = new List<GifFrame>();
+ List<GifExtension> controlExtensions = new List<GifExtension>();
+ List<GifExtension> specialExtensions = new List<GifExtension>();
+ while (true)
+ {
+ var block = GifBlock.ReadBlock(stream, controlExtensions, metadataOnly);
+
+ if (block.Kind == GifBlockKind.GraphicRendering)
+ controlExtensions = new List<GifExtension>();
+
+ if (block is GifFrame)
+ {
+ frames.Add((GifFrame)block);
+ }
+ else if (block is GifExtension)
+ {
+ var extension = (GifExtension)block;
+ switch (extension.Kind)
+ {
+ case GifBlockKind.Control:
+ controlExtensions.Add(extension);
+ break;
+ case GifBlockKind.SpecialPurpose:
+ specialExtensions.Add(extension);
+ break;
+ }
+ }
+ else if (block is GifTrailer)
+ {
+ break;
+ }
+ }
+
+ this.Frames = frames.AsReadOnly();
+ this.Extensions = specialExtensions.AsReadOnly();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFrame.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFrame.cs
new file mode 100644
index 000000000..85740ec87
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFrame.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal class GifFrame : GifBlock
+ {
+ internal const int ImageSeparator = 0x2C;
+
+ public GifImageDescriptor Descriptor { get; private set; }
+ public GifColor[] LocalColorTable { get; private set; }
+ public IList<GifExtension> Extensions { get; private set; }
+ public GifImageData ImageData { get; private set; }
+
+ private GifFrame()
+ {
+ }
+
+ internal override GifBlockKind Kind
+ {
+ get { return GifBlockKind.GraphicRendering; }
+ }
+
+ internal static GifFrame ReadFrame(Stream stream, IEnumerable<GifExtension> controlExtensions, bool metadataOnly)
+ {
+ var frame = new GifFrame();
+
+ frame.Read(stream, controlExtensions, metadataOnly);
+
+ return frame;
+ }
+
+ private void Read(Stream stream, IEnumerable<GifExtension> controlExtensions, bool metadataOnly)
+ {
+ // Note: at this point, the Image Separator (0x2C) has already been read
+
+ Descriptor = GifImageDescriptor.ReadImageDescriptor(stream);
+ if (Descriptor.HasLocalColorTable)
+ {
+ LocalColorTable = GifHelpers.ReadColorTable(stream, Descriptor.LocalColorTableSize);
+ }
+ ImageData = GifImageData.ReadImageData(stream, metadataOnly);
+ Extensions = controlExtensions.ToList().AsReadOnly();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifGraphicControlExtension.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifGraphicControlExtension.cs
new file mode 100644
index 000000000..a3782c011
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifGraphicControlExtension.cs
@@ -0,0 +1,52 @@
+using System;
+using System.IO;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ // label 0xF9
+ internal class GifGraphicControlExtension : GifExtension
+ {
+ internal const int ExtensionLabel = 0xF9;
+
+ public int BlockSize { get; private set; }
+ public int DisposalMethod { get; private set; }
+ public bool UserInput { get; private set; }
+ public bool HasTransparency { get; private set; }
+ public int Delay { get; private set; }
+ public int TransparencyIndex { get; private set; }
+
+ private GifGraphicControlExtension()
+ {
+
+ }
+
+ internal override GifBlockKind Kind
+ {
+ get { return GifBlockKind.Control; }
+ }
+
+ internal static GifGraphicControlExtension ReadGraphicsControl(Stream stream)
+ {
+ var ext = new GifGraphicControlExtension();
+ ext.Read(stream);
+ return ext;
+ }
+
+ private void Read(Stream stream)
+ {
+ // Note: at this point, the label (0xF9) has already been read
+
+ byte[] bytes = new byte[6];
+ stream.ReadAll(bytes, 0, bytes.Length);
+ BlockSize = bytes[0]; // should always be 4
+ if (BlockSize != 4)
+ throw GifHelpers.InvalidBlockSizeException("Graphic Control Extension", 4, BlockSize);
+ byte packedFields = bytes[1];
+ DisposalMethod = (packedFields & 0x1C) >> 2;
+ UserInput = (packedFields & 0x02) != 0;
+ HasTransparency = (packedFields & 0x01) != 0;
+ Delay = BitConverter.ToUInt16(bytes, 2) * 10; // milliseconds
+ TransparencyIndex = bytes[4];
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHeader.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHeader.cs
new file mode 100644
index 000000000..b903b83c9
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHeader.cs
@@ -0,0 +1,38 @@
+using System.IO;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal class GifHeader : GifBlock
+ {
+ public string Signature { get; private set; }
+ public string Version { get; private set; }
+ public GifLogicalScreenDescriptor LogicalScreenDescriptor { get; private set; }
+
+ private GifHeader()
+ {
+ }
+
+ internal override GifBlockKind Kind
+ {
+ get { return GifBlockKind.Other; }
+ }
+
+ internal static GifHeader ReadHeader(Stream stream)
+ {
+ var header = new GifHeader();
+ header.Read(stream);
+ return header;
+ }
+
+ private void Read(Stream stream)
+ {
+ Signature = GifHelpers.ReadString(stream, 3);
+ if (Signature != "GIF")
+ throw GifHelpers.InvalidSignatureException(Signature);
+ Version = GifHelpers.ReadString(stream, 3);
+ if (Version != "87a" && Version != "89a")
+ throw GifHelpers.UnsupportedVersionException(Version);
+ LogicalScreenDescriptor = GifLogicalScreenDescriptor.ReadLogicalScreenDescriptor(stream);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHelpers.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHelpers.cs
new file mode 100644
index 000000000..801b3da27
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHelpers.cs
@@ -0,0 +1,110 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal static class GifHelpers
+ {
+ public static string ReadString(Stream stream, int length)
+ {
+ byte[] bytes = new byte[length];
+ stream.ReadAll(bytes, 0, length);
+ return Encoding.ASCII.GetString(bytes);
+ }
+
+ public static byte[] ReadDataBlocks(Stream stream, bool discard)
+ {
+ MemoryStream ms = discard ? null : new MemoryStream();
+ using (ms)
+ {
+ int len;
+ while ((len = stream.ReadByte()) > 0)
+ {
+ byte[] bytes = new byte[len];
+ stream.ReadAll(bytes, 0, len);
+ if (ms != null)
+ ms.Write(bytes, 0, len);
+ }
+ if (ms != null)
+ return ms.ToArray();
+ return null;
+ }
+ }
+
+ public static GifColor[] ReadColorTable(Stream stream, int size)
+ {
+ int length = 3 * size;
+ byte[] bytes = new byte[length];
+ stream.ReadAll(bytes, 0, length);
+ GifColor[] colorTable = new GifColor[size];
+ for (int i = 0; i < size; i++)
+ {
+ byte r = bytes[3 * i];
+ byte g = bytes[3 * i + 1];
+ byte b = bytes[3 * i + 2];
+ colorTable[i] = new GifColor(r, g, b);
+ }
+ return colorTable;
+ }
+
+ public static bool IsNetscapeExtension(GifApplicationExtension ext)
+ {
+ return ext.ApplicationIdentifier == "NETSCAPE"
+ && Encoding.ASCII.GetString(ext.AuthenticationCode) == "2.0";
+ }
+
+ public static ushort GetRepeatCount(GifApplicationExtension ext)
+ {
+ if (ext.Data.Length >= 3)
+ {
+ return BitConverter.ToUInt16(ext.Data, 1);
+ }
+ return 1;
+ }
+
+ public static Exception UnexpectedEndOfStreamException()
+ {
+ return new GifDecoderException("Unexpected end of stream before trailer was encountered");
+ }
+
+ public static Exception UnknownBlockTypeException(int blockId)
+ {
+ return new GifDecoderException("Unknown block type: 0x" + blockId.ToString("x2"));
+ }
+
+ public static Exception UnknownExtensionTypeException(int extensionLabel)
+ {
+ return new GifDecoderException("Unknown extension type: 0x" + extensionLabel.ToString("x2"));
+ }
+
+ public static Exception InvalidBlockSizeException(string blockName, int expectedBlockSize, int actualBlockSize)
+ {
+ return new GifDecoderException(
+ string.Format(
+ "Invalid block size for {0}. Expected {1}, but was {2}",
+ blockName,
+ expectedBlockSize,
+ actualBlockSize));
+ }
+
+ public static Exception InvalidSignatureException(string signature)
+ {
+ return new GifDecoderException("Invalid file signature: " + signature);
+ }
+
+ public static Exception UnsupportedVersionException(string version)
+ {
+ return new GifDecoderException("Unsupported version: " + version);
+ }
+
+ public static void ReadAll(this Stream stream, byte[] buffer, int offset, int count)
+ {
+ int totalRead = 0;
+ while (totalRead < count)
+ {
+ totalRead += stream.Read(buffer, offset + totalRead, count - totalRead);
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageData.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageData.cs
new file mode 100644
index 000000000..32ae1359b
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageData.cs
@@ -0,0 +1,27 @@
+using System.IO;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal class GifImageData
+ {
+ public byte LzwMinimumCodeSize { get; set; }
+ public byte[] CompressedData { get; set; }
+
+ private GifImageData()
+ {
+ }
+
+ internal static GifImageData ReadImageData(Stream stream, bool metadataOnly)
+ {
+ var imgData = new GifImageData();
+ imgData.Read(stream, metadataOnly);
+ return imgData;
+ }
+
+ private void Read(Stream stream, bool metadataOnly)
+ {
+ LzwMinimumCodeSize = (byte)stream.ReadByte();
+ CompressedData = GifHelpers.ReadDataBlocks(stream, metadataOnly);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageDescriptor.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageDescriptor.cs
new file mode 100644
index 000000000..ad344ef4e
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageDescriptor.cs
@@ -0,0 +1,43 @@
+using System;
+using System.IO;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal class GifImageDescriptor
+ {
+ public int Left { get; private set; }
+ public int Top { get; private set; }
+ public int Width { get; private set; }
+ public int Height { get; private set; }
+ public bool HasLocalColorTable { get; private set; }
+ public bool Interlace { get; private set; }
+ public bool IsLocalColorTableSorted { get; private set; }
+ public int LocalColorTableSize { get; private set; }
+
+ private GifImageDescriptor()
+ {
+ }
+
+ internal static GifImageDescriptor ReadImageDescriptor(Stream stream)
+ {
+ var descriptor = new GifImageDescriptor();
+ descriptor.Read(stream);
+ return descriptor;
+ }
+
+ private void Read(Stream stream)
+ {
+ byte[] bytes = new byte[9];
+ stream.ReadAll(bytes, 0, bytes.Length);
+ Left = BitConverter.ToUInt16(bytes, 0);
+ Top = BitConverter.ToUInt16(bytes, 2);
+ Width = BitConverter.ToUInt16(bytes, 4);
+ Height = BitConverter.ToUInt16(bytes, 6);
+ byte packedFields = bytes[8];
+ HasLocalColorTable = (packedFields & 0x80) != 0;
+ Interlace = (packedFields & 0x40) != 0;
+ IsLocalColorTableSorted = (packedFields & 0x20) != 0;
+ LocalColorTableSize = 1 << ((packedFields & 0x07) + 1);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifLogicalScreenDescriptor.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifLogicalScreenDescriptor.cs
new file mode 100644
index 000000000..55bcb860b
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifLogicalScreenDescriptor.cs
@@ -0,0 +1,43 @@
+using System;
+using System.IO;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ internal class GifLogicalScreenDescriptor
+ {
+ public int Width { get; private set; }
+ public int Height { get; private set; }
+ public bool HasGlobalColorTable { get; private set; }
+ public int ColorResolution { get; private set; }
+ public bool IsGlobalColorTableSorted { get; private set; }
+ public int GlobalColorTableSize { get; private set; }
+ public int BackgroundColorIndex { get; private set; }
+ public double PixelAspectRatio { get; private set; }
+
+ internal static GifLogicalScreenDescriptor ReadLogicalScreenDescriptor(Stream stream)
+ {
+ var descriptor = new GifLogicalScreenDescriptor();
+ descriptor.Read(stream);
+ return descriptor;
+ }
+
+ private void Read(Stream stream)
+ {
+ byte[] bytes = new byte[7];
+ stream.ReadAll(bytes, 0, bytes.Length);
+
+ Width = BitConverter.ToUInt16(bytes, 0);
+ Height = BitConverter.ToUInt16(bytes, 2);
+ byte packedFields = bytes[4];
+ HasGlobalColorTable = (packedFields & 0x80) != 0;
+ ColorResolution = ((packedFields & 0x70) >> 4) + 1;
+ IsGlobalColorTableSorted = (packedFields & 0x08) != 0;
+ GlobalColorTableSize = 1 << ((packedFields & 0x07) + 1);
+ BackgroundColorIndex = bytes[5];
+ PixelAspectRatio =
+ bytes[5] == 0
+ ? 0.0
+ : (15 + bytes[5]) / 64.0;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifPlainTextExtension.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifPlainTextExtension.cs
new file mode 100644
index 000000000..ddd20c7b7
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifPlainTextExtension.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace Tango.AnimatedGif.Decoding
+{
+ // label 0x01
+ internal class GifPlainTextExtension : GifExtension
+ {
+ internal const int ExtensionLabel = 0x01;
+
+ public int BlockSize { get; private set; }
+ public int Left { get; private set; }
+ public int Top { get; private set; }
+ public int Width { get; private set; }
+ public int Height { get; private set; }
+ public int CellWidth { get; private set; }
+ public int CellHeight { get; private set; }
+ public int ForegroundColorIndex { get; private set; }
+ public int BackgroundColorIndex { get; private set; }
+ public string Text { get; private set; }
+
+ public IList<GifExtension> Extensions { get; private set; }
+
+ private GifPlainTextExtension()
+ {
+ }
+
+ internal override GifBlockKind Kind
+ {
+ get { return GifBlockKind.GraphicRendering; }
+ }
+
+ internal static GifPlainTextExtension ReadPlainText(Stream stream, IEnumerable<GifExtension> controlExtensions, bool metadataOnly)
+ {
+ var plainText = new GifPlainTextExtension();
+ plainText.Read(stream, controlExtensions, metadataOnly);
+ return plainText;
+ }
+
+ private void Read(Stream stream, IEnumerable<GifExtension> controlExtensions, bool metadataOnly)
+ {
+ // Note: at this point, the label (0x01) has already been read
+
+ byte[] bytes = new byte[13];
+ stream.ReadAll(bytes,0, bytes.Length);
+
+ BlockSize = bytes[0];
+ if (BlockSize != 12)
+ throw GifHelpers.InvalidBlockSizeException("Plain Text Extension", 12, BlockSize);
+
+ Left = BitConverter.ToUInt16(bytes, 1);
+ Top = BitConverter.ToUInt16(bytes, 3);
+ Width = BitConverter.ToUInt16(bytes, 5);
+ Height = BitConverter.ToUInt16(bytes, 7);
+ CellWidth = bytes[9];
+ CellHeight = bytes[10];
+ ForegroundColorIndex = bytes[11];
+ BackgroundColorIndex = bytes[12];
+
+ var dataBytes = GifHelpers.ReadDataBlocks(stream, metadataOnly);
+ Text = Encoding.ASCII.GetString(dataBytes);
+ Extensions = controlExtensions.ToList().AsReadOnly();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifTrailer.cs b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifTrailer.cs
new file mode 100644
index 000000000..82e153ad3
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifTrailer.cs
@@ -0,0 +1,21 @@
+namespace Tango.AnimatedGif.Decoding
+{
+ internal class GifTrailer : GifBlock
+ {
+ internal const int TrailerByte = 0x3B;
+
+ private GifTrailer()
+ {
+ }
+
+ internal override GifBlockKind Kind
+ {
+ get { return GifBlockKind.Other; }
+ }
+
+ internal static GifTrailer ReadTrailer()
+ {
+ return new GifTrailer();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/ImageAnimationController.cs b/Software/Visual_Studio/Tango.AnimatedGif/ImageAnimationController.cs
new file mode 100644
index 000000000..239af9c8b
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/ImageAnimationController.cs
@@ -0,0 +1,192 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows.Controls;
+using System.Windows.Media.Animation;
+
+namespace Tango.AnimatedGif
+{
+ /// <summary>
+ /// Provides a way to pause, resume or seek a GIF animation.
+ /// </summary>
+ public class ImageAnimationController : IDisposable
+ {
+ private static readonly DependencyPropertyDescriptor _sourceDescriptor;
+
+ static ImageAnimationController()
+ {
+ _sourceDescriptor = DependencyPropertyDescriptor.FromProperty(Image.SourceProperty, typeof (Image));
+ }
+
+ private readonly Image _image;
+ private readonly ObjectAnimationUsingKeyFrames _animation;
+ private readonly AnimationClock _clock;
+ private readonly ClockController _clockController;
+
+
+ public double SpeedRatio
+ {
+ get
+ {
+ return _clockController.SpeedRatio;
+ }
+ set
+ {
+ _clockController.SpeedRatio = value;
+ }
+ }
+
+ internal ImageAnimationController(Image image, ObjectAnimationUsingKeyFrames animation, bool autoStart)
+ {
+ _image = image;
+ _animation = animation;
+ _animation.Completed += AnimationCompleted;
+ _clock = _animation.CreateClock();
+ _clockController = _clock.Controller;
+ _sourceDescriptor.AddValueChanged(image, ImageSourceChanged);
+
+ // ReSharper disable once PossibleNullReferenceException
+ _clockController.Pause();
+
+ _image.ApplyAnimationClock(Image.SourceProperty, _clock);
+
+ if (autoStart)
+ _clockController.Resume();
+ }
+
+ void AnimationCompleted(object sender, EventArgs e)
+ {
+ _image.RaiseEvent(new System.Windows.RoutedEventArgs(ImageBehavior.AnimationCompletedEvent, _image));
+ }
+
+ private void ImageSourceChanged(object sender, EventArgs e)
+ {
+ OnCurrentFrameChanged();
+ }
+
+ /// <summary>
+ /// Returns the number of frames in the image.
+ /// </summary>
+ public int FrameCount
+ {
+ get { return _animation.KeyFrames.Count; }
+ }
+
+ /// <summary>
+ /// Returns the duration of the animation.
+ /// </summary>
+ public TimeSpan Duration
+ {
+ get
+ {
+ return _animation.Duration.HasTimeSpan
+ ? _animation.Duration.TimeSpan
+ : TimeSpan.Zero;
+ }
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the animation is paused.
+ /// </summary>
+ public bool IsPaused
+ {
+ get { return _clock.IsPaused; }
+ }
+
+ /// <summary>
+ /// Returns a value that indicates whether the animation is complete.
+ /// </summary>
+ public bool IsComplete
+ {
+ get { return _clock.CurrentState == ClockState.Filling; }
+ }
+
+ /// <summary>
+ /// Seeks the animation to the specified frame index.
+ /// </summary>
+ /// <param name="index">The index of the frame to seek to</param>
+ public void GotoFrame(int index)
+ {
+ var frame = _animation.KeyFrames[index];
+ _clockController.Seek(frame.KeyTime.TimeSpan, TimeSeekOrigin.BeginTime);
+ }
+
+ /// <summary>
+ /// Returns the current frame index.
+ /// </summary>
+ public int CurrentFrame
+ {
+ get
+ {
+ var time = _clock.CurrentTime;
+ var frameAndIndex =
+ _animation.KeyFrames
+ .Cast<ObjectKeyFrame>()
+ .Select((f, i) => new { Time = f.KeyTime.TimeSpan, Index = i })
+ .FirstOrDefault(fi => fi.Time >= time);
+ if (frameAndIndex != null)
+ return frameAndIndex.Index;
+ return -1;
+ }
+ }
+
+ /// <summary>
+ /// Pauses the animation.
+ /// </summary>
+ public void Pause()
+ {
+ _clockController.Pause();
+ }
+
+ /// <summary>
+ /// Starts or resumes the animation. If the animation is complete, it restarts from the beginning.
+ /// </summary>
+ public void Play()
+ {
+ _clockController.Resume();
+ }
+
+ /// <summary>
+ /// Raised when the current frame changes.
+ /// </summary>
+ public event EventHandler CurrentFrameChanged;
+
+ private void OnCurrentFrameChanged()
+ {
+ EventHandler handler = CurrentFrameChanged;
+ if (handler != null) handler(this, EventArgs.Empty);
+ }
+
+ /// <summary>
+ /// Finalizes the current object.
+ /// </summary>
+ ~ImageAnimationController()
+ {
+ Dispose(false);
+ }
+
+ /// <summary>
+ /// Disposes the current object.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Disposes the current object
+ /// </summary>
+ /// <param name="disposing">true to dispose both managed an unmanaged resources, false to dispose only managed resources</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _image.BeginAnimation(Image.SourceProperty, null);
+ _animation.Completed -= AnimationCompleted;
+ _sourceDescriptor.RemoveValueChanged(_image, ImageSourceChanged);
+ _image.Source = null;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/ImageBehavior.cs b/Software/Visual_Studio/Tango.AnimatedGif/ImageBehavior.cs
new file mode 100644
index 000000000..a159106e6
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/ImageBehavior.cs
@@ -0,0 +1,966 @@
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.IO.Packaging;
+using System.Linq;
+using System.Net;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Imaging;
+using System.Windows.Resources;
+using Tango.AnimatedGif.Decoding;
+
+namespace Tango.AnimatedGif
+{
+ /// <summary>
+ /// Provides attached properties that display animated GIFs in a standard Image control.
+ /// </summary>
+ public static class ImageBehavior
+ {
+ #region Public attached properties and events
+
+ /// <summary>
+ /// Gets the value of the <c>RepeatBehavior</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element from which to read the property value.</param>
+ /// <returns>The repeat behavior of the animated image.</returns>
+ [AttachedPropertyBrowsableForType(typeof(Image))]
+ public static double GetSpeedRatio(Image obj)
+ {
+ return (double)obj.GetValue(SpeedRatioProperty);
+ }
+
+ /// <summary>
+ /// Sets the value of the <c>RepeatBehavior</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element on which to set the property value.</param>
+ /// <param name="value">The repeat behavior of the animated image.</param>
+ public static void SetSpeedRatio(Image obj, double value)
+ {
+ obj.SetValue(SpeedRatioProperty, value);
+ }
+
+ /// <summary>
+ /// Identifies the <c>RepeatBehavior</c> attached property.
+ /// </summary>
+ public static readonly DependencyProperty SpeedRatioProperty =
+ DependencyProperty.RegisterAttached(
+ "SpeedRatio",
+ typeof(double),
+ typeof(ImageBehavior),
+ new UIPropertyMetadata(
+ 1.0,
+ SpeedRatioChanged));
+
+ private static void SpeedRatioChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var controller = GetAnimationController(d as Image);
+
+ if (controller != null)
+ {
+ controller.SpeedRatio = (double)e.NewValue;
+ }
+ }
+
+ /// <summary>
+ /// Gets the value of the <c>RepeatBehavior</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element from which to read the property value.</param>
+ /// <returns>The repeat behavior of the animated image.</returns>
+ [AttachedPropertyBrowsableForType(typeof(Image))]
+ public static bool GetEnableAnimation(Image obj)
+ {
+ return (bool)obj.GetValue(EnableAnimationProperty);
+ }
+
+ /// <summary>
+ /// Sets the value of the <c>RepeatBehavior</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element on which to set the property value.</param>
+ /// <param name="value">The repeat behavior of the animated image.</param>
+ public static void SetEnableAnimation(Image obj, bool value)
+ {
+ Application.Current.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ obj.SetValue(EnableAnimationProperty, value);
+ }));
+ }
+
+ /// <summary>
+ /// Identifies the <c>RepeatBehavior</c> attached property.
+ /// </summary>
+ public static readonly DependencyProperty EnableAnimationProperty =
+ DependencyProperty.RegisterAttached(
+ "EnableAnimation",
+ typeof(bool),
+ typeof(ImageBehavior),
+ new UIPropertyMetadata(
+ false,
+ EnableAnimationChanged));
+
+ private static void EnableAnimationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var controller = GetAnimationController(d as Image);
+
+ if (controller != null)
+ {
+ if (e.NewValue != e.OldValue)
+ {
+ if ((bool)e.NewValue)
+ {
+ controller.SpeedRatio = GetSpeedRatio(d as Image);
+ controller.Play();
+ }
+ else
+ {
+ controller.Pause();
+ controller.GotoFrame(0);
+ }
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// Gets the value of the <c>AnimatedSource</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element from which to read the property value.</param>
+ /// <returns>The currently displayed animated image.</returns>
+ [AttachedPropertyBrowsableForType(typeof(Image))]
+ public static ImageSource GetAnimatedSource(Image obj)
+ {
+ return (ImageSource)obj.GetValue(AnimatedSourceProperty);
+ }
+
+ /// <summary>
+ /// Sets the value of the <c>AnimatedSource</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element on which to set the property value.</param>
+ /// <param name="value">The animated image to display.</param>
+ public static void SetAnimatedSource(Image obj, ImageSource value)
+ {
+ obj.SetValue(AnimatedSourceProperty, value);
+ }
+
+ /// <summary>
+ /// Identifies the <c>AnimatedSource</c> attached property.
+ /// </summary>
+ public static readonly DependencyProperty AnimatedSourceProperty =
+ DependencyProperty.RegisterAttached(
+ "AnimatedSource",
+ typeof(ImageSource),
+ typeof(ImageBehavior),
+ new UIPropertyMetadata(
+ null,
+ AnimatedSourceChanged));
+
+ /// <summary>
+ /// Gets the value of the <c>RepeatBehavior</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element from which to read the property value.</param>
+ /// <returns>The repeat behavior of the animated image.</returns>
+ [AttachedPropertyBrowsableForType(typeof(Image))]
+ public static RepeatBehavior GetRepeatBehavior(Image obj)
+ {
+ return (RepeatBehavior)obj.GetValue(RepeatBehaviorProperty);
+ }
+
+ /// <summary>
+ /// Sets the value of the <c>RepeatBehavior</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element on which to set the property value.</param>
+ /// <param name="value">The repeat behavior of the animated image.</param>
+ public static void SetRepeatBehavior(Image obj, RepeatBehavior value)
+ {
+ obj.SetValue(RepeatBehaviorProperty, value);
+ }
+
+ /// <summary>
+ /// Identifies the <c>RepeatBehavior</c> attached property.
+ /// </summary>
+ public static readonly DependencyProperty RepeatBehaviorProperty =
+ DependencyProperty.RegisterAttached(
+ "RepeatBehavior",
+ typeof(RepeatBehavior),
+ typeof(ImageBehavior),
+ new UIPropertyMetadata(
+ default(RepeatBehavior),
+ RepeatBehaviorChanged));
+
+ /// <summary>
+ /// Gets the value of the <c>AnimateInDesignMode</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element from which to read the property value.</param>
+ /// <returns>true if GIF animations are shown in design mode; false otherwise.</returns>
+ public static bool GetAnimateInDesignMode(DependencyObject obj)
+ {
+ return (bool)obj.GetValue(AnimateInDesignModeProperty);
+ }
+
+ /// <summary>
+ /// Sets the value of the <c>AnimateInDesignMode</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element on which to set the property value.</param>
+ /// <param name="value">true to show GIF animations in design mode; false otherwise.</param>
+ public static void SetAnimateInDesignMode(DependencyObject obj, bool value)
+ {
+ obj.SetValue(AnimateInDesignModeProperty, value);
+ }
+
+ /// <summary>
+ /// Identifies the <c>AnimateInDesignMode</c> attached property.
+ /// </summary>
+ public static readonly DependencyProperty AnimateInDesignModeProperty =
+ DependencyProperty.RegisterAttached(
+ "AnimateInDesignMode",
+ typeof(bool),
+ typeof(ImageBehavior),
+ new FrameworkPropertyMetadata(
+ false,
+ FrameworkPropertyMetadataOptions.Inherits,
+ AnimateInDesignModeChanged));
+
+ /// <summary>
+ /// Gets the value of the <c>AutoStart</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element from which to read the property value.</param>
+ /// <returns>true if the animation should start immediately when loaded. Otherwise, false.</returns>
+ [AttachedPropertyBrowsableForType(typeof(Image))]
+ public static bool GetAutoStart(Image obj)
+ {
+ return (bool)obj.GetValue(AutoStartProperty);
+ }
+
+ /// <summary>
+ /// Sets the value of the <c>AutoStart</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="obj">The element from which to read the property value.</param>
+ /// <param name="value">true if the animation should start immediately when loaded. Otherwise, false.</param>
+ /// <remarks>The default value is true.</remarks>
+ public static void SetAutoStart(Image obj, bool value)
+ {
+ obj.SetValue(AutoStartProperty, value);
+ }
+
+ /// <summary>
+ /// Identifies the <c>AutoStart</c> attached property.
+ /// </summary>
+ public static readonly DependencyProperty AutoStartProperty =
+ DependencyProperty.RegisterAttached("AutoStart", typeof(bool), typeof(ImageBehavior), new PropertyMetadata(false));
+
+ /// <summary>
+ /// Gets the animation controller for the specified <c>Image</c> control.
+ /// </summary>
+ /// <param name="imageControl"></param>
+ /// <returns></returns>
+ public static ImageAnimationController GetAnimationController(Image imageControl)
+ {
+ return (ImageAnimationController)imageControl.GetValue(AnimationControllerPropertyKey.DependencyProperty);
+ }
+
+ private static void SetAnimationController(DependencyObject obj, ImageAnimationController value)
+ {
+ obj.SetValue(AnimationControllerPropertyKey, value);
+ }
+
+ private static readonly DependencyPropertyKey AnimationControllerPropertyKey =
+ DependencyProperty.RegisterAttachedReadOnly("AnimationController", typeof(ImageAnimationController), typeof(ImageBehavior), new PropertyMetadata(null));
+
+ /// <summary>
+ /// Gets the value of the <c>IsAnimationLoaded</c> attached property for the specified object.
+ /// </summary>
+ /// <param name="image">The element from which to read the property value.</param>
+ /// <returns>true if the animation is loaded. Otherwise, false.</returns>
+ public static bool GetIsAnimationLoaded(Image image)
+ {
+ return (bool)image.GetValue(IsAnimationLoadedProperty);
+ }
+
+ private static void SetIsAnimationLoaded(Image image, bool value)
+ {
+ image.SetValue(IsAnimationLoadedPropertyKey, value);
+ }
+
+ private static readonly DependencyPropertyKey IsAnimationLoadedPropertyKey =
+ DependencyProperty.RegisterAttachedReadOnly("IsAnimationLoaded", typeof(bool), typeof(ImageBehavior), new PropertyMetadata(false));
+
+ /// <summary>
+ /// Identifies the <c>IsAnimationLoaded</c> attached property.
+ /// </summary>
+ public static readonly DependencyProperty IsAnimationLoadedProperty =
+ IsAnimationLoadedPropertyKey.DependencyProperty;
+
+ /// <summary>
+ /// Identifies the <c>AnimationLoaded</c> attached event.
+ /// </summary>
+ public static readonly RoutedEvent AnimationLoadedEvent =
+ EventManager.RegisterRoutedEvent(
+ "AnimationLoaded",
+ RoutingStrategy.Bubble,
+ typeof(RoutedEventHandler),
+ typeof(ImageBehavior));
+
+ /// <summary>
+ /// Adds a handler for the AnimationLoaded attached event.
+ /// </summary>
+ /// <param name="image">The UIElement that listens to this event.</param>
+ /// <param name="handler">The event handler to be added.</param>
+ public static void AddAnimationLoadedHandler(Image image, RoutedEventHandler handler)
+ {
+ if (image == null)
+ throw new ArgumentNullException("image");
+ if (handler == null)
+ throw new ArgumentNullException("handler");
+ image.AddHandler(AnimationLoadedEvent, handler);
+ }
+
+ /// <summary>
+ /// Removes a handler for the AnimationLoaded attached event.
+ /// </summary>
+ /// <param name="image">The UIElement that listens to this event.</param>
+ /// <param name="handler">The event handler to be removed.</param>
+ public static void RemoveAnimationLoadedHandler(Image image, RoutedEventHandler handler)
+ {
+ if (image == null)
+ throw new ArgumentNullException("image");
+ if (handler == null)
+ throw new ArgumentNullException("handler");
+ image.RemoveHandler(AnimationLoadedEvent, handler);
+ }
+
+ /// <summary>
+ /// Identifies the <c>AnimationCompleted</c> attached event.
+ /// </summary>
+ public static readonly RoutedEvent AnimationCompletedEvent =
+ EventManager.RegisterRoutedEvent(
+ "AnimationCompleted",
+ RoutingStrategy.Bubble,
+ typeof(RoutedEventHandler),
+ typeof(ImageBehavior));
+
+ /// <summary>
+ /// Adds a handler for the AnimationCompleted attached event.
+ /// </summary>
+ /// <param name="d">The UIElement that listens to this event.</param>
+ /// <param name="handler">The event handler to be added.</param>
+ public static void AddAnimationCompletedHandler(Image d, RoutedEventHandler handler)
+ {
+ var element = d as UIElement;
+ if (element == null)
+ return;
+ element.AddHandler(AnimationCompletedEvent, handler);
+ }
+
+ /// <summary>
+ /// Removes a handler for the AnimationCompleted attached event.
+ /// </summary>
+ /// <param name="d">The UIElement that listens to this event.</param>
+ /// <param name="handler">The event handler to be removed.</param>
+ public static void RemoveAnimationCompletedHandler(Image d, RoutedEventHandler handler)
+ {
+ var element = d as UIElement;
+ if (element == null)
+ return;
+ element.RemoveHandler(AnimationCompletedEvent, handler);
+ }
+
+ #endregion
+
+ private static void AnimatedSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
+ {
+ Image imageControl = o as Image;
+ if (imageControl == null)
+ return;
+
+ var oldValue = e.OldValue as ImageSource;
+ var newValue = e.NewValue as ImageSource;
+ if (ReferenceEquals(oldValue, newValue))
+ {
+ if (imageControl.IsLoaded)
+ {
+ var isAnimLoaded = GetIsAnimationLoaded(imageControl);
+ if (!isAnimLoaded)
+ InitAnimationOrImage(imageControl);
+ }
+ return;
+ }
+ if (oldValue != null)
+ {
+ imageControl.Loaded -= ImageControlLoaded;
+ imageControl.Unloaded -= ImageControlUnloaded;
+ AnimationCache.DecrementReferenceCount(oldValue, GetRepeatBehavior(imageControl));
+ var controller = GetAnimationController(imageControl);
+ if (controller != null)
+ controller.Dispose();
+ imageControl.Source = null;
+ }
+ if (newValue != null)
+ {
+ imageControl.Loaded += ImageControlLoaded;
+ imageControl.Unloaded += ImageControlUnloaded;
+ if (imageControl.IsLoaded)
+ InitAnimationOrImage(imageControl);
+ }
+ }
+
+ private static void ImageControlLoaded(object sender, RoutedEventArgs e)
+ {
+ Image imageControl = sender as Image;
+ if (imageControl == null)
+ return;
+ InitAnimationOrImage(imageControl);
+ }
+
+ static void ImageControlUnloaded(object sender, RoutedEventArgs e)
+ {
+ Image imageControl = sender as Image;
+ if (imageControl == null)
+ return;
+ var source = GetAnimatedSource(imageControl);
+ if (source != null)
+ AnimationCache.DecrementReferenceCount(source, GetRepeatBehavior(imageControl));
+ var controller = GetAnimationController(imageControl);
+ if (controller != null)
+ controller.Dispose();
+ }
+
+ private static void RepeatBehaviorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
+ {
+ Image imageControl = o as Image;
+ if (imageControl == null)
+ return;
+
+ ImageSource source = GetAnimatedSource(imageControl);
+ if (source != null)
+ {
+ if (!Equals(e.OldValue, e.NewValue))
+ AnimationCache.DecrementReferenceCount(source, (RepeatBehavior)e.OldValue);
+ if (imageControl.IsLoaded)
+ InitAnimationOrImage(imageControl);
+ }
+ }
+
+ private static void AnimateInDesignModeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
+ {
+ Image imageControl = o as Image;
+ if (imageControl == null)
+ return;
+
+ bool newValue = (bool)e.NewValue;
+
+ ImageSource source = GetAnimatedSource(imageControl);
+ if (source != null && imageControl.IsLoaded)
+ {
+ if (newValue)
+ InitAnimationOrImage(imageControl);
+ else
+ imageControl.BeginAnimation(Image.SourceProperty, null);
+ }
+ }
+
+ private static void InitAnimationOrImage(Image imageControl)
+ {
+ var controller = GetAnimationController(imageControl);
+ if (controller != null)
+ controller.Dispose();
+ SetAnimationController(imageControl, null);
+ SetIsAnimationLoaded(imageControl, false);
+
+ BitmapSource source = GetAnimatedSource(imageControl) as BitmapSource;
+ bool isInDesignMode = DesignerProperties.GetIsInDesignMode(imageControl);
+ bool animateInDesignMode = GetAnimateInDesignMode(imageControl);
+ bool shouldAnimate = !isInDesignMode || animateInDesignMode;
+
+ // For a BitmapImage with a relative UriSource, the loading is deferred until
+ // BaseUri is set. This method will be called again when BaseUri is set.
+ bool isLoadingDeferred = IsLoadingDeferred(source);
+
+ if (source != null && shouldAnimate && !isLoadingDeferred)
+ {
+ // Case of image being downloaded: retry after download is complete
+ if (source.IsDownloading)
+ {
+ EventHandler handler = null;
+ handler = (sender, args) =>
+ {
+ source.DownloadCompleted -= handler;
+ InitAnimationOrImage(imageControl);
+ };
+ source.DownloadCompleted += handler;
+ imageControl.Source = source;
+ return;
+ }
+
+ var animation = GetAnimation(imageControl, source);
+ if (animation != null)
+ {
+ if (animation.KeyFrames.Count > 0)
+ {
+ // For some reason, it sometimes throws an exception the first time... the second time it works.
+ TryTwice(() => imageControl.Source = (ImageSource)animation.KeyFrames[0].Value);
+ }
+ else
+ {
+ imageControl.Source = source;
+ }
+
+ controller = new ImageAnimationController(imageControl, animation, GetAutoStart(imageControl));
+ SetAnimationController(imageControl, controller);
+ SetIsAnimationLoaded(imageControl, true);
+ imageControl.RaiseEvent(new RoutedEventArgs(AnimationLoadedEvent, imageControl));
+ return;
+ }
+ }
+ imageControl.Source = source;
+ if (source != null)
+ {
+ SetIsAnimationLoaded(imageControl, true);
+ imageControl.RaiseEvent(new RoutedEventArgs(AnimationLoadedEvent, imageControl));
+ }
+ }
+
+ private static ObjectAnimationUsingKeyFrames GetAnimation(Image imageControl, BitmapSource source)
+ {
+ var animation = AnimationCache.GetAnimation(source, GetRepeatBehavior(imageControl));
+ if (animation != null)
+ return animation;
+ GifFile gifMetadata;
+ var decoder = GetDecoder(source, out gifMetadata) as GifBitmapDecoder;
+ if (decoder != null && decoder.Frames.Count > 1)
+ {
+ var fullSize = GetFullSize(decoder, gifMetadata);
+ int index = 0;
+ animation = new ObjectAnimationUsingKeyFrames();
+ var totalDuration = TimeSpan.Zero;
+ BitmapSource baseFrame = null;
+ foreach (var rawFrame in decoder.Frames)
+ {
+ var metadata = GetFrameMetadata(decoder, gifMetadata, index);
+
+ var frame = MakeFrame(fullSize, rawFrame, metadata, baseFrame);
+ var keyFrame = new DiscreteObjectKeyFrame(frame, totalDuration);
+ animation.KeyFrames.Add(keyFrame);
+
+ totalDuration += metadata.Delay;
+
+ switch (metadata.DisposalMethod)
+ {
+ case FrameDisposalMethod.None:
+ case FrameDisposalMethod.DoNotDispose:
+ baseFrame = frame;
+ break;
+ case FrameDisposalMethod.RestoreBackground:
+ if (IsFullFrame(metadata, fullSize))
+ {
+ baseFrame = null;
+ }
+ else
+ {
+ baseFrame = ClearArea(frame, metadata);
+ }
+ break;
+ case FrameDisposalMethod.RestorePrevious:
+ // Reuse same base frame
+ break;
+ }
+
+ index++;
+ }
+ animation.Duration = totalDuration;
+
+ animation.RepeatBehavior = GetActualRepeatBehavior(imageControl, decoder, gifMetadata);
+
+ //Removed animation cache for PPC AsyncAdornerControl!
+
+ //AnimationCache.AddAnimation(source, GetRepeatBehavior(imageControl), animation);
+ //AnimationCache.IncrementReferenceCount(source, GetRepeatBehavior(imageControl));
+ return animation;
+ }
+ return null;
+ }
+
+ private static BitmapSource ClearArea(BitmapSource frame, FrameMetadata metadata)
+ {
+ DrawingVisual visual = new DrawingVisual();
+ using (var context = visual.RenderOpen())
+ {
+ var fullRect = new Rect(0, 0, frame.PixelWidth, frame.PixelHeight);
+ var clearRect = new Rect(metadata.Left, metadata.Top, metadata.Width, metadata.Height);
+ var clip = Geometry.Combine(
+ new RectangleGeometry(fullRect),
+ new RectangleGeometry(clearRect),
+ GeometryCombineMode.Exclude,
+ null);
+ context.PushClip(clip);
+ context.DrawImage(frame, fullRect);
+ }
+
+ var bitmap = new RenderTargetBitmap(
+ frame.PixelWidth, frame.PixelHeight,
+ frame.DpiX, frame.DpiY,
+ PixelFormats.Pbgra32);
+ bitmap.Render(visual);
+
+ var result = new WriteableBitmap(bitmap);
+
+ if (result.CanFreeze && !result.IsFrozen)
+ result.Freeze();
+ return result;
+ }
+
+ private static void TryTwice(Action action)
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception)
+ {
+ action();
+ }
+ }
+
+ private static bool IsLoadingDeferred(BitmapSource source)
+ {
+ var bmp = source as BitmapImage;
+ if (bmp == null)
+ return false;
+ if (bmp.UriSource != null && !bmp.UriSource.IsAbsoluteUri)
+ return bmp.BaseUri == null;
+ return false;
+ }
+
+ private static BitmapDecoder GetDecoder(BitmapSource image, out GifFile gifFile)
+ {
+ gifFile = null;
+ BitmapDecoder decoder = null;
+ Stream stream = null;
+ Uri uri = null;
+ BitmapCreateOptions createOptions = BitmapCreateOptions.None;
+
+ var bmp = image as BitmapImage;
+ if (bmp != null)
+ {
+ createOptions = bmp.CreateOptions;
+ if (bmp.StreamSource != null)
+ {
+ stream = bmp.StreamSource;
+ }
+ else if (bmp.UriSource != null)
+ {
+ uri = bmp.UriSource;
+ if (bmp.BaseUri != null && !uri.IsAbsoluteUri)
+ uri = new Uri(bmp.BaseUri, uri);
+ }
+ }
+ else
+ {
+ BitmapFrame frame = image as BitmapFrame;
+ if (frame != null)
+ {
+ decoder = frame.Decoder;
+ Uri.TryCreate(frame.BaseUri, frame.ToString(), out uri);
+ }
+ }
+
+ if (decoder == null)
+ {
+ if (stream != null)
+ {
+ stream.Position = 0;
+ decoder = BitmapDecoder.Create(stream, createOptions, BitmapCacheOption.OnLoad);
+ }
+ else if (uri != null && uri.IsAbsoluteUri)
+ {
+ decoder = BitmapDecoder.Create(uri, createOptions, BitmapCacheOption.OnLoad);
+ }
+ }
+
+ if (decoder is GifBitmapDecoder && !CanReadNativeMetadata(decoder))
+ {
+ if (stream != null)
+ {
+ stream.Position = 0;
+ gifFile = GifFile.ReadGifFile(stream, true);
+ }
+ else if (uri != null)
+ {
+ gifFile = DecodeGifFile(uri);
+ }
+ else
+ {
+ throw new InvalidOperationException("Can't get URI or Stream from the source. AnimatedSource should be either a BitmapImage, or a BitmapFrame constructed from a URI.");
+ }
+ }
+ if (decoder == null)
+ {
+ throw new InvalidOperationException("Can't get a decoder from the source. AnimatedSource should be either a BitmapImage or a BitmapFrame.");
+ }
+ return decoder;
+ }
+
+ private static bool CanReadNativeMetadata(BitmapDecoder decoder)
+ {
+ try
+ {
+ var m = decoder.Metadata;
+ return m != null;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ private static GifFile DecodeGifFile(Uri uri)
+ {
+ Stream stream = null;
+ if (uri.Scheme == PackUriHelper.UriSchemePack)
+ {
+ StreamResourceInfo sri;
+ if (uri.Authority == "siteoforigin:,,,")
+ sri = Application.GetRemoteStream(uri);
+ else
+ sri = Application.GetResourceStream(uri);
+
+ if (sri != null)
+ stream = sri.Stream;
+ }
+ else
+ {
+ WebClient wc = new WebClient();
+ stream = wc.OpenRead(uri);
+ }
+ if (stream != null)
+ {
+ using (stream)
+ {
+ return GifFile.ReadGifFile(stream, true);
+ }
+ }
+ return null;
+ }
+
+ private static bool IsFullFrame(FrameMetadata metadata, Int32Size fullSize)
+ {
+ return metadata.Left == 0
+ && metadata.Top == 0
+ && metadata.Width == fullSize.Width
+ && metadata.Height == fullSize.Height;
+ }
+
+ private static BitmapSource MakeFrame(
+ Int32Size fullSize,
+ BitmapSource rawFrame, FrameMetadata metadata,
+ BitmapSource baseFrame)
+ {
+ if (baseFrame == null && IsFullFrame(metadata, fullSize))
+ {
+ // No previous image to combine with, and same size as the full image
+ // Just return the frame as is
+ return rawFrame;
+ }
+
+ DrawingVisual visual = new DrawingVisual();
+ using (var context = visual.RenderOpen())
+ {
+ if (baseFrame != null)
+ {
+ var fullRect = new Rect(0, 0, fullSize.Width, fullSize.Height);
+ context.DrawImage(baseFrame, fullRect);
+ }
+
+ var rect = new Rect(metadata.Left, metadata.Top, metadata.Width, metadata.Height);
+ context.DrawImage(rawFrame, rect);
+ }
+ var bitmap = new RenderTargetBitmap(
+ fullSize.Width, fullSize.Height,
+ 96, 96,
+ PixelFormats.Pbgra32);
+ bitmap.Render(visual);
+
+ var result = new WriteableBitmap(bitmap);
+
+ if (result.CanFreeze && !result.IsFrozen)
+ result.Freeze();
+ return result;
+ }
+
+ private static RepeatBehavior GetActualRepeatBehavior(Image imageControl, BitmapDecoder decoder, GifFile gifMetadata)
+ {
+ // If specified explicitly, use this value
+ var repeatBehavior = GetRepeatBehavior(imageControl);
+ if (repeatBehavior != default(RepeatBehavior))
+ return repeatBehavior;
+
+ int repeatCount;
+ if (gifMetadata != null)
+ {
+ repeatCount = gifMetadata.RepeatCount;
+ }
+ else
+ {
+ repeatCount = GetRepeatCount(decoder);
+ }
+ if (repeatCount == 0)
+ return RepeatBehavior.Forever;
+ return new RepeatBehavior(repeatCount);
+ }
+
+ private static int GetRepeatCount(BitmapDecoder decoder)
+ {
+ var ext = GetApplicationExtension(decoder, "NETSCAPE2.0");
+ if (ext != null)
+ {
+ byte[] bytes = ext.GetQueryOrNull<byte[]>("/Data");
+ if (bytes != null && bytes.Length >= 4)
+ return BitConverter.ToUInt16(bytes, 2);
+ }
+ return 1;
+ }
+
+ private static BitmapMetadata GetApplicationExtension(BitmapDecoder decoder, string application)
+ {
+ int count = 0;
+ string query = "/appext";
+ BitmapMetadata extension = decoder.Metadata.GetQueryOrNull<BitmapMetadata>(query);
+ while (extension != null)
+ {
+ byte[] bytes = extension.GetQueryOrNull<byte[]>("/Application");
+ if (bytes != null)
+ {
+ string extApplication = Encoding.ASCII.GetString(bytes);
+ if (extApplication == application)
+ return extension;
+ }
+ query = string.Format("/[{0}]appext", ++count);
+ extension = decoder.Metadata.GetQueryOrNull<BitmapMetadata>(query);
+ }
+ return null;
+ }
+
+ private static FrameMetadata GetFrameMetadata(BitmapDecoder decoder, GifFile gifMetadata, int frameIndex)
+ {
+ if (gifMetadata != null && gifMetadata.Frames.Count > frameIndex)
+ {
+ return GetFrameMetadata(gifMetadata.Frames[frameIndex]);
+ }
+
+ return GetFrameMetadata(decoder.Frames[frameIndex]);
+ }
+
+ private static FrameMetadata GetFrameMetadata(BitmapFrame frame)
+ {
+ var metadata = (BitmapMetadata)frame.Metadata;
+ var delay = TimeSpan.FromMilliseconds(100);
+ var metadataDelay = metadata.GetQueryOrDefault("/grctlext/Delay", 10);
+ if (metadataDelay != 0)
+ delay = TimeSpan.FromMilliseconds(metadataDelay * 10);
+ var disposalMethod = (FrameDisposalMethod)metadata.GetQueryOrDefault("/grctlext/Disposal", 0);
+ var frameMetadata = new FrameMetadata
+ {
+ Left = metadata.GetQueryOrDefault("/imgdesc/Left", 0),
+ Top = metadata.GetQueryOrDefault("/imgdesc/Top", 0),
+ Width = metadata.GetQueryOrDefault("/imgdesc/Width", frame.PixelWidth),
+ Height = metadata.GetQueryOrDefault("/imgdesc/Height", frame.PixelHeight),
+ Delay = delay,
+ DisposalMethod = disposalMethod
+ };
+ return frameMetadata;
+ }
+
+ private static FrameMetadata GetFrameMetadata(GifFrame gifMetadata)
+ {
+ var d = gifMetadata.Descriptor;
+ var frameMetadata = new FrameMetadata
+ {
+ Left = d.Left,
+ Top = d.Top,
+ Width = d.Width,
+ Height = d.Height,
+ Delay = TimeSpan.FromMilliseconds(100),
+ DisposalMethod = FrameDisposalMethod.None
+ };
+
+ var gce = gifMetadata.Extensions.OfType<GifGraphicControlExtension>().FirstOrDefault();
+ if (gce != null)
+ {
+ if (gce.Delay != 0)
+ frameMetadata.Delay = TimeSpan.FromMilliseconds(gce.Delay);
+ frameMetadata.DisposalMethod = (FrameDisposalMethod)gce.DisposalMethod;
+ }
+ return frameMetadata;
+ }
+
+ private static Int32Size GetFullSize(BitmapDecoder decoder, GifFile gifMetadata)
+ {
+ if (gifMetadata != null)
+ {
+ var lsd = gifMetadata.Header.LogicalScreenDescriptor;
+ return new Int32Size(lsd.Width, lsd.Height);
+ }
+ int width = decoder.Metadata.GetQueryOrDefault("/logscrdesc/Width", 0);
+ int height = decoder.Metadata.GetQueryOrDefault("/logscrdesc/Height", 0);
+ return new Int32Size(width, height);
+ }
+
+ private struct Int32Size
+ {
+ public Int32Size(int width, int height) : this()
+ {
+ Width = width;
+ Height = height;
+ }
+
+ public int Width { get; private set; }
+ public int Height { get; private set; }
+ }
+
+ private class FrameMetadata
+ {
+ public int Left { get; set; }
+ public int Top { get; set; }
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public TimeSpan Delay { get; set; }
+ public FrameDisposalMethod DisposalMethod { get; set; }
+ }
+
+ private enum FrameDisposalMethod
+ {
+ None = 0,
+ DoNotDispose = 1,
+ RestoreBackground = 2,
+ RestorePrevious = 3
+ }
+
+ private static T GetQueryOrDefault<T>(this BitmapMetadata metadata, string query, T defaultValue)
+ {
+ if (metadata.ContainsQuery(query))
+ return (T)Convert.ChangeType(metadata.GetQuery(query), typeof(T));
+ return defaultValue;
+ }
+
+ private static T GetQueryOrNull<T>(this BitmapMetadata metadata, string query)
+ where T : class
+ {
+ if (metadata.ContainsQuery(query))
+ return metadata.GetQuery(query) as T;
+ return null;
+ }
+
+ // For debug purposes
+ //private static void Save(BitmapSource image, string path)
+ //{
+ // var encoder = new PngBitmapEncoder();
+ // encoder.Frames.Add(BitmapFrame.Create(image));
+ // using (var stream = File.OpenWrite(path))
+ // {
+ // encoder.Save(stream);
+ // }
+ //}
+ }
+}
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.AnimatedGif/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..2c8b459c4
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Properties/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Markup;
+
+[assembly: AssemblyTitle("Tango - GIF Animation Library")]
+[assembly: ComVisible(false)]
+
+[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/Tango.AnimatedGif.csproj b/Software/Visual_Studio/Tango.AnimatedGif/Tango.AnimatedGif.csproj
new file mode 100644
index 000000000..18d991271
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/Tango.AnimatedGif.csproj
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{D129789C-3096-4D0B-8DD7-FE24A4DF4B21}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Tango.AnimatedGif</RootNamespace>
+ <AssemblyName>Tango.AnimatedGif</AssemblyName>
+ <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <TargetFrameworkProfile>
+ </TargetFrameworkProfile>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>..\Build\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <Prefer32Bit>false</Prefer32Bit>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>..\Build\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <DocumentationFile>
+ </DocumentationFile>
+ <Prefer32Bit>false</Prefer32Bit>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ <Reference Include="System" />
+ <Reference Include="System.Xaml" />
+ <Reference Include="System.Xml" />
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\Versioning\Core.cs">
+ <Link>Core.cs</Link>
+ </Compile>
+ <Compile Include="AnimationCache.cs" />
+ <Compile Include="Decoding\GifApplicationExtension.cs" />
+ <Compile Include="Decoding\GifBlock.cs" />
+ <Compile Include="Decoding\GifBlockKind.cs" />
+ <Compile Include="Decoding\GifColor.cs" />
+ <Compile Include="Decoding\GifCommentExtension.cs" />
+ <Compile Include="Decoding\GifDecoderException.cs" />
+ <Compile Include="Decoding\GifExtension.cs" />
+ <Compile Include="Decoding\GifFile.cs" />
+ <Compile Include="Decoding\GifFrame.cs" />
+ <Compile Include="Decoding\GifGraphicControlExtension.cs" />
+ <Compile Include="Decoding\GifHeader.cs" />
+ <Compile Include="Decoding\GifHelpers.cs" />
+ <Compile Include="Decoding\GifImageData.cs" />
+ <Compile Include="Decoding\GifImageDescriptor.cs" />
+ <Compile Include="Decoding\GifLogicalScreenDescriptor.cs" />
+ <Compile Include="Decoding\GifPlainTextExtension.cs" />
+ <Compile Include="Decoding\GifTrailer.cs" />
+ <Compile Include="ImageAnimationController.cs" />
+ <Compile Include="ImageBehavior.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="UserControl1.xaml.cs">
+ <DependentUpon>UserControl1.xaml</DependentUpon>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <Page Include="UserControl1.xaml">
+ <SubType>Designer</SubType>
+ <Generator>MSBuild:Compile</Generator>
+ </Page>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml b/Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml
new file mode 100644
index 000000000..1ae2a1cd4
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml
@@ -0,0 +1,12 @@
+<UserControl x:Class="Tango.AnimatedGif.UserControl1"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:local="clr-namespace:Tango.AnimatedGif"
+ mc:Ignorable="d"
+ d:DesignHeight="300" d:DesignWidth="300">
+ <Grid>
+
+ </Grid>
+</UserControl>
diff --git a/Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml.cs b/Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml.cs
new file mode 100644
index 000000000..8d90270d3
--- /dev/null
+++ b/Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Tango.AnimatedGif
+{
+ /// <summary>
+ /// Interaction logic for UserControl1.xaml
+ /// </summary>
+ public partial class UserControl1 : UserControl
+ {
+ public UserControl1()
+ {
+ InitializeComponent();
+ }
+ }
+}