ImageAnimator
Animates an image that has time-based frames.
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing.Imaging;
using System.Runtime.CompilerServices;
using System.Threading;
namespace System.Drawing
{
[NullableContext(1)]
[Nullable(0)]
public sealed class ImageAnimator
{
[Nullable(0)]
private sealed class ImageInfo
{
private const int PropertyTagFrameDelay = 20736;
private const int PropertyTagLoopCount = 20737;
private int _frame;
private short _loop;
private readonly int _frameCount;
private readonly short _loopCount;
[Nullable(2)]
private readonly long[] _frameEndTimes;
private readonly long _totalAnimationTime;
private long _frameTimer;
public bool Animated { get; }
public bool FrameDirty { get; set; }
[Nullable(2)]
[field: Nullable(2)]
public EventHandler FrameChangedHandler {
[NullableContext(2)]
get;
[NullableContext(2)]
set;
}
private long TotalAnimationTime {
get {
if (!Animated)
return 0;
return _totalAnimationTime;
}
}
private bool ShouldAnimate {
get {
if (TotalAnimationTime > 0) {
if (_loopCount != 0)
return _loop <= _loopCount;
return true;
}
return false;
}
}
internal Image Image { get; }
public ImageInfo(Image image)
{
Image = image;
Animated = CanAnimate(image);
_frameEndTimes = null;
if (!Animated)
_frameCount = 1;
else {
_frameCount = image.GetFrameCount(FrameDimension.Time);
PropertyItem propertyItem = image.GetPropertyItem(20736);
if (propertyItem != null) {
byte[] value = propertyItem.Value;
_frameEndTimes = new long[_frameCount];
long num = 0;
int num2 = 0;
int num3 = 0;
while (num2 < _frameCount) {
if (num3 >= value.Length)
num3 = 0;
int num4 = BitConverter.ToInt32(value, num3) * 10;
num += ((num4 > 0) ? num4 : 40);
if (num < _totalAnimationTime)
num = _totalAnimationTime;
else
_totalAnimationTime = num;
_frameEndTimes[num2] = num;
num2++;
num3 += 4;
}
}
PropertyItem propertyItem2 = image.GetPropertyItem(20737);
if (propertyItem2 != null) {
byte[] value2 = propertyItem2.Value;
_loopCount = BitConverter.ToInt16(value2);
} else
_loopCount = 0;
}
}
public void AdvanceAnimationBy(long milliseconds)
{
if (ShouldAnimate) {
int frame = _frame;
_frameTimer += milliseconds;
if (_frameTimer > TotalAnimationTime) {
_loop += (short)Math.DivRem(_frameTimer, TotalAnimationTime, out long result);
_frameTimer = result;
if (!ShouldAnimate) {
_frame = _frameCount - 1;
_frameTimer = TotalAnimationTime;
} else if (_frame > 0 && _frameTimer < _frameEndTimes[_frame - 1]) {
_frame = 0;
}
}
while (_frameTimer > _frameEndTimes[_frame]) {
_frame++;
}
if (_frame != frame) {
FrameDirty = true;
OnFrameChanged(EventArgs.Empty);
}
}
}
internal void UpdateFrame()
{
if (FrameDirty) {
Image.SelectActiveFrame(FrameDimension.Time, _frame);
FrameDirty = false;
}
}
private void OnFrameChanged(EventArgs e)
{
FrameChangedHandler?.Invoke(Image, e);
}
}
internal const int AnimationDelayMS = 40;
[Nullable(new byte[] {
2,
1
})]
private static List<ImageInfo> s_imageInfoList;
private static bool s_anyFrameDirty;
[Nullable(2)]
private static Thread s_animationThread;
private static readonly ReaderWriterLock s_rwImgListLock = new ReaderWriterLock();
[ThreadStatic]
private static int t_threadWriterLockWaitCount;
private ImageAnimator()
{
}
[NullableContext(2)]
public static void UpdateFrames(Image image)
{
if (image != null && s_imageInfoList != null && t_threadWriterLockWaitCount <= 0) {
s_rwImgListLock.AcquireReaderLock(-1);
try {
bool flag = false;
bool flag2 = false;
foreach (ImageInfo s_imageInfo in s_imageInfoList) {
if (s_imageInfo.Image == image) {
if (s_imageInfo.FrameDirty) {
lock (s_imageInfo.Image) {
s_imageInfo.UpdateFrame();
}
}
flag2 = true;
} else if (s_imageInfo.FrameDirty) {
flag = true;
}
if (flag & flag2)
break;
}
s_anyFrameDirty = flag;
} finally {
s_rwImgListLock.ReleaseReaderLock();
}
}
}
public static void UpdateFrames()
{
if (s_anyFrameDirty && s_imageInfoList != null && t_threadWriterLockWaitCount <= 0) {
s_rwImgListLock.AcquireReaderLock(-1);
try {
foreach (ImageInfo s_imageInfo in s_imageInfoList) {
lock (s_imageInfo.Image) {
s_imageInfo.UpdateFrame();
}
}
s_anyFrameDirty = false;
} finally {
s_rwImgListLock.ReleaseReaderLock();
}
}
}
public static void Animate(Image image, EventHandler onFrameChangedHandler)
{
if (image != null) {
ImageInfo imageInfo = null;
lock (image) {
imageInfo = new ImageInfo(image);
}
StopAnimate(image, onFrameChangedHandler);
bool isReaderLockHeld = s_rwImgListLock.IsReaderLockHeld;
LockCookie lockCookie = default(LockCookie);
t_threadWriterLockWaitCount++;
try {
if (isReaderLockHeld)
lockCookie = s_rwImgListLock.UpgradeToWriterLock(-1);
else
s_rwImgListLock.AcquireWriterLock(-1);
} finally {
t_threadWriterLockWaitCount--;
}
try {
if (imageInfo.Animated) {
if (s_imageInfoList == null)
s_imageInfoList = new List<ImageInfo>();
imageInfo.FrameChangedHandler = onFrameChangedHandler;
s_imageInfoList.Add(imageInfo);
if (s_animationThread == null) {
s_animationThread = new Thread(AnimateImages) {
Name = "ImageAnimator",
IsBackground = true
};
s_animationThread.Start();
}
}
} finally {
if (isReaderLockHeld)
s_rwImgListLock.DowngradeFromWriterLock(ref lockCookie);
else
s_rwImgListLock.ReleaseWriterLock();
}
}
}
[NullableContext(2)]
public static bool CanAnimate([NotNullWhen(true)] Image image)
{
if (image == null)
return false;
lock (image) {
Guid[] frameDimensionsList = image.FrameDimensionsList;
for (int i = 0; i < frameDimensionsList.Length; i++) {
if (new FrameDimension(frameDimensionsList[i]).Equals(FrameDimension.Time))
return image.GetFrameCount(FrameDimension.Time) > 1;
}
}
return false;
}
public static void StopAnimate(Image image, EventHandler onFrameChangedHandler)
{
if (image != null && s_imageInfoList != null) {
bool isReaderLockHeld = s_rwImgListLock.IsReaderLockHeld;
LockCookie lockCookie = default(LockCookie);
t_threadWriterLockWaitCount++;
try {
if (isReaderLockHeld)
lockCookie = s_rwImgListLock.UpgradeToWriterLock(-1);
else
s_rwImgListLock.AcquireWriterLock(-1);
} finally {
t_threadWriterLockWaitCount--;
}
try {
int num = 0;
ImageInfo imageInfo;
while (true) {
if (num >= s_imageInfoList.Count)
return;
imageInfo = s_imageInfoList[num];
if (image == imageInfo.Image)
break;
num++;
}
if (onFrameChangedHandler == imageInfo.FrameChangedHandler || (onFrameChangedHandler != null && onFrameChangedHandler.Equals(imageInfo.FrameChangedHandler)))
s_imageInfoList.Remove(imageInfo);
} finally {
if (isReaderLockHeld)
s_rwImgListLock.DowngradeFromWriterLock(ref lockCookie);
else
s_rwImgListLock.ReleaseWriterLock();
}
}
}
private static void AnimateImages()
{
Stopwatch stopwatch = Stopwatch.StartNew();
while (true) {
Thread.Sleep(40);
long elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
stopwatch.Restart();
s_rwImgListLock.AcquireReaderLock(-1);
try {
for (int i = 0; i < s_imageInfoList.Count; i++) {
ImageInfo imageInfo = s_imageInfoList[i];
if (imageInfo.Animated) {
imageInfo.AdvanceAnimationBy(elapsedMilliseconds);
if (imageInfo.FrameDirty)
s_anyFrameDirty = true;
}
}
} finally {
s_rwImgListLock.ReleaseReaderLock();
}
}
}
}
}