OpenGL 13 - 案例:紋理圖片拉伸與保存


此案例用來處理紋理的拉伸,並對拉伸后圖片進行保存。

拉伸效果:

一、拉伸案例 - 主流程

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 }

Demo 地址 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM