1.滤镜链

在一个复合滤镜中,多种滤镜效果处理时,通常都是图片 -> 设置顶点/纹理坐标 -> 滤镜效果处理 -> 帧缓冲区 -> 新的纹理 -> 滤镜效果处理 -> 帧缓冲区 -> 新的纹理。
此过程就是滤镜链。
思考:那如何将滤镜处理完之后存到帧缓冲区,并生成纹理的呢?

2.滤镜处理 -> 帧缓冲区 -> 新的纹理 -> 相册

2.1 步骤

  1. 根据屏幕上的显示,重新获取顶点坐标和纹理坐标,并生成新的纹理(将纹理加载到帧缓冲区中)

    - 根据屏幕的显示,设置顶点坐标与纹理坐标:在滤镜处理时不管使用什么方式;
    (GLKit、GLSL、GPUImage等)都可以获取当前的顶点坐标与纹理坐标。
    - 生成帧缓存区,并绑定;
    - 生成纹理ID,绑定纹理;
    - 将纹理加载到帧缓冲区中;
    - 设置、获取着色器,将顶点着色器和片元着色器编译、链接;
    - 传值,将顶点着色器和片元着色器需要用到的参数出入进去;
    - 准备绘制,绘制。
    
  2. 在帧缓冲区中获取图片(UIImage)

    - 绑定帧缓冲区;
    - 将帧缓存区内的图片纹理绘制到图片上;使用到了glReadPixels和CGDataProviderRef方法,
    glReadPixels:将已经绘制好的像素,从显存中读取到内存中;
    CGDataProviderCreateWithData : 生成新的数据类型, 方便访问二进制数据;- 将帧缓存区里像素点绘制到一张图片上;
    - 此时的 imageRef 是上下颠倒的,调用 CG 的方法重新绘制一遍,刚好翻转过来。
    
  3. 将图片保存到相册

    使用PHPhotoLibrary将图片保存到系统相册。
    

2.2代码的实现

2.2.1生成纹理部分

顶点着色器:

//顶点坐标
attribute vec4 Position;
//纹理坐标
attribute vec2 TextureCoords;
//需要传入片元着色器的纹理坐标
varying vec2 TextureCoordsVarying;void main (void) {gl_Position = Position;TextureCoordsVarying = TextureCoords;
}

片元着色器:

//声明高精度float
precision highp float;
//纹理
uniform sampler2D Texture;
//纹理坐标
varying vec2 TextureCoordsVarying;void main (void) {vec4 mask = texture2D(Texture, TextureCoordsVarying);gl_FragColor = vec4(mask.rgb, 1.0);
}

获取顶点坐标与纹理坐标:

