要實現任意形狀視窗 (如角色外型、不規則面板),核心在於根據位圖透明色掃描生成矩形區域陣列 (RGNDATA),再以 ExtCreateRegion 建立複雜區域並套用 SetWindowRgn。本文提供完整掃描演算法與程式碼。
typedef struct _RGNDATAHEADER {
DWORD dwSize; // sizeof(RGNDATAHEADER)
DWORD iType; // RDH_RECTANGLES
DWORD nCount; // 矩形數量
DWORD nRgnSize; // nCount * sizeof(RECT)
RECT rcBound; // 邊界矩形
} RGNDATAHEADER;
typedef struct _RGNDATA {
RGNDATAHEADER rdh;
char Buffer[1]; // 實際為 RECT[nCount]
} RGNDATA;
Graphics::TBitmap* Sprite = new Graphics::TBitmap();
bool bKey = true;
int iCount = 0;
TColor ColorKey = clWhite; // 透明色
Sprite->LoadFromFile("D:\\bcbbak\\resource\\traffic.bmp");
Width = Sprite->Width; Height = Sprite->Height;
// 第一遍掃描:計算非透明像素連續水平段數 (即矩形數量)
for (int i = 0; i < Sprite->Height; i++) {
bKey = true;
for (int j = 0; j < Sprite->Width; j++) {
if (Sprite->Canvas->Pixels[j][i] == ColorKey)
bKey = true;
else {
if (bKey) iCount++;
bKey = false;
}
}
}
// 配置 RGNDATA 記憶體
BYTE* pData = new BYTE[sizeof(RGNDATAHEADER) + sizeof(RECT) * iCount];
RGNDATA* pRgnData = (RGNDATA*)pData;
RECT* pRect = (RECT*)(pData + sizeof(RGNDATAHEADER));
// 填寫 Header
pRgnData->rdh.dwSize = sizeof(RGNDATAHEADER);
pRgnData->rdh.iType = RDH_RECTANGLES;
pRgnData->rdh.nCount = iCount;
pRgnData->rdh.nRgnSize = sizeof(RECT) * iCount;
pRgnData->rdh.rcBound.left = 0;
pRgnData->rdh.rcBound.top = 0;
pRgnData->rdh.rcBound.right = Sprite->Width;
pRgnData->rdh.rcBound.bottom = Sprite->Height;
// 第二遍掃描:生成矩形 (注意 Y 軸翻轉)
int iIndex = 0, iLeft = 0;
for (int i = Sprite->Height - 1; i >= 0; i--) { // 位圖 Y 軸自下而上,需從底部掃描
bKey = TRUE; iLeft = -1;
for (int j = 0; j < Sprite->Width; j++) {
if (Sprite->Canvas->Pixels[j][i] == ColorKey) {
if (!bKey) {
pRect[iIndex].left = iLeft;
pRect[iIndex].right = j;
pRect[iIndex].top = Sprite->Width - i - 1; // Y 座標轉換
pRect[iIndex].bottom = Sprite->Height - i;
bKey = TRUE; iIndex++; iLeft = -1;
}
} else if (bKey) {
iLeft = j; bKey = false;
}
}
if (iLeft >= 0) { // 行尾處理
pRect[iIndex].left = iLeft;
pRect[iIndex].right = Sprite->Width;
pRect[iIndex].top = i; // 注意:此處邏輯與上方不一致,原碼可能有誤,建議統一座標轉換
pRect[iIndex].bottom = i;
iIndex++;
}
}
// 建立區域並套用
bool bR = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + sizeof(RECT) * iCount, pRgnData);
if (!bR) { ShowMessage("Create Form Failed."); Close(); }
SetWindowRgn(Handle, (HRGN)pRgnData, TRUE); // 實際應用中需用返回的 HRGN
delete[] pData;
delete Sprite;
top/bottom。nCount 提升效能 (本碼未實現)。iCount 再一次性配置 RGNDATA,避免動態擴展。top/bottom 賦值 (i) 與主迴圈 (Sprite->Width - i - 1) 不一致。正確統一為:top = Height - i - 1; bottom = Height - i; (假設視窗高度 = 位圖高度)。NULL 使用預設世界變換;第二參數為緩衝區總大小;第三參數為 RGNDATA*。HRGN,緩衝區 pData 亦不可釋放 (系統持有指標)。HRGN,調整大小需重新生成。class TSpriteWindow : public TForm {
protected:
HRGN FRegion;
void __fastcall LoadShapeFromBitmap(Graphics::TBitmap* Bmp, TColor TransparentColor);
void __fastcall ApplyRegion();
public:
__fastcall TSpriteWindow(TComponent* Owner);
__fastcall ~TSpriteWindow();
};
// LoadShapeFromBitmap 內含上述掃描邏輯,ApplyRegion 呼叫 SetWindowRgn
// 衍生類別僅需在建構函式呼叫 LoadShapeFromBitmap 即可擁有任意形狀
掌握位圖掃描生成 RGNDATA 技術,是實現任意形狀視窗的基石。配合現代 WS_EX_LAYERED + PNG Alpha 通道 (Win2000+),可獲得更自然的邊緣抗鋸齒與半透明效果,建議新專案優先考慮分層視窗方案,本技術作為舊系統相容或特殊區域邏輯需求的補充。