Put a sketch of effect above all, the leg of big power power becomes fine long have.

1. The tensile

1.1 Stretching principle

The vertex coordinates and texture coordinates we used earlier look like this, as shown below. In this case, the vertex coordinates and texture coordinates are in accordance with a certain contrast relationship, and the picture is fully displayed in accordance with the original image proportion.And then we wondered, could the effect of local stretching of long legs be achieved by changing the contrast between the local parts? We divide the vertex coordinate data like this, as shown in the figure below. We will use eight vertex coordinates, and we will also use eight texture coordinates. The region V2, V3, V4, V5, is the region we can stretch. According to the height of stretching, set the corresponding vertex coordinates, texture coordinates to achieve the effect of stretching.When stretching, change vertex coordinates and texture coordinates

1.2 How to calculate texture coordinates

Now that you know that stretching is done by setting vertex coordinates and texture coordinates. So it’s time to show you how to compute coordinates. Defines a method that calculates texture coordinates based on the size of the current control and the size of the texture.

  • Size Specifies the size of the original texture
  • The starting ordinate of the startY middle region ranges from 0 to 1
  • The end ordinate of the endY region is 0 to 1
  • Parameter newHeight New middle region height

First calculate the stretch of the texturedelta.

CGFloat delta = (newHeight - (endY -  startY)) * textureHeight;
Copy the code

Then you can set the coordinates according to the Settings below.

GLKVector3 pointLT = {-textureWidth, textureHeight + delta, 0}; // GLKVector3 pointRT = {textureWidth, textureHeight + delta, 0}; GLKVector3 pointLB = {-textureWidth, -textureheight -delta, 0}; GLKVector3 pointRB = {textureWidth, -textureheight-delta, 0}; // 0.7-2 * 0.7 * 0.25 CGFloat tempStartYCoord = textureHeight - 2 * textureHeight * startY; CGFloat tempEndYCoord = textureHeight - 2 * textureHeight * endY; CGFloat startYCoord = MIN(tempStartYCoord, textureHeight); CGFloat endYCoord = MAX(tempEndYCoord, -textureHeight); GLKVector3 centerPointLT = {-textureWidth, startYCoord + delta, 0}; GLKVector3 centerPointRT = {textureWidth, startYCoord + delta, 0}; GLKVector3 centerPointLB = {-textureWidth, endycoord-delta, 0}; GLKVector3 centerPointRB = {textureWidth, endycoord-delta, 0}; // Vertex coordinates of V0 and texture coordinates; self.vertices[0].positionCoord = pointRT; self.vertices[0].textureCoord = GLKVector2Make(1, 1); // Vertex V1 vertex coordinates and texture coordinates; self.vertices[1].positionCoord = pointLT; self.vertices[1].textureCoord = GLKVector2Make(0, 1); // 4 vertices in the middle region // Vertex coordinates of vertex V2 and texture coordinates; self.vertices[2].positionCoord = centerPointRT; self.vertices[2].textureCoord = GLKVector2Make(1, 1 - startY); // Vertex V3 vertex coordinates and texture coordinates; self.vertices[3].positionCoord = centerPointLT; self.vertices[3].textureCoord = GLKVector2Make(0, 1 - startY); // Vertex V4's vertex coordinates and texture coordinates; self.vertices[4].positionCoord = centerPointRB; self.vertices[4].textureCoord = GLKVector2Make(1, 1 - endY); // Vertex V5 coordinates and texture coordinates; self.vertices[5].positionCoord = centerPointLB; self.vertices[5].textureCoord = GLKVector2Make(0, 1 - endY); // The next two vertices of the texture // the vertex coordinates of vertex V6 and the texture coordinates; self.vertices[6].positionCoord = pointRB; self.vertices[6].textureCoord = GLKVector2Make(1, 0); // Vertex coordinates and texture coordinates for vertex V7; self.vertices[7].positionCoord = pointLB; self.vertices[7].textureCoord = GLKVector2Make(0, 0);Copy the code

Well, the core idea of long legs is introduced here, how to combine the SPECIFIC OPERATION of THE UI, please refer to demo.

2. Save the picture

The procedure for saving pictures is as follows.

2.1 Texture coordinates are calculated

Before we save the image, we need to realize that the image is stretched and displayed using baseEffect. There is no way to get images from baseEffect to save. But we can get the current vertex coordinates and texture coordinates. Then GLSL can use these coordinates to draw the stretched image, save the image data to the frame buffer, and then extract the data from the buffer to get a new image. Refer to the filter chain in GPUImage for the flow. The new texture coordinates are calculated as follows:

