C# 图片自由变换 任意扭曲
2013-11-17 22:07
281 查看
之前想过要做个地铁驾驶的游戏,其中想把一些原本是矩形图片弄成一个梯形,但是发现GID+上面没有类似的方法。于是在谷歌谷了一下。没有!只能找到令人垂涎的,并没有源码。按照自己的想法尝试了一两天,有点效果,但实际上不是那样。后来知道那个在数字图像处理中叫“透视变换”。于是上网找了相关资料,原理找了,看了不明白。代码没多少,有ActionScript的,不明;有C的,不明。真笨啊!后来在CodeProject上面看到一份外国人的博文,全英文看不太明白,但看了一幅图,大概知道他意思了。下了份源码看看,C++的。好不容易翻译成C#的(感觉还是保留了不少C++风格的东西),编译通过,运行正常。后来才一步一步的阅读代码。还没全懂,先把懂的部分记录下来。以后继续研究继续补充。
先看看效果
Aaform
先看看效果
public delegate bool Aaf_callback(double paraDouble); struct AafPnt { public double x, y; public AafPnt(double x, double y) { this.x = x < 0 ? 0 : x; this.y = y < 0 ? 0 : y; } } class aaf_dblrgbquad { public double Red{get;set;} public double Green{get;set;} public double Blue{get;set;} public double Alpha { get; set; } } class aaf_indll { public aaf_indll next; public int ind; } class Aaform { private AafPnt[] pixelgrid; private AafPnt[] polyoverlap; private AafPnt[] polysorted; private AafPnt[] corners; private int outstartx; private int outstarty; private int outwidth; private int outheight; int polyoverlapsize; int polysortedsize; int[] ja = new int[] { 1, 2, 3, 0 }; public void CreateTransform(Bitmap src,ref Bitmap dst, List<double> xcorner, List<double> ycorner, Aaf_callback callbackfunc) { int right = 0, bottom = 0; //主要是根据新图片的坐标,计算出图片的宽和高,构造目标图片的Bitmap的 double offx = xcorner[0]; double offy = ycorner[0]; for (int i = 1; i < 4; i++) { if (xcorner[i] < offx) offx = xcorner[i]; if (ycorner[i] < offy) offy = ycorner[i]; } for (int i = 0; i < 4; i++) { xcorner[i] -= offx; ycorner[i] -= offy; if (roundup(xcorner[i]) > right) right = roundup(xcorner[i]); if (roundup(ycorner[i]) > bottom) bottom = roundup(ycorner[i]); } dst = new Bitmap(right, bottom); Transform(src, dst, xcorner, ycorner, null); } private void Transform(Bitmap src,Bitmap dst, List<double> xcorner, List<double> ycorner, Aaf_callback callbackfunc) { //Make sure the coordinates are valid if (xcorner.Count != 4 || ycorner.Count != 4) return ; //Load the src bitmaps information //create the intial arrays //根据原图片生成一个比原图宽高多一个单位的图片, //这个矩阵就是用来记录转换后各个像素点左上角的坐标 pixelgrid = new AafPnt[(src.Width + 1) * (src.Height + 1)]; polyoverlap = new AafPnt[16]; polysorted = new AafPnt[16]; corners = new AafPnt[4]; //Load the corners array double[] dx = { 0.0, 1.0, 1.0, 0.0 }; double[] dy = { 0.0, 0.0, 1.0, 1.0 }; for (int i = 0; i < 4; i++) { corners[i].x = dx[i]; corners[i].y = dy[i]; } //Find the rectangle of dst to draw to outstartx = rounddown(xcorner[0]); outstarty = rounddown(ycorner[0]); outwidth = 0; outheight = 0; //这里计算出变换后起始点的坐标 for (int i = 1; i < 4; i++) { if (rounddown(xcorner[i]) < outstartx) outstartx = rounddown(xcorner[i]); if (rounddown(ycorner[i]) < outstarty) outstarty = rounddown(ycorner[i]); } for (int i = 0; i < 4; i++) { if (roundup(xcorner[i] - outstartx) > outwidth) outwidth = roundup(xcorner[i] - outstartx); if (roundup(ycorner[i] - outstarty) > outheight) outheight = roundup(ycorner[i] - outstarty); } //fill out pixelgrid array //计算出理想目标图片中各个“像素格”中的左上角的坐标 if (CreateGrid(src, xcorner, ycorner)) { //Do the transformation //进行转换 DoTransform(src,dst, callbackfunc); } //Return if the function completed properly return ; } private bool CreateGrid(Bitmap src, List<double> xcorner, List<double> ycorner) { //mmm geometry double[] sideradius = new double[4]; double[] sidecos = new double[4]; double[] sidesin = new double[4]; //First we find the radius, cos, and sin of each side of the polygon created by xcorner and ycorner int j; for (int i = 0; i < 4; i++) { j = ja[i]; sideradius[i] = Math.Sqrt((xcorner[i] - xcorner[j]) * (xcorner[i] - xcorner[j]) + (ycorner[i] - ycorner[j]) * (ycorner[i] - ycorner[j])); sidecos[i] = (xcorner[j] - xcorner[i]) / sideradius[i]; sidesin[i] = (ycorner[j] - ycorner[i]) / sideradius[i]; } //Next we create two lines in Ax + By = C form for (int x = 0; x < src.Width + 1; x++) { double topdist = ((double)x / (src.Width)) * sideradius[0];//每个像素点变换后的坐标点 double ptxtop = xcorner[0] + topdist * sidecos[0]; double ptytop = ycorner[0] + topdist * sidesin[0]; double botdist = (1.0 - (double)x / (src.Width)) * sideradius[2]; double ptxbot = xcorner[2] + botdist * sidecos[2]; double ptybot = ycorner[2] + botdist * sidesin[2]; double Ah = ptybot - ptytop; double Bh = ptxtop - ptxbot; double Ch = Ah * ptxtop + Bh * ptytop;//叉乘 for (int y = 0; y < src.Height + 1; y++) { double leftdist = (1.0 - (double)y / (src.Height)) * sideradius[3]; double ptxleft = xcorner[3] + leftdist * sidecos[3]; double ptyleft = ycorner[3] + leftdist * sidesin[3]; double rightdist = ((double)y / (src.Height)) * sideradius[1]; double ptxright = xcorner[1] + rightdist * sidecos[1]; double ptyright = ycorner[1] + rightdist * sidesin[1]; double Av = ptyright - ptyleft; double Bv = ptxleft - ptxright; double Cv = Av * ptxleft + Bv * ptyleft; //Find where the lines intersect and store that point in the pixelgrid array double det = Ah * Bv - Av * Bh; if (AafAbs(det) < 1e-9) { return false; } else { int ind = x + y * (src.Width + 1); pixelgrid[ind].x = (Bv * Ch - Bh * Cv) / det; pixelgrid[ind].y = (Ah * Cv - Av * Ch) / det; } } } //Yayy we didn't fail return true; } private void DoTransform(Bitmap src,Bitmap dst, Aaf_callback callbackfunc) { //Get source bitmap's information if (src == null) return ; //Create the source dib array and the dstdib array aaf_dblrgbquad[] dbldstdib = new aaf_dblrgbquad[outwidth * outheight]; for (int i = 0; i < dbldstdib.Length; i++) dbldstdib[i] = new aaf_dblrgbquad(); //Create polygon arrays AafPnt[] p = new AafPnt[4]; AafPnt[] poffset = new AafPnt[4]; //Loop through the source's pixels //遍历原图(实质上是pixelgrid)各个点 for (int x = 0; x < src.Width; x++) { for (int y = 0; y < src.Height; y++) { //取当前点 下一点 右一点 斜右下角点 四点才组成一个四边形 //这个四边形是原图像的一个像素点 //Construct the source pixel's rotated polygon from pixelgrid p[0] = pixelgrid[x + y * (src.Width + 1)]; p[1] = pixelgrid[(x + 1) + y * (src.Width + 1)]; p[2] = pixelgrid[(x + 1) + (y + 1) * (src.Width + 1)]; p[3] = pixelgrid[x + (y + 1) * (src.Width + 1)]; //Find the scan area on the destination's pixels int mindx = int.MaxValue; int mindy = int.MaxValue; int maxdx = int.MinValue; int maxdy = int.MinValue; for (int i = 0; i < 4; i++) { if (rounddown(p[i].x) < mindx) mindx = rounddown(p[i].x); if (roundup(p[i].x) > maxdx) maxdx = roundup(p[i].x); if (rounddown(p[i].y) < mindy) mindy = rounddown(p[i].y); if (roundup(p[i].y) > maxdy) maxdy = roundup(p[i].y); } int SrcIndex = x + y * src.Width; //遍历四边形包含了目标图几个像素点 //按照相交面积占整个像素的的百分比,把颜色按照该比例存放一个目标像素点颜色的数组中 //这里计算出来的颜色只是初步颜色,还没到最终结果 //loop through the scan area to find where source(x, y) overlaps with the destination pixels for (int xx = mindx - 1; xx <= maxdx; xx++) { if (xx < 0 || xx >= dst.Width) continue; for (int yy = mindy - 1; yy <= maxdy; yy++) { if (yy < 0 || yy >= dst.Height) continue; //offset p and by (xx,yy) and put that into poffset for (int i = 0; i < 4; i++) { poffset[i].x = p[i].x - xx; poffset[i].y = p[i].y - yy; } //FIND THE OVERLAP *a whole lot of code pays off here* //这里则是计算出覆盖了面积占当前像素的百分比 double dbloverlap = PixOverlap(poffset); //按照百分比来为目标像素点累加颜色 //因为一个目标像素点有可能有几个原来像素的覆盖了 if (dbloverlap > 0) { int dstindex = xx + yy * outwidth; int srcWidth = src.Width; Color srcColor; if (SrcIndex == 0) srcColor = src.GetPixel(0, 0); else srcColor = src.GetPixel(SrcIndex%src.Width , SrcIndex/src.Width ); //Add the rgb and alpha values in proportion to the overlap area dbldstdib[dstindex].Red += (double)((srcColor.R) * dbloverlap); dbldstdib[dstindex].Blue += (double)(srcColor.B) * dbloverlap; dbldstdib[dstindex].Green += (double)(srcColor.G) * dbloverlap; dbldstdib[dstindex].Alpha += dbloverlap; } } } } if (callbackfunc != null) { //Send the callback message double percentdone = (double)(x + 1) / (double)(src.Width); if (callbackfunc(percentdone)) { dbldstdib = null; p = null; poffset = null; return ; } } } //Free memory no longer needed //Create final destination bits RGBQUDA[] dstdib = new RGBQUDA[dst.Width * dst.Height]; for (int i = 0; i < dstdib.Length; i++) dstdib[i] = new RGBQUDA(){R= 0,G= 0,B= 0}; //这里是实际上真正像素点的颜色,并且填到了目标图片中去 //Write to dstdib with the information stored in dbldstdib for (int x = 0; x < outwidth; x++) { if (x + outstartx >= dst.Width) continue; for (int y = 0; y < outheight; y++) { if (y + outstarty >= dst.Height) continue; int offindex = x + y * outwidth; int dstindex = x + outstartx + (y + outstarty) * dst.Width; int dstIndexX = dstindex / dst.Width; int dstIndexY = dstindex % dst.Width; if (dbldstdib[offindex].Alpha > 1) { //handles wrap around for non-convex transformations dstdib[dstindex].R = byterange(dbldstdib[offindex].Red / dbldstdib[offindex].Alpha); dstdib[dstindex].G = byterange(dbldstdib[offindex].Green / dbldstdib[offindex].Alpha); dstdib[dstindex].B = byterange(dbldstdib[offindex].Blue / dbldstdib[offindex].Alpha); } else { //Color dstColor = dst.GetPixel(dstIndexX, dstIndexY); dstdib[dstindex].R = byterange(dbldstdib[offindex].Red + (1 - dbldstdib[offindex].Alpha) * (double)dstdib[dstindex].R); dstdib[dstindex].G = byterange(dbldstdib[offindex].Green + (1 - dbldstdib[offindex].Alpha) * (double)dstdib[dstindex].G); dstdib[dstindex].B = byterange(dbldstdib[offindex].Blue + (1 - dbldstdib[offindex].Alpha) * (double)dstdib[dstindex].B); } dst.SetPixel(dstIndexY,dstIndexX , Color.FromArgb(dstdib[dstindex].R, dstdib[dstindex].G, dstdib[dstindex].B)); } } //:D return ; } double PixOverlap(AafPnt[] p) { polyoverlapsize = 0; polysortedsize = 0; double minx, maxx, miny, maxy; int j; double z; for (int i = 0; i < 4; i++) { //Search for source points within the destination quadrolateral if (p[i].x >= 0 && p[i].x <= 1 && p[i].y >= 0 && p[i].y <= 1) polyoverlap[polyoverlapsize++] = p[i]; //Search for destination points within the source quadrolateral if (PtinConvexPolygon(p, corners[i])) polyoverlap[polyoverlapsize++] = corners[i]; //Search for line intersections j = ja[i]; minx = aaf_min(p[i].x, p[j].x); miny = aaf_min(p[i].y, p[j].y); maxx = aaf_max(p[i].x, p[j].x); maxy = aaf_max(p[i].y, p[j].y); if (minx < 0.0 && 0.0 < maxx) {//Cross left z = p[i].y - p[i].x * (p[i].y - p[j].y) / (p[i].x - p[j].x); if (z >= 0.0 && z <= 1.0) { polyoverlap[polyoverlapsize].x = 0.0; polyoverlap[polyoverlapsize++].y = z; } } if (minx < 1.0 && 1.0 < maxx) {//Cross right z = p[i].y + (1 - p[i].x) * (p[i].y - p[j].y) / (p[i].x - p[j].x); if (z >= 0.0 && z <= 1.0) { polyoverlap[polyoverlapsize].x = 1.0; polyoverlap[polyoverlapsize++].y = z; } } if (miny < 0.0 && 0.0 < maxy) {//Cross bottom z = p[i].x - p[i].y * (p[i].x - p[j].x) / (p[i].y - p[j].y); if (z >= 0.0 && z <= 1.0) { polyoverlap[polyoverlapsize].x = z; polyoverlap[polyoverlapsize++].y = 0.0; } } if (miny < 1.0 && 1.0 < maxy) {//Cross top z = p[i].x + (1 - p[i].y) * (p[i].x - p[j].x) / (p[i].y - p[j].y); if (z >= 0.0 && z <= 1.0) { polyoverlap[polyoverlapsize].x = z; polyoverlap[polyoverlapsize++].y = 1.0; } } } //Sort the points and return the area SortPoints(); return Area(); } private double Area() { double ret = 0.0; //Loop through each triangle with respect to (0, 0) and add the cross multiplication for (int i = 0; i + 1 < polysortedsize; i++) ret += polysorted[i].x * polysorted[i + 1].y - polysorted[i + 1].x * polysorted[i].y; //Take the absolute value over 2 return AafAbs(ret) / 2.0; } private void SortPoints() { //Why even bother? if (polyoverlapsize < 3) return; //polyoverlap is a triangle, points cannot be out of order if (polyoverlapsize == 3) { polysortedsize = polyoverlapsize - 1; polysorted[0].x = polyoverlap[1].x - polyoverlap[0].x; polysorted[0].y = polyoverlap[1].y - polyoverlap[0].y; polysorted[1].x = polyoverlap[2].x - polyoverlap[0].x; polysorted[1].y = polyoverlap[2].y - polyoverlap[0].y; return; } aaf_indll root = new aaf_indll(); root.next = null; //begin sorting the points. Note that the first element is left out and all other elements are offset by it's values for (int i = 1; i < polyoverlapsize; i++) { polyoverlap[i].x = polyoverlap[i].x - polyoverlap[0].x; polyoverlap[i].y = polyoverlap[i].y - polyoverlap[0].y; aaf_indll node = root; //Loop until the point polyoverlap[i] is can be sorted (counter) clockwiswe (I'm not sure which way it's sorted) while (true) { if (node.next != null) { if (polyoverlap[i].x * polyoverlap[node.next.ind].y - polyoverlap[node.next.ind].x * polyoverlap[i].y < 0) { //Insert point before this element aaf_indll temp = node.next; node.next = new aaf_indll(); node.next.ind = i; node.next.next = temp; break; } } else { //Add point to the end of list node.next = new aaf_indll(); node.next.ind = i; node.next.next = null; break; } node = node.next; } } //We can leave out the first point because it's offset position is going to be (0, 0) polysortedsize = 0; aaf_indll node2 = root; aaf_indll temp2; //Add the sorted points to polysorted and clean up memory while (node2 != null) { temp2 = node2; node2 = node2.next; if (node2 != null) polysorted[polysortedsize++] = polyoverlap[node2.ind]; } } private bool PtinConvexPolygon(AafPnt[] p, AafPnt pt) { int dir = 0; int j; //Basically what we are doing is seeing if pt is on the same side of each face of the polygon through cross multiplication for (int i = 0; i < 4; i++) { j = ja[i]; double cross = (p[i].x - pt.x) * (p[j].y - pt.y) - (p[j].x - pt.x) * (p[i].y - pt.y); if (cross == 0) continue; if (cross > 0) { if (dir == -1) return false; dir = 1; } else { if (dir == 1) return false; dir = -1; } } return true; } int roundup(double a) { if (AafAbs(a - round(a)) < 1e-9) return round(a); else if ((int)a > a) return (int)a; else return (int)a + 1; } int rounddown(double a) { if (AafAbs(a - round(a)) < 1e-9) return round(a); else if ((int)a < a) return (int)a; else return (int)a - 1; } int round(double a) { return (int)(a + 0.5); } byte byterange(double a) { int b = round(a); if (b <= 0) return 0; else if (b >= 255) return 255; else return (byte)b; } double AafAbs(double a) { return (((a) < 0) ? (-(a)) : (a)); } double aaf_min(double a, double b) { if (a < b) return a; else return b; } double aaf_max(double a, double b) { if (a > b) return a; else return b; } } class RGBQUDA { public byte R { get; set; } public byte G { get; set; } public byte B { get; set; } }
Aaform
相关文章推荐
- C#实现图片自由变换 任意扭曲的算法
- C# 图片自由变换 任意扭曲
- C#中基于GDI+(Graphics)图像处理系列之文字或者图片水印(透明、任意角度旋转)
- 原生JS,实现图片可拖拽,并且移动四个角和四条变能够自由变换图片大小
- C# 实现图片任意裁剪以及羽化功能
- 【C#】图像的变形/变换/扭曲。用Emgu或YLScsFreeTransform(FreeImageTransformation)或MagickImage
- 图片任意角度旋转 梯形变换 任意四边形变换
- C#实现任意角度旋转图片(方法1)
- 扭曲图片(验证码)的较正处理C#代码
- C#实现任意角度旋转图片(方法1)
- C#实现任意角度旋转图片(方法1)
- C#实现任意角度旋转图片(方法2)
- C#实现任意角度旋转图片(方法2)
- 【C#/WPF】Image图片的Transform变换:平移、缩放、旋转
- 鼠标图片icon(选中物体变换图片)C#代码
- C#源码 任意语言任意大小任意字体任意排列 字符汉字取模及显示 可以自由编辑点阵 通过串口发送 pc端和手机端
- 使用.net(C#)处理图片之:旋转图片任意角度
- 巧用C#实现所有窗体控件跟随窗体尺寸自由变换
- C#任意变换图像大小
- C# 截图图片为任意