想給單文檔/對話框加個好看的漸變背景?**不需要調色板、不需要 DIB、不需要 GDI+**,純 GDI `FillSolidRect` + 迴圈幾行代碼即可實現平滑漸變,並支援滑鼠點擊隨機換色。本文以單文檔視圖為例完整演示。
將客戶區寬度 `nWidth` 分成 `nWidth` 條 1 像素寬的垂直矩形,每條根據 X 座標計算 RGB 分量,形成線性漸變。
class CShadowView : public CView {
int m_nColorR, m_nColorG; // 藍色固定 255,紅/綠動態
// ...
};
CShadowView::CShadowView() : m_nColorR(255), m_nColorG(255) {}
void CShadowView::OnDraw(CDC* pDC) {
CShadowDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CRect rcClient;
GetClientRect(&rcClient);
int nWidth = rcClient.Width();
int nHeight = rcClient.Height();
CRect rectBand;
for (int x = 0; x < nWidth; ++x) {
rectBand.SetRect(x, 0, x + 1, nHeight); // 1px 寬垂直帶
// 紅、綠固定,藍從 255 線性遞減到 0
BYTE bBlue = (BYTE)(255 - MulDiv(x, 255, nWidth));
COLORREF cr = RGB(m_nColorR, m_nColorG, bBlue);
pDC->FillSolidRect(&rectBand, cr);
}
}
void CShadowView::OnLButtonDown(UINT nFlags, CPoint point) {
// 產生 0~255 隨機數
auto randByte = []() -> BYTE {
return (BYTE)((rand() * 255.0) / RAND_MAX + 0.5);
};
m_nColorR = randByte();
m_nColorG = randByte();
Invalidate(TRUE); // 觸發 WM_PAINT → OnDraw 重繪
CView::OnLButtonDown(nFlags, point);
}
| 模式 | 核心修改 | 代碼片段 |
|---|---|---|
| 垂直漸變 (上→下) | 外層迴圈 `y`,`MulDiv(y, 255, nHeight)` | `rectBand.SetRect(0, y, nWidth, y+1);` |
| 對角線漸變 | `MulDiv(x+y, 255, nWidth+nHeight)` | 單一雙層迴圈或預計算查表 |
| 雙色線性插值 | 起始色 `c1`、結束色 `c2`,`LERP(c1, c2, t)` | `BYTE r = c1.R + (c2.R - c1.R) * x / nWidth;` |
| 多色段漸變 | 定義色標陣列 `stops[]`,區間內插值 | 參考 GDI+ `ColorBlend` 邏輯 |
| 徑向漸變 (圓心擴散) | 計算到中心距離 `dist`,`t = dist / maxRadius` | 需雙層迴圈,效能較低 |
逐像素 `FillSolidRect` 在 4K 視窗 (3840×2160) 需 3840 次 GDI 呼叫,略慢。優化方案:
void CShadowView::OnDraw(CDC* pDC) {
CRect rc; GetClientRect(&rc);
CDC memDC; memDC.CreateCompatibleDC(pDC);
CBitmap bmp; bmp.CreateCompatibleBitmap(pDC, rc.Width(), rc.Height());
CBitmap* pOld = memDC.SelectObject(&bmp);
// 在 memDC 繪製漸變 (同前邏輯)
for (int x = 0; x < rc.Width(); ++x) {
CRect band(x, 0, x+1, rc.Height());
BYTE b = 255 - MulDiv(x, 255, rc.Width());
memDC.FillSolidRect(&band, RGB(m_nColorR, m_nColorG, b));
}
pDC->BitBlt(0, 0, rc.Width(), rc.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOld);
}
**GDI 像素級填充** 是理解漸變本質的最佳入門:**顏色空間線性插值 + 掃描線光柵化**。雖然生產環境建議用 GDI+/Direct2D,但這段「純 GDI、無依賴、幾十行」的代碼,在嵌入式、舊系統維護、輕量級工具中極具價值。掌握 `MulDiv`、`FillSolidRect`、`Invalidate` 三板斧,漸變、波紋、掃描光、甚至簡易光線投射,信手拈來。