<PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.1.25080.3" />

ImageAnimator

public sealed class 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(); } } } } }