123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- using System;
- using System.ComponentModel;
- using System.IO;
- using System.Linq;
- using System.Runtime.InteropServices;
- using System.Runtime.Versioning;
- using System.Security.Permissions;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Interop;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- using System.Windows.Resources;
- using HandyControl.Data;
- using HandyControl.Tools;
- using HandyControl.Tools.Interop;
- namespace HandyControl.Controls;
- public class GifImage : Image, IDisposable
- {
- private static readonly Guid GifGuid = new("{b96b3caa-0728-11d3-9d7b-0000f81ef32e}");
- private static readonly Guid GifSingleFrameGuid = new("{b96b3cb0-0728-11d3-9d7b-0000f81ef32e}");
- internal IntPtr NativeImage;
- private byte[] _rawData;
- private bool _isStart;
- private bool _isLoaded;
- static GifImage()
- {
- VisibilityProperty.OverrideMetadata(typeof(GifImage), new PropertyMetadata(default(Visibility), OnVisibilityChanged));
- }
- public static readonly DependencyProperty UriProperty = DependencyProperty.Register(
- nameof(Uri), typeof(Uri), typeof(GifImage), new PropertyMetadata(default(Uri), OnUriChanged));
- private static void OnUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- var ctl = (GifImage) d;
- ctl.StopAnimate();
- if (e.NewValue != null)
- {
- var v = (Uri) e.NewValue;
- ctl.GetGifStreamFromPack(v);
- ctl.StartAnimate();
- }
- else
- {
- ctl.Source = null;
- }
- }
- public Uri Uri
- {
- get => (Uri) GetValue(UriProperty);
- set => SetValue(UriProperty, value);
- }
- private static void OnVisibilityChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
- {
- var ctl = (GifImage) s;
- if (ctl.NativeImage == IntPtr.Zero) return;
- var v = (Visibility) e.NewValue;
- if (v != Visibility.Visible)
- {
- ctl.StopAnimate();
- }
- else if (!ctl._isStart)
- {
- ctl.StartAnimate();
- }
- }
- ~GifImage() => Dispose(false);
- public GifImage()
- {
- Loaded += (s, e) =>
- {
- if (DesignerProperties.GetIsInDesignMode(this)) return;
- if (_isLoaded) return;
- _isLoaded = true;
- if (Uri != null)
- {
- GetGifStreamFromPack(Uri);
- StartAnimate();
- }
- else if (Source is BitmapImage image)
- {
- GetGifStreamFromPack(image.UriSource);
- StartAnimate();
- }
- };
- Unloaded += (s, e) => Dispose();
- }
- public GifImage(string filename)
- {
- Source = null;
- CreateSourceFromFile(filename);
- StartAnimate();
- }
- public GifImage(Stream stream)
- {
- Source = null;
- CreateSourceFromStream(stream);
- StartAnimate();
- }
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- protected virtual void Dispose(bool disposing)
- {
- if (NativeImage == IntPtr.Zero) return;
- try
- {
- StopAnimate();
- InteropMethods.Gdip.GdipDisposeImage(new HandleRef(this, NativeImage));
- }
- catch
- {
- // ignored
- }
- finally
- {
- NativeImage = IntPtr.Zero;
- }
- }
- private void CreateSourceFromFile(string filename)
- {
- filename = Path.GetFullPath(filename);
- var status = InteropMethods.Gdip.GdipCreateBitmapFromFile(filename, out var bitmap);
- if (status != InteropMethods.Gdip.Ok)
- throw InteropMethods.Gdip.StatusException(status);
- status = InteropMethods.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
- if (status != InteropMethods.Gdip.Ok)
- {
- InteropMethods.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
- throw InteropMethods.Gdip.StatusException(status);
- }
- SetNativeImage(bitmap);
- EnsureSave(this, filename, null);
- }
- private void CreateSourceFromStream(Stream stream)
- {
- if (stream == null)
- throw new ArgumentException("stream null");
- var status = InteropMethods.Gdip.GdipCreateBitmapFromStream(new GPStream(stream), out var bitmap);
- if (status != InteropMethods.Gdip.Ok)
- throw InteropMethods.Gdip.StatusException(status);
- status = InteropMethods.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
- if (status != InteropMethods.Gdip.Ok)
- {
- InteropMethods.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
- throw InteropMethods.Gdip.StatusException(status);
- }
- SetNativeImage(bitmap);
- EnsureSave(this, null, stream);
- }
- private void GetGifStreamFromPack(Uri uri)
- {
- try
- {
- StreamResourceInfo streamInfo;
- if (!uri.IsAbsoluteUri)
- {
- streamInfo = Application.GetContentStream(uri) ?? Application.GetResourceStream(uri);
- }
- else
- {
- if (uri.GetLeftPart(UriPartial.Authority).Contains("siteoforigin"))
- {
- streamInfo = Application.GetRemoteStream(uri);
- }
- else
- {
- streamInfo = Application.GetContentStream(uri) ?? Application.GetResourceStream(uri);
- }
- }
- if (streamInfo == null)
- {
- throw new FileNotFoundException("Resource not found.", uri.ToString());
- }
- CreateSourceFromStream(streamInfo.Stream);
- }
- catch
- {
- // ignored
- }
- }
- private void SwitchToCommonImage()
- {
- if (Source == null && Uri != null)
- {
- SetCurrentValue(SourceProperty, new BitmapImage(Uri));
- }
- }
- [ResourceExposure(ResourceScope.None)]
- [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
- internal static void EnsureSave(GifImage image, string filename, Stream dataStream)
- {
- if (!image.RawGuid.Equals(GifGuid) && !image.RawGuid.Equals(GifSingleFrameGuid))
- {
- image.SwitchToCommonImage();
- return;
- }
- var animatedGif = false;
- var dimensions = image.FrameDimensionsList;
- if (dimensions.Select(guid => new GifFrameDimension(guid)).Contains(GifFrameDimension.Time))
- {
- animatedGif = image.GetFrameCount(GifFrameDimension.Time) > 1;
- }
- if (!animatedGif)
- {
- image.SwitchToCommonImage();
- return;
- }
- try
- {
- Stream created = null;
- long lastPos = 0;
- if (dataStream != null)
- {
- lastPos = dataStream.Position;
- dataStream.Position = 0;
- }
- try
- {
- if (dataStream == null)
- {
- created = dataStream = File.OpenRead(filename);
- }
- image._rawData = new byte[(int) dataStream.Length];
- dataStream.Read(image._rawData, 0, (int) dataStream.Length);
- }
- finally
- {
- if (created != null)
- {
- created.Close();
- }
- else
- {
- // ReSharper disable once PossibleNullReferenceException
- dataStream.Position = lastPos;
- }
- }
- }
- // possible exceptions for reading the filename
- catch (UnauthorizedAccessException)
- {
- }
- catch (DirectoryNotFoundException)
- {
- }
- catch (IOException)
- {
- }
- // possible exceptions for setting/getting the position inside dataStream
- catch (NotSupportedException)
- {
- }
- catch (ObjectDisposedException)
- {
- }
- // possible exception when reading stuff into dataStream
- catch (ArgumentException)
- {
- }
- }
- internal void SetNativeImage(IntPtr handle)
- {
- if (handle == IntPtr.Zero)
- throw new ArgumentException("NativeHandle0");
- NativeImage = handle;
- }
- internal Guid RawGuid
- {
- get
- {
- var guid = new Guid();
- var status = InteropMethods.Gdip.GdipGetImageRawFormat(new HandleRef(this, NativeImage), ref guid);
- if (status != InteropMethods.Gdip.Ok)
- throw InteropMethods.Gdip.StatusException(status);
- return guid;
- }
- }
- internal void StartAnimate()
- {
- ImageAnimator.Animate(this, OnFrameChanged);
- _isStart = true;
- }
- internal void StopAnimate()
- {
- ImageAnimator.StopAnimate(this, OnFrameChanged);
- _isStart = false;
- }
- internal Guid[] FrameDimensionsList
- {
- get
- {
- var status = InteropMethods.Gdip.GdipImageGetFrameDimensionsCount(new HandleRef(this, NativeImage), out var count);
- if (status != InteropMethods.Gdip.Ok)
- {
- throw InteropMethods.Gdip.StatusException(status);
- }
- if (count <= 0)
- {
- return new Guid[0];
- }
- var size = Marshal.SizeOf(typeof(Guid));
- var buffer = Marshal.AllocHGlobal(checked(size * count));
- if (buffer == IntPtr.Zero)
- {
- throw InteropMethods.Gdip.StatusException(InteropMethods.Gdip.OutOfMemory);
- }
- status = InteropMethods.Gdip.GdipImageGetFrameDimensionsList(new HandleRef(this, NativeImage), buffer, count);
- if (status != InteropMethods.Gdip.Ok)
- {
- Marshal.FreeHGlobal(buffer);
- throw InteropMethods.Gdip.StatusException(status);
- }
- var guids = new Guid[count];
- try
- {
- for (var i = 0; i < count; i++)
- {
- guids[i] = (Guid) InteropMethods.PtrToStructure((IntPtr) ((long) buffer + size * i), typeof(Guid));
- }
- }
- finally
- {
- Marshal.FreeHGlobal(buffer);
- }
- return guids;
- }
- }
- internal int GetFrameCount(GifFrameDimension dimension)
- {
- var count = new[] { 0 };
- var dimensionId = dimension.Guid;
- var status = InteropMethods.Gdip.GdipImageGetFrameCount(new HandleRef(this, NativeImage), ref dimensionId, count);
- if (status != InteropMethods.Gdip.Ok)
- throw InteropMethods.Gdip.StatusException(status);
- return count[0];
- }
- internal GifPropertyItem GetPropertyItem(int propid)
- {
- GifPropertyItem propitem;
- var status = InteropMethods.Gdip.GdipGetPropertyItemSize(new HandleRef(this, NativeImage), propid, out var size);
- if (status != InteropMethods.Gdip.Ok)
- throw InteropMethods.Gdip.StatusException(status);
- if (size == 0)
- return null;
- var propdata = Marshal.AllocHGlobal(size);
- if (propdata == IntPtr.Zero)
- throw InteropMethods.Gdip.StatusException(InteropMethods.Gdip.OutOfMemory);
- status = InteropMethods.Gdip.GdipGetPropertyItem(new HandleRef(this, NativeImage), propid, size, propdata);
- try
- {
- if (status != InteropMethods.Gdip.Ok)
- {
- throw InteropMethods.Gdip.StatusException(status);
- }
- propitem = GifPropertyItemInternal.ConvertFromMemory(propdata, 1)[0];
- }
- finally
- {
- Marshal.FreeHGlobal(propdata);
- }
- return propitem;
- }
- internal int SelectActiveFrame(GifFrameDimension dimension, int frameIndex)
- {
- var count = new[] { 0 };
- var dimensionId = dimension.Guid;
- var status = InteropMethods.Gdip.GdipImageSelectActiveFrame(new HandleRef(this, NativeImage), ref dimensionId, frameIndex);
- if (status != InteropMethods.Gdip.Ok)
- throw InteropMethods.Gdip.StatusException(status);
- return count[0];
- }
- [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
- [EditorBrowsable(EditorBrowsableState.Advanced)]
- [ResourceExposure(ResourceScope.Machine)]
- [ResourceConsumption(ResourceScope.Machine)]
- internal IntPtr GetHbitmap() => GetHbitmap(Colors.LightGray);
- [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
- [EditorBrowsable(EditorBrowsableState.Advanced)]
- [ResourceExposure(ResourceScope.Machine)]
- [ResourceConsumption(ResourceScope.Machine)]
- internal IntPtr GetHbitmap(Color background)
- {
- var status = InteropMethods.Gdip.GdipCreateHBITMAPFromBitmap(new HandleRef(this, NativeImage), out var hBitmap,
- ColorHelper.ToWin32(background));
- if (status == 2 && (Width >= short.MaxValue || Height >= short.MaxValue))
- {
- throw new ArgumentException("GdiplusInvalidSize");
- }
- if (status != InteropMethods.Gdip.Ok && NativeImage != IntPtr.Zero)
- {
- throw InteropMethods.Gdip.StatusException(status);
- }
- return hBitmap;
- }
- private void GetBitmapSource()
- {
- var handle = IntPtr.Zero;
- try
- {
- handle = GetHbitmap();
- Source = Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty,
- BitmapSizeOptions.FromEmptyOptions());
- }
- catch
- {
- // ignored
- }
- finally
- {
- if (handle != IntPtr.Zero)
- {
- InteropMethods.DeleteObject(handle);
- }
- }
- }
- private void OnFrameChanged(object sender, EventArgs e)
- {
- Dispatcher.BeginInvoke(new Action(() =>
- {
- ImageAnimator.UpdateFrames();
- Source?.Freeze();
- GetBitmapSource();
- InvalidateVisual();
- }));
- }
- }
|