正向映射(源像素→目標)會導致目標圖像出現「空洞」(未被覆蓋的像素)。標準做法是逆向映射:遍歷目標圖像每個像素,計算其對應的源圖像浮點坐標,通過雙線性插值計算像素值,保證目標圖像無空洞、邊緣平滑。
void S_PicXZ(Graphics::TBitmap *Source, Graphics::TBitmap *NewPic, int angle) {
// 1. 角度歸一化到 [-180, 180]
if (angle > 180) angle = 360 - angle;
if (angle < -180) angle = 360 + angle;
// 2. 角度轉弧度
float radians = (2 * 3.1415926535f * angle) / 360.0f;
float cosine = cos(radians);
float sine = sin(radians);
// 3. 計算源圖像四角旋轉後的坐標(以中心為原點)
// 源圖像中心為 (0,0),四角相對坐標:
// (-w/2, -h/2), (w/2, -h/2), (w/2, h/2), (-w/2, h/2)
// 旋轉後坐標:
float w = Source->Width;
float h = Source->Height;
float hw = w / 2.0f;
float hh = h / 2.0f;
float corners[4][2] = {
{-hw, -hh}, {hw, -hh}, {hw, hh}, {-hw, hh}
};
float rotCorners[4][2];
float minX = 0, minY = 0, maxX = 0, maxY = 0;
for (int i = 0; i < 4; i++) {
float x = corners[i][0];
float y = corners[i][1];
rotCorners[i][0] = x * cosine - y * sine;
rotCorners[i][1] = x * sine + y * cosine;
if (i == 0) { minX = maxX = rotCorners[i][0]; minY = maxY = rotCorners[i][1]; }
else {
minX = std::min(minX, rotCorners[i][0]);
maxX = std::max(maxX, rotCorners[i][0]);
minY = std::min(minY, rotCorners[i][1]);
maxY = std::max(maxY, rotCorners[i][1]);
}
}
// 4. 目標圖像尺寸 = 旋轉包圍盒尺寸(向上取整)
int destW = (int)ceil(maxX - minX);
int destH = (int)ceil(maxY - minY);
NewPic->Width = destW;
NewPic->Height = destH;
NewPic->PixelFormat = pf24bit; // 或 pf32bit,避免調色板模式慢
// 5. 逆向映射 + 雙線性插值
// 目標像素 (dx, dy) 對應源圖像中心坐標系:
// sx = (dx - destW/2) * cosine + (dy - destH/2) * sine
// sy = -(dx - destW/2) * sine + (dy - destH/2) * cosine
// 源圖像實際坐標 = (sx + w/2, sy + h/2)
float destHalfW = destW / 2.0f;
float destHalfH = destH / 2.0f;
for (int dy = 0; dy < destH; dy++) {
for (int dx = 0; dx < destW; dx++) {
// 目標像素中心相對坐標
float tx = dx - destHalfW + 0.5f; // +0.5 對齊像素中心
float ty = dy - destHalfH + 0.5f;
// 逆旋轉得到源圖像相對中心坐標
float sx = tx * cosine + ty * sine;
float sy = -tx * sine + ty * cosine;
// 轉換為源圖像絕對坐標(基於左上角 0,0)
float srcX = sx + hw;
float srcY = sy + hh;
// 邊界檢查
if (srcX >= 0 && srcX < w - 1 && srcY >= 0 && srcY < h - 1) {
// 雙線性插值
int x1 = (int)srcX;
int y1 = (int)srcY;
int x2 = x1 + 1;
int y2 = y1 + 1;
float dx1 = srcX - x1;
float dy1 = srcY - y1;
float dx2 = 1.0f - dx1;
float dy2 = 1.0f - dy1;
// 獲取四鄰域像素 (假設 24bit RGB)
TColor c11 = Source->Canvas->Pixels[x1][y1];
TColor c21 = Source->Canvas->Pixels[x2][y1];
TColor c12 = Source->Canvas->Pixels[x1][y2];
TColor c22 = Source->Canvas->Pixels[x2][y2];
// 各通道獨立插值
int r = (int)(GetRValue(c11)*dx2*dy2 + GetRValue(c21)*dx1*dy2 +
GetRValue(c12)*dx2*dy1 + GetRValue(c22)*dx1*dy1);
int g = (int)(GetGValue(c11)*dx2*dy2 + GetGValue(c21)*dx1*dy2 +
GetGValue(c12)*dx2*dy1 + GetGValue(c22)*dx1*dy1);
int b = (int)(GetBValue(c11)*dx2*dy2 + GetBValue(c21)*dx1*dy2 +
GetBValue(c12)*dx2*dy1 + GetBValue(c22)*dx1*dy1);
NewPic->Canvas->Pixels[dx][dy] = RGB(r, g, b);
}
else {
NewPic->Canvas->Pixels[dx][dy] = clWhite; // 背景色
}
}
}
}
Pixels[][] 直接取整),消除鋸齒,圖像質量顯著提升。cosine、sine、destHalfW 等,減少重複運算。Pixels 讀寫極慢的問題。TBitmap::ScanLine[y] 返回行指針,連續內存訪問,速度提升 10-50 倍。Graphics::TBitmap *src = new Graphics::TBitmap();
src->LoadFromFile("test.bmp");
Graphics::TBitmap *dst = new Graphics::TBitmap();
S_PicXZ(src, dst, 45); // 旋轉 45 度
dst->SaveToFile("rotated.bmp");
delete src;
delete dst;
本文提供生產級任意角度旋轉函數,採用逆向映射+雙線性插值,解決原代碼的邊界錯誤、鋸齒、性能問題。理解坐標系變換、插值算法、邊界處理是圖像處理核心基本功。