先看效果看
加載了一張image,根據四個頂點任意變換。
知識點:1.BitmapContext 2.矩陣變換
一.什么是BitmapContext
官方解釋:
The number of components for each pixel in a bitmap graphics context is specified by a color space, defined by a CGColorSpaceRef. The bitmap graphics context specifies whether the bitmap should contain an alpha channel, and how the bitmap is generated.
通俗來講:
首先,我們根據圖片創建一個BitmapContext,把一張圖片繪制到這個BitmapContext上。這時你可以把圖片看成是由很多個彩色的點組成的。
圖片局部放大后就形成了一個表格。每個單元代表一個像素點其中包含了4個元素:alpha(透明度)紅 綠 藍
每個像素點包含的信息
1.創建
CGContextRef contexRef = CGBitmapContextCreate(void *data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef space, uint32_t bitmapInfo);
data: 創建BitmapContext所需的內存空間,由malloc創建
width: 圖片的寬度
height: 圖片的高度
bitsPerComponent: data中的每個數據所占的字節數
bytesPerRow: 圖片每行的位數 = 圖片列數*4(因為每個點有4個通道)
space: 顏色區間
bitmapInfo: bitmap類型,一般選擇PremultipliedFirst(ARGB)
2.讀取圖片中的所有像素點數據
unsigned char* needData = malloc(需要多大); needData = CGBitmapContextGetData(contexRef);
3.讀取某個點的內容
int alpha = needData[4*n]; int red = needData[4*n+1]; int green = needData[4*n+2]; int blue = needData[4*n+3];
一個點由4個信息表示
4.修改某個點的內容
newData[4*n ] = alpha; newData[4*n + 1] = red; newData[4*n + 2] = green; newData[4*n + 3] = blue;
5.把data數據從新編程UIImage
CGImageRef cgImage = CGBitmapContextCreateImage(newContext); _imageView.image = [UIImage imageWithCGImage:cgImage ];
有個了幾個函數,接下來就需要你來指定一套規則,獲取到的所有像素點,然后從組元素生成一張新的圖片。 而這套規則就是下面對應的矩陣變換
二.矩陣變換 注:這個部分原理居多,若有不是可以直接跳過copy代碼,不影響你功能的實現
1.什么是矩陣
若用一個行向量[X1 X2 ….Xn]表示n維空間中的一個點的坐標,那么n維空間中m個點坐標就可以表示為一個向量集合,這個集合就是一個矩陣。
也就是說我們二維圖片中所有的(x,y)坐標的所有點組成的一個集合就是一個矩陣.
圖片和其中的某些點
2.圖形變換
我們對圖像的常見操作其實就是對圖形進行評議、旋轉、縮放等矩陣變換,實質上就是改變了各個點的坐標。
所有的圖形點由老的x y坐標變成了新的x` y`坐標
3.常見的圖形變換
基本的二維變換可包括旋轉、縮放、扭曲,和平移四種,
而這些幾何運算則可以轉換為一些基本的矩陣運算:
這幾個變換都是線性的,但平移運算不是線性的,不能通過2*2矩陣運算完成。若要將點 (2, 1)在 x 方向將其平移 3 個單位,在 y 方向將其平移 4 個單位。 可通過先使用矩陣乘法再使用矩陣加法來完成此操作。
綜合這幾種基本運算,數學家們將其統一為一個3*3矩陣,存儲形式如下:
由於表示仿射變換的矩陣的第三列總是(0,0,1),在存儲矩陣的時候,大多只存成一個2*3的數組。
三.從由4個新頂點組成的圖片中找到對應圖形變換的規律
方法1.通過矩陣乘法計算
原始坐標和變換好后的四個頂點坐標我們都是已知的,可以通過已知數據建立一個4元一次方程式,得出a b c d四個位置數然后計算出新的圖形坐標。此方式適用於做數學應用題。
方法2.通過x y的線性變換找出規律
我們之前拿到的圖片都是很規規矩矩的,是一個矩形。加入得到x相對於自己圖形空間的坐標的話,這個x的坐標就是 (7/11 * 圖片寬度,4/8 *圖片高度)
如果一個圖形變化了,那么這個j 和i向量就不在是以前相互垂直90度了,然后里面所有的點坐標可能就位移了。
比如我要把x點移動到b點怎么實現呢?
我們可以把上面的直角坐標系想象成一輛公交車,所有的點都坐在公交車里面,我們在車里沒有動,然后公交車到達目的地后,我們相對於城市而言已經走了很遠很遠了。
就像這樣,x點是不是就移動到b點了。
重點:所以計算的思路就是,通過老的直角坐標系的規律獲取到每一個點的位置,再找出新的坐標系的規律,把點移動上去,就完成了第一張圖片所達到的效果了
下面開始找規律
假如新的圖片變成了這樣
由於4個頂點已知,那么這4條邊有多長,每個邊上面的點的具體坐標我們就可以確認下來。
由上下兩條邊線確認兩個頂點,我們就可以確認某一個X坐標上面y的線性變化規律。 比如以前圖片位於x方面1/4位置的坐標點y方向線性變換在新圖中的線性變換就變成了左圖y’. X坐標固定,這時候我們只需要傳入以前坐標中的y值,就能得到相對於自己寬度1/4出的新圖中所有的坐標點。
同理可得,以前2/4 3/4等等所有頂點方向的y線性變換規律我們都可以得到。
然后我們要做的就是通過循環遍歷出所有向下頂點然后得到第n個位置處的所有y方向的點,從而得出所有的坐標。然后這一組坐標的就組成了新的圖片。
最后附上關鍵代碼
//數組包含了一次是 左上 右上 右下 左下 4個點位置不同,圖片顯示的就不同
1 -(void)changeImageByPoints:(NSArray *)pointArray{ 2 UIImage * image = _image; //全局需要變換的圖片 3 float width = CGImageGetWidth(image.CGImage); 4 float height = CGImageGetHeight(image.CGImage); 5 6 CGPoint p0 = [pointArray[0]CGPointValue]; 7 CGPoint p1 = [pointArray[1]CGPointValue]; 8 CGPoint p2 = [pointArray[2]CGPointValue]; 9 CGPoint p3 = [pointArray[3]CGPointValue]; 10 11 //痛覺相對於父視圖的絕對4個頂點計算出新的寬度和高度 12 float minLeft = MIN(MIN(p0.x, p1.x), MIN(p2.x, p3.x)); 13 float minTop = MIN(MIN(p0.y, p1.y), MIN(p2.y, p3.y)); 14 float shapW = KINT((MAX(MAX(p0.x, p1.x), MAX(p2.x, p3.x)) - minLeft)); 15 float shapH = KINT((MAX(MAX(p0.y, p1.y), MAX(p2.y, p3.y)) - minTop)); 16 17 //change point relative to image not superview 18 p0.x = p0.x - minLeft; 19 p1.x = p1.x - minLeft; 20 p2.x = p2.x - minLeft; 21 p3.x = p3.x - minLeft; 22 p0.y = p0.y - minTop; 23 p1.y = p1.y - minTop; 24 p2.y = p2.y - minTop; 25 p3.y = p3.y - minTop; 26 27 //創建一個bitmapcontext 28 if (!_first) { 29 needData = malloc(KINT(width)* KINT(height) * 4); 30 CGContextRef imageContext = CGBitmapContextCreate(needData, width, height, 8, width * 4, CGImageGetColorSpace(image.CGImage), CGImageGetAlphaInfo(image.CGImage)); 31 CGContextDrawImage(imageContext, CGRectMake(0, 0, width, height), image.CGImage); 32 data = malloc(KINT(width) * KINT(height) * 4); 33 data = CGBitmapContextGetData(imageContext); 34 _first = YES; 35 } 36 37 //初始化新的圖片需要的data 38 unsigned char* shapeData = malloc(shapW * shapH * 4); 39 for (int i = 0; i < shapH -1; i ++) { 40 for (int j = 0; j < shapW -1; j++) { 41 int offset = (i * shapW + j) * 4; 42 shapeData[offset] = 255; 43 shapeData[offset + 1] = 255; 44 shapeData[offset + 2] = 255; 45 shapeData[offset + 3] = 255; 46 } 47 } 48 49 //給data添加對應的像素值 50 for (int i = 0; i < height -1; i++) { 51 for (int j = 0; j < width -1; j++) { 52 CGPoint originPoint = CGPointMake(j, i); 53 int originOffset = (i * width + j) * 4; 54 // 計算原圖每個點在新圖中的位置 55 float xFunc = (float)originPoint.x / (float)width; 56 float yFunc = (float)originPoint.y / (float)height; 57 58 float delx = (p1.x - p0.x) * xFunc; 59 float dely = (p1.y - p0.y) * xFunc; 60 CGPoint topPoint = CGPointMake(p0.x + delx, p0.y + dely); 61 62 delx = (p2.x - p3.x) * xFunc; 63 dely = (p2.y - p3.y) * xFunc; 64 CGPoint bottomPoint = CGPointMake(p3.x + delx, p3.y + dely); 65 66 delx = (bottomPoint.x - topPoint.x) * yFunc; 67 dely = (bottomPoint.y - topPoint.y) * yFunc; 68 69 CGPoint newPoint = CGPointMake(topPoint.x + delx, topPoint.y + dely); 70 71 int newOffset = ((KINT(newPoint.y) * shapW + KINT(newPoint.x))) * 4; 72 73 //give shapeView new value 74 shapeData[newOffset] = data[originOffset]; 75 shapeData[newOffset + 1] = data[originOffset + 1]; 76 shapeData[newOffset + 2] = data[originOffset + 2]; 77 shapeData[newOffset + 3] = data[originOffset + 3]; 78 79 } 80 } 81 //創建新圖片 82 CGContextRef newContext = CGBitmapContextCreate(shapeData, shapW, shapH, 8, shapW * 4, CGImageGetColorSpace(image.CGImage), CGImageGetAlphaInfo(image.CGImage)); 83 84 CGImageRef cgImage = CGBitmapContextCreateImage(newContext); 85 _imageView.image = [UIImage imageWithCGImage:cgImage ]; //這個_imageView就是貼上viewcontroller上面的UIImageview 86 _imageView.frame = CGRectMake(minLeft, minTop, shapW, shapH); 87 CGContextRelease(newContext); 88 CGImageRelease(cgImage); 89 free(shapeData); 90 }
