diff --git a/WpfAnimatedGif/AnimationCache.cs b/WpfAnimatedGif/AnimationCache.cs index b6babf4..459f851 100644 --- a/WpfAnimatedGif/AnimationCache.cs +++ b/WpfAnimatedGif/AnimationCache.cs @@ -147,14 +147,14 @@ public static AnimationCacheEntry Get(ImageSource source) internal class AnimationCacheEntry { - public AnimationCacheEntry(ObjectKeyFrameCollection keyFrames, Duration duration, int repeatCountFromMetadata) + public AnimationCacheEntry(DelayFrameCollection keyFrames, Duration duration, int repeatCountFromMetadata) { KeyFrames = keyFrames; Duration = duration; RepeatCountFromMetadata = repeatCountFromMetadata; } - public ObjectKeyFrameCollection KeyFrames { get; } + public DelayFrameCollection KeyFrames { get; } public Duration Duration { get; } public int RepeatCountFromMetadata { get; } } diff --git a/WpfAnimatedGif/DelayFrameAnimation.cs b/WpfAnimatedGif/DelayFrameAnimation.cs new file mode 100644 index 0000000..e41a46c --- /dev/null +++ b/WpfAnimatedGif/DelayFrameAnimation.cs @@ -0,0 +1,658 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Packaging; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Windows; +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 +{ + internal class DelayFrameAnimation : ObjectAnimationBase + { + public DelayFrameCollection KeyFrames { get; } + public DelayFrame FirstFrame => KeyFrames[0]; + + private TimeSpan _oversleep = TimeSpan.Zero; + + public DelayFrameAnimation(DelayFrameCollection keyFrames) + { + KeyFrames = keyFrames; + Duration = keyFrames.Duration; + } + + protected override Freezable CreateInstanceCore() + => new DelayFrameAnimation(KeyFrames); + + protected override object GetCurrentValueCore(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock) + { + if (KeyFrames is null) + { + return defaultDestinationValue; + } + + TimeSpan value = animationClock.CurrentTime.Value - _oversleep; + + while (KeyFrames.Duration < value) + { + value -= KeyFrames.Duration; + } + + DelayFrame prev = null; + foreach (var frame in KeyFrames) + { + var imageTask = frame.Bitmap.Task; + + if (imageTask.IsFaulted) + { + throw imageTask.Exception; + } + + if (!imageTask.IsCompleted) + { + if (prev is null) + { + _oversleep = animationClock.CurrentTime.Value; + return defaultDestinationValue; + } + else + { + _oversleep = animationClock.CurrentTime.Value - prev.StartTime; + return prev.Bitmap.Task.Result; + } + } + + prev = frame; + + if (frame.StartTime <= value && value < frame.EndTime) + return frame.Bitmap.Task.Result; + } + + return prev.Bitmap.Task.Result; + } + } + + internal class DelayFrameCollection : IEnumerable + { + private List _frames; + public TimeSpan Duration { get; } + public int RepeatCount { get; } + + public DelayFrame this[int idx] => _frames[idx]; + public int Count => _frames.Count; + + public DelayFrameCollection(GifFile gifMetadata) + { + _frames = new List(); + + int index = 0; + var fullSize = GetFullSize(gifMetadata); + var duration = TimeSpan.Zero; + + var baseFrameTask = new DelayBitmapSource(new WriteableBitmap(fullSize.Width, fullSize.Height, 96, 96, PixelFormats.Pbgra32, null)); + foreach (var rawFrame in gifMetadata.Frames) + { + var frame = + MakeDelayFrame( + rawFrame, + fullSize, + duration, + baseFrameTask, + gifMetadata, index, + out baseFrameTask); + + duration = frame.EndTime; + index += 1; + + _frames.Add(frame); + } + Duration = duration; + RepeatCount = gifMetadata.RepeatCount; + } + + private DelayFrame MakeDelayFrame( + GifFrame rawFrame, + Int32Size fullSize, + TimeSpan duration, + DelayBitmapSource baseFrameTask, + GifFile gifMetadata, + int index, + out DelayBitmapSource nextBaseFrameTask) + { + var frameMeta = GetFrameMetadata(gifMetadata.Frames[index]); + + var frame = index == 0 ? + DelayBitmapSource.Create(gifMetadata, rawFrame, frameMeta, baseFrameTask.Task) : + DelayBitmapSource.CreateAsync(gifMetadata, rawFrame, frameMeta, baseFrameTask.Task); + + switch (frameMeta.DisposalMethod) + { + case FrameDisposalMethod.None: + case FrameDisposalMethod.DoNotDispose: + nextBaseFrameTask = frame; + break; + case FrameDisposalMethod.RestoreBackground: + if (IsFullFrame(frameMeta, fullSize)) + { + nextBaseFrameTask = new DelayBitmapSource(new WriteableBitmap(fullSize.Width, fullSize.Height, 96, 96, PixelFormats.Pbgra32, null)); + } + else + { + nextBaseFrameTask = DelayBitmapSource.CreateClearAsync(frame.Task, rawFrame); + } + break; + case FrameDisposalMethod.RestorePrevious: + // Reuse same base frame + nextBaseFrameTask = baseFrameTask; + break; + + default: + throw new InvalidOperationException(); + } + + return new DelayFrame(frame, duration, duration + frameMeta.Delay); + } + + public static bool TryCreate(BitmapSource source, IUriContext context, out DelayFrameCollection collection) + { + if (TryCreateGifFile(source, context, out var gifFile)) + { + collection = new DelayFrameCollection(gifFile); + return true; + } + else + { + collection = null; + return false; + } + } + + private static bool TryCreateGifFile(BitmapSource image, IUriContext context, out GifFile gifFile) + { + if (image is BitmapFrame frame) + { + if (frame.Decoder is GifBitmapDecoder) + { + if (Uri.TryCreate(frame.BaseUri, frame.ToString(), out var uri)) + { + gifFile = Load(null, uri); + return gifFile is null ? false : gifFile.Frames.Count > 1; + } + } + else + { + Debug.Print("AnimatedSource is not GIF image source"); + } + + gifFile = null; + return false; + } + + if (image is BitmapImage bmp) + { + Stream stream = null; + Uri uri = null; + + if (bmp.StreamSource != null) + { + stream = bmp.StreamSource; + } + else if (bmp.UriSource != null) + { + uri = bmp.UriSource; + if (!uri.IsAbsoluteUri) + { + var baseUri = bmp.BaseUri ?? context?.BaseUri; + if (baseUri != null) + uri = new Uri(baseUri, uri); + } + } + + if (stream is null && uri is null) + { + Debug.Print("Can't get uri or stream from the source."); + gifFile = null; + return false; + } + + try + { + gifFile = Load(stream, uri); + return gifFile is null ? false : gifFile.Frames.Count > 1; + + } + catch (Exception e) + { + Debug.Print("Can't parse gif image: " + e); + gifFile = null; + return false; + } + } + + Debug.Print("Can't get a decoder from the source. AnimatedSource should be either a BitmapImage or a BitmapFrame."); + gifFile = null; + return false; + + + GifFile Load(Stream imgStream, Uri imgUri) + { + if (imgStream != null) + { + imgStream.Position = 0; + return GifFile.ReadGifFile(imgStream, false); + } + else if (imgUri != null) + { + return DecodeGifFile(imgUri); + } + else + { + return null; + } + } + } + + private static GifFile DecodeGifFile(Uri imgUri) + { + Stream stream = null; + if (imgUri.Scheme == PackUriHelper.UriSchemePack) + { + StreamResourceInfo sri; + if (imgUri.Authority == "siteoforigin:,,,") + sri = Application.GetRemoteStream(imgUri); + else + sri = Application.GetResourceStream(imgUri); + + if (sri != null) + stream = sri.Stream; + } + else + { + WebClient wc = new WebClient(); + stream = wc.OpenRead(imgUri); + } + if (stream != null) + { + using (stream) + { + return GifFile.ReadGifFile(stream, false); + } + } + return null; + } + + private static Int32Size GetFullSize(GifFile gifMetadata) + { + var lsd = gifMetadata.Header.LogicalScreenDescriptor; + return new Int32Size(lsd.Width, lsd.Height); + } + + private static FrameMetadata GetFrameMetadata(GifFrame gifMetadata) + { + var d = gifMetadata.Descriptor; + var frameMetadata = new FrameMetadata + { + Left = d.Left, + Top = d.Top, + Width = d.Width, + Height = d.Height, + Delay = TimeSpan.FromMilliseconds(100), + DisposalMethod = FrameDisposalMethod.None + }; + + var gce = gifMetadata.Extensions.OfType().FirstOrDefault(); + if (gce != null) + { + if (gce.Delay != 0) + frameMetadata.Delay = TimeSpan.FromMilliseconds(gce.Delay); + frameMetadata.DisposalMethod = (FrameDisposalMethod)gce.DisposalMethod; + + frameMetadata.HasTransparency = gce.HasTransparency; + frameMetadata.TransparencyIndex = gce.TransparencyIndex; + } + return frameMetadata; + } + + private static bool IsFullFrame(FrameMetadata metadata, Int32Size fullSize) + { + return metadata.Left == 0 + && metadata.Top == 0 + && metadata.Width == fullSize.Width + && metadata.Height == fullSize.Height; + } + + public IEnumerator GetEnumerator() => _frames.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + 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; } + } + } + + internal 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; } + public bool HasTransparency { get; set; } + public int TransparencyIndex { get; set; } + } + + internal enum FrameDisposalMethod + { + None = 0, + DoNotDispose = 1, + RestoreBackground = 2, + RestorePrevious = 3 + } + + internal class DelayFrame + { + public DelayBitmapSource Bitmap { get; } + public TimeSpan StartTime { get; } + public TimeSpan EndTime { get; } + + public DelayFrame(DelayBitmapSource bitmap, TimeSpan start, TimeSpan end) + { + Bitmap = bitmap; + StartTime = start; + EndTime = end; + } + } + + internal class DelayBitmapSource + { + public Task Task { get; } + + public DelayBitmapSource(WriteableBitmap value) + { + Task = System.Threading.Tasks.Task.FromResult(value); + } + + public DelayBitmapSource(Task task) + { + Task = task; + } + + public static DelayBitmapSource CreateClearAsync(Task baseImageTask, GifFrame frame) + { + var task = System.Threading.Tasks.Task.Run(async () => + { + var dispatcher = Application.Current.Dispatcher; + var baseImage = await baseImageTask; + + return dispatcher.Invoke(() => + { + var bitmap = new WriteableBitmap(baseImage); + + var rect = new Int32Rect( + frame.Descriptor.Left, frame.Descriptor.Top, + frame.Descriptor.Width, frame.Descriptor.Height); + + bitmap.WritePixels(rect, new byte[4 * rect.Width * rect.Height], 4 * rect.Width, 0); + + if (bitmap.CanFreeze) + bitmap.Freeze(); + + return bitmap; + }); + }); + + return new DelayBitmapSource(task); + } + + public static DelayBitmapSource CreateAsync(GifFile metadata, GifFrame frame, FrameMetadata framemeta, Task baseImageTask) + { + var data = new DelayBitmapData(frame); + var task = System.Threading.Tasks.Task.Run(async () => + { + var dispatcher = Application.Current.Dispatcher; + + var colormap = data.Frame.Descriptor.HasLocalColorTable ? + data.Frame.LocalColorTable : + metadata.GlobalColorTable; + + var indics = data.Decompress(); + + var baseImage = await baseImageTask; + + return dispatcher.Invoke(() => + { + var bitmap = new WriteableBitmap(baseImage); + + var rect = new Int32Rect( + frame.Descriptor.Left, frame.Descriptor.Top, + frame.Descriptor.Width, frame.Descriptor.Height); + + Draw( + bitmap, + rect, + indics, + colormap, + framemeta.HasTransparency ? framemeta.TransparencyIndex : -1); + + if (bitmap.CanFreeze) + bitmap.Freeze(); + + return bitmap; + }); + }); + + return new DelayBitmapSource(task); + } + + public static DelayBitmapSource Create(GifFile metadata, GifFrame frame, FrameMetadata framemeta, Task backgroundTask) + { + var data = new DelayBitmapData(frame); + var dispatcher = Application.Current.Dispatcher; + + var colormap = data.Frame.Descriptor.HasLocalColorTable ? + data.Frame.LocalColorTable : + metadata.GlobalColorTable; + + var indics = data.Decompress(); + + var bitmap = new WriteableBitmap(backgroundTask.Result); + var rect = new Int32Rect( + frame.Descriptor.Left, frame.Descriptor.Top, + frame.Descriptor.Width, frame.Descriptor.Height); + + Draw( + bitmap, + rect, + indics, + colormap, + framemeta.HasTransparency ? framemeta.TransparencyIndex : -1); + + if (bitmap.CanFreeze) + bitmap.Freeze(); + + return new DelayBitmapSource(bitmap); + } + + private static void Draw(WriteableBitmap bitmap, Int32Rect rect, byte[] indics, GifColor[] colormap, int transparencyIdx) + { + var colors = new byte[indics.Length * 4]; + + bitmap.CopyPixels(rect, colors, 4 * rect.Width, 0); + + for (var i = 0; i < indics.Length; ++i) + { + var idx = indics[i]; + + if (idx == transparencyIdx) + continue; + + var color = colormap[idx]; + colors[4 * i + 0] = color.B; + colors[4 * i + 1] = color.G; + colors[4 * i + 2] = color.R; + colors[4 * i + 3] = 255; + } + + bitmap.WritePixels(rect, colors, 4 * rect.Width, 0); + } + } + + internal class DelayBitmapData + { + private static readonly int MaxStackSize = 4096; + private static readonly int MaxBits = 4097; + + public GifFrame Frame { get; } + public GifImageData Data { get; } + + public DelayBitmapData(GifFrame frame) + { + Frame = frame; + Data = frame.ImageData; + } + + public byte[] Decompress() + { + var totalPixels = Frame.Descriptor.Width * Frame.Descriptor.Height; + + // Initialize GIF data stream decoder. + var dataSize = Data.LzwMinimumCodeSize; + var clear = 1 << dataSize; + var endOfInformation = clear + 1; + var available = clear + 2; + var oldCode = -1; + var codeSize = dataSize + 1; + var codeMask = (1 << codeSize) - 1; + + var prefixBuf = new short[MaxStackSize]; + var suffixBuf = new byte[MaxStackSize]; + var pixelStack = new byte[MaxStackSize]; + var indics = new byte[totalPixels]; + + for (var code = 0; code < clear; code++) + { + suffixBuf[code] = (byte)code; + } + + // Decode GIF pixel stream. + int bits, first, top, pixelIndex; + var datum = bits = first = top = pixelIndex = 0; + + var blockSize = Data.CompressedData.Length; + var tempBuf = Data.CompressedData; + + var blockPos = 0; + + while (blockPos < blockSize) + { + datum += tempBuf[blockPos] << bits; + blockPos++; + + bits += 8; + + while (bits >= codeSize) + { + // Get the next code. + var code = datum & codeMask; + datum >>= codeSize; + bits -= codeSize; + + // Interpret the code + if (code == clear) + { + // Reset decoder. + codeSize = dataSize + 1; + codeMask = (1 << codeSize) - 1; + available = clear + 2; + oldCode = -1; + continue; + } + + // Check for explicit end-of-stream + if (code == endOfInformation) + return indics; + + if (oldCode == -1) + { + indics[pixelIndex++] = suffixBuf[code]; + oldCode = code; + first = code; + continue; + } + + var inCode = code; + if (code >= available) + { + pixelStack[top++] = (byte)first; + code = oldCode; + + if (top == 4097) + ThrowException(); + } + + while (code >= clear) + { + if (code >= MaxBits || code == prefixBuf[code]) + ThrowException(); + + pixelStack[top++] = suffixBuf[code]; + code = prefixBuf[code]; + + if (top == MaxBits) + ThrowException(); + } + + first = suffixBuf[code]; + pixelStack[top++] = (byte)first; + + // Add new code to the dictionary + if (available < MaxStackSize) + { + prefixBuf[available] = (short)oldCode; + suffixBuf[available] = (byte)first; + available++; + + if (((available & codeMask) == 0) && (available < MaxStackSize)) + { + codeSize++; + codeMask += available; + } + } + + oldCode = inCode; + + // Drain the pixel stack. + do + { + indics[pixelIndex++] = pixelStack[--top]; + } while (top > 0); + } + } + + while (pixelIndex < totalPixels) + indics[pixelIndex++] = 0; // clear missing pixels + + return indics; + + void ThrowException() => throw new InvalidDataException(); + } + } +} diff --git a/WpfAnimatedGif/ImageAnimationController.cs b/WpfAnimatedGif/ImageAnimationController.cs index 2d13560..bae06ce 100644 --- a/WpfAnimatedGif/ImageAnimationController.cs +++ b/WpfAnimatedGif/ImageAnimationController.cs @@ -15,15 +15,15 @@ public class ImageAnimationController : IDisposable static ImageAnimationController() { - _sourceDescriptor = DependencyPropertyDescriptor.FromProperty(Image.SourceProperty, typeof (Image)); + _sourceDescriptor = DependencyPropertyDescriptor.FromProperty(Image.SourceProperty, typeof(Image)); } private readonly Image _image; - private readonly ObjectAnimationUsingKeyFrames _animation; + private readonly DelayFrameAnimation _animation; private readonly AnimationClock _clock; private readonly ClockController _clockController; - internal ImageAnimationController(Image image, ObjectAnimationUsingKeyFrames animation, bool autoStart) + internal ImageAnimationController(Image image, DelayFrameAnimation animation, bool autoStart) { _image = image; _animation = animation; @@ -93,7 +93,7 @@ public bool IsComplete public void GotoFrame(int index) { var frame = _animation.KeyFrames[index]; - _clockController.Seek(frame.KeyTime.TimeSpan, TimeSeekOrigin.BeginTime); + _clockController.Seek(frame.StartTime, TimeSeekOrigin.BeginTime); } /// @@ -106,8 +106,7 @@ public int CurrentFrame var time = _clock.CurrentTime; var frameAndIndex = _animation.KeyFrames - .Cast() - .Select((f, i) => new { Time = f.KeyTime.TimeSpan, Index = i }) + .Select((f, i) => new { Time = f.StartTime, Index = i }) .FirstOrDefault(fi => fi.Time >= time); if (frameAndIndex != null) return frameAndIndex.Index; diff --git a/WpfAnimatedGif/ImageBehavior.cs b/WpfAnimatedGif/ImageBehavior.cs index e045949..9a644b3 100644 --- a/WpfAnimatedGif/ImageBehavior.cs +++ b/WpfAnimatedGif/ImageBehavior.cs @@ -303,8 +303,8 @@ public static void RemoveAnimationLoadedHandler(Image image, RoutedEventHandler EventManager.RegisterRoutedEvent( "AnimationCompleted", RoutingStrategy.Bubble, - typeof (RoutedEventHandler), - typeof (ImageBehavior)); + typeof(RoutedEventHandler), + typeof(ImageBehavior)); /// /// Adds a handler for the AnimationCompleted attached event. @@ -429,7 +429,7 @@ private static void AnimateInDesignModeChanged(DependencyObject o, DependencyPro if (imageControl == null) return; - bool newValue = (bool) e.NewValue; + bool newValue = (bool)e.NewValue; ImageSource source = GetAnimatedSource(imageControl); if (source != null && imageControl.IsLoaded) @@ -487,7 +487,7 @@ private static void InitAnimationOrImage(Image imageControl) 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); + TryTwice(() => imageControl.Source = animation.FirstFrame.Bitmap.Task.Result); } else { @@ -509,65 +509,22 @@ private static void InitAnimationOrImage(Image imageControl) } } - private static ObjectAnimationUsingKeyFrames GetAnimation(Image imageControl, BitmapSource source) + private static DelayFrameAnimation 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) + if (DelayFrameCollection.TryCreate(source, imageControl, out var collection)) { - 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); + cacheEntry = new AnimationCacheEntry(collection, collection.Duration, collection.RepeatCount); AnimationCache.Add(source, cacheEntry); } } if (cacheEntry != null) { - var animation = new ObjectAnimationUsingKeyFrames + var animation = new DelayFrameAnimation(cacheEntry.KeyFrames) { - KeyFrames = cacheEntry.KeyFrames, - Duration = cacheEntry.Duration, RepeatBehavior = GetActualRepeatBehavior(imageControl, cacheEntry.RepeatCountFromMetadata), SpeedRatio = GetActualSpeedRatio(imageControl, cacheEntry.Duration) }; @@ -602,35 +559,6 @@ private static double GetActualSpeedRatio(Image imageControl, Duration naturalDu 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 @@ -653,166 +581,6 @@ private static bool IsLoadingDeferred(BitmapSource source, Image imageControl) 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 @@ -825,25 +593,6 @@ private static RepeatBehavior GetActualRepeatBehavior(Image imageControl, int re 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("/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; @@ -864,116 +613,6 @@ private static BitmapMetadata GetApplicationExtension(BitmapDecoder decoder, str return null; } - private static FrameMetadata GetFrameMetadata(BitmapDecoder decoder, GifFile gifMetadata, int frameIndex) - { - if (gifMetadata != null && gifMetadata.Frames.Count > frameIndex) - { - return GetFrameMetadata(gifMetadata.Frames[frameIndex]); - } - - return GetFrameMetadata(decoder.Frames[frameIndex]); - } - - private static FrameMetadata GetFrameMetadata(BitmapFrame frame) - { - var metadata = (BitmapMetadata)frame.Metadata; - var delay = TimeSpan.FromMilliseconds(100); - var metadataDelay = metadata.GetQueryOrDefault("/grctlext/Delay", 10); - if (metadataDelay != 0) - delay = TimeSpan.FromMilliseconds(metadataDelay * 10); - var disposalMethod = (FrameDisposalMethod) metadata.GetQueryOrDefault("/grctlext/Disposal", 0); - var frameMetadata = new FrameMetadata - { - Left = metadata.GetQueryOrDefault("/imgdesc/Left", 0), - Top = metadata.GetQueryOrDefault("/imgdesc/Top", 0), - Width = metadata.GetQueryOrDefault("/imgdesc/Width", frame.PixelWidth), - Height = metadata.GetQueryOrDefault("/imgdesc/Height", frame.PixelHeight), - Delay = delay, - DisposalMethod = disposalMethod - }; - return frameMetadata; - } - - private static FrameMetadata GetFrameMetadata(GifFrame gifMetadata) - { - var d = gifMetadata.Descriptor; - var frameMetadata = new FrameMetadata - { - Left = d.Left, - Top = d.Top, - Width = d.Width, - Height = d.Height, - Delay = TimeSpan.FromMilliseconds(100), - DisposalMethod = FrameDisposalMethod.None - }; - - var gce = gifMetadata.Extensions.OfType().FirstOrDefault(); - if (gce != null) - { - if (gce.Delay != 0) - frameMetadata.Delay = TimeSpan.FromMilliseconds(gce.Delay); - frameMetadata.DisposalMethod = (FrameDisposalMethod) gce.DisposalMethod; - } - return frameMetadata; - } - - private static Int32Size GetFullSize(BitmapDecoder decoder, GifFile gifMetadata) - { - if (gifMetadata != null) - { - var lsd = gifMetadata.Header.LogicalScreenDescriptor; - return new Int32Size(lsd.Width, lsd.Height); - } - int width = decoder.Metadata.GetQueryOrDefault("/logscrdesc/Width", 0); - int height = decoder.Metadata.GetQueryOrDefault("/logscrdesc/Height", 0); - return new Int32Size(width, height); - } - - private struct Int32Size - { - public Int32Size(int width, int height) : this() - { - Width = width; - Height = height; - } - - public int Width { get; private set; } - public int Height { get; private set; } - } - - private class FrameMetadata - { - public int Left { get; set; } - public int Top { get; set; } - public int Width { get; set; } - public int Height { get; set; } - public TimeSpan Delay { get; set; } - public FrameDisposalMethod DisposalMethod { get; set; } - } - - private enum FrameDisposalMethod - { - None = 0, - DoNotDispose = 1, - RestoreBackground = 2, - RestorePrevious = 3 - } - - private static T GetQueryOrDefault(this BitmapMetadata metadata, string query, T defaultValue) - { - if (metadata.ContainsQuery(query)) - return (T)Convert.ChangeType(metadata.GetQuery(query), typeof(T)); - return defaultValue; - } - - private static T GetQueryOrNull(this BitmapMetadata metadata, string query) - where T : class - { - if (metadata.ContainsQuery(query)) - return metadata.GetQuery(query) as T; - return null; - } - // For debug purposes //private static void Save(BitmapSource image, string path) //{ diff --git a/WpfAnimatedGif/MetadataExt.cs b/WpfAnimatedGif/MetadataExt.cs new file mode 100644 index 0000000..909509e --- /dev/null +++ b/WpfAnimatedGif/MetadataExt.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Media.Imaging; + +namespace WpfAnimatedGif +{ + internal static class MetadataExt + { + public static T GetQueryOrDefault(this BitmapMetadata metadata, string query, T defaultValue) + { + if (metadata.ContainsQuery(query)) + return (T)Convert.ChangeType(metadata.GetQuery(query), typeof(T)); + return defaultValue; + } + + public static T GetQueryOrNull(this BitmapMetadata metadata, string query) + where T : class + { + if (metadata.ContainsQuery(query)) + return metadata.GetQuery(query) as T; + return null; + } + } +} diff --git a/WpfAnimatedGif/WpfAnimatedGif.csproj b/WpfAnimatedGif/WpfAnimatedGif.csproj index 6090587..407c437 100644 --- a/WpfAnimatedGif/WpfAnimatedGif.csproj +++ b/WpfAnimatedGif/WpfAnimatedGif.csproj @@ -1,7 +1,7 @@  - net35;net40;netcoreapp3.0 + net45;netcoreapp3.0 true true WpfAnimatedGif.snk