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

GDI+ 顯示動態 GIF 圖片:幀解析、延遲讀取與多執行緒播放

     編輯:關於VC++

GDI+ 原生支援靜態 GIF 直接繪製,但動態 GIF 需自行解析幀序列、延遲時間並實作播放迴圈。本文封裝 ImageEx : public Image 類別,自動偵測動畫屬性、啟動背景執行緒逐幀繪製,提供 Play()/Pause()/Stop() 控制介面。

GIF 格式關鍵結構 (節錄)

  • Header:Signature "GIF" + Version "87a"/"89a"
  • Logical Screen Descriptor:畫布尺寸、全域調色盤旗標、背景色索引
  • Global Color Table (可選):RGB 三元組陣列
  • Image Descriptor (每幀):座標、尺寸、區域調色盤旗標、交織旗標
  • Graphic Control Extension (每幀):**延遲時間 (1/100 秒)、透明色索引、處置方式**
  • Image Data:LZW 壓縼索引資料
  • Trailer:0x3B 結束標記

GDI+ 內部已解析上述結構,透過 Image 類別方法暴露動畫屬性。

ImageEx 核心實作

1. 偵測動畫幀數與延遲


bool ImageEx::TestForAnimatedGIF()
{
    UINT count = GetFrameDimensionsCount();
    GUID* pDimIDs = new GUID[count];
    GetFrameDimensionsList(pDimIDs, count);
    
    m_nFrameCount = GetFrameCount(&pDimIDs[0]); // 總幀數
    
    // 取得 PropertyTagFrameDelay (0x5100) 屬性項
    int nSize = GetPropertyItemSize(PropertyTagFrameDelay);
    m_pPropertyItem = (PropertyItem*)malloc(nSize);
    GetPropertyItem(PropertyTagFrameDelay, nSize, m_pPropertyItem);
    // m_pPropertyItem->value 為 long 陣列,每項 = 該幀延遲 (單位 1/100 秒)
    
    delete[] pDimIDs;
    return m_nFrameCount > 1;
}

2. 逐幀繪製執行緒


bool ImageEx::DrawFrameGIF()
{
    WaitForSingleObject(m_hPause, INFINITE); // 支援暫停/繼續
    
    GUID pageGuid = FrameDimensionTime;
    HDC hDC = GetDC(m_hWnd);
    if (hDC) {
        Graphics g(hDC);
        g.DrawImage(this, m_rc.left, m_rc.top, GetWidth(), GetHeight());
        ReleaseDC(m_hWnd, hDC);
    }
    
    // 切換至下一幀
    SelectActiveFrame(&pageGuid, m_nFramePosition++);
    if (m_nFramePosition == m_nFrameCount) m_nFramePosition = 0; // 循環
    
    // 取得當前幀延遲 (毫秒)
    long lPause = ((long*)m_pPropertyItem->value)[m_nFramePosition] * 10;
    
    // 等待延遲或退出事件
    DWORD dwErr = WaitForSingleObject(m_hExitEvent, lPause);
    return dwErr == WAIT_OBJECT_0; // true = 請求退出
}

3. 控制介面


void Play()  { ResetEvent(m_hPause);  CreateThread(..., ThreadProc, this); }
void Pause() { SetEvent(m_hPause); }
void Stop()  { SetEvent(m_hExitEvent); SetEvent(m_hPause); }

使用範例


// 視窗 OnPaint
void CMyWnd::OnPaint() {
    CPaintDC dc(this);
    Graphics g(dc);
    if (m_img.TestForAnimatedGIF()) {
        m_img.Play(); // 自動在背景執行緒播放
    } else {
        g.DrawImage(&m_img, 0, 0, m_img.GetWidth(), m_img.GetHeight());
    }
}

// 視窗銷毀
void CMyWnd::OnDestroy() {
    m_img.Stop();
    CWnd::OnDestroy();
}

效果展示

GDI+ 動態 GIF 播放效果

注意事項與限制

  • ⚠️ SelectActiveFrame 會修改 Image 內部狀態,**非執行緒安全**。若多處同時繪製同一 ImageEx 實例會導致幀序混亂。解決:每執行緒/視窗持有獨立實例,或加入關鍵區同步。
  • ⚠️ GDI+ 不支援 處置方式 還原上一幀 (Disposal Method 2/3),複雜動畫 (局部更新、疊加) 可能顯示異常。完整相容需自行解析 GCE 並合成幀緩衝。
  • ⚠️ PropertyTagFrameDelay 單位為 1/100 秒,乘 10 轉毫秒。部分 GIF 延遲為 0,建議下限 50ms 避免 CPU 佔用過高。
  • ✅ 適用於簡易 UI 動畫 (Loading spinner、表情符號、狀態指示)。

替代方案比較

方案優點缺點
GDI+ ImageEx (本文)程式碼少、無外部相依、整合 GDI+ 繪圖管線處置方式支援不全、執行緒不安全
Windows Imaging Component (WIC)系統級、完整 GIF 規範支援、多執行緒安全API 複雜、需 Vista+、程式碼量大
第三方庫功能完整 (APNG、WebP)、高效能增加相依、授權考量

總結

利用 GDI+ 內建 FrameDimensionTime、PropertyTagFrameDelay、SelectActiveFrame,配合背景執行緒即可實作動態 GIF 播放器。ImageEx 封裝降低使用門檻,適合快速整合至 MFC/WinForms/WPF (透過 Interop) 專案。完整原始碼含 ImageEx.h/cpp、測試對話框專案,請下載附件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved