add GifImage
Currently, there are only a few basic features, and more features will be added in version 2.0
This commit is contained in:
1 parent
4d72778190
commit
e4dc65675a
26 files changed
+1686
-7
No files matched your search
@@ -0,0 +1,461 @@
|
|||||||
|
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();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,271 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Security.Permissions;
|
||||||
|
using HandyControl.Tools;
|
||||||
|
|
||||||
|
namespace HandyControl.Data
|
||||||
|
{
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
internal class GPStream : ExternDllHelper.IStream
|
||||||
|
{
|
||||||
|
protected Stream DataStream;
|
||||||
|
|
||||||
|
// to support seeking ahead of the stream length...
|
||||||
|
long _virtualPosition = -1;
|
||||||
|
|
||||||
|
internal GPStream(Stream stream)
|
||||||
|
{
|
||||||
|
if (!stream.CanSeek)
|
||||||
|
{
|
||||||
|
const int readBlock = 256;
|
||||||
|
var bytes = new byte[readBlock];
|
||||||
|
int readLen;
|
||||||
|
var current = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (bytes.Length < current + readBlock)
|
||||||
|
{
|
||||||
|
var newData = new byte[bytes.Length * 2];
|
||||||
|
Array.Copy(bytes, newData, bytes.Length);
|
||||||
|
bytes = newData;
|
||||||
|
}
|
||||||
|
readLen = stream.Read(bytes, current, readBlock);
|
||||||
|
current += readLen;
|
||||||
|
} while (readLen != 0);
|
||||||
|
|
||||||
|
DataStream = new MemoryStream(bytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DataStream = stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ActualizeVirtualPosition()
|
||||||
|
{
|
||||||
|
if (_virtualPosition == -1) return;
|
||||||
|
|
||||||
|
if (_virtualPosition > DataStream.Length)
|
||||||
|
DataStream.SetLength(_virtualPosition);
|
||||||
|
|
||||||
|
DataStream.Position = _virtualPosition;
|
||||||
|
|
||||||
|
_virtualPosition = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ExternDllHelper.IStream Clone()
|
||||||
|
{
|
||||||
|
NotImplemented();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Commit(int grfCommitFlags)
|
||||||
|
{
|
||||||
|
DataStream.Flush();
|
||||||
|
// Extend the length of the file if needed.
|
||||||
|
ActualizeVirtualPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
[
|
||||||
|
UIPermission(SecurityAction.Demand, Window = UIPermissionWindow.AllWindows),
|
||||||
|
SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)
|
||||||
|
]
|
||||||
|
public virtual long CopyTo(ExternDllHelper.IStream pstm, long cb, long[] pcbRead)
|
||||||
|
{
|
||||||
|
|
||||||
|
const int bufsize = 4096;
|
||||||
|
var buffer = Marshal.AllocHGlobal(bufsize);
|
||||||
|
if (buffer == IntPtr.Zero) throw new OutOfMemoryException();
|
||||||
|
long written = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (written < cb)
|
||||||
|
{
|
||||||
|
var toRead = bufsize;
|
||||||
|
if (written + toRead > cb) toRead = (int)(cb - written);
|
||||||
|
var read = Read(buffer, toRead);
|
||||||
|
if (read == 0) break;
|
||||||
|
if (pstm.Write(buffer, read) != read)
|
||||||
|
{
|
||||||
|
throw EFail("Wrote an incorrect number of bytes");
|
||||||
|
}
|
||||||
|
written += read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(buffer);
|
||||||
|
}
|
||||||
|
if (pcbRead != null && pcbRead.Length > 0)
|
||||||
|
{
|
||||||
|
pcbRead[0] = written;
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual Stream GetDataStream()
|
||||||
|
{
|
||||||
|
return DataStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void LockRegion(long libOffset, long cb, int dwLockType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static ExternalException EFail(string msg)
|
||||||
|
{
|
||||||
|
throw new ExternalException(msg, ExternDllHelper.E_FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void NotImplemented()
|
||||||
|
{
|
||||||
|
throw new ExternalException("NotImplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int Read(IntPtr buf, int length)
|
||||||
|
{
|
||||||
|
var buffer = new byte[length];
|
||||||
|
var count = Read(buffer, length);
|
||||||
|
Marshal.Copy(buffer, 0, buf, length);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int Read(byte[] buffer, int length)
|
||||||
|
{
|
||||||
|
ActualizeVirtualPosition();
|
||||||
|
return DataStream.Read(buffer, 0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Revert()
|
||||||
|
{
|
||||||
|
NotImplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual long Seek(long offset, int origin)
|
||||||
|
{
|
||||||
|
var pos = _virtualPosition;
|
||||||
|
if (_virtualPosition == -1)
|
||||||
|
{
|
||||||
|
pos = DataStream.Position;
|
||||||
|
}
|
||||||
|
var len = DataStream.Length;
|
||||||
|
switch (origin)
|
||||||
|
{
|
||||||
|
case ExternDllHelper.StreamConsts.STREAM_SEEK_SET:
|
||||||
|
if (offset <= len)
|
||||||
|
{
|
||||||
|
DataStream.Position = offset;
|
||||||
|
_virtualPosition = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_virtualPosition = offset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ExternDllHelper.StreamConsts.STREAM_SEEK_END:
|
||||||
|
if (offset <= 0)
|
||||||
|
{
|
||||||
|
DataStream.Position = len + offset;
|
||||||
|
_virtualPosition = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_virtualPosition = len + offset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ExternDllHelper.StreamConsts.STREAM_SEEK_CUR:
|
||||||
|
if (offset + pos <= len)
|
||||||
|
{
|
||||||
|
DataStream.Position = pos + offset;
|
||||||
|
_virtualPosition = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_virtualPosition = offset + pos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return _virtualPosition != -1 ? _virtualPosition : DataStream.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void SetSize(long value)
|
||||||
|
{
|
||||||
|
DataStream.SetLength(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stat(IntPtr pstatstg, int grfStatFlag)
|
||||||
|
{
|
||||||
|
var stats = new STATSTG {cbSize = DataStream.Length};
|
||||||
|
Marshal.StructureToPtr(stats, pstatstg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void UnlockRegion(long libOffset, long cb, int dwLockType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int Write(IntPtr buf, int length)
|
||||||
|
{
|
||||||
|
var buffer = new byte[length];
|
||||||
|
Marshal.Copy(buf, buffer, 0, length);
|
||||||
|
return Write(buffer, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual int Write(byte[] buffer, int length)
|
||||||
|
{
|
||||||
|
ActualizeVirtualPosition();
|
||||||
|
DataStream.Write(buffer, 0, length);
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
|
public class STATSTG
|
||||||
|
{
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
|
||||||
|
public IntPtr pwcsName = IntPtr.Zero;
|
||||||
|
public int type;
|
||||||
|
[MarshalAs(UnmanagedType.I8)]
|
||||||
|
public long cbSize;
|
||||||
|
[MarshalAs(UnmanagedType.I8)]
|
||||||
|
public long mtime;
|
||||||
|
[MarshalAs(UnmanagedType.I8)]
|
||||||
|
public long ctime;
|
||||||
|
[MarshalAs(UnmanagedType.I8)]
|
||||||
|
public long atime;
|
||||||
|
[MarshalAs(UnmanagedType.I4)]
|
||||||
|
public int grfMode;
|
||||||
|
[MarshalAs(UnmanagedType.I4)]
|
||||||
|
public int grfLocksSupported;
|
||||||
|
|
||||||
|
public int clsid_data1;
|
||||||
|
[MarshalAs(UnmanagedType.I2)]
|
||||||
|
public short clsid_data2;
|
||||||
|
[MarshalAs(UnmanagedType.I2)]
|
||||||
|
public short clsid_data3;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public byte clsid_b0;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public byte clsid_b1;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public byte clsid_b2;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public byte clsid_b3;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public byte clsid_b4;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public byte clsid_b5;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public byte clsid_b6;
|
||||||
|
[MarshalAs(UnmanagedType.U1)]
|
||||||
|
public byte clsid_b7;
|
||||||
|
[MarshalAs(UnmanagedType.I4)]
|
||||||
|
public int grfStateBits;
|
||||||
|
[MarshalAs(UnmanagedType.I4)]
|
||||||
|
public int reserved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HandyControl.Data
|
||||||
|
{
|
||||||
|
internal class GifFrameDimension
|
||||||
|
{
|
||||||
|
public GifFrameDimension(Guid guid) => Guid = guid;
|
||||||
|
|
||||||
|
public Guid Guid { get; }
|
||||||
|
|
||||||
|
public static GifFrameDimension Time { get; } = new GifFrameDimension(new Guid("{6aedbd6d-3fb5-418a-83a6-7f45229dc872}"));
|
||||||
|
|
||||||
|
public static GifFrameDimension Resolution { get; } = new GifFrameDimension(new Guid("{84236f7b-3bd3-428f-8dab-4ea1439ca315}"));
|
||||||
|
|
||||||
|
public static GifFrameDimension Page { get; } = new GifFrameDimension(new Guid("{7462dc86-6180-4c7e-8e3f-ee7333a7a483}"));
|
||||||
|
|
||||||
|
public override bool Equals(object o) => o is GifFrameDimension format && Guid == format.Guid;
|
||||||
|
|
||||||
|
public override int GetHashCode() => Guid.GetHashCode();
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
if (Equals(this, Time)) return "Time";
|
||||||
|
if (Equals(this, Resolution)) return "Resolution";
|
||||||
|
if (Equals(this, Page)) return "Page";
|
||||||
|
return "[FrameDimension: " + Guid + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
using System;
|
||||||
|
using HandyControl.Controls;
|
||||||
|
|
||||||
|
namespace HandyControl.Data
|
||||||
|
{
|
||||||
|
public class GifImageInfo
|
||||||
|
{
|
||||||
|
private const int PropertyTagFrameDelay = 0x5100;
|
||||||
|
|
||||||
|
private int _frame;
|
||||||
|
|
||||||
|
private readonly int[] _frameDelay;
|
||||||
|
|
||||||
|
public int FrameCount { get; }
|
||||||
|
|
||||||
|
internal GifImage Image { get; }
|
||||||
|
|
||||||
|
public bool Animated { get; }
|
||||||
|
|
||||||
|
public EventHandler FrameChangedHandler { get; set; }
|
||||||
|
|
||||||
|
internal int FrameTimer { get; set; }
|
||||||
|
|
||||||
|
public bool FrameDirty { get; private set; }
|
||||||
|
|
||||||
|
public int Frame
|
||||||
|
{
|
||||||
|
get => _frame;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_frame != value)
|
||||||
|
{
|
||||||
|
if (value < 0 || value >= FrameCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("InvalidFrame");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Animated)
|
||||||
|
{
|
||||||
|
_frame = value;
|
||||||
|
FrameDirty = true;
|
||||||
|
|
||||||
|
OnFrameChanged(EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GifImageInfo(GifImage image)
|
||||||
|
{
|
||||||
|
Image = image;
|
||||||
|
Animated = ImageAnimator.CanAnimate(image);
|
||||||
|
|
||||||
|
if (Animated)
|
||||||
|
{
|
||||||
|
FrameCount = image.GetFrameCount(GifFrameDimension.Time);
|
||||||
|
|
||||||
|
var frameDelayItem = image.GetPropertyItem(PropertyTagFrameDelay);
|
||||||
|
|
||||||
|
// If the image does not have a frame delay, we just return 0.
|
||||||
|
//
|
||||||
|
if (frameDelayItem != null)
|
||||||
|
{
|
||||||
|
// Convert the frame delay from byte[] to int
|
||||||
|
//
|
||||||
|
var values = frameDelayItem.Value;
|
||||||
|
_frameDelay = new int[FrameCount];
|
||||||
|
for (int i = 0; i < FrameCount; ++i)
|
||||||
|
{
|
||||||
|
_frameDelay[i] = values[i * 4] + 256 * values[i * 4 + 1] + 256 * 256 * values[i * 4 + 2] + 256 * 256 * 256 * values[i * 4 + 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FrameCount = 1;
|
||||||
|
}
|
||||||
|
if (_frameDelay == null)
|
||||||
|
{
|
||||||
|
_frameDelay = new int[FrameCount];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int FrameDelay(int frame) => _frameDelay[frame];
|
||||||
|
|
||||||
|
internal void UpdateFrame()
|
||||||
|
{
|
||||||
|
if (FrameDirty)
|
||||||
|
{
|
||||||
|
Image.SelectActiveFrame(GifFrameDimension.Time, Frame);
|
||||||
|
FrameDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void OnFrameChanged(EventArgs e) => FrameChangedHandler?.Invoke(Image, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace HandyControl.Data
|
||||||
|
{
|
||||||
|
public class GifPropertyItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int Len { get; set; }
|
||||||
|
|
||||||
|
public short Type { get; set; }
|
||||||
|
|
||||||
|
public byte[] Value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using HandyControl.Tools;
|
||||||
|
|
||||||
|
namespace HandyControl.Data
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public class GifPropertyItemInternal : IDisposable
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public int len;
|
||||||
|
public short type;
|
||||||
|
public IntPtr value = IntPtr.Zero;
|
||||||
|
|
||||||
|
internal GifPropertyItemInternal()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (len == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var bytes = new byte[len];
|
||||||
|
|
||||||
|
Marshal.Copy(value,
|
||||||
|
bytes,
|
||||||
|
0,
|
||||||
|
len);
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
~GifPropertyItemInternal()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (value != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(value);
|
||||||
|
value = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static GifPropertyItemInternal ConvertFromPropertyItem(GifPropertyItem propItem)
|
||||||
|
{
|
||||||
|
var propItemInternal = new GifPropertyItemInternal
|
||||||
|
{
|
||||||
|
id = propItem.Id,
|
||||||
|
len = 0,
|
||||||
|
type = propItem.Type
|
||||||
|
};
|
||||||
|
|
||||||
|
var propItemValue = propItem.Value;
|
||||||
|
if (propItemValue != null)
|
||||||
|
{
|
||||||
|
var length = propItemValue.Length;
|
||||||
|
propItemInternal.len = length;
|
||||||
|
propItemInternal.value = Marshal.AllocHGlobal(length);
|
||||||
|
Marshal.Copy(propItemValue, 0, propItemInternal.value, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return propItemInternal;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static GifPropertyItem[] ConvertFromMemory(IntPtr propdata, int count)
|
||||||
|
{
|
||||||
|
var props = new GifPropertyItem[count];
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
GifPropertyItemInternal propcopy = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
propcopy = (GifPropertyItemInternal) ExternDllHelper.PtrToStructure(propdata,
|
||||||
|
typeof(GifPropertyItemInternal));
|
||||||
|
|
||||||
|
props[i] = new GifPropertyItem
|
||||||
|
{
|
||||||
|
Id = propcopy.id,
|
||||||
|
Len = propcopy.len,
|
||||||
|
Type = propcopy.type,
|
||||||
|
Value = propcopy.Value
|
||||||
|
};
|
||||||
|
|
||||||
|
// this calls Marshal.Copy and creates a copy of the original memory into a byte array.
|
||||||
|
|
||||||
|
propcopy.value = IntPtr.Zero; // we dont actually own this memory so dont free it.
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
propcopy?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
propdata = (IntPtr) ((long) propdata + Marshal.SizeOf(typeof(GifPropertyItemInternal)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -49,6 +49,7 @@
|
|||||||
<Compile Include="Controls\Carousel.cs" />
|
<Compile Include="Controls\Carousel.cs" />
|
||||||
<Compile Include="Controls\CirclePanel.cs" />
|
<Compile Include="Controls\CirclePanel.cs" />
|
||||||
<Compile Include="Controls\CircleProgressBar.cs" />
|
<Compile Include="Controls\CircleProgressBar.cs" />
|
||||||
|
<Compile Include="Controls\GifImage.cs" />
|
||||||
<Compile Include="Controls\Input\SearchBar.cs" />
|
<Compile Include="Controls\Input\SearchBar.cs" />
|
||||||
<Compile Include="Controls\Tag\TagPanel.cs" />
|
<Compile Include="Controls\Tag\TagPanel.cs" />
|
||||||
<Compile Include="Controls\Tag\Tag.cs" />
|
<Compile Include="Controls\Tag\Tag.cs" />
|
||||||
@@ -78,15 +79,22 @@
|
|||||||
<Compile Include="Controls\InternalCanvas.cs" />
|
<Compile Include="Controls\InternalCanvas.cs" />
|
||||||
<Compile Include="Controls\StepBar\StepBar.cs" />
|
<Compile Include="Controls\StepBar\StepBar.cs" />
|
||||||
<Compile Include="Controls\StepBar\StepBarItem.cs" />
|
<Compile Include="Controls\StepBar\StepBarItem.cs" />
|
||||||
<Compile Include="Data\CancelRoutedEventArgs.cs" />
|
<Compile Include="Data\Args\CancelRoutedEventArgs.cs" />
|
||||||
<Compile Include="Data\Enum\InfoType.cs" />
|
<Compile Include="Data\Enum\InfoType.cs" />
|
||||||
<Compile Include="Data\Enum\ResultType.cs" />
|
<Compile Include="Data\Enum\ResultType.cs" />
|
||||||
<Compile Include="Data\Enum\SkinType.cs" />
|
<Compile Include="Data\Enum\SkinType.cs" />
|
||||||
<Compile Include="Data\Enum\StepStatus.cs" />
|
<Compile Include="Data\Enum\StepStatus.cs" />
|
||||||
<Compile Include="Data\Enum\TipPlacement.cs" />
|
<Compile Include="Data\Enum\TipPlacement.cs" />
|
||||||
|
<Compile Include="Data\Gif\GifFrameDimension.cs" />
|
||||||
|
<Compile Include="Data\Gif\GifImageInfo.cs" />
|
||||||
|
<Compile Include="Data\Gif\GifPropertyItem.cs" />
|
||||||
|
<Compile Include="Data\Gif\GifPropertyItemInternal.cs" />
|
||||||
|
<Compile Include="Data\Gif\GPStream.cs" />
|
||||||
|
<Compile Include="Tools\ColorHelper.cs" />
|
||||||
|
<Compile Include="Tools\GifImageAnimator.cs" />
|
||||||
<Compile Include="Data\GrowlInfo.cs" />
|
<Compile Include="Data\GrowlInfo.cs" />
|
||||||
<Compile Include="Data\OperationResult!1.cs" />
|
<Compile Include="Data\Operation\OperationResult!1.cs" />
|
||||||
<Compile Include="Data\OperationResult.cs" />
|
<Compile Include="Data\Operation\OperationResult.cs" />
|
||||||
<Compile Include="Data\ResourceToken.cs">
|
<Compile Include="Data\ResourceToken.cs">
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
@@ -124,6 +132,7 @@
|
|||||||
<Compile Include="Expression\Media\UnitType.cs" />
|
<Compile Include="Expression\Media\UnitType.cs" />
|
||||||
<Compile Include="Expression\Shapes\Arc.cs" />
|
<Compile Include="Expression\Shapes\Arc.cs" />
|
||||||
<Compile Include="Expression\Shapes\PrimitiveShape.cs" />
|
<Compile Include="Expression\Shapes\PrimitiveShape.cs" />
|
||||||
|
<Compile Include="Tools\ExternDllHelper.cs" />
|
||||||
<Compile Include="Tools\SingleOpenHelper.cs" />
|
<Compile Include="Tools\SingleOpenHelper.cs" />
|
||||||
<Compile Include="Controls\TabControl\TabControl.cs" />
|
<Compile Include="Controls\TabControl\TabControl.cs" />
|
||||||
<Compile Include="Controls\TabControl\TabItem.cs" />
|
<Compile Include="Controls\TabControl\TabItem.cs" />
|
||||||
@@ -141,7 +150,7 @@
|
|||||||
<Compile Include="Data\Enum\IpType.cs" />
|
<Compile Include="Data\Enum\IpType.cs" />
|
||||||
<Compile Include="Data\Enum\TextType.cs" />
|
<Compile Include="Data\Enum\TextType.cs" />
|
||||||
<Compile Include="Data\Enum\TitleAlignment.cs" />
|
<Compile Include="Data\Enum\TitleAlignment.cs" />
|
||||||
<Compile Include="Data\FunctionEventArgs!1.cs" />
|
<Compile Include="Data\Args\FunctionEventArgs!1.cs" />
|
||||||
<Compile Include="Interactivity\AdornerContainer.cs" />
|
<Compile Include="Interactivity\AdornerContainer.cs" />
|
||||||
<Compile Include="Interactivity\AttachableCollection!1.cs" />
|
<Compile Include="Interactivity\AttachableCollection!1.cs" />
|
||||||
<Compile Include="Interactivity\Behavior!1.cs" />
|
<Compile Include="Interactivity\Behavior!1.cs" />
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace HandyControl.Tools
|
||||||
|
{
|
||||||
|
public class ColorHelper
|
||||||
|
{
|
||||||
|
private const int Win32RedShift = 0;
|
||||||
|
private const int Win32GreenShift = 8;
|
||||||
|
private const int Win32BlueShift = 16;
|
||||||
|
|
||||||
|
public static int ToWin32(Color c) => c.R << Win32RedShift | c.G << Win32GreenShift | c.B << Win32BlueShift;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,332 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.ConstrainedExecution;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
using System.Security.Permissions;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace HandyControl.Tools
|
||||||
|
{
|
||||||
|
internal class ExternDllHelper
|
||||||
|
{
|
||||||
|
private const string Gdi32 = "gdi32.dll";
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
public const int E_FAIL = unchecked((int) 0x80004005);
|
||||||
|
|
||||||
|
[ReflectionPermission(SecurityAction.Assert, Unrestricted = true), SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
||||||
|
public static object PtrToStructure(IntPtr lparam, Type cls) => Marshal.PtrToStructure(lparam, cls);
|
||||||
|
|
||||||
|
[DllImport(Gdi32, CharSet = CharSet.Auto, SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static extern bool DeleteObject(IntPtr hObject);
|
||||||
|
|
||||||
|
[ComImport, Guid("0000000C-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
public interface IStream
|
||||||
|
{
|
||||||
|
int Read(
|
||||||
|
[In]
|
||||||
|
IntPtr buf,
|
||||||
|
[In]
|
||||||
|
int len);
|
||||||
|
|
||||||
|
|
||||||
|
int Write(
|
||||||
|
[In]
|
||||||
|
IntPtr buf,
|
||||||
|
[In]
|
||||||
|
int len);
|
||||||
|
|
||||||
|
[return: MarshalAs(UnmanagedType.I8)]
|
||||||
|
long Seek(
|
||||||
|
[In, MarshalAs(UnmanagedType.I8)]
|
||||||
|
long dlibMove,
|
||||||
|
[In]
|
||||||
|
int dwOrigin);
|
||||||
|
|
||||||
|
|
||||||
|
void SetSize(
|
||||||
|
[In, MarshalAs(UnmanagedType.I8)]
|
||||||
|
long libNewSize);
|
||||||
|
|
||||||
|
[return: MarshalAs(UnmanagedType.I8)]
|
||||||
|
long CopyTo(
|
||||||
|
[In, MarshalAs(UnmanagedType.Interface)]
|
||||||
|
IStream pstm,
|
||||||
|
[In, MarshalAs(UnmanagedType.I8)]
|
||||||
|
long cb,
|
||||||
|
[Out, MarshalAs(UnmanagedType.LPArray)]
|
||||||
|
long[] pcbRead);
|
||||||
|
|
||||||
|
|
||||||
|
void Commit(
|
||||||
|
[In]
|
||||||
|
int grfCommitFlags);
|
||||||
|
|
||||||
|
|
||||||
|
void Revert();
|
||||||
|
|
||||||
|
|
||||||
|
void LockRegion(
|
||||||
|
[In, MarshalAs(UnmanagedType.I8)]
|
||||||
|
long libOffset,
|
||||||
|
[In, MarshalAs(UnmanagedType.I8)]
|
||||||
|
long cb,
|
||||||
|
[In]
|
||||||
|
int dwLockType);
|
||||||
|
|
||||||
|
|
||||||
|
void UnlockRegion(
|
||||||
|
[In, MarshalAs(UnmanagedType.I8)]
|
||||||
|
long libOffset,
|
||||||
|
[In, MarshalAs(UnmanagedType.I8)]
|
||||||
|
long cb,
|
||||||
|
[In]
|
||||||
|
int dwLockType);
|
||||||
|
|
||||||
|
|
||||||
|
void Stat(
|
||||||
|
[In]
|
||||||
|
IntPtr pStatstg,
|
||||||
|
[In]
|
||||||
|
int grfStatFlag);
|
||||||
|
|
||||||
|
[return: MarshalAs(UnmanagedType.Interface)]
|
||||||
|
IStream Clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
|
public class StreamConsts
|
||||||
|
{
|
||||||
|
public const int LOCK_WRITE = 0x1;
|
||||||
|
public const int LOCK_EXCLUSIVE = 0x2;
|
||||||
|
public const int LOCK_ONLYONCE = 0x4;
|
||||||
|
public const int STATFLAG_DEFAULT = 0x0;
|
||||||
|
public const int STATFLAG_NONAME = 0x1;
|
||||||
|
public const int STATFLAG_NOOPEN = 0x2;
|
||||||
|
public const int STGC_DEFAULT = 0x0;
|
||||||
|
public const int STGC_OVERWRITE = 0x1;
|
||||||
|
public const int STGC_ONLYIFCURRENT = 0x2;
|
||||||
|
public const int STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE = 0x4;
|
||||||
|
public const int STREAM_SEEK_SET = 0x0;
|
||||||
|
public const int STREAM_SEEK_CUR = 0x1;
|
||||||
|
public const int STREAM_SEEK_END = 0x2;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class Gdip
|
||||||
|
{
|
||||||
|
private const string Gdiplus = "gdiplus.dll";
|
||||||
|
|
||||||
|
private const string ThreadDataSlotName = "system.drawing.threaddata";
|
||||||
|
|
||||||
|
private static IntPtr InitToken;
|
||||||
|
|
||||||
|
private static bool Initialized => InitToken != IntPtr.Zero;
|
||||||
|
|
||||||
|
static Gdip()
|
||||||
|
{
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct StartupInput
|
||||||
|
{
|
||||||
|
private int GdiplusVersion;
|
||||||
|
|
||||||
|
private readonly IntPtr DebugEventCallback;
|
||||||
|
|
||||||
|
private bool SuppressBackgroundThread;
|
||||||
|
|
||||||
|
private bool SuppressExternalCodecs;
|
||||||
|
|
||||||
|
public static StartupInput GetDefault()
|
||||||
|
{
|
||||||
|
var result = new StartupInput
|
||||||
|
{
|
||||||
|
GdiplusVersion = 1,
|
||||||
|
SuppressBackgroundThread = false,
|
||||||
|
SuppressExternalCodecs = false
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct StartupOutput
|
||||||
|
{
|
||||||
|
private readonly IntPtr hook;
|
||||||
|
|
||||||
|
private readonly IntPtr unhook;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
[ResourceConsumption(ResourceScope.AppDomain, ResourceScope.AppDomain)]
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals")]
|
||||||
|
private static void Initialize()
|
||||||
|
{
|
||||||
|
var input = StartupInput.GetDefault();
|
||||||
|
|
||||||
|
var status = GdiplusStartup(out InitToken, ref input, out _);
|
||||||
|
|
||||||
|
if (status != Ok)
|
||||||
|
{
|
||||||
|
throw StatusException(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentDomain = AppDomain.CurrentDomain;
|
||||||
|
currentDomain.ProcessExit += OnProcessExit;
|
||||||
|
|
||||||
|
if (!currentDomain.IsDefaultAppDomain())
|
||||||
|
{
|
||||||
|
currentDomain.DomainUnload += OnProcessExit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[PrePrepareMethod]
|
||||||
|
[ResourceExposure(ResourceScope.AppDomain)]
|
||||||
|
[ResourceConsumption(ResourceScope.AppDomain)]
|
||||||
|
private static void OnProcessExit(object sender, EventArgs e) => Shutdown();
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")]
|
||||||
|
[ResourceExposure(ResourceScope.AppDomain)]
|
||||||
|
[ResourceConsumption(ResourceScope.AppDomain)]
|
||||||
|
private static void Shutdown()
|
||||||
|
{
|
||||||
|
if (Initialized)
|
||||||
|
{
|
||||||
|
ClearThreadData();
|
||||||
|
// unhook our shutdown handlers as we do not need to shut down more than once
|
||||||
|
var currentDomain = AppDomain.CurrentDomain;
|
||||||
|
currentDomain.ProcessExit -= OnProcessExit;
|
||||||
|
if (!currentDomain.IsDefaultAppDomain())
|
||||||
|
{
|
||||||
|
currentDomain.DomainUnload -= OnProcessExit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static void ClearThreadData()
|
||||||
|
{
|
||||||
|
var slot = Thread.GetNamedDataSlot(ThreadDataSlotName);
|
||||||
|
Thread.SetData(slot, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------------------
|
||||||
|
// Status codes
|
||||||
|
//----------------------------------------------------------------------------------------
|
||||||
|
internal const int Ok = 0;
|
||||||
|
internal const int GenericError = 1;
|
||||||
|
internal const int InvalidParameter = 2;
|
||||||
|
internal const int OutOfMemory = 3;
|
||||||
|
internal const int ObjectBusy = 4;
|
||||||
|
internal const int InsufficientBuffer = 5;
|
||||||
|
internal const int NotImplemented = 6;
|
||||||
|
internal const int Win32Error = 7;
|
||||||
|
internal const int WrongState = 8;
|
||||||
|
internal const int Aborted = 9;
|
||||||
|
internal const int FileNotFound = 10;
|
||||||
|
internal const int ValueOverflow = 11;
|
||||||
|
internal const int AccessDenied = 12;
|
||||||
|
internal const int UnknownImageFormat = 13;
|
||||||
|
internal const int FontFamilyNotFound = 14;
|
||||||
|
internal const int FontStyleNotFound = 15;
|
||||||
|
internal const int NotTrueTypeFont = 16;
|
||||||
|
internal const int UnsupportedGdiplusVersion = 17;
|
||||||
|
internal const int GdiplusNotInitialized = 18;
|
||||||
|
internal const int PropertyNotFound = 19;
|
||||||
|
internal const int PropertyNotSupported = 20;
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
internal static extern int GdipImageGetFrameDimensionsCount(HandleRef image, out int count);
|
||||||
|
|
||||||
|
internal static Exception StatusException(int status)
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case GenericError: return new ExternalException("GdiplusGenericError");
|
||||||
|
case InvalidParameter: return new ArgumentException("GdiplusInvalidParameter");
|
||||||
|
case OutOfMemory: return new OutOfMemoryException("GdiplusOutOfMemory");
|
||||||
|
case ObjectBusy: return new InvalidOperationException("GdiplusObjectBusy");
|
||||||
|
case InsufficientBuffer: return new OutOfMemoryException("GdiplusInsufficientBuffer");
|
||||||
|
case NotImplemented: return new NotImplementedException("GdiplusNotImplemented");
|
||||||
|
case Win32Error: return new ExternalException("GdiplusGenericError");
|
||||||
|
case WrongState: return new InvalidOperationException("GdiplusWrongState");
|
||||||
|
case Aborted: return new ExternalException("GdiplusAborted");
|
||||||
|
case FileNotFound: return new FileNotFoundException("GdiplusFileNotFound");
|
||||||
|
case ValueOverflow: return new OverflowException("GdiplusOverflow");
|
||||||
|
case AccessDenied: return new ExternalException("GdiplusAccessDenied");
|
||||||
|
case UnknownImageFormat: return new ArgumentException("GdiplusUnknownImageFormat");
|
||||||
|
case PropertyNotFound: return new ArgumentException("GdiplusPropertyNotFoundError");
|
||||||
|
case PropertyNotSupported: return new ArgumentException("GdiplusPropertyNotSupportedError");
|
||||||
|
case FontFamilyNotFound: return new ArgumentException("GdiplusFontFamilyNotFound");
|
||||||
|
case FontStyleNotFound: return new ArgumentException("GdiplusFontStyleNotFound");
|
||||||
|
case NotTrueTypeFont: return new ArgumentException("GdiplusNotTrueTypeFont_NoName");
|
||||||
|
case UnsupportedGdiplusVersion: return new ExternalException("GdiplusUnsupportedGdiplusVersion");
|
||||||
|
case GdiplusNotInitialized: return new ExternalException("GdiplusNotInitialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExternalException("GdiplusUnknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
internal static extern int GdipImageGetFrameDimensionsList(HandleRef image, IntPtr buffer, int count);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
internal static extern int GdipImageGetFrameCount(HandleRef image, ref Guid dimensionId, int[] count);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
internal static extern int GdipGetPropertyItemSize(HandleRef image, int propid, out int size);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
internal static extern int GdipGetPropertyItem(HandleRef image, int propid, int size, IntPtr buffer);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.Machine)]
|
||||||
|
internal static extern int GdipCreateHBITMAPFromBitmap(HandleRef nativeBitmap, out IntPtr hbitmap, int argbBackground);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
internal static extern int GdipImageSelectActiveFrame(HandleRef image, ref Guid dimensionId, int frameIndex);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.Machine)]
|
||||||
|
internal static extern int GdipCreateBitmapFromFile(string filename, out IntPtr bitmap);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
internal static extern int GdipImageForceValidation(HandleRef image);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, EntryPoint = "GdipDisposeImage", CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
private static extern int IntGdipDisposeImage(HandleRef image);
|
||||||
|
|
||||||
|
internal static int GdipDisposeImage(HandleRef image)
|
||||||
|
{
|
||||||
|
if (!Initialized) return Ok;
|
||||||
|
var result = IntGdipDisposeImage(image);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.Process)]
|
||||||
|
private static extern int GdiplusStartup(out IntPtr token, ref StartupInput input, out StartupOutput output);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.None)]
|
||||||
|
internal static extern int GdipGetImageRawFormat(HandleRef image, ref Guid format);
|
||||||
|
|
||||||
|
[DllImport(Gdiplus, SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)]
|
||||||
|
[ResourceExposure(ResourceScope.Machine)]
|
||||||
|
internal static extern int GdipCreateBitmapFromStream(IStream stream, out IntPtr bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using HandyControl.Controls;
|
||||||
|
|
||||||
|
namespace HandyControl.Data
|
||||||
|
{
|
||||||
|
internal class ImageAnimator
|
||||||
|
{
|
||||||
|
private static List<GifImageInfo> ImageInfoList;
|
||||||
|
|
||||||
|
private static readonly ReaderWriterLock RwImgListLock = new ReaderWriterLock();
|
||||||
|
|
||||||
|
private static Thread AnimationThread;
|
||||||
|
|
||||||
|
private static bool AnyFrameDirty;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static int ThreadWriterLockWaitCount;
|
||||||
|
|
||||||
|
public static bool CanAnimate(GifImage image)
|
||||||
|
{
|
||||||
|
if (image == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (image)
|
||||||
|
{
|
||||||
|
var dimensions = image.FrameDimensionsList;
|
||||||
|
|
||||||
|
if (dimensions.Select(guid => new GifFrameDimension(guid)).Contains(GifFrameDimension.Time))
|
||||||
|
{
|
||||||
|
return image.GetFrameCount(GifFrameDimension.Time) > 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Animate(GifImage image, EventHandler onFrameChangedHandler)
|
||||||
|
{
|
||||||
|
if (image == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GifImageInfo imageInfo;
|
||||||
|
|
||||||
|
// See comment in the class header about locking the image ref.
|
||||||
|
lock (image)
|
||||||
|
{
|
||||||
|
// could we avoid creating an ImageInfo object if FrameCount == 1 ?
|
||||||
|
imageInfo = new GifImageInfo(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the image is already animating, stop animating it
|
||||||
|
StopAnimate(image, onFrameChangedHandler);
|
||||||
|
|
||||||
|
// Acquire a writer lock to modify the image info list. If the thread has a reader lock we need to upgrade
|
||||||
|
// it to a writer lock; acquiring a reader lock in this case would block the thread on itself.
|
||||||
|
// If the thread already has a writer lock its ref count will be incremented w/o placing the request in the
|
||||||
|
// writer queue. See ReaderWriterLock.AcquireWriterLock method in the MSDN.
|
||||||
|
|
||||||
|
var readerLockHeld = RwImgListLock.IsReaderLockHeld;
|
||||||
|
var lockDowngradeCookie = new LockCookie();
|
||||||
|
|
||||||
|
ThreadWriterLockWaitCount++;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (readerLockHeld)
|
||||||
|
{
|
||||||
|
lockDowngradeCookie = RwImgListLock.UpgradeToWriterLock(Timeout.Infinite);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RwImgListLock.AcquireWriterLock(Timeout.Infinite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ThreadWriterLockWaitCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (imageInfo.Animated)
|
||||||
|
{
|
||||||
|
// Construct the image array
|
||||||
|
//
|
||||||
|
if (ImageInfoList == null)
|
||||||
|
{
|
||||||
|
ImageInfoList = new List<GifImageInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new image
|
||||||
|
//
|
||||||
|
imageInfo.FrameChangedHandler = onFrameChangedHandler;
|
||||||
|
ImageInfoList.Add(imageInfo);
|
||||||
|
|
||||||
|
// Construct a new timer thread if we haven't already
|
||||||
|
//
|
||||||
|
if (AnimationThread == null)
|
||||||
|
{
|
||||||
|
AnimationThread = new Thread(AnimateImages50Ms)
|
||||||
|
{
|
||||||
|
Name = typeof(ImageAnimator).Name,
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
AnimationThread.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (readerLockHeld)
|
||||||
|
{
|
||||||
|
RwImgListLock.DowngradeFromWriterLock(ref lockDowngradeCookie);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RwImgListLock.ReleaseWriterLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StopAnimate(GifImage image, EventHandler onFrameChangedHandler)
|
||||||
|
{
|
||||||
|
// Make sure we have a list of images
|
||||||
|
if (image == null || ImageInfoList == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire a writer lock to modify the image info list - See comments on Animate() about this locking.
|
||||||
|
|
||||||
|
var readerLockHeld = RwImgListLock.IsReaderLockHeld;
|
||||||
|
var lockDowngradeCookie = new LockCookie();
|
||||||
|
|
||||||
|
ThreadWriterLockWaitCount++;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (readerLockHeld)
|
||||||
|
{
|
||||||
|
lockDowngradeCookie = RwImgListLock.UpgradeToWriterLock(Timeout.Infinite);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RwImgListLock.AcquireWriterLock(Timeout.Infinite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ThreadWriterLockWaitCount--;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Find the corresponding reference and remove it
|
||||||
|
for (int i = 0; i < ImageInfoList.Count; i++)
|
||||||
|
{
|
||||||
|
var imageInfo = ImageInfoList[i];
|
||||||
|
|
||||||
|
if (Equals(image, imageInfo.Image))
|
||||||
|
{
|
||||||
|
if ((onFrameChangedHandler == imageInfo.FrameChangedHandler) || (onFrameChangedHandler != null && onFrameChangedHandler.Equals(imageInfo.FrameChangedHandler)))
|
||||||
|
{
|
||||||
|
ImageInfoList.Remove(imageInfo);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (readerLockHeld)
|
||||||
|
{
|
||||||
|
RwImgListLock.DowngradeFromWriterLock(ref lockDowngradeCookie);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RwImgListLock.ReleaseWriterLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UpdateFrames()
|
||||||
|
{
|
||||||
|
if (!AnyFrameDirty || ImageInfoList == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ThreadWriterLockWaitCount > 0)
|
||||||
|
{
|
||||||
|
// Cannot acquire reader lock at this time, frames update will be missed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RwImgListLock.AcquireReaderLock(Timeout.Infinite);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var imageInfo in ImageInfoList)
|
||||||
|
{
|
||||||
|
// See comment in the class header about locking the image ref.
|
||||||
|
lock (imageInfo.Image)
|
||||||
|
{
|
||||||
|
imageInfo.UpdateFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnyFrameDirty = false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RwImgListLock.ReleaseReaderLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals")]
|
||||||
|
private static void AnimateImages50Ms()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// Acquire reader-lock to access imageInfoList, elemens in the list can be modified w/o needing a writer-lock.
|
||||||
|
// Observe that we don't need to check if the thread is waiting or a writer lock here since the thread this
|
||||||
|
// method runs in never acquires a writer lock.
|
||||||
|
RwImgListLock.AcquireReaderLock(Timeout.Infinite);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var imageInfo in ImageInfoList)
|
||||||
|
{
|
||||||
|
// Frame delay is measured in 1/100ths of a second. This thread
|
||||||
|
// sleeps for 50 ms = 5/100ths of a second between frame updates,
|
||||||
|
// so we increase the frame delay count 5/100ths of a second
|
||||||
|
// at a time.
|
||||||
|
//
|
||||||
|
imageInfo.FrameTimer += 5;
|
||||||
|
if (imageInfo.FrameTimer >= imageInfo.FrameDelay(imageInfo.Frame))
|
||||||
|
{
|
||||||
|
imageInfo.FrameTimer = 0;
|
||||||
|
|
||||||
|
if (imageInfo.Frame + 1 < imageInfo.FrameCount)
|
||||||
|
{
|
||||||
|
imageInfo.Frame++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
imageInfo.Frame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageInfo.FrameDirty)
|
||||||
|
{
|
||||||
|
AnyFrameDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RwImgListLock.ReleaseReaderLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,6 +54,8 @@
|
|||||||
|
|
||||||
public static readonly string TagDemoCtl = nameof(TagDemoCtl);
|
public static readonly string TagDemoCtl = nameof(TagDemoCtl);
|
||||||
|
|
||||||
|
public static readonly string GifImageDemoCtl = nameof(GifImageDemoCtl);
|
||||||
|
|
||||||
public static readonly string ButtonDemoCtl = nameof(ButtonDemoCtl);
|
public static readonly string ButtonDemoCtl = nameof(ButtonDemoCtl);
|
||||||
|
|
||||||
public static readonly string ToggleButtonDemoCtl = nameof(ToggleButtonDemoCtl);
|
public static readonly string ToggleButtonDemoCtl = nameof(ToggleButtonDemoCtl);
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ var controlList = new List<string>
|
|||||||
"DateTimePickerDemoCtl",
|
"DateTimePickerDemoCtl",
|
||||||
"CalendarWithClockDemoCtl",
|
"CalendarWithClockDemoCtl",
|
||||||
"SearchBarDemoCtl",
|
"SearchBarDemoCtl",
|
||||||
"TagDemoCtl"
|
"TagDemoCtl",
|
||||||
|
"GifImageDemoCtl"
|
||||||
};
|
};
|
||||||
var styleList = new List<string>
|
var styleList = new List<string>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -129,6 +129,9 @@
|
|||||||
<Compile Include="UserControl\Controls\DateTimePickerDemoCtl.xaml.cs">
|
<Compile Include="UserControl\Controls\DateTimePickerDemoCtl.xaml.cs">
|
||||||
<DependentUpon>DateTimePickerDemoCtl.xaml</DependentUpon>
|
<DependentUpon>DateTimePickerDemoCtl.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="UserControl\Controls\GifImageDemoCtl.xaml.cs">
|
||||||
|
<DependentUpon>GifImageDemoCtl.xaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="UserControl\Controls\GrowlDemoCtl.xaml.cs">
|
<Compile Include="UserControl\Controls\GrowlDemoCtl.xaml.cs">
|
||||||
<DependentUpon>GrowlDemoCtl.xaml</DependentUpon>
|
<DependentUpon>GrowlDemoCtl.xaml</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -383,6 +386,10 @@
|
|||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="UserControl\Controls\GifImageDemoCtl.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
<Page Include="UserControl\Controls\GrowlDemoCtl.xaml">
|
<Page Include="UserControl\Controls\GrowlDemoCtl.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@@ -599,6 +606,8 @@
|
|||||||
<Generator>TextTemplatingFileGenerator</Generator>
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
<LastGenOutput>MessageToken.cs</LastGenOutput>
|
<LastGenOutput>MessageToken.cs</LastGenOutput>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Resource Include="Resources\Img\car_chase.gif" />
|
||||||
|
<Resource Include="Resources\Img\LeftMainContent\Animation_16x.png" />
|
||||||
<Resource Include="Resources\Img\LeftMainContent\Tag_16x.png" />
|
<Resource Include="Resources\Img\LeftMainContent\Tag_16x.png" />
|
||||||
<Resource Include="Resources\Img\LeftMainContent\Search_16x.png" />
|
<Resource Include="Resources\Img\LeftMainContent\Search_16x.png" />
|
||||||
<Resource Include="Resources\Img\LeftMainContent\ToolBar_16x.png" />
|
<Resource Include="Resources\Img\LeftMainContent\ToolBar_16x.png" />
|
||||||
|
|||||||
+10
-1
@@ -47,7 +47,7 @@ namespace HandyControlDemo.Properties.Langs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 重写当前线程的 CurrentUICulture 属性
|
/// 使用此强类型资源类,为所有资源查找
|
||||||
/// 重写当前线程的 CurrentUICulture 属性。
|
/// 重写当前线程的 CurrentUICulture 属性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
@@ -384,6 +384,15 @@ namespace HandyControlDemo.Properties.Langs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查找类似 Gif图片 的本地化字符串。
|
||||||
|
/// </summary>
|
||||||
|
public static string GifImage {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("GifImage", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查找类似 分组框 的本地化字符串。
|
/// 查找类似 分组框 的本地化字符串。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -429,4 +429,7 @@
|
|||||||
<data name="Tag" xml:space="preserve">
|
<data name="Tag" xml:space="preserve">
|
||||||
<value>Tag</value>
|
<value>Tag</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="GifImage" xml:space="preserve">
|
||||||
|
<value>GifImage</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -409,4 +409,7 @@
|
|||||||
<data name="Tag" xml:space="preserve">
|
<data name="Tag" xml:space="preserve">
|
||||||
<value>标签</value>
|
<value>标签</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="GifImage" xml:space="preserve">
|
||||||
|
<value>Gif图片</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 494 B |
Binary file not shown.
|
After Width: | Height: | Size: 4.5 MiB |
@@ -0,0 +1,13 @@
|
|||||||
|
<UserControl x:Class="HandyControlDemo.UserControl.GifImageDemoCtl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="clr-namespace:HandyControl.Controls;assembly=HandyControl"
|
||||||
|
Background="{DynamicResource RegionBrush}">
|
||||||
|
<controls:TransitioningContentControl>
|
||||||
|
<controls:GifImage x:Name="GifImageMain" Margin="32" Width="400" Height="300">
|
||||||
|
<controls:GifImage.Source>
|
||||||
|
<BitmapImage UriSource="/HandyControlDemo;component/Resources/Img/car_chase.gif"/>
|
||||||
|
</controls:GifImage.Source>
|
||||||
|
</controls:GifImage>
|
||||||
|
</controls:TransitioningContentControl>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HandyControlDemo.UserControl
|
||||||
|
{
|
||||||
|
public partial class GifImageDemoCtl : IDisposable
|
||||||
|
{
|
||||||
|
public GifImageDemoCtl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
GifImageMain.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -199,6 +199,11 @@
|
|||||||
<Image Source="../../Resources/Img/LeftMainContent/Tag_16x.png"/>
|
<Image Source="../../Resources/Img/LeftMainContent/Tag_16x.png"/>
|
||||||
</controls:EdgeElement.LeftContent>
|
</controls:EdgeElement.LeftContent>
|
||||||
</ListBoxItem>
|
</ListBoxItem>
|
||||||
|
<ListBoxItem Style="{StaticResource ListBoxItemNew}" Tag="{x:Static data:MessageToken.GifImageDemoCtl}" Content="{x:Static langs:Lang.GifImage}">
|
||||||
|
<controls:EdgeElement.LeftContent>
|
||||||
|
<Image Source="../../Resources/Img/LeftMainContent/Animation_16x.png"/>
|
||||||
|
</controls:EdgeElement.LeftContent>
|
||||||
|
</ListBoxItem>
|
||||||
<ListBoxItem Tag="{x:Static data:MessageToken.CirclePanelDemoCtl}" Content="{x:Static langs:Lang.CirclePanel}">
|
<ListBoxItem Tag="{x:Static data:MessageToken.CirclePanelDemoCtl}" Content="{x:Static langs:Lang.CirclePanel}">
|
||||||
<controls:EdgeElement.LeftContent>
|
<controls:EdgeElement.LeftContent>
|
||||||
<Image Source="../../Resources/Img/LeftMainContent/ColorWheel_16x.png"/>
|
<Image Source="../../Resources/Img/LeftMainContent/ColorWheel_16x.png"/>
|
||||||
|
|||||||
@@ -38,7 +38,14 @@ namespace HandyControlDemo.ViewModel
|
|||||||
|
|
||||||
public MainViewModel(DataService dataService)
|
public MainViewModel(DataService dataService)
|
||||||
{
|
{
|
||||||
Messenger.Default.Register<object>(this, MessageToken.LoadShowContent, obj => SubContent = obj);
|
Messenger.Default.Register<object>(this, MessageToken.LoadShowContent, obj =>
|
||||||
|
{
|
||||||
|
if (SubContent is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
SubContent = obj;
|
||||||
|
});
|
||||||
Messenger.Default.Register<object>(this, MessageToken.ClearLeftSelected, obj =>
|
Messenger.Default.Register<object>(this, MessageToken.ClearLeftSelected, obj =>
|
||||||
{
|
{
|
||||||
if (_listBoxItemCurrent == null) return;
|
if (_listBoxItemCurrent == null) return;
|
||||||
|
|||||||
Reference in new issue
Block a user