Files
HandyControl/Net35/HandyControl_Net35/Controls/Image/GifImage.cs
T
2019-01-21 00:43:10 +08:00

461 lines
14 KiB
C#

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;
namespace HandyControl.Controls
{
public class GifImage : Image, IDisposable
{
private static readonly Guid GifGuid = new Guid("{b96b3caa-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));
}
private static void OnVisibilityChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
{
var ctl = (GifImage)s;
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 (Source is BitmapImage image)
{
var uri = image.UriSource;
GetGifStreamFromPack(uri);
StartAnimate();
}
};
}
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();
ExternDllHelper.Gdip.GdipDisposeImage(new HandleRef(this, NativeImage));
}
catch
{
// ignored
}
finally
{
NativeImage = IntPtr.Zero;
}
}
private void CreateSourceFromFile(string filename)
{
filename = Path.GetFullPath(filename);
var status = ExternDllHelper.Gdip.GdipCreateBitmapFromFile(filename, out var bitmap);
if (status != ExternDllHelper.Gdip.Ok)
throw ExternDllHelper.Gdip.StatusException(status);
status = ExternDllHelper.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
if (status != ExternDllHelper.Gdip.Ok)
{
ExternDllHelper.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
throw ExternDllHelper.Gdip.StatusException(status);
}
SetNativeImage(bitmap);
EnsureSave(this, filename, null);
}
private void CreateSourceFromStream(Stream stream)
{
if (stream == null)
throw new ArgumentException("stream null");
var status = ExternDllHelper.Gdip.GdipCreateBitmapFromStream(new GPStream(stream), out var bitmap);
if (status != ExternDllHelper.Gdip.Ok)
throw ExternDllHelper.Gdip.StatusException(status);
status = ExternDllHelper.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
if (status != ExternDllHelper.Gdip.Ok)
{
ExternDllHelper.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
throw ExternDllHelper.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
}
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
internal static void EnsureSave(GifImage image, string filename, Stream dataStream)
{
if (!image.RawGuid.Equals(GifGuid)) 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) 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 = ExternDllHelper.Gdip.GdipGetImageRawFormat(new HandleRef(this, NativeImage), ref guid);
if (status != ExternDllHelper.Gdip.Ok)
throw ExternDllHelper.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 = ExternDllHelper.Gdip.GdipImageGetFrameDimensionsCount(new HandleRef(this, NativeImage), out var count);
if (status != ExternDllHelper.Gdip.Ok)
{
throw ExternDllHelper.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 ExternDllHelper.Gdip.StatusException(ExternDllHelper.Gdip.OutOfMemory);
}
status = ExternDllHelper.Gdip.GdipImageGetFrameDimensionsList(new HandleRef(this, NativeImage), buffer, count);
if (status != ExternDllHelper.Gdip.Ok)
{
Marshal.FreeHGlobal(buffer);
throw ExternDllHelper.Gdip.StatusException(status);
}
var guids = new Guid[count];
try
{
for (int i = 0; i < count; i++)
{
guids[i] = (Guid)ExternDllHelper.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 = ExternDllHelper.Gdip.GdipImageGetFrameCount(new HandleRef(this, NativeImage), ref dimensionId, count);
if (status != ExternDllHelper.Gdip.Ok)
throw ExternDllHelper.Gdip.StatusException(status);
return count[0];
}
internal GifPropertyItem GetPropertyItem(int propid)
{
GifPropertyItem propitem;
var status = ExternDllHelper.Gdip.GdipGetPropertyItemSize(new HandleRef(this, NativeImage), propid, out var size);
if (status != ExternDllHelper.Gdip.Ok)
throw ExternDllHelper.Gdip.StatusException(status);
if (size == 0)
return null;
var propdata = Marshal.AllocHGlobal(size);
if (propdata == IntPtr.Zero)
throw ExternDllHelper.Gdip.StatusException(ExternDllHelper.Gdip.OutOfMemory);
status = ExternDllHelper.Gdip.GdipGetPropertyItem(new HandleRef(this, NativeImage), propid, size, propdata);
try
{
if (status != ExternDllHelper.Gdip.Ok)
{
throw ExternDllHelper.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 = ExternDllHelper.Gdip.GdipImageSelectActiveFrame(new HandleRef(this, NativeImage), ref dimensionId, frameIndex);
if (status != ExternDllHelper.Gdip.Ok)
throw ExternDllHelper.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 = ExternDllHelper.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 != ExternDllHelper.Gdip.Ok && NativeImage != IntPtr.Zero)
{
throw ExternDllHelper.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)
{
ExternDllHelper.DeleteObject(handle);
}
}
}
private void OnFrameChanged(object sender, EventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
ImageAnimator.UpdateFrames();
Source?.Freeze();
GetBitmapSource();
InvalidateVisual();
}));
}
}
}