BMP 檔案結構速覽
- `BITMAPFILEHEADER` (14 bytes):檔案類型 `BM`、大小、資料偏移。
- `BITMAPINFOHEADER` (40 bytes):寬/高、位元深度 (24-bit)、壓縮方式、調色板資訊。
- 像素資料:按行儲存,每行 4-byte 對齊。
完整實作步驟
1. 讀取 BMP 到記憶體
CFile file; file.Open(path, CFile::modeRead);
BITMAPFILEHEADER bfh; file.Read(&bfh, sizeof(bfh));
BITMAPINFOHEADER bih; file.Read(&bih, sizeof(bih));
int w = bih.biWidth, h = bih.biHeight;
DWORD dataSize = bfh.bfSize - bfh.bfOffBits;
BYTE* pSrcBits = new BYTE[dataSize];
file.ReadHuge(pSrcBits, dataSize); file.Close();
2. 建立記憶體 DC 並繪製原圖
CClientDC dc(this);
HDC hMemSrc = CreateCompatibleDC(dc.m_hDC);
HBITMAP hBmp = CreateCompatibleBitmap(dc.m_hDC, w, h);
SelectObject(hMemSrc, hBmp);
BitBlt(hMemSrc, 0, 0, w, h, dc.m_hDC, 0, 0, SRCCOPY); // 將螢幕內容複製到記憶體位圖 (示範用)
// 實際應用: 用 SetDIBits 將 pSrcBits 直接寫入 hBmp
3. 旋轉核心算法 (逆向映射 + 雙線性插值可選)
void RotateAnyAngle(HDC hSrc, int srcW, int srcH, double angle, HDC hDst) {
double sinA = sin(angle), cosA = cos(angle);
// 計算旋轉後邊界
double x1 = -srcH * sinA, y1 = srcH * cosA;
double x2 = srcW * cosA - srcH * sinA, y2 = srcH * cosA + srcW * sinA;
double x3 = srcW * cosA, y3 = srcW * sinA;
double minX = min(0.0, min(x1, min(x2, x3)));
double maxX = max(0.0, max(x1, max(x2, x3)));
double minY = min(0.0, min(y1, min(y2, y3)));
double maxY = max(0.0, max(y1, max(y2, y3)));
int dstW = (int)ceil(maxX - minX), dstH = (int)ceil(maxY - minY);
// 建立目標記憶體 DC
HDC hMemDst = CreateCompatibleDC(hSrc);
HBITMAP hDstBmp = CreateCompatibleBitmap(hSrc, dstW, dstH);
SelectObject(hMemDst, hDstBmp);
// 逆向映射:遍歷目標像素,反推源座標
for (int dy = 0; dy < dstH; ++dy) {
for (int dx = 0; dx < dstW; ++dx) {
double sx = (dx + minX) * cosA + (dy + minY) * sinA;
double sy = (dy + minY) * cosA - (dx + minX) * sinA;
if (sx >= 0 && sx < srcW && sy >= 0 && sy < srcH) {
// 最近鄰採樣 (簡單快速)
BitBlt(hMemDst, dx, dy, 1, 1, hSrc, (int)sx, (int)sy, SRCCOPY);
// 進階:雙線性插值需自行 GetPixel/SetPixel 或操作 DIB bits
}
}
}
// 輸出到螢幕或儲存
BitBlt(hDst, 200, 200, dstW, dstH, hMemDst, 0, 0, SRCCOPY);
DeleteObject(hDstBmp); DeleteDC(hMemDst);
}
4. 呼叫範例
double angle = 45.0 * 3.1415926 / 180.0; // 45 度 -> 弧度
RotateAnyAngle(hMemSrc, w, h, angle, dc.m_hDC);
優化方向
- **雙線性插值**:計算四鄰域像素加權平均,消除鋸齒。
- **DIB 直接操作**:鎖定 `BITMAP` bits 指標,避免 `BitBlt` 單像素呼叫開銷,速度提升 100 倍以上。
- **SIMD/GPU**:大圖旋轉建議使用 GDI+ `Graphics::RotateTransform` 或 Direct2D/GPU Shader。
常見坑點
- 角度單位:C/C++ `sin/cos` 接受**弧度**,度數需 `* PI / 180`。
- 座標系:Windows GDI 原點左上,Y 向下;數學旋轉公式假設 Y 向上,需對 `sinA` 取負或調整公式。
- 目標 DC 尺寸不足導致裁切:必須依旋轉後外接矩形建立足夠大的位圖。