使用 CImageList::Create + Add 載入工具列/樹狀圖示位圖時,發現**橙色 (RGB 255,165,0) 變成黃色 (255,255,0)**,其他顏色正常。
CImageList 預設使用**系統預設調色板 (20 色系統色 + 236 色預設色)**,共 256 色。Add 會忽略調色板,強制映射到系統調色板。核心思路:**從資源直接載入 DIB Section,提取調色板,建立邏輯調色板,Select/Realize 到 DC,再 CreateDIBitmap**。
// 工具函數:從資源載入位圖並返回邏輯調色板
HBITMAP LoadResourceBitmap(HINSTANCE hInstance, LPSTR lpString, HPALETTE* lphPalette)
{
HRSRC hRsrc = FindResource(hInstance, lpString, RT_BITMAP);
if (!hRsrc) return NULL;
HGLOBAL hGlobal = LoadResource(hInstance, hRsrc);
if (!hGlobal) return NULL;
LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)LockResource(hGlobal);
if (!lpbi) return NULL;
HDC hdc = GetDC(NULL);
int iNumColors = 0;
*lphPalette = CreateDIBPalette((LPBITMAPINFO)lpbi, &iNumColors);
if (*lphPalette) {
SelectPalette(hdc, *lphPalette, FALSE);
RealizePalette(hdc);
}
HBITMAP hBitmapFinal = CreateDIBitmap(hdc,
lpbi, CBM_INIT,
(LPSTR)lpbi + lpbi->biSize + iNumColors * sizeof(RGBQUAD),
(LPBITMAPINFO)lpbi, DIB_RGB_COLORS);
ReleaseDC(NULL, hdc);
// UnlockResource/FreeResource 不需在 Win32 顯式調用
return hBitmapFinal;
}
// 內部:從 DIB 創建邏輯調色板
HPALETTE CreateDIBPalette(LPBITMAPINFO lpbmi, LPINT lpiNumColors)
{
LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)lpbmi;
if (lpbi->biBitCount <= 8)
*lpiNumColors = 1 << lpbi->biBitCount;
else
*lpiNumColors = 0; // 24/32-bit 無需調色板
if (lpbi->biClrUsed > 0)
*lpiNumColors = lpbi->biClrUsed;
if (*lpiNumColors == 0) return NULL;
HANDLE hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * (*lpiNumColors));
LPLOGPALETTE lpPal = (LPLOGPALETTE)GlobalLock(hLogPal);
lpPal->palVersion = 0x300;
lpPal->palNumEntries = *lpiNumColors;
for (int i = 0; i < *lpiNumColors; ++i) {
lpPal->palPalEntry[i].peRed = lpbmi->bmiColors[i].rgbRed;
lpPal->palPalEntry[i].peGreen = lpbmi->bmiColors[i].rgbGreen;
lpPal->palPalEntry[i].peBlue = lpbmi->bmiColors[i].rgbBlue;
lpPal->palPalEntry[i].peFlags = 0;
}
GlobalUnlock(hLogPal);
HPALETTE hPal = CreatePalette(lpPal);
GlobalFree(hLogPal);
return hPal;
}
void CMyTreeView::OnInitialUpdate()
{
CTreeView::OnInitialUpdate();
HPALETTE hPal = NULL;
HBITMAP hBmp = LoadResourceBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_TREE_IMAGES), &hPal);
if (hBmp) {
m_imageList.Create(16, 16, ILC_COLOR8 | ILC_MASK, 10, 5); // 8-bit 色 + 遮罩
m_imageList.Add(CBitmap::FromHandle(hBmp), (CBitmap*)NULL); // 遮罩可另載入或用透明色
GetTreeCtrl().SetImageList(&m_imageList, TVSIL_NORMAL);
// 重要:若 TreeCtrl 所在 DC 需實現調色板,覆寫 OnQueryNewPalette/OnPaletteChanged
// 參考下方「視窗調色板實現」
}
}
若父視窗/控制項不處理調色板訊息,系統不會實現您的調色板,顏色仍會錯誤。
// 在包含 CImageList 的視窗類別 (如 CTreeView, CListView, CFormView)
BOOL CMyTreeView::OnQueryNewPalette()
{
if (m_hPal) { // 成員 HPALETTE m_hPal 保存 LoadResourceBitmap 返回的調色板
CClientDC dc(this);
SelectPalette(dc, m_hPal, FALSE);
UINT u = RealizePalette(dc);
if (u) Invalidate(); // 色彩變化需重繪
return TRUE;
}
return FALSE;
}
void CMyTreeView::OnPaletteChanged(CWnd* pFocusWnd)
{
if (pFocusWnd != this && m_hPal) {
CClientDC dc(this);
SelectPalette(dc, m_hPal, FALSE);
RealizePalette(dc);
Invalidate();
}
}
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| LoadResourceBitmap + 調色板 | 完美保留 256 色位圖原始色彩 | 需處理 WM_QUERYNEWPALETTE/WM_PALETTECHANGED、代碼較多 | 高色彩保真需求、舊版 256 色資源 |
| 使用 24/32-bit 位圖 (ILC_COLOR24/32) | 無調色板問題、真彩色、現代系統預設 | 記憶體佔用大 3/4 倍、舊系統 (Win95/NT4) 可能不支援 | 新專案、資源可重製、不需相容極舊系統 |
| CImageList::Add (CBitmap*, mask) 直接加載 | 代碼簡單 | 預設映射系統調色板,自定義色彩失真 | 僅使用系統 20 色、或可接受色彩近似 |
m_imageList.Create(16, 16, ILC_COLOR32 | ILC_MASK, 10, 5); // 32-bit ARGB
// 位圖資源製作為 32-bit (含 Alpha 通道),或載入後轉換
// 無需調色板處理,Win2000+ 完美支援
CImageList 橙色變黃色是**8-bit 位圖調色板映射到系統預設調色板**導致。根治方案:**LoadResourceBitmap 提取 DIB 調色板 + CreateDIBPalette 建立邏輯調色板 + 視窗處理 WM_QUERYNEWPALETTE/WM_PALETTECHANGED 實現調色板**。現代專案**首選 32-bit 位圖 (ILC_COLOR32) 徹底避開調色板問題**,兼具真彩色與 Alpha 透明。