C#图像旋转
如果平面上的点绕原点逆时针旋转θº,则其坐标变换公式为:
x'=xcosθ+ysinθ y=-xsinθ+ycosθ
其中,(x, y)为原图坐标,(x’, y’)为旋转后的坐标。它的逆变换公式为:
x=x'cosθ-y'sinθ y=x'sinθ+y'cosθ
矩阵形式为:
和缩放类似,旋转后的图像的像素点也需要经过坐标转换为原始图像上的坐标来确定像素值,同样也可能找不到对应点,因此旋转也用到插值法。在此选用性能较好的双线性插值法。为提高速度,在处理旋转90º、-90º、±180º时使用了镜像来处理。
1 /// <summary> 2 /// 图像旋转 3 /// </summary> 4 /// <param name="srcBmp">原始图像</param> 5 /// <param name="angle">旋转角度</param> 6 /// <param name="dstBmp">目标图像</param> 7 /// <returns>处理成功 true 失败 false</returns> 8 public static bool Rotation(Bitmap srcBmp, double angle, out Bitmap dstBmp) 9 { 10 if (srcBmp == null) 11 { 12 dstBmp = null; 13 return false; 14 } 15 dstBmp = null; 16 BitmapData srcBmpData = null; 17 BitmapData dstBmpData = null; 18 switch ((int)angle) 19 { 20 case 0: 21 dstBmp = new Bitmap(srcBmp); 22 break; 23 case -90: 24 dstBmp = new Bitmap(srcBmp.Height, srcBmp.Width); 25 srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); 26 dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); 27 unsafe 28 { 29 byte* ptrSrc = (byte*)srcBmpData.Scan0; 30 byte* ptrDst = (byte*)dstBmpData.Scan0; 31 for (int i = 0; i < srcBmp.Height; i++) 32 { 33 for (int j = 0; j < srcBmp.Width; j++) 34 { 35 ptrDst[j * dstBmpData.Stride + (dstBmp.Height - i - 1) * 3] = ptrSrc[i * srcBmpData.Stride + j * 3]; 36 ptrDst[j * dstBmpData.Stride + (dstBmp.Height - i - 1) * 3 + 1] = ptrSrc[i * srcBmpData.Stride + j * 3 + 1]; 37 ptrDst[j * dstBmpData.Stride + (dstBmp.Height - i - 1) * 3 + 2] = ptrSrc[i * srcBmpData.Stride + j * 3 + 2]; 38 } 39 } 40 } 41 srcBmp.UnlockBits(srcBmpData); 42 dstBmp.UnlockBits(dstBmpData); 43 break; 44 case 90: 45 dstBmp = new Bitmap(srcBmp.Height, srcBmp.Width); 46 srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); 47 dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); 48 unsafe 49 { 50 byte* ptrSrc = (byte*)srcBmpData.Scan0; 51 byte* ptrDst = (byte*)dstBmpData.Scan0; 52 for (int i = 0; i < srcBmp.Height; i++) 53 { 54 for (int j = 0; j < srcBmp.Width; j++) 55 { 56 ptrDst[(srcBmp.Width - j - 1) * dstBmpData.Stride + i * 3] = ptrSrc[i * srcBmpData.Stride + j * 3]; 57 ptrDst[(srcBmp.Width - j - 1) * dstBmpData.Stride + i * 3 + 1] = ptrSrc[i * srcBmpData.Stride + j * 3 + 1]; 58 ptrDst[(srcBmp.Width - j - 1) * dstBmpData.Stride + i * 3 + 2] = ptrSrc[i * srcBmpData.Stride + j * 3 + 2]; 59 } 60 } 61 } 62 srcBmp.UnlockBits(srcBmpData); 63 dstBmp.UnlockBits(dstBmpData); 64 break; 65 case 180: 66 case -180: 67 dstBmp = new Bitmap(srcBmp.Width, srcBmp.Height); 68 srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); 69 dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); 70 unsafe 71 { 72 byte* ptrSrc = (byte*)srcBmpData.Scan0; 73 byte* ptrDst = (byte*)dstBmpData.Scan0; 74 for (int i = 0; i < srcBmp.Height; i++) 75 { 76 for (int j = 0; j < srcBmp.Width; j++) 77 { 78 ptrDst[(srcBmp.Width - i - 1) * dstBmpData.Stride + (dstBmp.Height - j - 1) * 3] = ptrSrc[i * srcBmpData.Stride + j * 3]; 79 ptrDst[(srcBmp.Width - i - 1) * dstBmpData.Stride + (dstBmp.Height - j - 1) * 3 + 1] = ptrSrc[i * srcBmpData.Stride + j * 3 + 1]; 80 ptrDst[(srcBmp.Width - i - 1) * dstBmpData.Stride + (dstBmp.Height - j - 1) * 3 + 2] = ptrSrc[i * srcBmpData.Stride + j * 3 + 2]; 81 } 82 } 83 } 84 srcBmp.UnlockBits(srcBmpData); 85 dstBmp.UnlockBits(dstBmpData); 86 break; 87 default://任意角度 88 double radian = angle * Math.PI / 180.0;//将角度转换为弧度 89 //计算正弦和余弦 90 double sin = Math.Sin(radian); 91 double cos = Math.Cos(radian); 92 //计算旋转后的图像大小 93 int widthDst = (int)(srcBmp.Height * Math.Abs(sin) + srcBmp.Width * Math.Abs(cos)); 94 int heightDst = (int)(srcBmp.Width * Math.Abs(sin) + srcBmp.Height * Math.Abs(cos)); 95 96 dstBmp = new Bitmap(widthDst, heightDst); 97 //确定旋转点 98 int dx = (int)(srcBmp.Width / 2 * (1 - cos) + srcBmp.Height / 2 * sin); 99 int dy = (int)(srcBmp.Width / 2 * (0 - sin) + srcBmp.Height / 2 * (1 - cos)); 100 101 int insertBeginX = srcBmp.Width / 2 - widthDst / 2; 102 int insertBeginY = srcBmp.Height / 2 - heightDst / 2; 103 104 //插值公式所需参数 105 double ku = insertBeginX * cos - insertBeginY * sin + dx; 106 double kv = insertBeginX * sin + insertBeginY * cos + dy; 107 double cu1 = cos, cu2 = sin; 108 double cv1 = sin, cv2 = cos; 109 110 double fu, fv, a, b, F1, F2; 111 int Iu, Iv; 112 srcBmpData = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); 113 dstBmpData = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); 114 115 unsafe 116 { 117 byte* ptrSrc = (byte*)srcBmpData.Scan0; 118 byte* ptrDst = (byte*)dstBmpData.Scan0; 119 for (int i = 0; i < heightDst; i++) 120 { 121 for (int j = 0; j < widthDst; j++) 122 { 123 fu = j * cu1 - i * cu2 + ku; 124 fv = j * cv1 + i * cv2 + kv; 125 if ((fv < 1) || (fv > srcBmp.Height - 1) || (fu < 1) || (fu > srcBmp.Width - 1)) 126 { 127 128 ptrDst[i * dstBmpData.Stride + j * 3] = 150; 129 ptrDst[i * dstBmpData.Stride + j * 3 + 1] = 150; 130 ptrDst[i * dstBmpData.Stride + j * 3 + 2] = 150; 131 } 132 else 133 {//双线性插值 134 Iu = (int)fu; 135 Iv = (int)fv; 136 a = fu - Iu; 137 b = fv - Iv; 138 for (int k = 0; k < 3; k++) 139 { 140 F1 = (1 - b) * *(ptrSrc + Iv * srcBmpData.Stride + Iu * 3 + k) + b * *(ptrSrc + (Iv + 1) * srcBmpData.Stride + Iu * 3 + k); 141 F2 = (1 - b) * *(ptrSrc + Iv * srcBmpData.Stride + (Iu + 1) * 3 + k) + b * *(ptrSrc + (Iv + 1) * srcBmpData.Stride + (Iu + 1) * 3 + k); 142 *(ptrDst + i * dstBmpData.Stride + j * 3 + k) = (byte)((1 - a) * F1 + a * F2); 143 } 144 } 145 } 146 } 147 } 148 srcBmp.UnlockBits(srcBmpData); 149 dstBmp.UnlockBits(dstBmpData); 150 break; 151 } 152 return true; 153 }