此案例用來處理紋理的拉伸,並對拉伸后圖片進行保存。
拉伸效果:
一、拉伸案例 - 主流程
1、加載原圖
2、拉伸區域的滑塊處理 -- sliderView
3、圖片拉伸繪制
4、保存圖片到本地相冊
二、拉伸,頂點/紋理坐標處理過程
1、手動指定拉伸區域、選取合適的圖元裝配方式
8個頂點,通過方式 GL_LINE_STRIP 連接繪制。--> V2 ~ V5,拉伸區域 --> 拉伸區域高度 = V5.y - V3.y
2、設置紋理寬高比 得到拉伸量
根據圖片實際size計算出紋理高,寬一直不變。
設置初識紋理高度占控件 LongLegView 高度 的 0.8
radio = 當前紋理圖片的高寬比 * 控件的寬高比 = img.height/img.width * (view.width/view.height);
紋理高度 textureHeight = textureWidth * radio;
拉伸量 delta = (newHeight - (endY - startY)) * textureHeight; --> newHeight: 拉伸后的紋理高度; startY和endY: 當前的拉伸區域上下的紋理值
3、根據傳入的開始結束紋理坐標,計算拉伸后的頂點坐標 [-1 ~ 1]
textureWidth = 0.8455;
4、換算拉伸后對應的紋理坐標 [0 ~ 1]
5、記錄拉伸后的當前值,繪制
GLKitView 的 display 方法執行,觸發delegate:glkView:drawInRect
1、准備繪制
2、清空緩沖區
3、頂點數據、紋理數據的綁定傳遞 bind()、glVertexAttribPointer()
4、開始繪制 draw
三、主要代碼
1、拉伸后紋理、頂點坐標的計算
// 獲取圖片的中間拉伸區域高度: (currentBottom - currentTop)*sliderValue + 0.5; CGFloat newHeight = (self.currentBottom - self.currentTop) * ((sender.value) + 0.5);
1 /** 2 根據當前控件的尺寸和紋理的尺寸,計算初始紋理、頂點坐標 3 4 @param size 原始紋理尺寸 5 @param startY 中間區域的開始縱坐標位置 0~1 6 @param endY 中間區域的結束縱坐標位置 0~1 7 @param newHeight 新的中間區域的高度 8 */ 9 - (void)calculateOriginTextureCoordWithTextureSize:(CGSize)size 10 startY:(CGFloat)startY 11 endY:(CGFloat)endY 12 newHeight:(CGFloat)newHeight { 13 NSLog(@"%f,%f",size.height,size.width); 14 15 // 1. 計算拉伸后的寬高比; 16 CGFloat ratio = (size.height / size.width) * 17 (self.bounds.size.width / self.bounds.size.height); 18 CGFloat rr = self.bounds.size.width / self.bounds.size.height; 19 // 2. 寬度=紋理本身寬度; 20 CGFloat textureWidth = self.currentTextureWidth; 21 // 3. 高度=紋理高度*radio(寬高比) 22 CGFloat textureHeight = textureWidth * ratio; 23 24 NSLog(@"%f,%f,%f,%f",newHeight,endY,startY,textureHeight); 25 // 4. 拉伸量 (newHeight - (endY-startY)) * 紋理高度; 26 CGFloat delta = (newHeight - (endY - startY)) * textureHeight; 27 28 // 5. 判斷紋理高度+拉伸量是否超出最大值1 29 if (textureHeight + delta >= 1) { 30 delta = 1 - textureHeight; 31 newHeight = delta / textureHeight + (endY - startY); 32 } 33 34 // 6. 紋理4個角的頂點 35 // 左上角 36 GLKVector3 pointLT = {-textureWidth, textureHeight + delta, 0}; 37 // 右上角 38 GLKVector3 pointRT = {textureWidth, textureHeight + delta, 0}; 39 // 左下角 40 GLKVector3 pointLB = {-textureWidth, -textureHeight - delta, 0}; 41 // 右下角 42 GLKVector3 pointRB = {textureWidth, -textureHeight - delta, 0}; 43 44 // 中間矩形區域的頂點 45 CGFloat tempStartYCoord = textureHeight - 2 * textureHeight * startY; 46 CGFloat tempEndYCoord = textureHeight - 2 * textureHeight * endY; 47 48 CGFloat startYCoord = MIN(tempStartYCoord, textureHeight); 49 CGFloat endYCoord = MAX(tempEndYCoord, -textureHeight); 50 51 // 中間部分左上角 52 GLKVector3 centerPointLT = {-textureWidth, startYCoord + delta, 0}; 53 // 中間部分右上角 54 GLKVector3 centerPointRT = {textureWidth, startYCoord + delta, 0}; 55 // 中間部分左下角 56 GLKVector3 centerPointLB = {-textureWidth, endYCoord - delta, 0}; 57 // 中間部分右下角 58 GLKVector3 centerPointRB = {textureWidth, endYCoord - delta, 0}; 59 60 // --紋理的上面兩個頂點 61 // 頂點V0的頂點坐標以及紋理坐標; 62 self.vertices[0].positionCoord = pointRT; 63 self.vertices[0].textureCoord = GLKVector2Make(1, 1); 64 65 // 頂點V1的頂點坐標以及紋理坐標; 66 self.vertices[1].positionCoord = pointLT; 67 self.vertices[1].textureCoord = GLKVector2Make(0, 1); 68 69 // -- 中間區域的4個頂點 70 //頂點V2的頂點坐標以及紋理坐標; 71 self.vertices[2].positionCoord = centerPointRT; 72 self.vertices[2].textureCoord = GLKVector2Make(1, 1 - startY); 73 74 // 頂點V3的頂點坐標以及紋理坐標; 75 self.vertices[3].positionCoord = centerPointLT; 76 self.vertices[3].textureCoord = GLKVector2Make(0, 1 - startY); 77 78 // 頂點V4的頂點坐標以及紋理坐標; 79 self.vertices[4].positionCoord = centerPointRB; 80 self.vertices[4].textureCoord = GLKVector2Make(1, 1 - endY); 81 82 // 頂點V5的頂點坐標以及紋理坐標; 83 self.vertices[5].positionCoord = centerPointLB; 84 self.vertices[5].textureCoord = GLKVector2Make(0, 1 - endY); 85 86 // 紋理的下面兩個頂點 87 // 頂點V6的頂點坐標以及紋理坐標; 88 self.vertices[6].positionCoord = pointRB; 89 self.vertices[6].textureCoord = GLKVector2Make(1, 0); 90 91 // 頂點V7的頂點坐標以及紋理坐標; 92 self.vertices[7].positionCoord = pointLB; 93 self.vertices[7].textureCoord = GLKVector2Make(0, 0); 94 95 // 保存臨時值 96 self.currentTextureStartY = startY; 97 self.currentTextureEndY = endY; 98 self.currentNewHeight = newHeight; 99 }
2、圖片保存至相冊
1 // 從幀緩存區中獲取紋理圖片文件,獲取當前的渲染結果 2 - (UIImage *)createResult { 3 4 // 1. 根據屏幕上顯示結果, 重新獲取頂點/紋理坐標 5 [self resetTextureWithOriginWidth:self.currentImageSize.width 6 originHeight:self.currentImageSize.height 7 topY:self.currentTextureStartY 8 bottomY:self.currentTextureEndY 9 newHeight:self.currentNewHeight]; 10 11 // 2.綁定幀緩存區; 12 glBindFramebuffer(GL_FRAMEBUFFER, self.tmpFrameBuffer); 13 // 3.獲取新的圖片Size 14 CGSize imageSize = [self newImageSize]; 15 // 4.從幀緩存中獲取拉伸后的圖片; 16 UIImage *image = [self imageFromTextureWithWidth:imageSize.width height:imageSize.height]; 17 // 5. 將幀緩存綁定0,清空; 18 glBindFramebuffer(GL_FRAMEBUFFER, 0); 19 20 // 6. 返回拉伸后的圖片 21 return image; 22 }
1 /** 2 根據當前屏幕上的顯示,重新創建紋理 3 4 @param originWidth 紋理的原始實際寬度 5 @param originHeight 紋理的原始實際高度 6 @param topY 0 ~ 1,拉伸區域的頂邊的縱坐標 7 @param bottomY 0 ~ 1,拉伸區域的底邊的縱坐標 8 @param newHeight 0 ~ 1,拉伸區域的新高度 9 */ 10 - (void)resetTextureWithOriginWidth:(CGFloat)originWidth 11 originHeight:(CGFloat)originHeight 12 topY:(CGFloat)topY 13 bottomY:(CGFloat)bottomY 14 newHeight:(CGFloat)newHeight { 15 16 // 1.新的紋理尺寸(新紋理圖片的寬高) 17 GLsizei newTextureWidth = originWidth; 18 GLsizei newTextureHeight = originHeight * (newHeight - (bottomY - topY)) + originHeight; 19 20 // 2.高度變化百分比 21 CGFloat heightScale = newTextureHeight / originHeight; 22 23 // 3.在新的紋理坐標下,重新計算 topY、bottomY 24 CGFloat newTopY = topY / heightScale; 25 CGFloat newBottomY = (topY + newHeight) / heightScale; 26 27 // 4.創建頂點數組與紋理數組(邏輯與calculateOriginTextureCoordWithTextureSize 中關於紋理坐標以及頂點坐標邏輯是一樣的) 28 SenceVertex *tmpVertices = malloc(sizeof(SenceVertex) * kVerticesCount); 29 tmpVertices[0] = (SenceVertex){{-1, 1, 0}, {0, 1}}; 30 tmpVertices[1] = (SenceVertex){{1, 1, 0}, {1, 1}}; 31 tmpVertices[2] = (SenceVertex){{-1, -2 * newTopY + 1, 0}, {0, 1 - topY}}; 32 tmpVertices[3] = (SenceVertex){{1, -2 * newTopY + 1, 0}, {1, 1 - topY}}; 33 tmpVertices[4] = (SenceVertex){{-1, -2 * newBottomY + 1, 0}, {0, 1 - bottomY}}; 34 tmpVertices[5] = (SenceVertex){{1, -2 * newBottomY + 1, 0}, {1, 1 - bottomY}}; 35 tmpVertices[6] = (SenceVertex){{-1, -1, 0}, {0, 0}}; 36 tmpVertices[7] = (SenceVertex){{1, -1, 0}, {1, 0}}; 37 38 39 /// 下面開始渲染到紋理的流程 40 41 // 1. 生成幀緩存區; 42 GLuint frameBuffer; 43 GLuint texture; 44 // glGenFramebuffers 生成幀緩存區對象名稱; 45 glGenFramebuffers(1, &frameBuffer); 46 // glBindFramebuffer 綁定一個幀緩存區對象; 47 glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); 48 49 // 2. 生成紋理ID,綁定紋理; 50 // glGenTextures 生成紋理ID 51 glGenTextures(1, &texture); 52 // glBindTexture 將一個紋理綁定到紋理目標上; 53 glBindTexture(GL_TEXTURE_2D, texture); 54 // glTexImage2D 指定一個二維紋理圖像; 55 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, newTextureWidth, newTextureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); 56 57 // 3. 設置紋理相關參數 58 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 59 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 60 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 61 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 62 63 // 4. 將紋理圖像加載到幀緩存區對象上; 64 /* 65 glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) 66 target: 指定幀緩沖目標,符合常量必須是GL_FRAMEBUFFER; 67 attachment: 指定附着紋理對象的附着點GL_COLOR_ATTACHMENT0 68 textarget: 指定紋理目標, 符合常量:GL_TEXTURE_2D 69 teture: 指定要附加圖像的紋理對象; 70 level: 指定要附加的紋理圖像的mipmap級別,該級別必須為0。 71 */ 72 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); 73 74 // 5. 設置視口尺寸 75 glViewport(0, 0, newTextureWidth, newTextureHeight); 76 77 // 6. 獲取着色器程序 78 GLuint program = [LongLegHelper programWithShaderName:@"spring"]; 79 glUseProgram(program); 80 81 // 7. 獲取參數ID 82 GLuint positionSlot = glGetAttribLocation(program, "Position"); 83 GLuint textureSlot = glGetUniformLocation(program, "Texture"); 84 GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords"); 85 86 // 8. 傳值 87 glActiveTexture(GL_TEXTURE0); 88 glBindTexture(GL_TEXTURE_2D, self.baseEffect.texture2d0.name); 89 glUniform1i(textureSlot, 0); 90 91 // 9.初始化緩存區 92 LongLegVertexAttribArrayBuffer *vbo = [[LongLegVertexAttribArrayBuffer alloc] initWithAttribStride:sizeof(SenceVertex) numberOfVertices:kVerticesCount data:tmpVertices usage:GL_STATIC_DRAW]; 93 94 // 10.准備繪制,將紋理/頂點坐標傳遞進去; 95 [vbo prepareToDrawWithAttrib:positionSlot numberOfCoordinates:3 attribOffset:offsetof(SenceVertex, positionCoord) shouldEnable:YES]; 96 [vbo prepareToDrawWithAttrib:textureCoordsSlot numberOfCoordinates:2 attribOffset:offsetof(SenceVertex, textureCoord) shouldEnable:YES]; 97 98 // 11. 繪制 99 [vbo drawArrayWithMode:GL_TRIANGLE_STRIP startVertexIndex:0 numberOfVertices:kVerticesCount]; 100 101 // 12.解綁緩存 102 glBindFramebuffer(GL_FRAMEBUFFER, 0); 103 // 13.釋放頂點數組 104 free(tmpVertices); 105 106 // 14.保存臨時的紋理對象/幀緩存區對象; 107 self.tmpTexture = texture; 108 self.tmpFrameBuffer = frameBuffer; 109 }
1 // 返回某個紋理對應的 UIImage,調用前先綁定對應的幀緩存 2 - (UIImage *)imageFromTextureWithWidth:(int)width height:(int)height { 3 4 // 1.綁定幀緩存區; 5 glBindFramebuffer(GL_FRAMEBUFFER, self.tmpFrameBuffer); 6 7 // 2.將幀緩存區內的圖片紋理繪制到圖片上; 8 int size = width * height * 4; 9 GLubyte *buffer = malloc(size); 10 11 /* 12 13 glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels); 14 @功能: 讀取像素(理解為將已經繪制好的像素,從顯存中讀取到內存中;) 15 @參數解讀: 16 參數x,y,width,height: xy坐標以及讀取的寬高; 17 參數format: 顏色格式; GL_RGBA; 18 參數type: 讀取到的內容保存到內存所用的格式;GL_UNSIGNED_BYTE 會把數據保存為GLubyte類型; 19 參數pixels: 指針,像素數據讀取后, 將會保存到該指針指向的地址內存中; 20 21 注意: pixels指針,必須保證該地址有足夠的可以使用的空間, 以容納讀取的像素數據; 例如一副256 * 256的圖像,如果讀取RGBA 數據, 且每個數據保存在GLUbyte. 總大小就是 256 * 256 * 4 = 262144字節, 即256M; 22 int size = width * height * 4; 23 GLubyte *buffer = malloc(size); 24 */ 25 glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 26 27 // 使用data和size 數組來訪問buffer數據; 28 /* 29 CGDataProviderRef CGDataProviderCreateWithData(void *info, const void *data, size_t size, CGDataProviderReleaseDataCallback releaseData); 30 @功能: 新的數據類型, 方便訪問二進制數據; 31 @參數: 32 參數info: 指向任何類型數據的指針, 或者為Null; 33 參數data: 數據存儲的地址,buffer 34 參數size: buffer的數據大小; 35 參數releaseData: 釋放的回調,默認為空; 36 37 */ 38 CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, size, NULL); 39 // 每個組件的位數; 40 int bitsPerComponent = 8; 41 // 像素占用的比特數4 * 8 = 32; 42 int bitsPerPixel = 32; 43 // 每一行的字節數 44 int bytesPerRow = 4 * width; 45 // 顏色空間格式; 46 CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); 47 // 位圖圖形的組件信息 - 默認的 48 CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; 49 // 顏色映射 50 CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; 51 52 // 3.將幀緩存區里像素點繪制到一張圖片上; 53 /* 54 CGImageCreate(size_t width, size_t height,size_t bitsPerComponent, size_t bitsPerPixel, size_t bytesPerRow,CGColorSpaceRef space, CGBitmapInfo bitmapInfo, CGDataProviderRef provider,const CGFloat decode[], bool shouldInterpolate,CGColorRenderingIntent intent); 55 @功能:根據你提供的數據創建一張位圖; 56 注意:size_t 定義的是一個可移植的單位,在64位機器上為8字節,在32位機器上是4字節; 57 參數width: 圖片的寬度像素; 58 參數height: 圖片的高度像素; 59 參數bitsPerComponent: 每個顏色組件所占用的位數, 比如R占用8位; 60 參數bitsPerPixel: 每個顏色的比特數, 如果是RGBA則是32位, 4 * 8 = 32位; 61 參數bytesPerRow :每一行占用的字節數; 62 參數space:顏色空間模式,CGColorSpaceCreateDeviceRGB 63 參數bitmapInfo:kCGBitmapByteOrderDefault 位圖像素布局; 64 參數provider: 圖片數據源提供者, 在CGDataProviderCreateWithData ,將buffer 轉為 provider 對象; 65 參數decode: 解碼渲染數組, 默認NULL 66 參數shouldInterpolate: 是否抗鋸齒; 67 參數intent: 圖片相關參數;kCGRenderingIntentDefault 68 69 */ 70 CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); 71 72 // 4. 此時的 imageRef 是上下顛倒的,調用 CG 的方法重新繪制一遍,翻轉過來 73 // 創建一個圖片context 74 UIGraphicsBeginImageContext(CGSizeMake(width, height)); 75 CGContextRef context = UIGraphicsGetCurrentContext(); 76 // 將圖片繪制上去 77 CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); 78 // 從context中獲取圖片 79 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 80 // 結束圖片context處理 81 UIGraphicsEndImageContext(); 82 83 // 釋放buffer 84 free(buffer); 85 // 返回圖片 86 return image; 87 }