mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-04-27 11:43:24 +08:00
项目结构调整
This commit is contained in:
161
Others/WpfAnimatedGif/AnimationCache.cs
Normal file
161
Others/WpfAnimatedGif/AnimationCache.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace WpfAnimatedGif
|
||||
{
|
||||
static class AnimationCache
|
||||
{
|
||||
private struct CacheKey
|
||||
{
|
||||
private readonly ImageSource _source;
|
||||
|
||||
public CacheKey(ImageSource source)
|
||||
{
|
||||
_source = source;
|
||||
}
|
||||
|
||||
private bool Equals(CacheKey other)
|
||||
{
|
||||
return ImageEquals(_source, other._source);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
return ImageGetHashCode(_source);
|
||||
}
|
||||
|
||||
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, AnimationCacheEntry> _animationCache = new Dictionary<CacheKey, AnimationCacheEntry>();
|
||||
private static readonly Dictionary<CacheKey, HashSet<Image>> _imageControls = new Dictionary<CacheKey, HashSet<Image>>();
|
||||
|
||||
public static void AddControlForSource(ImageSource source, Image imageControl)
|
||||
{
|
||||
var cacheKey = new CacheKey(source);
|
||||
if (!_imageControls.TryGetValue(cacheKey, out var controls))
|
||||
{
|
||||
_imageControls[cacheKey] = controls = new HashSet<Image>();
|
||||
}
|
||||
|
||||
controls.Add(imageControl);
|
||||
}
|
||||
|
||||
public static void RemoveControlForSource(ImageSource source, Image imageControl)
|
||||
{
|
||||
var cacheKey = new CacheKey(source);
|
||||
if (_imageControls.TryGetValue(cacheKey, out var controls))
|
||||
{
|
||||
if (controls.Remove(imageControl))
|
||||
{
|
||||
if (controls.Count == 0)
|
||||
{
|
||||
_animationCache.Remove(cacheKey);
|
||||
_imageControls.Remove(cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(ImageSource source, AnimationCacheEntry entry)
|
||||
{
|
||||
var key = new CacheKey(source);
|
||||
_animationCache[key] = entry;
|
||||
}
|
||||
|
||||
public static void Remove(ImageSource source)
|
||||
{
|
||||
var key = new CacheKey(source);
|
||||
_animationCache.Remove(key);
|
||||
}
|
||||
|
||||
public static AnimationCacheEntry Get(ImageSource source)
|
||||
{
|
||||
var key = new CacheKey(source);
|
||||
_animationCache.TryGetValue(key, out var entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
internal class AnimationCacheEntry
|
||||
{
|
||||
public AnimationCacheEntry(ObjectKeyFrameCollection keyFrames, Duration duration, int repeatCountFromMetadata)
|
||||
{
|
||||
KeyFrames = keyFrames;
|
||||
Duration = duration;
|
||||
RepeatCountFromMetadata = repeatCountFromMetadata;
|
||||
}
|
||||
|
||||
public ObjectKeyFrameCollection KeyFrames { get; }
|
||||
public Duration Duration { get; }
|
||||
public int RepeatCountFromMetadata { get; }
|
||||
}
|
||||
}
|
||||
15
Others/WpfAnimatedGif/AssemblyInfo.cs
Normal file
15
Others/WpfAnimatedGif/AssemblyInfo.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Markup;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
|
||||
|
||||
[assembly: XmlnsDefinition("http://wpfanimatedgif.codeplex.com", "WpfAnimatedGif")]
|
||||
[assembly: XmlnsPrefix("http://wpfanimatedgif.codeplex.com", "gif")]
|
||||
50
Others/WpfAnimatedGif/Decoding/GifApplicationExtension.cs
Normal file
50
Others/WpfAnimatedGif/Decoding/GifApplicationExtension.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace WpfAnimatedGif.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Others/WpfAnimatedGif/Decoding/GifBlock.cs
Normal file
28
Others/WpfAnimatedGif/Decoding/GifBlock.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace WpfAnimatedGif.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; }
|
||||
}
|
||||
}
|
||||
10
Others/WpfAnimatedGif/Decoding/GifBlockKind.cs
Normal file
10
Others/WpfAnimatedGif/Decoding/GifBlockKind.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace WpfAnimatedGif.Decoding
|
||||
{
|
||||
internal enum GifBlockKind
|
||||
{
|
||||
Control,
|
||||
GraphicRendering,
|
||||
SpecialPurpose,
|
||||
Other
|
||||
}
|
||||
}
|
||||
25
Others/WpfAnimatedGif/Decoding/GifColor.cs
Normal file
25
Others/WpfAnimatedGif/Decoding/GifColor.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace WpfAnimatedGif.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Others/WpfAnimatedGif/Decoding/GifCommentExtension.cs
Normal file
37
Others/WpfAnimatedGif/Decoding/GifCommentExtension.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace WpfAnimatedGif.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Others/WpfAnimatedGif/Decoding/GifDecoderException.cs
Normal file
16
Others/WpfAnimatedGif/Decoding/GifDecoderException.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace WpfAnimatedGif.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) { }
|
||||
}
|
||||
}
|
||||
32
Others/WpfAnimatedGif/Decoding/GifExtension.cs
Normal file
32
Others/WpfAnimatedGif/Decoding/GifExtension.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace WpfAnimatedGif.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Others/WpfAnimatedGif/Decoding/GifFile.cs
Normal file
86
Others/WpfAnimatedGif/Decoding/GifFile.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace WpfAnimatedGif.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Others/WpfAnimatedGif/Decoding/GifFrame.cs
Normal file
47
Others/WpfAnimatedGif/Decoding/GifFrame.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace WpfAnimatedGif.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Others/WpfAnimatedGif/Decoding/GifGraphicControlExtension.cs
Normal file
52
Others/WpfAnimatedGif/Decoding/GifGraphicControlExtension.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace WpfAnimatedGif.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];
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Others/WpfAnimatedGif/Decoding/GifHeader.cs
Normal file
38
Others/WpfAnimatedGif/Decoding/GifHeader.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.IO;
|
||||
|
||||
namespace WpfAnimatedGif.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Others/WpfAnimatedGif/Decoding/GifHelpers.cs
Normal file
110
Others/WpfAnimatedGif/Decoding/GifHelpers.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace WpfAnimatedGif.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Others/WpfAnimatedGif/Decoding/GifImageData.cs
Normal file
27
Others/WpfAnimatedGif/Decoding/GifImageData.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.IO;
|
||||
|
||||
namespace WpfAnimatedGif.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Others/WpfAnimatedGif/Decoding/GifImageDescriptor.cs
Normal file
43
Others/WpfAnimatedGif/Decoding/GifImageDescriptor.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace WpfAnimatedGif.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Others/WpfAnimatedGif/Decoding/GifLogicalScreenDescriptor.cs
Normal file
43
Others/WpfAnimatedGif/Decoding/GifLogicalScreenDescriptor.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace WpfAnimatedGif.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Others/WpfAnimatedGif/Decoding/GifPlainTextExtension.cs
Normal file
68
Others/WpfAnimatedGif/Decoding/GifPlainTextExtension.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace WpfAnimatedGif.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Others/WpfAnimatedGif/Decoding/GifTrailer.cs
Normal file
21
Others/WpfAnimatedGif/Decoding/GifTrailer.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace WpfAnimatedGif.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
201
Others/WpfAnimatedGif/ImageAnimationController.cs
Normal file
201
Others/WpfAnimatedGif/ImageAnimationController.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
namespace WpfAnimatedGif
|
||||
{
|
||||
/// <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;
|
||||
|
||||
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);
|
||||
|
||||
IsPaused = !autoStart;
|
||||
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; private set; }
|
||||
|
||||
/// <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()
|
||||
{
|
||||
IsPaused = true;
|
||||
_clockController.Pause();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts or resumes the animation. If the animation is complete, it restarts from the beginning.
|
||||
/// </summary>
|
||||
public void Play()
|
||||
{
|
||||
IsPaused = false;
|
||||
if (!_isSuspended)
|
||||
_clockController.Resume();
|
||||
}
|
||||
|
||||
private bool _isSuspended;
|
||||
internal void SetSuspended(bool isSuspended)
|
||||
{
|
||||
if (isSuspended == _isSuspended)
|
||||
return;
|
||||
|
||||
bool wasSuspended = _isSuspended;
|
||||
_isSuspended = isSuspended;
|
||||
if (wasSuspended)
|
||||
{
|
||||
if (!IsPaused)
|
||||
{
|
||||
_clockController.Resume();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_clockController.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
981
Others/WpfAnimatedGif/ImageBehavior.cs
Normal file
981
Others/WpfAnimatedGif/ImageBehavior.cs
Normal file
@@ -0,0 +1,981 @@
|
||||
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.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Resources;
|
||||
using WpfAnimatedGif.Decoding;
|
||||
|
||||
namespace WpfAnimatedGif
|
||||
{
|
||||
/// <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>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 PropertyMetadata(
|
||||
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 PropertyMetadata(
|
||||
default(RepeatBehavior),
|
||||
AnimationPropertyChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the <c>AnimationSpeedRatio</c> attached property for the specified object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The element from which to read the property value.</param>
|
||||
/// <returns>The speed ratio for the animated image.</returns>
|
||||
public static double? GetAnimationSpeedRatio(DependencyObject obj)
|
||||
{
|
||||
return (double?)obj.GetValue(AnimationSpeedRatioProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the <c>AnimationSpeedRatio</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 speed ratio of the animated image.</param>
|
||||
/// <remarks>The <c>AnimationSpeedRatio</c> and <c>AnimationDuration</c> properties are mutually exclusive, only one can be set at a time.</remarks>
|
||||
public static void SetAnimationSpeedRatio(DependencyObject obj, double? value)
|
||||
{
|
||||
obj.SetValue(AnimationSpeedRatioProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <c>AnimationSpeedRatio</c> attached property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty AnimationSpeedRatioProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"AnimationSpeedRatio",
|
||||
typeof(double?),
|
||||
typeof(ImageBehavior),
|
||||
new PropertyMetadata(
|
||||
null,
|
||||
AnimationPropertyChanged));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the <c>AnimationDuration</c> attached property for the specified object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The element from which to read the property value.</param>
|
||||
/// <returns>The duration for the animated image.</returns>
|
||||
public static Duration? GetAnimationDuration(DependencyObject obj)
|
||||
{
|
||||
return (Duration?)obj.GetValue(AnimationDurationProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the <c>AnimationDuration</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 duration of the animated image.</param>
|
||||
/// <remarks>The <c>AnimationSpeedRatio</c> and <c>AnimationDuration</c> properties are mutually exclusive, only one can be set at a time.</remarks>
|
||||
public static void SetAnimationDuration(DependencyObject obj, Duration? value)
|
||||
{
|
||||
obj.SetValue(AnimationDurationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the <c>AnimationDuration</c> attached property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty AnimationDurationProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"AnimationDuration",
|
||||
typeof(Duration?),
|
||||
typeof(ImageBehavior),
|
||||
new PropertyMetadata(
|
||||
null,
|
||||
AnimationPropertyChanged));
|
||||
|
||||
/// <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(true));
|
||||
|
||||
/// <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;
|
||||
imageControl.IsVisibleChanged -= VisibilityChanged;
|
||||
|
||||
AnimationCache.RemoveControlForSource(oldValue, imageControl);
|
||||
var controller = GetAnimationController(imageControl);
|
||||
if (controller != null)
|
||||
controller.Dispose();
|
||||
imageControl.Source = null;
|
||||
}
|
||||
if (newValue != null)
|
||||
{
|
||||
imageControl.Loaded += ImageControlLoaded;
|
||||
imageControl.Unloaded += ImageControlUnloaded;
|
||||
imageControl.IsVisibleChanged += VisibilityChanged;
|
||||
|
||||
if (imageControl.IsLoaded)
|
||||
InitAnimationOrImage(imageControl);
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisibilityChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (sender is Image img && img.IsLoaded)
|
||||
{
|
||||
var controller = GetAnimationController(img);
|
||||
if (controller != null)
|
||||
{
|
||||
bool isVisible = (bool)e.NewValue;
|
||||
controller.SetSuspended(!isVisible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.RemoveControlForSource(source, imageControl);
|
||||
var controller = GetAnimationController(imageControl);
|
||||
if (controller != null)
|
||||
controller.Dispose();
|
||||
}
|
||||
|
||||
private static void AnimationPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
Image imageControl = o as Image;
|
||||
if (imageControl == null)
|
||||
return;
|
||||
|
||||
ImageSource source = GetAnimatedSource(imageControl);
|
||||
if (source != null)
|
||||
{
|
||||
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, imageControl);
|
||||
|
||||
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 cacheEntry = AnimationCache.Get(source);
|
||||
if (cacheEntry == null)
|
||||
{
|
||||
var decoder = GetDecoder(source, imageControl, out GifFile gifMetadata) as GifBitmapDecoder;
|
||||
if (decoder != null && decoder.Frames.Count > 1)
|
||||
{
|
||||
var fullSize = GetFullSize(decoder, gifMetadata);
|
||||
int index = 0;
|
||||
var keyFrames = new ObjectKeyFrameCollection();
|
||||
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);
|
||||
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++;
|
||||
}
|
||||
|
||||
int repeatCount = GetRepeatCountFromMetadata(decoder, gifMetadata);
|
||||
cacheEntry = new AnimationCacheEntry(keyFrames, totalDuration, repeatCount);
|
||||
AnimationCache.Add(source, cacheEntry);
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheEntry != null)
|
||||
{
|
||||
var animation = new ObjectAnimationUsingKeyFrames
|
||||
{
|
||||
KeyFrames = cacheEntry.KeyFrames,
|
||||
Duration = cacheEntry.Duration,
|
||||
RepeatBehavior = GetActualRepeatBehavior(imageControl, cacheEntry.RepeatCountFromMetadata),
|
||||
SpeedRatio = GetActualSpeedRatio(imageControl, cacheEntry.Duration)
|
||||
};
|
||||
|
||||
AnimationCache.AddControlForSource(source, imageControl);
|
||||
return animation;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static double GetActualSpeedRatio(Image imageControl, Duration naturalDuration)
|
||||
{
|
||||
var speedRatio = GetAnimationSpeedRatio(imageControl);
|
||||
var duration = GetAnimationDuration(imageControl);
|
||||
|
||||
if (speedRatio.HasValue && duration.HasValue)
|
||||
throw new InvalidOperationException("Cannot set both AnimationSpeedRatio and AnimationDuration");
|
||||
|
||||
if (speedRatio.HasValue)
|
||||
return speedRatio.Value;
|
||||
|
||||
if (duration.HasValue)
|
||||
{
|
||||
if (!duration.Value.HasTimeSpan)
|
||||
throw new InvalidOperationException("AnimationDuration cannot be Automatic or Forever");
|
||||
if (duration.Value.TimeSpan.Ticks <= 0)
|
||||
throw new InvalidOperationException("AnimationDuration must be strictly positive");
|
||||
return naturalDuration.TimeSpan.Ticks / (double)duration.Value.TimeSpan.Ticks;
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
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, Image imageControl)
|
||||
{
|
||||
var bmp = source as BitmapImage;
|
||||
if (bmp == null)
|
||||
return false;
|
||||
if (bmp.UriSource != null && !bmp.UriSource.IsAbsoluteUri)
|
||||
return bmp.BaseUri == null && (imageControl as IUriContext)?.BaseUri == null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static BitmapDecoder GetDecoder(BitmapSource image, Image imageControl, 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 (!uri.IsAbsoluteUri)
|
||||
{
|
||||
var baseUri = bmp.BaseUri ?? (imageControl as IUriContext)?.BaseUri;
|
||||
if (baseUri != null)
|
||||
uri = new Uri(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, int repeatCountFromMetadata)
|
||||
{
|
||||
// If specified explicitly, use this value
|
||||
var repeatBehavior = GetRepeatBehavior(imageControl);
|
||||
if (repeatBehavior != default(RepeatBehavior))
|
||||
return repeatBehavior;
|
||||
|
||||
if (repeatCountFromMetadata == 0)
|
||||
return RepeatBehavior.Forever;
|
||||
return new RepeatBehavior(repeatCountFromMetadata);
|
||||
}
|
||||
|
||||
private static int GetRepeatCountFromMetadata(BitmapDecoder decoder, GifFile gifMetadata)
|
||||
{
|
||||
if (gifMetadata != null)
|
||||
{
|
||||
return gifMetadata.RepeatCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
8
Others/WpfAnimatedGif/WpfAnimatedGif.csproj
Normal file
8
Others/WpfAnimatedGif/WpfAnimatedGif.csproj
Normal file
@@ -0,0 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp3.1;net4</TargetFrameworks>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user