From bd49bdcb109a227130d7db6856e659b435d16530 Mon Sep 17 00:00:00 2001 From: Roy Ben-Shabat Date: Wed, 11 Jul 2018 12:37:11 +0300 Subject: Embedded Tango.AnimatedGif ! --- .../Tango.MachineStudio.UI/Resources/BuildDate.txt | 2 +- .../Tango.PPC.UI/Images/preloader_rectangles.gif | Bin 0 -> 11500 bytes .../PPCApplication/DefaultPPCApplicationManager.cs | 2 +- .../PPC/Tango.PPC.UI/Tango.PPC.UI.csproj | 5 + .../PPC/Tango.PPC.UI/Views/LayoutView.xaml | 11 +- .../PPC/Tango.PPC.UI/Views/LoginView.xaml | 5 +- .../Tango.AnimatedGif/AnimationCache.cs | 151 ++++ .../Decoding/GifApplicationExtension.cs | 50 ++ .../Tango.AnimatedGif/Decoding/GifBlock.cs | 28 + .../Tango.AnimatedGif/Decoding/GifBlockKind.cs | 10 + .../Tango.AnimatedGif/Decoding/GifColor.cs | 25 + .../Decoding/GifCommentExtension.cs | 37 + .../Decoding/GifDecoderException.cs | 16 + .../Tango.AnimatedGif/Decoding/GifExtension.cs | 32 + .../Tango.AnimatedGif/Decoding/GifFile.cs | 86 ++ .../Tango.AnimatedGif/Decoding/GifFrame.cs | 47 + .../Decoding/GifGraphicControlExtension.cs | 52 ++ .../Tango.AnimatedGif/Decoding/GifHeader.cs | 38 + .../Tango.AnimatedGif/Decoding/GifHelpers.cs | 110 +++ .../Tango.AnimatedGif/Decoding/GifImageData.cs | 27 + .../Decoding/GifImageDescriptor.cs | 43 + .../Decoding/GifLogicalScreenDescriptor.cs | 43 + .../Decoding/GifPlainTextExtension.cs | 68 ++ .../Tango.AnimatedGif/Decoding/GifTrailer.cs | 21 + .../Tango.AnimatedGif/ImageAnimationController.cs | 192 ++++ .../Tango.AnimatedGif/ImageBehavior.cs | 966 +++++++++++++++++++++ .../Tango.AnimatedGif/Properties/AssemblyInfo.cs | 10 + .../Tango.AnimatedGif/Tango.AnimatedGif.csproj | 90 ++ .../Tango.AnimatedGif/UserControl1.xaml | 12 + .../Tango.AnimatedGif/UserControl1.xaml.cs | 28 + .../Tango.Settings/SettingsManager.cs | 17 +- .../Tango.Touch/Controls/TouchGifAnimation.cs | 58 ++ .../Tango.Touch/Controls/TouchGifAnimation.xaml | 21 + .../Visual_Studio/Tango.Touch/Tango.Touch.csproj | 9 + .../Visual_Studio/Tango.Touch/Themes/Generic.xaml | 3 +- Software/Visual_Studio/Tango.sln | 42 + 36 files changed, 2343 insertions(+), 14 deletions(-) create mode 100644 Software/Visual_Studio/PPC/Tango.PPC.UI/Images/preloader_rectangles.gif create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/AnimationCache.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifApplicationExtension.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlock.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifBlockKind.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifColor.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifCommentExtension.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifDecoderException.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifExtension.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFile.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifFrame.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifGraphicControlExtension.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHeader.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifHelpers.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageData.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifImageDescriptor.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifLogicalScreenDescriptor.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifPlainTextExtension.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Decoding/GifTrailer.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/ImageAnimationController.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/ImageBehavior.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/Tango.AnimatedGif.csproj create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml create mode 100644 Software/Visual_Studio/Tango.AnimatedGif/UserControl1.xaml.cs create mode 100644 Software/Visual_Studio/Tango.Touch/Controls/TouchGifAnimation.cs create mode 100644 Software/Visual_Studio/Tango.Touch/Controls/TouchGifAnimation.xaml (limited to 'Software/Visual_Studio') diff --git a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Resources/BuildDate.txt b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Resources/BuildDate.txt index 00990166f..0aa0af85a 100644 --- a/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Resources/BuildDate.txt +++ b/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Resources/BuildDate.txt @@ -1 +1 @@ -Tue 07/10/2018 16:54:33.30 +Wed 07/11/2018 11:23:32.16 diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/Images/preloader_rectangles.gif b/Software/Visual_Studio/PPC/Tango.PPC.UI/Images/preloader_rectangles.gif new file mode 100644 index 000000000..5bb37cc50 Binary files /dev/null and b/Software/Visual_Studio/PPC/Tango.PPC.UI/Images/preloader_rectangles.gif differ diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs b/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs index 4978fa6e0..c67e83fc6 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/PPCApplication/DefaultPPCApplicationManager.cs @@ -194,7 +194,7 @@ namespace Tango.PPC.UI.PPCApplication private async void ConnectToMachine() { Transport.Discovery.UsbCommunicationScanner scanner = new Transport.Discovery.UsbCommunicationScanner(UsbSerialBaudRates.BR_9600); - var response = await scanner.Scan(new ConnectRequest() { Password = "1234" }, TimeSpan.FromSeconds(60)); + var response = await scanner.Scan(new ConnectRequest() { Password = "1234" }, TimeSpan.FromSeconds(10)); var machine = new MachineOperator(response.Adapter); await machine.Connect(); diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/Tango.PPC.UI.csproj b/Software/Visual_Studio/PPC/Tango.PPC.UI/Tango.PPC.UI.csproj index 4f6221144..159484c0b 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/Tango.PPC.UI.csproj +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/Tango.PPC.UI.csproj @@ -224,6 +224,10 @@ + + {d129789c-3096-4d0b-8dd7-fe24a4df4b21} + Tango.AnimatedGif + {f441feee-322a-4943-b566-110e12fd3b72} Tango.BL @@ -325,6 +329,7 @@ Tango.ColorLib.dll PreserveNewest + diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/Views/LayoutView.xaml b/Software/Visual_Studio/PPC/Tango.PPC.UI/Views/LayoutView.xaml index 621bfabdb..2f1b7dd59 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/Views/LayoutView.xaml +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/Views/LayoutView.xaml @@ -105,7 +105,7 @@ - + Ready to Dye @@ -127,12 +127,9 @@ - - - - - - + + + diff --git a/Software/Visual_Studio/PPC/Tango.PPC.UI/Views/LoginView.xaml b/Software/Visual_Studio/PPC/Tango.PPC.UI/Views/LoginView.xaml index 74bb85e7d..027e61d08 100644 --- a/Software/Visual_Studio/PPC/Tango.PPC.UI/Views/LoginView.xaml +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/Views/LoginView.xaml @@ -29,9 +29,8 @@ - - - Logging in... + + 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 _animationCache = new Dictionary(); + private static readonly Dictionary _referenceCount = new Dictionary(); + + 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 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 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 Frames { get; set; } + public IList 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() + .FirstOrDefault(GifHelpers.IsNetscapeExtension); + + if (netscapeExtension != null) + RepeatCount = GifHelpers.GetRepeatCount(netscapeExtension); + else + RepeatCount = 1; + } + + private void ReadFrames(Stream stream, bool metadataOnly) + { + List frames = new List(); + List controlExtensions = new List(); + List specialExtensions = new List(); + while (true) + { + var block = GifBlock.ReadBlock(stream, controlExtensions, metadataOnly); + + if (block.Kind == GifBlockKind.GraphicRendering) + controlExtensions = new List(); + + 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 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 controlExtensions, bool metadataOnly) + { + var frame = new GifFrame(); + + frame.Read(stream, controlExtensions, metadataOnly); + + return frame; + } + + private void Read(Stream stream, IEnumerable 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 Extensions { get; private set; } + + private GifPlainTextExtension() + { + } + + internal override GifBlockKind Kind + { + get { return GifBlockKind.GraphicRendering; } + } + + internal static GifPlainTextExtension ReadPlainText(Stream stream, IEnumerable controlExtensions, bool metadataOnly) + { + var plainText = new GifPlainTextExtension(); + plainText.Read(stream, controlExtensions, metadataOnly); + return plainText; + } + + private void Read(Stream stream, IEnumerable 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 +{ + /// + /// Provides a way to pause, resume or seek a GIF animation. + /// + 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(); + } + + /// + /// Returns the number of frames in the image. + /// + public int FrameCount + { + get { return _animation.KeyFrames.Count; } + } + + /// + /// Returns the duration of the animation. + /// + public TimeSpan Duration + { + get + { + return _animation.Duration.HasTimeSpan + ? _animation.Duration.TimeSpan + : TimeSpan.Zero; + } + } + + /// + /// Returns a value that indicates whether the animation is paused. + /// + public bool IsPaused + { + get { return _clock.IsPaused; } + } + + /// + /// Returns a value that indicates whether the animation is complete. + /// + public bool IsComplete + { + get { return _clock.CurrentState == ClockState.Filling; } + } + + /// + /// Seeks the animation to the specified frame index. + /// + /// The index of the frame to seek to + public void GotoFrame(int index) + { + var frame = _animation.KeyFrames[index]; + _clockController.Seek(frame.KeyTime.TimeSpan, TimeSeekOrigin.BeginTime); + } + + /// + /// Returns the current frame index. + /// + public int CurrentFrame + { + get + { + var time = _clock.CurrentTime; + var frameAndIndex = + _animation.KeyFrames + .Cast() + .Select((f, i) => new { Time = f.KeyTime.TimeSpan, Index = i }) + .FirstOrDefault(fi => fi.Time >= time); + if (frameAndIndex != null) + return frameAndIndex.Index; + return -1; + } + } + + /// + /// Pauses the animation. + /// + public void Pause() + { + _clockController.Pause(); + } + + /// + /// Starts or resumes the animation. If the animation is complete, it restarts from the beginning. + /// + public void Play() + { + _clockController.Resume(); + } + + /// + /// Raised when the current frame changes. + /// + public event EventHandler CurrentFrameChanged; + + private void OnCurrentFrameChanged() + { + EventHandler handler = CurrentFrameChanged; + if (handler != null) handler(this, EventArgs.Empty); + } + + /// + /// Finalizes the current object. + /// + ~ImageAnimationController() + { + Dispose(false); + } + + /// + /// Disposes the current object. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the current object + /// + /// true to dispose both managed an unmanaged resources, false to dispose only managed resources + 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 +{ + /// + /// Provides attached properties that display animated GIFs in a standard Image control. + /// + public static class ImageBehavior + { + #region Public attached properties and events + + /// + /// Gets the value of the RepeatBehavior attached property for the specified object. + /// + /// The element from which to read the property value. + /// The repeat behavior of the animated image. + [AttachedPropertyBrowsableForType(typeof(Image))] + public static double GetSpeedRatio(Image obj) + { + return (double)obj.GetValue(SpeedRatioProperty); + } + + /// + /// Sets the value of the RepeatBehavior attached property for the specified object. + /// + /// The element on which to set the property value. + /// The repeat behavior of the animated image. + public static void SetSpeedRatio(Image obj, double value) + { + obj.SetValue(SpeedRatioProperty, value); + } + + /// + /// Identifies the RepeatBehavior attached property. + /// + 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; + } + } + + /// + /// Gets the value of the RepeatBehavior attached property for the specified object. + /// + /// The element from which to read the property value. + /// The repeat behavior of the animated image. + [AttachedPropertyBrowsableForType(typeof(Image))] + public static bool GetEnableAnimation(Image obj) + { + return (bool)obj.GetValue(EnableAnimationProperty); + } + + /// + /// Sets the value of the RepeatBehavior attached property for the specified object. + /// + /// The element on which to set the property value. + /// The repeat behavior of the animated image. + public static void SetEnableAnimation(Image obj, bool value) + { + Application.Current.Dispatcher.BeginInvoke(new Action(() => + { + obj.SetValue(EnableAnimationProperty, value); + })); + } + + /// + /// Identifies the RepeatBehavior attached property. + /// + 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); + } + } + } + } + + + /// + /// Gets the value of the AnimatedSource attached property for the specified object. + /// + /// The element from which to read the property value. + /// The currently displayed animated image. + [AttachedPropertyBrowsableForType(typeof(Image))] + public static ImageSource GetAnimatedSource(Image obj) + { + return (ImageSource)obj.GetValue(AnimatedSourceProperty); + } + + /// + /// Sets the value of the AnimatedSource attached property for the specified object. + /// + /// The element on which to set the property value. + /// The animated image to display. + public static void SetAnimatedSource(Image obj, ImageSource value) + { + obj.SetValue(AnimatedSourceProperty, value); + } + + /// + /// Identifies the AnimatedSource attached property. + /// + public static readonly DependencyProperty AnimatedSourceProperty = + DependencyProperty.RegisterAttached( + "AnimatedSource", + typeof(ImageSource), + typeof(ImageBehavior), + new UIPropertyMetadata( + null, + AnimatedSourceChanged)); + + /// + /// Gets the value of the RepeatBehavior attached property for the specified object. + /// + /// The element from which to read the property value. + /// The repeat behavior of the animated image. + [AttachedPropertyBrowsableForType(typeof(Image))] + public static RepeatBehavior GetRepeatBehavior(Image obj) + { + return (RepeatBehavior)obj.GetValue(RepeatBehaviorProperty); + } + + /// + /// Sets the value of the RepeatBehavior attached property for the specified object. + /// + /// The element on which to set the property value. + /// The repeat behavior of the animated image. + public static void SetRepeatBehavior(Image obj, RepeatBehavior value) + { + obj.SetValue(RepeatBehaviorProperty, value); + } + + /// + /// Identifies the RepeatBehavior attached property. + /// + public static readonly DependencyProperty RepeatBehaviorProperty = + DependencyProperty.RegisterAttached( + "RepeatBehavior", + typeof(RepeatBehavior), + typeof(ImageBehavior), + new UIPropertyMetadata( + default(RepeatBehavior), + RepeatBehaviorChanged)); + + /// + /// Gets the value of the AnimateInDesignMode attached property for the specified object. + /// + /// The element from which to read the property value. + /// true if GIF animations are shown in design mode; false otherwise. + public static bool GetAnimateInDesignMode(DependencyObject obj) + { + return (bool)obj.GetValue(AnimateInDesignModeProperty); + } + + /// + /// Sets the value of the AnimateInDesignMode attached property for the specified object. + /// + /// The element on which to set the property value. + /// true to show GIF animations in design mode; false otherwise. + public static void SetAnimateInDesignMode(DependencyObject obj, bool value) + { + obj.SetValue(AnimateInDesignModeProperty, value); + } + + /// + /// Identifies the AnimateInDesignMode attached property. + /// + public static readonly DependencyProperty AnimateInDesignModeProperty = + DependencyProperty.RegisterAttached( + "AnimateInDesignMode", + typeof(bool), + typeof(ImageBehavior), + new FrameworkPropertyMetadata( + false, + FrameworkPropertyMetadataOptions.Inherits, + AnimateInDesignModeChanged)); + + /// + /// Gets the value of the AutoStart attached property for the specified object. + /// + /// The element from which to read the property value. + /// true if the animation should start immediately when loaded. Otherwise, false. + [AttachedPropertyBrowsableForType(typeof(Image))] + public static bool GetAutoStart(Image obj) + { + return (bool)obj.GetValue(AutoStartProperty); + } + + /// + /// Sets the value of the AutoStart attached property for the specified object. + /// + /// The element from which to read the property value. + /// true if the animation should start immediately when loaded. Otherwise, false. + /// The default value is true. + public static void SetAutoStart(Image obj, bool value) + { + obj.SetValue(AutoStartProperty, value); + } + + /// + /// Identifies the AutoStart attached property. + /// + public static readonly DependencyProperty AutoStartProperty = + DependencyProperty.RegisterAttached("AutoStart", typeof(bool), typeof(ImageBehavior), new PropertyMetadata(false)); + + /// + /// Gets the animation controller for the specified Image control. + /// + /// + /// + 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)); + + /// + /// Gets the value of the IsAnimationLoaded attached property for the specified object. + /// + /// The element from which to read the property value. + /// true if the animation is loaded. Otherwise, false. + 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)); + + /// + /// Identifies the IsAnimationLoaded attached property. + /// + public static readonly DependencyProperty IsAnimationLoadedProperty = + IsAnimationLoadedPropertyKey.DependencyProperty; + + /// + /// Identifies the AnimationLoaded attached event. + /// + public static readonly RoutedEvent AnimationLoadedEvent = + EventManager.RegisterRoutedEvent( + "AnimationLoaded", + RoutingStrategy.Bubble, + typeof(RoutedEventHandler), + typeof(ImageBehavior)); + + /// + /// Adds a handler for the AnimationLoaded attached event. + /// + /// The UIElement that listens to this event. + /// The event handler to be added. + 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); + } + + /// + /// Removes a handler for the AnimationLoaded attached event. + /// + /// The UIElement that listens to this event. + /// The event handler to be removed. + 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); + } + + /// + /// Identifies the AnimationCompleted attached event. + /// + public static readonly RoutedEvent AnimationCompletedEvent = + EventManager.RegisterRoutedEvent( + "AnimationCompleted", + RoutingStrategy.Bubble, + typeof(RoutedEventHandler), + typeof(ImageBehavior)); + + /// + /// Adds a handler for the AnimationCompleted attached event. + /// + /// The UIElement that listens to this event. + /// The event handler to be added. + public static void AddAnimationCompletedHandler(Image d, RoutedEventHandler handler) + { + var element = d as UIElement; + if (element == null) + return; + element.AddHandler(AnimationCompletedEvent, handler); + } + + /// + /// Removes a handler for the AnimationCompleted attached event. + /// + /// The UIElement that listens to this event. + /// The event handler to be removed. + 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("/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(query); + while (extension != null) + { + byte[] bytes = extension.GetQueryOrNull("/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(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().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(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(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 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21} + Library + Properties + Tango.AnimatedGif + Tango.AnimatedGif + v4.6 + 512 + + + + + true + full + false + ..\Build\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + ..\Build\Release\ + TRACE + prompt + 4 + + + false + + + + + + + + + + + + Core.cs + + + + + + + + + + + + + + + + + + + + + + + + UserControl1.xaml + + + + + Designer + MSBuild:Compile + + + + + \ 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 @@ + + + + + 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 +{ + /// + /// Interaction logic for UserControl1.xaml + /// + public partial class UserControl1 : UserControl + { + public UserControl1() + { + InitializeComponent(); + } + } +} diff --git a/Software/Visual_Studio/Tango.Settings/SettingsManager.cs b/Software/Visual_Studio/Tango.Settings/SettingsManager.cs index cd8b97c92..88a3f59e1 100644 --- a/Software/Visual_Studio/Tango.Settings/SettingsManager.cs +++ b/Software/Visual_Studio/Tango.Settings/SettingsManager.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Tango.Logging; namespace Tango.Settings { @@ -37,6 +38,7 @@ namespace Tango.Settings private List _settingsCollection; private JsonSerializerSettings _jsonSettings; + private LogManager _logManager; private bool _loaded; /// @@ -54,6 +56,7 @@ namespace Tango.Settings /// private SettingsManager() { + _logManager = LogManager.Default; FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Twine", "Tango", "Settings", Path.GetFileNameWithoutExtension(AppDomain.CurrentDomain.FriendlyName) + ".json"); Folder = Path.GetDirectoryName(FilePath); Directory.CreateDirectory(Folder); @@ -88,12 +91,21 @@ namespace Tango.Settings /// public T GetOrCreate() where T : SettingsBase { - EnsureLoaded(); + try + { + _logManager.Log("Retrieving settings for " + typeof(T).Name); + EnsureLoaded(); + } + catch (Exception ex) + { + _logManager.Log(ex, "Error deserializing settings for " + typeof(T).Name); + } var settings = _settingsCollection.SingleOrDefault(x => x.GetType() == typeof(T)) as T; if (settings == null) { + _logManager.Log("Settings for " + typeof(T).Name + " were not found. Initializing default settings."); settings = Activator.CreateInstance(); settings.SaveAction = Save; _settingsCollection.Add(settings); @@ -109,6 +121,8 @@ namespace Tango.Settings { if (File.Exists(FilePath)) { + _logManager.Log("Loading settings from " + FilePath + "..."); + _settingsCollection = JsonConvert.DeserializeObject>(File.ReadAllText(FilePath), _jsonSettings); foreach (var settings in _settingsCollection) @@ -135,6 +149,7 @@ namespace Tango.Settings { EnsureLoaded(); + _logManager.Log("Saving settings to " + FilePath + "..."); String json = JsonConvert.SerializeObject(_settingsCollection, _jsonSettings); File.WriteAllText(FilePath, json); } diff --git a/Software/Visual_Studio/Tango.Touch/Controls/TouchGifAnimation.cs b/Software/Visual_Studio/Tango.Touch/Controls/TouchGifAnimation.cs new file mode 100644 index 000000000..45e552a43 --- /dev/null +++ b/Software/Visual_Studio/Tango.Touch/Controls/TouchGifAnimation.cs @@ -0,0 +1,58 @@ +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.Touch.Controls +{ + public class TouchGifAnimation : Control + { + public ImageSource Source + { + get { return (ImageSource)GetValue(SourceProperty); } + set { SetValue(SourceProperty, value); } + } + public static readonly DependencyProperty SourceProperty = + DependencyProperty.Register("Source", typeof(ImageSource), typeof(TouchGifAnimation), new PropertyMetadata(null)); + + public double SpeedRatio + { + get { return (double)GetValue(SpeedRatioProperty); } + set { SetValue(SpeedRatioProperty, value); } + } + public static readonly DependencyProperty SpeedRatioProperty = + DependencyProperty.Register("SpeedRatio", typeof(double), typeof(TouchGifAnimation), new PropertyMetadata(1.0)); + + public Stretch Stretch + { + get { return (Stretch)GetValue(StretchProperty); } + set { SetValue(StretchProperty, value); } + } + public static readonly DependencyProperty StretchProperty = + DependencyProperty.Register("Stretch", typeof(Stretch), typeof(TouchGifAnimation), new PropertyMetadata(Stretch.None)); + + public bool EnableAnimation + { + get { return (bool)GetValue(EnableAnimationProperty); } + set { SetValue(EnableAnimationProperty, value); } + } + public static readonly DependencyProperty EnableAnimationProperty = + DependencyProperty.Register("EnableAnimation", typeof(bool), typeof(TouchGifAnimation), new PropertyMetadata(false)); + + + static TouchGifAnimation() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TouchGifAnimation), new FrameworkPropertyMetadata(typeof(TouchGifAnimation))); + } + } +} diff --git a/Software/Visual_Studio/Tango.Touch/Controls/TouchGifAnimation.xaml b/Software/Visual_Studio/Tango.Touch/Controls/TouchGifAnimation.xaml new file mode 100644 index 000000000..15154125e --- /dev/null +++ b/Software/Visual_Studio/Tango.Touch/Controls/TouchGifAnimation.xaml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Touch/Tango.Touch.csproj b/Software/Visual_Studio/Tango.Touch/Tango.Touch.csproj index fada6cad8..e078d0887 100644 --- a/Software/Visual_Studio/Tango.Touch/Tango.Touch.csproj +++ b/Software/Visual_Studio/Tango.Touch/Tango.Touch.csproj @@ -68,6 +68,7 @@ + @@ -118,6 +119,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -331,6 +336,10 @@ + + {d129789c-3096-4d0b-8dd7-fe24a4df4b21} + Tango.AnimatedGif + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} Tango.Core diff --git a/Software/Visual_Studio/Tango.Touch/Themes/Generic.xaml b/Software/Visual_Studio/Tango.Touch/Themes/Generic.xaml index 971f3dd46..e077d1388 100644 --- a/Software/Visual_Studio/Tango.Touch/Themes/Generic.xaml +++ b/Software/Visual_Studio/Tango.Touch/Themes/Generic.xaml @@ -38,7 +38,8 @@ - + + diff --git a/Software/Visual_Studio/Tango.sln b/Software/Visual_Studio/Tango.sln index bfb278f0f..794b8be2d 100644 --- a/Software/Visual_Studio/Tango.sln +++ b/Software/Visual_Studio/Tango.sln @@ -210,6 +210,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.Hive", "Tango.Hive\Ta EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.CatalogGenerator", "Utilities\Tango.CatalogGenerator\Tango.CatalogGenerator.csproj", "{808E47B6-BAA7-4D23-83CB-40C53DC4B38A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.AnimatedGif", "Tango.AnimatedGif\Tango.AnimatedGif.csproj", "{D129789C-3096-4D0B-8DD7-FE24A4DF4B21}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AppVeyor|Any CPU = AppVeyor|Any CPU @@ -3569,6 +3571,46 @@ Global {808E47B6-BAA7-4D23-83CB-40C53DC4B38A}.Release|x64.Build.0 = Release|Any CPU {808E47B6-BAA7-4D23-83CB-40C53DC4B38A}.Release|x86.ActiveCfg = Release|Any CPU {808E47B6-BAA7-4D23-83CB-40C53DC4B38A}.Release|x86.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|Any CPU.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|ARM.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|ARM.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|ARM64.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|x64.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|x64.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|x86.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.AppVeyor|x86.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|ARM.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|ARM.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|ARM64.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|x64.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|x64.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|x86.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Debug|x86.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|ARM.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|x64.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.DefaultBuild|x86.Build.0 = Debug|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|Any CPU.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|ARM.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|ARM.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|ARM64.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|ARM64.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|x64.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|x64.Build.0 = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|x86.ActiveCfg = Release|Any CPU + {D129789C-3096-4D0B-8DD7-FE24A4DF4B21}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE -- cgit v1.3.1