/**
着色器的编译、链接
*/
//加载shader、链接
-(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag
{//1.定义2个零时着色器对象GLuint verShader, fragShader;//创建programGLint program = glCreateProgram();//2.编译顶点着色程序、片元着色器程序//参数1:编译完存储的底层地址//参数2:编译的类型,GL_VERTEX_SHADER(顶点)、GL_FRAGMENT_SHADER(片元)//参数3:文件路径[self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];[self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];//3.创建最终的程序glAttachShader(program, verShader);glAttachShader(program, fragShader);//4.释放不需要的shaderglDeleteShader(verShader);glDeleteShader(fragShader);//3. 链接 programglLinkProgram(program);//4. 检查链接是否成功GLint linkSuccess;glGetProgramiv(program, GL_LINK_STATUS, &linkSuccess);if (linkSuccess == GL_FALSE) {GLchar messages[256];glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);NSString *messageString = [NSString stringWithUTF8String:messages];NSAssert(NO, @"program链接失败:%@", messageString);}return program;}//编译shader
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{//1.读取文件路径字符串NSString* content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];const GLchar* source = (GLchar *)[content UTF8String];//2.创建一个shader(根据type类型)*shader = glCreateShader(type);//3.将着色器源码附加到着色器对象上。//参数1:shader,要编译的着色器对象 *shader//参数2:numOfStrings,传递的源码字符串数量 1个//参数3:strings,着色器程序的源码(真正的着色器程序源码)//参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的glShaderSource(*shader, 1, &source,NULL);//4.把着色器源代码编译成目标代码glCompileShader(*shader); // 查询 shader 是否编译成功GLint compileSuccess;glGetShaderiv(shader, GL_COMPILE_STATUS, &compileSuccess);if (compileSuccess == GL_FALSE) {GLchar messages[256];glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);NSString *messageString = [NSString stringWithUTF8String:messages];NSAssert(NO, @"shader编译失败:%@", messageString);}
}/**根据当前屏幕上的显示,来重新创建纹理@param originWidth 纹理的原始实际宽度@param originHeight 纹理的原始实际高度@param topY 0 ~ 1,拉伸区域的顶边的纵坐标@param bottomY 0 ~ 1,拉伸区域的底边的纵坐标@param newHeight 0 ~ 1,拉伸区域的新高度*/
- (void)resetTextureWithOriginWidth:(CGFloat)originWidthoriginHeight:(CGFloat)originHeighttopY:(CGFloat)topYbottomY:(CGFloat)bottomYnewHeight:(CGFloat)newHeight {//1.新的纹理尺寸(新纹理图片的宽高)GLsizei newTextureWidth = originWidth;GLsizei newTextureHeight = originHeight * (newHeight - (bottomY - topY)) + originHeight;//2.高度变化百分比CGFloat heightScale = newTextureHeight / originHeight;//3.在新的纹理坐标下,重新计算topY、bottomYCGFloat newTopY = topY / heightScale;CGFloat newBottomY = (topY + newHeight) / heightScale;//4.创建顶点数组与纹理数组(逻辑与calculateOriginTextureCoordWithTextureSize 中关于纹理坐标以及顶点坐标逻辑是一模一样的)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}};///下面开始渲染到纹理的流程//1. 生成帧缓存区;GLuint frameBuffer;GLuint texture;//glGenFramebuffers 生成帧缓存区对象名称;glGenFramebuffers(1, &frameBuffer);//glBindFramebuffer 绑定一个帧缓存区对象;glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);//2. 生成纹理ID,绑定纹理;//glGenTextures 生成纹理IDglGenTextures(1, &texture);//glBindTexture 将一个纹理绑定到纹理目标上;glBindTexture(GL_TEXTURE_2D, texture);//glTexImage2D 指定一个二维纹理图像;glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, newTextureWidth, newTextureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);//3. 设置纹理相关参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//4. 将纹理图像加载到帧缓存区对象上;/*glFramebufferTexture2D (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)target: 指定帧缓冲目标,符合常量必须是GL_FRAMEBUFFER;attachment: 指定附着纹理对象的附着点GL_COLOR_ATTACHMENT0textarget: 指定纹理目标, 符合常量:GL_TEXTURE_2Dteture: 指定要附加图像的纹理对象;level: 指定要附加的纹理图像的mipmap级别,该级别必须为0。*/glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);//5. 设置视口尺寸glViewport(0, 0, newTextureWidth, newTextureHeight);//6. 获取着色器程序GLuint program = [self loadShaders:"spring.vsh" Withfrag:"spring.fsh" ];glUseProgram(program);//7. 获取参数ID//(1)注意:第二参数字符串必须和shaderv.vsh中的输入变量:position保持一致GLuint position = glGetAttribLocation(self.myPrograme, "position");//(2).设置合适的格式从buffer里面读取数据glEnableVertexAttribArray(position);//(3).设置读取方式//参数1:index,顶点数据的索引//参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.//参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT//参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)//参数5:stride,连续顶点属性之间的偏移量,默认为0;//参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, positionSlot, NULL+offsetof(SenceVertex, positionCoord));//9.----处理纹理数据-------//(1).glGetAttribLocation,用来获取vertex attribute的入口的.//注意:第二参数字符串必须和shaderv.vsh中的输入变量:textCoordinate保持一致GLuint textCoor = glGetAttribLocation(self.myPrograme, "textCoordinate");//(2).设置合适的格式从buffer里面读取数据glEnableVertexAttribArray(textCoor);//(3).设置读取方式//参数1:index,顶点数据的索引//参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.//参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT//参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)//参数5:stride,连续顶点属性之间的偏移量,默认为0;//参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, textureCoordsSlot, (float *)NULL + offsetof(SenceVertex, textureCoord));//8. 传值glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, self.baseEffect.texture2d0.name);glUniform1i(textureSlot, 0);}//开始绘制glDrawArrays(mode, first, count);//12.解绑缓存glBindFramebuffer(GL_FRAMEBUFFER, 0);//13.释放顶点数组free(tmpVertices);//14.保存临时的纹理对象/帧缓存区对象;self.tmpTexture = texture;self.tmpFrameBuffer = frameBuffer;

2.2.2 在帧缓冲区中获取图片(UIImage)

// 返回某个纹理对应的 UIImage,调用前先绑定对应的帧缓存
- (UIImage *)imageFromTextureWithWidth:(int)width height:(int)height {//1.绑定帧缓存区;glBindFramebuffer(GL_FRAMEBUFFER, self.tmpFrameBuffer);//2.将帧缓存区内的图片纹理绘制到图片上;int size = width * height * 4;GLubyte *buffer = malloc(size);/*glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels);@功能: 读取像素(理解为将已经绘制好的像素,从显存中读取到内存中;)@参数解读:参数x,y,width,height: xy坐标以及读取的宽高;参数format: 颜色格式; GL_RGBA;参数type: 读取到的内容保存到内存所用的格式;GL_UNSIGNED_BYTE 会把数据保存为GLubyte类型;参数pixels: 指针,像素数据读取后, 将会保存到该指针指向的地址内存中;注意: pixels指针,必须保证该地址有足够的可以使用的空间, 以容纳读取的像素数据; 例如一副256 * 256的图像,如果读取RGBA 数据, 且每个数据保存在GLUbyte. 总大小就是 256 * 256 * 4 = 262144字节, 即256M;int size = width * height * 4;GLubyte *buffer = malloc(size);*/glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);//使用data和size 数组来访问buffer数据;/*CGDataProviderRef CGDataProviderCreateWithData(void *info, const void *data, size_t size, CGDataProviderReleaseDataCallback releaseData);@功能: 新的数据类型, 方便访问二进制数据;@参数:参数info: 指向任何类型数据的指针, 或者为Null;参数data: 数据存储的地址,buffer参数size: buffer的数据大小;参数releaseData: 释放的回调,默认为空;*/CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, size, NULL);//每个组件的位数;int bitsPerComponent = 8;//像素占用的比特数4 * 8 = 32;int bitsPerPixel = 32;//每一行的字节数int bytesPerRow = 4 * width;//颜色空间格式;CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();//位图图形的组件信息 - 默认的CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;//颜色映射CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;//3.将帧缓存区里像素点绘制到一张图片上;/*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);@功能:根据你提供的数据创建一张位图;注意:size_t 定义的是一个可移植的单位,在64位机器上为8字节,在32位机器上是4字节;参数width: 图片的宽度像素;参数height: 图片的高度像素;参数bitsPerComponent: 每个颜色组件所占用的位数, 比如R占用8位;参数bitsPerPixel: 每个颜色的比特数, 如果是RGBA则是32位, 4 * 8 = 32位;参数bytesPerRow :每一行占用的字节数;参数space:颜色空间模式,CGColorSpaceCreateDeviceRGB参数bitmapInfo:kCGBitmapByteOrderDefault 位图像素布局;参数provider: 图片数据源提供者, 在CGDataProviderCreateWithData ,将buffer 转为 provider 对象;参数decode: 解码渲染数组, 默认NULL参数shouldInterpolate: 是否抗锯齿;参数intent: 图片相关参数;kCGRenderingIntentDefault*/CGImageRef imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);//4. 此时的 imageRef 是上下颠倒的,调用 CG 的方法重新绘制一遍,刚好翻转过来//创建一个图片contextUIGraphicsBeginImageContext(CGSizeMake(width, height));CGContextRef context = UIGraphicsGetCurrentContext();//将图片绘制上去CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);//从context中获取图片UIImage *image = UIGraphicsGetImageFromCurrentImageContext();//结束图片context处理UIGraphicsEndImageContext();//释放bufferfree(buffer);//返回图片return image;
}

2.2.3 将图片存到相册

// 保存图片到相册
- (void)saveImage:(UIImage *)image {//将图片通过PHPhotoLibrary保存到系统相册[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{[PHAssetChangeRequest creationRequestForAssetFromImage:image];} completionHandler:^(BOOL success, NSError * _Nullable error) {NSLog(@"success = %d, error = %@ 图片已保存到相册", success, error);}];
}

OpenGLES(八)GPUImage滤镜链与将滤镜修改后的图片保存到相册相关推荐

  1. FFmpeg 滤镜AVFilter编程——简单滤镜

    一.认识滤镜 ffmpeg里面的滤镜(也叫过滤器)十分重要,功能也十分强大,除了我们所谓的滤镜,还有贴图.伸缩变形.拆解合并等功能,这里放2个滤镜.变形来证明他的强大. ffmpeg里面用(AVFil ...

  2. GPUImage使用之stillCamera多滤镜

    2019独角兽企业重金招聘Python工程师标准>>> 捕获照片并且对照片进行滤镜,我们可以使用GPUStillCamera.我在工程中写了一个"截图"功能,保存 ...

  3. FFmpeg使用滤镜链为视频插入多张图片

    以下命令定义了三个图片mask,使用滤镜链的方式,分别在视频的右下角在三个不同的时间段保持. ffmpeg -i input.mp4 -strict -2 -vf "movie=1.jpg, ...

  4. OpenGLSL初探(六)使用GLSL实现滤镜之灰度滤镜、正方形马赛克滤镜、六边形马赛克滤镜和三角形马赛克滤镜

    此博客只为记录滤镜的算法,所以修改的只是片元着色器代码 注:具体代码请查看上一篇博客:https://blog.csdn.net/weixin_40918107/article/details/107 ...

  5. html图片加波浪滤镜,CSS滤镜wave属性(波形滤镜)

    [实例介绍] CSS滤镜wave属性(波形滤镜) wave滤镜用来把对象按照垂直的波纹样式打乱.waVe的表达式还是比较复杂的,它一共有5个参数. [基本语法] filter:wave(add=参数值 ...

  6. JavaScript学习(五十八)—作用域链

    JavaScript学习(五十八)-作用域链 一.作用域链 在每个作用域中都有一个对象,这个对象被称为变量对象. 变量对象的作用就是用来管理该作用域下面定义的变量和函数的,也就是在该作用域下面定义的变 ...

  7. 图像滤镜艺术---(Instagram)1977滤镜

    原文:图像滤镜艺术---(Instagram)1977滤镜 图像特效---(Instagram)1977滤镜 本文介绍1977这个滤镜的具体实现,这个滤镜最早是Instagram中使用的 ,由于Ins ...

  8. iOS 利用 Metal 实现滤镜与动效滤镜

    Harbeth 是 Apple 的 Metal 框架上的一小部分实用程序和扩展,致力于使您的 Swift GPU 代码更加简洁,让您更快地构建管道原型.本文就来介绍与设计基于GPU的滤镜,图形处理和滤 ...

  9. ps2021神经ai滤镜无法使用,ps2021神经滤镜出现错误

    photoshop 滤镜的下载以及安装方法 photoshop本身就带有很多滤镜.需要自己安装的滤镜我们都叫做外挂滤镜.photoshop怎么安装滤镜要看你下载的滤镜是什么样子的了. 有的滤镜下载下来 ...

最新文章

  1. Asp.Net MVC 自定义的MVC框架(非EF操作数据库)
  2. 福师计算机导论在线作业一,福师《计算机导论》在线作业一..doc
  3. OSS全球传输加速开启公测,助力企业业务全地域覆盖...
  4. html底部自动加载下一页,js页面滚动到底部时自动加载下一页数据
  5. 计算机科学导论第二章,计算机科学导论第二章.doc
  6. yml eureka defaultzone 只生效第一个_SpringCloud基础教程(三)-Eureka进阶
  7. 工作108:vue里面wangEdit编辑器使用
  8. Python: PS滤镜--径向模糊
  9. 走过 Google 2005年[zt]
  10. Expect学习笔记(1)
  11. 堆插入和删除的简单实现
  12. 最让IT技术支持人员头痛的10件事
  13. hsv白色h值是多少_rgb颜色模型与hsv颜色模型的理解
  14. 推荐系统工业界顶会论文总结——WSDM 2021
  15. 第二次作业:支付宝案例分析1
  16. 哪些人适合读博士?哪些人不适合读博士?
  17. 2021年全球自动内窥镜清洗消毒机行业调研及趋势分析报告
  18. iconic 框架中的滚动条高度
  19. Moodle平台——API
  20. 《中国哲学史大纲》读书笔记

热门文章

  1. datax——全量、增量同步
  2. 怎么压缩照片内存大小?网上如何压缩照片?
  3. C语言-函数(自定义函数)
  4. Linux小实验11|添加组group,添加用户aa、bb并加入group组 (2)新建文件/abc.txt (3)设置用户aa对文件拥有读、写和执行权限
  5. OSGi跨bundle调用,jaxb-impl异常
  6. Python:计算欧氏距离的三种写法
  7. Unity3D和UE4游戏开发引擎哪个是游戏开发者最爱?
  8. wxpy实现微信机器人
  9. jmeter 打开报错_Jmeter-打开jmx文件报错
  10. Oracle图书管理系统