如果平面上的點繞原點逆時針旋轉θº,則其坐標變換公式為:
x'=xcosθ+ysinθ y=-xsinθ+ycosθ
其中,(x, y)為原圖坐標,(x’, y’)為旋轉后的坐標。它的逆變換公式為:
x=x'cosθ-y'sinθ y=x'sinθ+y'cosθ
矩陣形式為:
和縮放類似,旋轉后的圖像的像素點也需要經過坐標轉換為原始圖像上的坐標來確定像素值,同樣也可能找不到對應點,因此旋轉也用到插值法。在此選用性能較好的雙線性插值法。為提高速度,在處理旋轉90º、-90º、±180º時使用了鏡像來處理。
/// <summary> /// 圖像旋轉 /// </summary> /// <param name="srcBmp">原始圖像</param> /// <param name="degree">旋轉角度</param> /// <param name="dstBmp">目標圖像</param> /// <returns>處理成功 true 失敗 false</returns> public static bool Rotation(Bitmap srcBmp, double degree, out Bitmap dstBmp) { if (srcBmp == null) { dstBmp = null; return false; } dstBmp = null; BitmapData srcBmpData = null; BitmapData dstBmpData = null; switch ((int)degree) { case 0: dstBmp = new Bitmap(srcBmp); break; case -90: dstBmp = new Bitmap(srcBmp.Height, srcBmp.Width); srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* ptrSrc = (byte*)srcBmpData.Scan0; byte* ptrDst = (byte*)dstBmpData.Scan0; for (int i = 0; i < srcBmp.Height; i++) { for (int j = 0; j < srcBmp.Width; j++) { ptrDst[j * dstBmpData.Stride + (dstBmp.Height - i - 1) * 3] = ptrSrc[i * srcBmpData.Stride + j * 3]; ptrDst[j * dstBmpData.Stride + (dstBmp.Height - i - 1) * 3 + 1] = ptrSrc[i * srcBmpData.Stride + j * 3 + 1]; ptrDst[j * dstBmpData.Stride + (dstBmp.Height - i - 1) * 3 + 2] = ptrSrc[i * srcBmpData.Stride + j * 3 + 2]; } } } srcBmp.UnlockBits(srcBmpData); dstBmp.UnlockBits(dstBmpData); break; case 90: dstBmp = new Bitmap(srcBmp.Height, srcBmp.Width); srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* ptrSrc = (byte*)srcBmpData.Scan0; byte* ptrDst = (byte*)dstBmpData.Scan0; for (int i = 0; i < srcBmp.Height; i++) { for (int j = 0; j < srcBmp.Width; j++) { ptrDst[(srcBmp.Width - j - 1) * dstBmpData.Stride + i * 3] = ptrSrc[i * srcBmpData.Stride + j * 3]; ptrDst[(srcBmp.Width - j - 1) * dstBmpData.Stride + i * 3 + 1] = ptrSrc[i * srcBmpData.Stride + j * 3 + 1]; ptrDst[(srcBmp.Width - j - 1) * dstBmpData.Stride + i * 3 + 2] = ptrSrc[i * srcBmpData.Stride + j * 3 + 2]; } } } srcBmp.UnlockBits(srcBmpData); dstBmp.UnlockBits(dstBmpData); break; case 180: case -180: dstBmp = new Bitmap(srcBmp.Width, srcBmp.Height); srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* ptrSrc = (byte*)srcBmpData.Scan0; byte* ptrDst = (byte*)dstBmpData.Scan0; for (int i = 0; i < srcBmp.Height; i++) { for (int j = 0; j < srcBmp.Width; j++) { ptrDst[(srcBmp.Width - i - 1) * dstBmpData.Stride + (dstBmp.Height - j - 1) * 3] = ptrSrc[i * srcBmpData.Stride + j * 3]; ptrDst[(srcBmp.Width - i - 1) * dstBmpData.Stride + (dstBmp.Height - j - 1) * 3 + 1] = ptrSrc[i * srcBmpData.Stride + j * 3 + 1]; ptrDst[(srcBmp.Width - i - 1) * dstBmpData.Stride + (dstBmp.Height - j - 1) * 3 + 2] = ptrSrc[i * srcBmpData.Stride + j * 3 + 2]; } } } srcBmp.UnlockBits(srcBmpData); dstBmp.UnlockBits(dstBmpData); break; default://任意角度 double radian = degree * Math.PI / 180.0;//將角度轉換為弧度 //計算正弦和余弦 double sin = Math.Sin(radian); double cos = Math.Cos(radian); //計算旋轉后的圖像大小 int widthDst = (int)(srcBmp.Height * Math.Abs(sin) + srcBmp.Width * Math.Abs(cos)); int heightDst = (int)(srcBmp.Width * Math.Abs(sin) + srcBmp.Height * Math.Abs(cos)); dstBmp = new Bitmap(widthDst, heightDst); //確定旋轉點 int dx = (int)(srcBmp.Width / 2 * (1 - cos) + srcBmp.Height / 2 * sin); int dy = (int)(srcBmp.Width / 2 * (0 - sin) + srcBmp.Height / 2 * (1 - cos)); int insertBeginX = srcBmp.Width / 2 - widthDst / 2; int insertBeginY = srcBmp.Height / 2 - heightDst / 2; //插值公式所需參數 double ku = insertBeginX * cos - insertBeginY * sin + dx; double kv = insertBeginX * sin + insertBeginY * cos + dy; double cu1 = cos, cu2 = sin; double cv1 = sin, cv2 = cos; double fu, fv, a, b, F1, F2; int Iu, Iv; srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { byte* ptrSrc = (byte*)srcBmpData.Scan0; byte* ptrDst = (byte*)dstBmpData.Scan0; for (int i = 0; i < heightDst; i++) { for (int j = 0; j < widthDst; j++) { fu = j * cu1 - i * cu2 + ku; fv = j * cv1 + i * cv2 + kv; if ((fv < 1) || (fv > srcBmp.Height - 1) || (fu < 1) || (fu > srcBmp.Width - 1)) { ptrDst[i * dstBmpData.Stride + j * 3] = 150; ptrDst[i * dstBmpData.Stride + j * 3 + 1] = 150; ptrDst[i * dstBmpData.Stride + j * 3 + 2] = 150; } else {//雙線性插值 Iu = (int)fu; Iv = (int)fv; a = fu - Iu; b = fv - Iv; for (int k = 0; k < 3; k++) { F1 = (1 - b) * *(ptrSrc + Iv * srcBmpData.Stride + Iu * 3 + k) + b * *(ptrSrc + (Iv + 1) * srcBmpData.Stride + Iu * 3 + k); F2 = (1 - b) * *(ptrSrc + Iv * srcBmpData.Stride + (Iu + 1) * 3 + k) + b * *(ptrSrc + (Iv + 1) * srcBmpData.Stride + (Iu + 1) * 3 + k); *(ptrDst + i * dstBmpData.Stride + j * 3 + k) = (byte)((1 - a) * F1 + a * F2); } } } } } srcBmp.UnlockBits(srcBmpData); dstBmp.UnlockBits(dstBmpData); break; } return true; }