圖像漸顯/漸隱廣泛用於多媒體、簡報、遊戲轉場。傳統逐像素修改速度太慢,Windows 調色板動畫 透過直接修改邏輯調色板表項顏色,配合 多媒體計時器 實現高效、流暢的顏色漸變。本文完整解析原理、關鍵 API、計時迴圈架構,並提供可直接整合的 FadeIn/FadeOut 函式碼。
CPalette::CreatePalette — 建立邏輯調色板,peFlags = PC_RESERVED 防止系統色彩映射干擾CDC::SelectPalette / RealizePalette — 選入並實現調色板CPalette::AnimatePalette — 核心,快速批量更新調色板表項顏色,無需逐像素重繪CPalette::GetPaletteEntries / SetPaletteEntries — 讀取/設定表項 RGB 值typedef struct tagPALETTEENTRY {
BYTE peRed, peGreen, peBlue, peFlags; // peFlags 必為 PC_RESERVED
} PALETTEENTRY;
為避免阻塞訊息泵,採用訊息截獲迴圈:
pWnd->SetTimer(0x100, uTimeOut, NULL); // 設定計時器
pWnd->SetCapture(); // 捕獲滑鼠,防止外部干擾
BOOL bDone = FALSE;
MSG msg;
while (!bDone)
{
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_TIMER && msg.wParam == 0x100)
{
// ★ 執行漸變步驟:修改調色板、呼叫 AnimatePalette
// 判斷是否完成 → bDone = TRUE
}
::TranslateMessage(&msg);
::DispatchMessage(&msg); // 分發非計時器訊息,保持 UI 回應
}
}
::ReleaseCapture();
pWnd->KillTimer(0x100);
// 還原原始調色板
void FadeIn(CWnd* pWnd, CPalette* pPal, int nDelta, UINT uTimeOut)
{
int nTotal = pPal->GetEntryCount();
PALETTEENTRY palOrig[256], palCurrent[256];
pPal->GetPaletteEntries(0, nTotal, palOrig); // 備份原始色彩
// 1. 將調色板設為全黑
for (int i=0; iSetPaletteEntries(0, nTotal, palCurrent);
pPal->AnimatePalette(0, nTotal, palCurrent);
// 2. 計時漸變迴圈
pWnd->SetTimer(0x100, uTimeOut, NULL);
pWnd->SetCapture();
BOOL bDone = FALSE;
MSG msg;
while (!bDone)
{
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_TIMER && msg.wParam == 0x100)
{
CClientDC dc(pWnd);
CPalette* pOld = dc.SelectPalette(pPal, FALSE);
dc.RealizePalette();
pPal->GetPaletteEntries(0, nTotal, palCurrent);
BOOL rZero=TRUE, gZero=TRUE, bZero=TRUE;
for (int i=0; iAnimatePalette(0, nTotal, palCurrent);
}
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
::ReleaseCapture();
pWnd->KillTimer(0x100);
// 還原精確原始調色板
pPal->SetPaletteEntries(0, nTotal, palOrig);
pPal->AnimatePalette(0, nTotal, palOrig);
}
void FadeOut(CWnd* pWnd, CPalette* pPal, int nDelta, UINT uTimeOut)
{
int nTotal = pPal->GetEntryCount();
PALETTEENTRY palOrig[256], palCurrent[256];
pPal->GetPaletteEntries(0, nTotal, palOrig);
pWnd->SetTimer(0x100, uTimeOut, NULL);
pWnd->SetCapture();
BOOL bDone = FALSE;
MSG msg;
while (!bDone)
{
if (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_TIMER && msg.wParam == 0x100)
{
CClientDC dc(pWnd);
CPalette* pOld = dc.SelectPalette(pPal, FALSE);
dc.RealizePalette();
pPal->GetPaletteEntries(0, nTotal, palCurrent);
BOOL rZero=TRUE, gZero=TRUE, bZero=TRUE;
for (int i=0; i nDelta) { palCurrent[i].peRed -= nDelta; rZero=FALSE; }
else if (palCurrent[i].peRed > 1) { palCurrent[i].peRed--; rZero=FALSE; }
if (palCurrent[i].peGreen > nDelta) { palCurrent[i].peGreen -= nDelta; gZero=FALSE; }
else if (palCurrent[i].peGreen > 1) { palCurrent[i].peGreen--; gZero=FALSE; }
if (palCurrent[i].peBlue > nDelta) { palCurrent[i].peBlue -= nDelta; bZero=FALSE; }
else if (palCurrent[i].peBlue > 1) { palCurrent[i].peBlue--; bZero=FALSE; }
}
bDone = rZero && gZero && bZero;
pPal->AnimatePalette(0, nTotal, palCurrent);
}
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
::ReleaseCapture();
pWnd->KillTimer(0x100);
pPal->SetPaletteEntries(0, nTotal, palOrig);
pPal->AnimatePalette(0, nTotal, palOrig);
}
// 載入 256 色位圖並取得調色板
CBitmap bmp; bmp.LoadBitmap(IDB_MYIMAGE);
CPalette pal;
{
CDC dc; dc.CreateCompatibleDC(NULL);
CBitmap* pOld = dc.SelectObject(&bmp);
// 從位圖建立調色板(略,需 GetDIBColorTable 或手動建構)
// 此處假設 pal 已正確建立並 SelectObject 到顯示 DC
}
FadeIn(this, &pal, 10, 50); // 每步增 10,間隔 50ms
// ... 顯示一段時間 ...
FadeOut(this, &pal, 10, 50);
| 優點 | 限制 |
|---|---|
| 極高效能:無像素級操作,適合全螢幕轉場 | 僅適用 256 色 (8bpp) 裝置環境;高色彩/真彩色模式無調色板動畫機制 |
| 平滑度由計時器間隔與 nDelta 控制 | 現代系統多為 32bpp,需強制 8bpp 模式或改用 Alpha Blending / DirectX |
| 程式碼簡潔,易封裝為工具函式 | 調色板為全域資源,全螢幕獨佔時體驗最佳;視窗模式易受其他視窗調色板干擾 |
WS_EX_LAYERED, UpdateLayeredWindow) — 支援真彩色、視窗級漸變,Win2000+ 原生支援。ColorMatrix — 矩陣調整 Alpha 通道繪製。調色板動畫是 Windows 早期多媒體編程的經典技術,理解其「修改查找表而非像素資料」的核心思想,有助於掌握現代圖形管線中 LUT、Shader、Color Matrix 等同源概念。維護舊專案或開發 8bpp 嵌入式介面時,本文提供的 FadeIn/FadeOut 可直接落地使用。