首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# WinForms GIF 動畫繪製完整實戰(二):AnimateImage 元件整合與雙緩衝優化

     編輯:關於C語言

AnimateImage 使用範例


using System;
using System.Drawing;
using System.Windows.Forms;

namespace GifTest {
    public partial class Form1 : Form {
        private AnimateImage _image;

        public Form1() {
            InitializeComponent();
            _image = new AnimateImage(Image.FromFile(@"C:\Pictures\animation.gif"));
            _image.OnFrameChanged += Image_OnFrameChanged;
            // 雙緩衝三件套:消除閃爍
            SetStyle(ControlStyles.OptimizedDoubleBuffer |
                     ControlStyles.AllPaintingInWmPaint |
                     ControlStyles.UserPaint, true);
        }

        private void Image_OnFrameChanged(object sender, EventArgs e) {
            Invalidate(); // 觸發重繪
        }

        private void Form1_Load(object sender, EventArgs e) {
            _image.Play();
        }

        protected override void OnPaint(PaintEventArgs e) {
            base.OnPaint(e);
            lock (_image.Image) { // 必須鎖定,避免跨執行緒存取衝突
                e.Graphics.DrawImage(_image.Image, Point.Empty);
            }
        }

        private void btnToggle_Click(object sender, EventArgs e) {
            if (_image.IsPlaying) {
                _image.Stop();
                btnToggle.Text = "Play";
            } else {
                _image.Play();
                btnToggle.Text = "Stop";
            }
            Invalidate();
        }

        private void btnReset_Click(object sender, EventArgs e) {
            _image.Reset();
            btnToggle.Text = "Play";
            Invalidate();
        }
    }
}

關鍵技術點解析

1. 雙緩衝三件套


SetStyle(ControlStyles.OptimizedDoubleBuffer |  // 雙緩衝
         ControlStyles.AllPaintingInWmPaint |   // 禁用 WM_ERASEBKGND
         ControlStyles.UserPaint, true);        // 自繪模式
  • OptimizedDoubleBuffer:在記憶體位圖繪製完成後一次性複製到螢幕
  • AllPaintingInWmPaint:跳過背景擦除,減少閃爍
  • UserPaint:完全接管繪製,不呼叫預設 OnPaintBackground

2. 執行緒安全:lock(_image.Image)

ImageAnimator 內部執行緒會周期性呼叫 SelectActiveFrame 修改 Image 物件,而 UI 執行緒在 OnPaint 同時讀取。未加鎖會拋出 InvalidOperationException: Object currently in use elsewhere。

3. 替代方案:複製幀避免鎖


protected override void OnPaint(PaintEventArgs e) {
    base.OnPaint(e);
    Image frame;
    lock (_image.Image) {
        // 複製當前幀,縮短鎖持有時間
        frame = (Image)_image.Image.Clone();
    }
    e.Graphics.DrawImage(frame, Point.Empty);
    frame.Dispose();
}

適合高幀率/大尺寸圖片,權衡記憶體配置開銷。

AnimateImage 核心實作回顧(第一部分)


public class AnimateImage {
    private readonly Image _image;
    private readonly FrameDimension _dimension;
    private readonly int _frameCount;
    private int _currentFrame;
    private bool _playing;

    public event EventHandler OnFrameChanged;
    public Image Image => _image;
    public bool CanAnimate { get; }
    public int FrameCount => _frameCount;
    public int CurrentFrame => _currentFrame;
    public bool IsPlaying => _playing;

    public AnimateImage(Image img) {
        _image = img;
        lock (_image) {
            CanAnimate = ImageAnimator.CanAnimate(_image);
            if (CanAnimate) {
                var guid = _image.FrameDimensionsList[0];
                _dimension = new FrameDimension(guid);
                _frameCount = _image.GetFrameCount(_dimension);
            }
        }
    }

    public void Play() {
        if (!CanAnimate || _playing) return;
        lock (_image) {
            ImageAnimator.Animate(_image, OnFrameChangedHandler);
            _playing = true;
        }
    }

    public void Stop() {
        if (!CanAnimate || !_playing) return;
        lock (_image) {
            ImageAnimator.StopAnimate(_image, OnFrameChangedHandler);
            _playing = false;
        }
    }

    public void Reset() {
        Stop();
        lock (_image) {
            _image.SelectActiveFrame(_dimension, 0);
            _currentFrame = 0;
        }
    }

    private void OnFrameChangedHandler(object sender, EventArgs e) {
        lock (_image) {
            _currentFrame = (_currentFrame + 1) % _frameCount;
            _image.SelectActiveFrame(_dimension, _currentFrame);
        }
        OnFrameChanged?.Invoke(this, e);
    }
}

效能優化建議

  1. 大圖片預先縮放至顯示尺寸,避免 DrawImage 即時縮放
  2. 使用 Graphics.InterpolationMode = HighQualityBicubic 平衡品質/速度
  3. 考慮 BufferedGraphics 手動管理緩衝區
  4. 高幀率(>30fps)建議用 WPF/Direct2D 替代 GDI+
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved