diff options
| author | Roy Ben-Shabat <Roy@Twine-s.com> | 2018-07-11 12:37:11 +0300 |
|---|---|---|
| committer | Roy Ben-Shabat <Roy@Twine-s.com> | 2018-07-11 12:37:11 +0300 |
| commit | bd49bdcb109a227130d7db6856e659b435d16530 (patch) | |
| tree | db55cfdc5dae423ccdba5c7efb1162d33bdcdc48 /Software/Visual_Studio | |
| parent | d65e5f1a23374de2872a73034e430e4a70ee4269 (diff) | |
| download | Tango-bd49bdcb109a227130d7db6856e659b435d16530.tar.gz Tango-bd49bdcb109a227130d7db6856e659b435d16530.zip | |
Embedded Tango.AnimatedGif !
Diffstat (limited to 'Software/Visual_Studio')
36 files changed, 2343 insertions, 14 deletions
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 Binary files differnew file mode 100644 index 000000000..5bb37cc50 --- /dev/null +++ b/Software/Visual_Studio/PPC/Tango.PPC.UI/Images/preloader_rectangles.gif 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<ConnectRequest, ConnectResponse> scanner = new Transport.Discovery.UsbCommunicationScanner<ConnectRequest, ConnectResponse>(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 @@ <None Include="App.config" /> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\..\Tango.AnimatedGif\Tango.AnimatedGif.csproj"> + <Project>{d129789c-3096-4d0b-8dd7-fe24a4df4b21}</Project> + <Name>Tango.AnimatedGif</Name> + </ProjectReference> <ProjectReference Include="..\..\Tango.BL\Tango.BL.csproj"> <Project>{f441feee-322a-4943-b566-110e12fd3b72}</Project> <Name>Tango.BL</Name> @@ -325,6 +329,7 @@ <Link>Tango.ColorLib.dll</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Resource Include="Images\preloader_rectangles.gif" /> <Resource Include="Images\logo.png" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> 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 @@ </touch:TouchHamburgerButton> </Border> <Grid DockPanel.Dock="Right" Margin="0 0 20 0"> - <StackPanel VerticalAlignment="Center"> + <StackPanel VerticalAlignment="Center" > <Image Source="/Images/GlobalStatus/ready-to-dye.png" Width="33" /> <TextBlock Margin="0 10 0 0">Ready to Dye</TextBlock> </StackPanel> @@ -127,12 +127,9 @@ </Style.Triggers> </Style> </commonControls:AsyncAdornerControl.Style> - <Grid> - <StackPanel Orientation="Horizontal"> - <touch:TouchBusyIndicator Width="50" Height="50" IsIndeterminate="{Binding NotificationProvider.IsInGlobalBusyState}" /> - <TextBlock VerticalAlignment="Center" Margin="10 0 0 0" FontSize="20" Text="{Binding NotificationProvider.GlobalBusyMessage}"></TextBlock> - </StackPanel> - </Grid> + <StackPanel VerticalAlignment="Center"> + <touch:TouchGifAnimation Source="/Images/preloader_rectangles.gif" EnableAnimation="{Binding NotificationProvider.IsInGlobalBusyState}" /> + </StackPanel> </commonControls:AsyncAdornerControl> </Grid> </DockPanel> 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 @@ </StackPanel> <StackPanel> <controls:AsyncAdornerControl Height="130" Visibility="{Binding IsLoading,Converter={StaticResource BooleanToVisibilityConverter}}"> - <StackPanel> - <touch:TouchBusyIndicator IsIndeterminate="{Binding IsLoading}" Width="80" Height="80" /> - <TextBlock HorizontalAlignment="Center" Margin="0 20 0 0" FontSize="{StaticResource TangoTitleFontSize}">Logging in...</TextBlock> + <StackPanel VerticalAlignment="Center"> + <touch:TouchGifAnimation Source="/Images/preloader_rectangles.gif" EnableAnimation="{Binding IsLoading}" /> </StackPanel> </controls:AsyncAdornerControl> </StackPanel> 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(); + } + } +} 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<SettingsBase> _settingsCollection; private JsonSerializerSettings _jsonSettings; + private LogManager _logManager; private bool _loaded; /// <summary> @@ -54,6 +56,7 @@ namespace Tango.Settings /// </summary> 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 /// <returns></returns> public T GetOrCreate<T>() 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<T>(); 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<List<SettingsBase>>(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 @@ +<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:gif="clr-namespace:Tango.AnimatedGif;assembly=Tango.AnimatedGif" + xmlns:local="clr-namespace:Tango.Touch.Controls"> + + <Style TargetType="{x:Type local:TouchGifAnimation}"> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="{x:Type local:TouchGifAnimation}"> + <Image + Stretch="{TemplateBinding Stretch}" + gif:ImageBehavior.AnimatedSource="{TemplateBinding Source}" + gif:ImageBehavior.EnableAnimation="{TemplateBinding EnableAnimation}" + gif:ImageBehavior.SpeedRatio="{TemplateBinding SpeedRatio}" + /> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + +</ResourceDictionary>
\ 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 @@ <Compile Include="Controls\TouchCheckBox.cs" /> <Compile Include="Controls\TouchComboBox.cs" /> <Compile Include="Controls\TouchExpander.cs" /> + <Compile Include="Controls\TouchGifAnimation.cs" /> <Compile Include="Controls\TouchIcon.cs" /> <Compile Include="Controls\TouchIconKind.cs" /> <Compile Include="Controls\TouchImageButton.cs" /> @@ -118,6 +119,10 @@ <SubType>Designer</SubType> <Generator>MSBuild:Compile</Generator> </Page> + <Page Include="Controls\TouchGifAnimation.xaml"> + <SubType>Designer</SubType> + <Generator>MSBuild:Compile</Generator> + </Page> <Page Include="Controls\TouchIcon.xaml"> <SubType>Designer</SubType> <Generator>MSBuild:Compile</Generator> @@ -331,6 +336,10 @@ </None> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\Tango.AnimatedGif\Tango.AnimatedGif.csproj"> + <Project>{d129789c-3096-4d0b-8dd7-fe24a4df4b21}</Project> + <Name>Tango.AnimatedGif</Name> + </ProjectReference> <ProjectReference Include="..\Tango.Core\Tango.Core.csproj"> <Project>{a34ee0f0-649d-41c8-8489-b6f1cc6924ee}</Project> <Name>Tango.Core</Name> 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 @@ <ResourceDictionary Source="pack://application:,,,/Tango.Touch;component/Controls/TouchStaticListBox.xaml" /> <ResourceDictionary Source="pack://application:,,,/Tango.Touch;component/Controls/TouchRingProgress.xaml" /> <ResourceDictionary Source="pack://application:,,,/Tango.Touch;component/Controls/TouchNativeListBox.xaml" /> - + <ResourceDictionary Source="pack://application:,,,/Tango.Touch;component/Controls/TouchGifAnimation.xaml" /> + <ResourceDictionary Source="pack://application:,,,/Tango.Touch;component/Styles/TouchToggleButton.xaml" /> <ResourceDictionary Source="pack://application:,,,/Tango.Touch;component/Styles/TouchButton.xaml" /> 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 |