- (void)resetTextureWithOriginWidth:(CGFloat)originWidth originHeight:(CGFloat)originHeight topY:(CGFloat)topY bottomY:(CGFloat)bottomY newHeight:(CGFloat)newHeight { //1. GLsizei newTextureWidth = originWidth; GLsizei newTextureWidth = originWidth; GLsizei newTextureHeight = originHeight * (newHeight - (bottomY - topY)) + originHeight; //2. Height change percentage CGFloat heightScale = newTextureHeight/originHeight; //3. Recalculate topY, bottomY CGFloat newTopY = topY/heightScale at the new texture coordinates; CGFloat newBottomY = (topY + newHeight) / heightScale; / / 4. Create a vertex arrays and texture (logic and calculateOriginTextureCoordWithTextureSize on texture coordinates, and vertex coordinate logic is the same) SenceVertex * tmpVertices = malloc(sizeof(SenceVertex) * kVerticesCount); tmpVertices[0] = (SenceVertex){{-1, 1, 0}, {0, 1}}; tmpVertices[1] = (SenceVertex){{1, 1, 0}, {1, 1}}; tmpVertices[2] = (SenceVertex){{-1, -2 * newTopY + 1, 0}, {0, 1 - topY}}; tmpVertices[3] = (SenceVertex){{1, -2 * newTopY + 1, 0}, {1, 1 - topY}}; tmpVertices[4] = (SenceVertex){{-1, -2 * newBottomY + 1, 0}, {0, 1 - bottomY}}; tmpVertices[5] = (SenceVertex){{1, -2 * newBottomY + 1, 0}, {1, 1 - bottomY}}; tmpVertices[6] = (SenceVertex){{-1, -1, 0}, {0, 0}}; tmpVertices[7] = (SenceVertex){{1, -1, 0}, {1, 0}}; . }Copy the code

2.2 Loading into the frame cache

Use glFramebufferTexture2D to load the texture image onto the frame cache object.

  • Target: specifies the frame buffer target. The constant must be GL_FRAMEBUFFER.
  • Attachment: GL_COLOR_ATTACHMENT0 specifies the attachment point of the attached texture object
  • Textarget: Specifies the texture target, consistent with the constant GL_TEXTURE_2D
  • Teture: Specifies the texture object to attach the image to;
  • Level: Specifies the MIPMap level of the texture image to attach, which must be 0.
   /*
     glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)
     */
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
Copy the code

2.3 Obtaining Images

The code to get the image from the frame buffer is as follows:

- (UIImage *)imageFromTextureWithWidth:(int)width height:(int)height { //1. Bind frame cache; glBindFramebuffer(GL_FRAMEBUFFER, self.tmpFrameBuffer); //2. Draw the image texture in the frame cache onto the image; int size = width * height * 4; GLubyte *buffer = malloc(size); /* glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels); @ function: read pixels (read pixels that have been drawn from video memory to memory;) X,y,width,height: xy coordinates and read the width and height; Parameter format: color format. GL_RGBA; Parameter type: the format in which the read is saved to memory; GL_UNSIGNED_BYTE stores data as GLubyte; Parameter Pixels: Pointer. After data is read, it is stored in the address memory to which the pointer points. Note: Pixels pointer: Ensure that the address has enough usable space to accommodate the pixel data read; For example, a 256 * 256 image, if RGBA data is read and each data is stored in GLUbyte. The total size is 256 * 256 * 4 = 262144 bytes, namely 256M; int size = width * height * 4; GLubyte *buffer = malloc(size); */ glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); // Use data and size arrays to access buffer data; /* CGDataProviderRef CGDataProviderCreateWithData(void *info, const void *data, size_t size, CGDataProviderReleaseDataCallback releaseData); @ function: new data type, easy access to binary data; @ Parameter: parameter info: pointer to any type of data, or Null; Parameter data: indicates the address of the data store. Parameter size: indicates the size of the buffer. Parameter releaseData: Release callback, null by default; */ CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, size, NULL); // The number of bits per component; int bitsPerComponent = 8; // The number of bits occupied by a pixel is 4 * 8 = 32; int bitsPerPixel = 32; Int bytesPerRow = 4 * width; // Color space format; CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); / / bitmap graphics component information - the default CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault; / / color mapping CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; //3. Draw the pixels in the frame buffer onto an image; /* 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); @ Function: create a bitmap according to the data you provide; Note :size_t defines a portable unit of 8 bytes on 64-bit machines and 4 bytes on 32-bit machines; Parameter width: the width of the image pixels; Height: The height pixel of the image; Parameter bitsPerComponent: The number of bits occupied by each color component, for example, R occupies 8 bits; Parameter bitsPerPixel: The number of bits per color, 32 bits if RGBA, 4 * 8 = 32 bits; Parameter bytesPerRow: the number of bytesPerRow; Parameter space, color space model, bitmapInfo CGColorSpaceCreateDeviceRGB parameters: kCGBitmapByteOrderDefault bitmap pixel layout; Parameters for the provider: image data provider in CGDataProviderCreateWithData, a buffer to the provider object; Parameter decode: decode render array, default NULL parameter shouldInterpolate: anti-aliasing or not; Parameter intent: image related parameter; kCGRenderingIntentDefault */ CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); / / 4. At this point the imageRef is upside down, call the CG method to draw again, just over / / create a graphics context UIGraphicsBeginImageContext (CGSizeMake (width, height)); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); / / to get pictures from context UIImage * image = UIGraphicsGetImageFromCurrentImageContext (); / / end graphics context handle UIGraphicsEndImageContext (); // Release buffer free(buffer); // return image; }Copy the code