在開發圖片檢視器時發現,Graphics.DrawImage 僅能繪製 GIF 的第一幀。PictureBox 雖支援動畫,但在效能與縮放控制上受限。解決方案是使用 .NET 內建的 System.Drawing.ImageAnimator 類別,配合自訂繪製實現流暢的 GIF 播放。
ImageAnimator.CanAnimate(image):判斷圖片是否為動畫。ImageAnimator.Animate(image, onFrameChanged):啟動動畫,每幀觸發回呼。image.SelectActiveFrame(dimension, frameIndex) 切換幀,並 Invalidate() 觸發重繪。lock(image) 避免跨執行緒存取衝突。using System;
using System.Drawing;
using System.Drawing.Imaging;
public class AnimateImage
{
private readonly Image _image;
private readonly FrameDimension _frameDimension;
private readonly bool _canAnimate;
private readonly int _frameCount;
private int _currentFrame;
public event EventHandler<EventArgs> OnFrameChanged;
public Image Image => _image;
public bool CanAnimate => _canAnimate;
public int FrameCount => _frameCount;
public int CurrentFrame => _currentFrame;
public AnimateImage(Image img)
{
_image = img;
lock (_image)
{
_canAnimate = ImageAnimator.CanAnimate(_image);
if (_canAnimate)
{
var guids = _image.FrameDimensionsList;
_frameDimension = new FrameDimension(guids[0]);
_frameCount = _image.GetFrameCount(_frameDimension);
}
}
}
public void Play()
{
if (!_canAnimate) return;
lock (_image)
ImageAnimator.Animate(_image, FrameChanged);
}
public void Stop()
{
if (!_canAnimate) return;
lock (_image)
ImageAnimator.StopAnimate(_image, FrameChanged);
}
public void Reset()
{
if (!_canAnimate) return;
lock (_image)
{
ImageAnimator.StopAnimate(_image, FrameChanged);
_image.SelectActiveFrame(_frameDimension, 0);
_currentFrame = 0;
}
}
private void FrameChanged(object sender, EventArgs e)
{
_currentFrame = (_currentFrame + 1) % _frameCount;
lock (_image)
_image.SelectActiveFrame(_frameDimension, _currentFrame);
OnFrameChanged?.Invoke(_image, e);
}
}
public partial class Form1 : Form
{
private readonly AnimateImage _animImage;
public Form1()
{
InitializeComponent();
_animImage = new AnimateImage(Image.FromFile(@"test.gif"));
_animImage.OnFrameChanged += (s, e) => Invalidate(); // 觸發重繪
// 雙緩衝減少閃爍
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_animImage.Play();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
lock (_animImage.Image)
e.Graphics.DrawImage(_animImage.Image, Point.Empty);
}
private void btnPlayStop_Click(object sender, EventArgs e)
{
if (_animImage.CanAnimate)
{
if (btnPlayStop.Text == "Stop") { _animImage.Stop(); btnPlayStop.Text = "Play"; }
else { _animImage.Play(); btnPlayStop.Text = "Stop"; }
}
Invalidate();
}
private void btnReset_Click(object sender, EventArgs e)
{
_animImage.Reset();
btnPlayStop.Text = "Play";
Invalidate();
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
_animImage.Stop(); // 釋放動畫資源
_animImage.Image?.Dispose();
base.OnFormClosing(e);
}
}
| 問題 | 解決方案 |
|---|---|
| 跨執行緒存取 Image 導致「物件正在別處使用」 | 所有存取 Image 的地方 (SelectActiveFrame, DrawImage) 必須 lock(image) |
| 動畫不停止導致記憶體洩漏 | Form 關閉時呼叫 Stop() 並 Dispose() Image |
| 高 CPU 佔用 | GIF 本身幀率決定,不可控;複雜場景考慮 WPF/MediaElement |
| 縮放畫質 | 使用 e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic |
| 透明背景異常 | 確保 e.Graphics.Clear(Color.Transparent) 或父容器背景色一致 |
ImageAnimator 或使用 Timer 手動控制幀切換間隔。AnimationManager 統一管理多個 AnimateImage,批次 Play/Stop。MediaElement、BitmapImage + ImageBehavior (WpfAnimatedGif) 效能更佳。ImageAnimator 是 WinForms 實現 GIF 動畫的關鍵。封裝 AnimateImage 類別後,即可像操作靜態圖片一樣控制動畫播放、暫停、重置,並透過雙緩衝繪製獲得流暢體驗。注意執行緒安全與資源釋放,即可在專案中穩定使用。