最近工作之余在做一个美图秀秀的仿品 做到滤镜这块的时候 自己就参考了网上几位博主(名字忘了记,非常抱歉)的博客,但是发现跟着他们的demo做的滤镜处理,都会有很严重的内存泄漏,于是就自己按照大体的思路将代码重新整理了下,并解决了内存泄漏问题。
大体思路如下:
根据图片创建一个CoreGraphic的图形上文->根据图形上下文获取图片每个像素的RGBA的色值数组->遍历数组,按照颜色矩阵进行像素色值调整->输出绘制新的图片
具体流程如下:
首先创建一个RGBA通道位图上下文:注意在以下方法中,不要立刻释放malloc方法生成的bitmapData内存空间指针,(可能有的朋友觉得已经把内存空间地址给了位图上下文就可以立马释放掉了,但是由于位图上下文在后来的图像渲染时,仍然需要这一块内存,因此不能在此处立马释放掉内存,之前拜读的几篇博客索性就不释放内存了,因此会导致内存泄漏,处理一些高清图像时,手机内存会轻易飙升到1G以上,而导致程序挂掉)不然会导致位图上下文的内容数据不能正常存在而导致图片生成失败,在这里需要一个全局内存指针来指向它,并且在合适的时候释放内存,具体看如下代码:
创建RGBA通道位图上下文:该位图上下文主要提供了一个画板,配置了画板的绘图所占用的字节数,设备依赖的RGB通道等信息。该上下文主要用于提供所有渲染图像的像素的RGBA值数组,以便后续对像素值的遍历处理。
#pragma mark---------------------------------------->创建一个使用RGBA通道的位图上下文
static CGContextRef CreateRGBABitmapContex(CGImageRef inImage){
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void *bitmapData;//内存空间的指针,该内存空间的大小等于图像使用RGB通道所占用的字节数。
long bitmapByteCount;
long bitmapBytePerRow;
/* 获取像素的横向和纵向个数 */
size_t pixelsWith = CGImageGetWidth(inImage);
size_t pixelsHigh = CGImageGetHeight(inImage);
/* 每一行的像素点占用的字节数,每个像素点的RGBA四个通道各占8bit空间 */
bitmapBytePerRow = (pixelsWith * 4);
/* 整张图片占用的字节数 */
bitmapByteCount = (bitmapBytePerRow * pixelsHigh);
/* 创建依赖设备的RGB通道 */
colorSpace = CGColorSpaceCreateDeviceRGB();
/* 分配足够容纳图片字节数的内存空间 */
bitmapData = malloc(bitmapByteCount);
/* 引用内存地址 以便在合适的地方释放内存空间 */
bitmap = bitmapData;
/* 创建CoreGraphic的图形上下文 该上下文描述了bitmaData指向的内存空间需要绘制的图像的一些绘制参数 */
context = CGBitmapContextCreate(bitmapData, pixelsWith, pixelsHigh, 8, bitmapBytePerRow, colorSpace, kCGImageAlphaPremultipliedLast);
/* Core Foundation中含有Create、Alloc的方法名字创建的指针,需要使用CFRelease()函数释放 */
CGColorSpaceRelease(colorSpace);
/* 此处必须手动释放内存 不然会有内存暴增的现象 但如果在这里释放 真机运行时情况可能就不太好了 (注意:在模拟器上 在此释放不会有任何问题 模拟器的图形上下文和画板机制与真机不同) */
// free(bitmapData);
return context;
}
返回目标图像的RBGA像素色值的数组指针:该指针指向一个数组,数组中的每四个元素都是图像上的一个像素点的RGBA的数值(0-255),用无符号的char是因为它正好的取值范围就是0-255
static unsigned char *RequestImagePixelData(UIImage * inImage){
CGImageRef img = [inImage CGImage];
CGSize size = [inImage size];
//使用上面的函数创建上下文
CGContextRef cgctx = CreateRGBABitmapContex(img);
CGRect rect = {{0,0},{size.width,size.height}};
//将目标图像绘制到指定的上下文,实际为上下文内的bitmapData。
CGContextDrawImage(cgctx, rect, img);
unsigned char *data = CGBitmapContextGetData(cgctx);
//释放上面的函数创建的上下文
CGContextRelease(cgctx);
cgctx = NULL;
return data;
}
将一个像素RGBA值数组通过一个颜色矩阵进行转换:颜色矩阵决定了图像的渲染效果,因此不同的滤镜效果可以通过设置不同的颜色矩阵进行转换。如果不懂颜色矩阵,可以参考如下的博客:http://www.cnblogs.com/yjmyzz/archive/2010/10/16/1852878.html,在这里就不过多描述了。
注意:在以下方法中,建议先取值并赋值给变量,因为每个像素点的色值,都要调用这个方法,对于一张稍大的高清图,会遍历非常多的次数,因此,里面的每一步多余的操作,都会引起积累起来的长时间处理,博主当时也踩了这个坑,导致处理一张图片时极度耗时。
static void changeRGB(int *red,int* green,int*blue,int*alpha ,const float *f){
//先取值并赋值给变量
int redV = *red;
int greenV = *green;
int blueV = *blue;
int alphaV = *alpha;
*red = f[0] * redV + f[1]*greenV + f[2]*blueV + f[3] * alphaV + f[4];
*green = f[5] * redV + f[6]*greenV + f[7]*blueV + f[8] * alphaV+ f[9];
*blue = f[10] * redV + f[11]*greenV + f[12]*blueV + f[11] * alphaV+ f[14];
*alpha = f[15] * redV + f[16]*greenV + f[17]*blueV + f[18] * alphaV+ f[19];
//超出边界值的都默认为边界值
if (*red<0) {
*red=0;
}
if (*red>255) {
*red = 255;
}
if (*green<0) {
*green = 0;
}
if (*green>255) {
*green = 255;
}
if (*blue<0) {
*blue = 0;
}
if (*blue>255) {
*blue = 255;
}
if (*alpha>255) {
*alpha=255;
}
if (*alpha<0) {
*alpha = 0;
}
}
以下方法就是暴露给大家的最终图片处理方法了,通过传入一张图片和一个颜色矩阵f,即可完成一张图片的滤镜渲染,并且,在生成一张图片后,最好是将该图像转换为NSData类型进行存储,然后释放掉之前全局变量内存指针,最后再将NSData数据回传给需要的方法。如果不将生成图像转化为NSData存储,而直接使用生成的UIImage对象,则在释放掉内存指针后,UIImage对象也将不存在,楼主亲测,是个大坑,读者尽量避免此类情况。
- (UIImage *)createImageWithImage:(UIImage *)inImage andColorMatrix:(const float *)f{
/* 图片位图像素值数组 */
unsigned char *imgPixel = RequestImagePixelData(inImage);
CGImageRef inImageRef = [inImage CGImage];
long w = CGImageGetWidth(inImageRef);
long h = CGImageGetHeight(inImageRef);
int wOff = 0;
int pixOff = 0;
/* 遍历修改位图像素值 */
for (long y = 0; y<h; y++) {
pixOff = wOff;
for (long x = 0; x<w; x++) {
int red = (unsigned char)imgPixel[pixOff];
int green = (unsigned char)imgPixel[pixOff+1];
int blue = (unsigned char)imgPixel[pixOff +2];
int alpha = (unsigned char)imgPixel[pixOff +3];
changeRGB(&red, &green, &blue, &alpha,f);
imgPixel[pixOff] = red;
imgPixel[pixOff + 1] = green;
imgPixel[pixOff + 2] = blue;
imgPixel[pixOff + 3] = alpha;
pixOff += 4;
}
wOff += w * 4 ;
}
NSInteger dataLength = w * h * 4;
//创建要输出的图像的相关参数
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, imgPixel, dataLength, NULL);
if (!provider) {
NSLog(@"创建输出图像相关参数失败!");
}else{
int bitsPerComponent = 8;
int bitsPerPixel = 32;
ItemCount bytesPerRow = 4 * w;
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault;
CGColorRenderingIntent rederingIntent = kCGRenderingIntentDefault;
//创建要输出的图像
CGImageRef imageRef = CGImageCreate(w, h,bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider,NULL, NO, rederingIntent);
if (!imageRef) {
NSLog(@"创建输出图像失败");
}else{
UIImage *my_image = [UIImage imageWithCGImage:imageRef];
CFRelease(imageRef);
CGColorSpaceRelease(colorSpaceRef);
CGDataProviderRelease(provider);
NSData *data = UIImageJPEGRepresentation(my_image, 1.0);
/* 在这里就可以释放内存了 并且在此之后my_image由于运行时和图形上下文机制已经没有图像内容了 只能使用刚刚生成的图片二进制数据进行图片回传 */
free(bitmap);
//这里的block是demo中需要的 可以不做关注
if (_imageBLOCK) {
_imageBLOCK([UIImage imageWithData:data]);
}
return [UIImage imageWithData:data];
}
}
return nil;
}
我的demo的github地址为:https://github.com/China131/JHFilterDemo.git,效果图如下:
demo的操作很简单,即动态改变颜色矩阵的值,实时生成渲染图片,您可以慢慢调试,如果发现您喜欢的渲染类型,直接点击保存图片,Xcode即可打印一个完整的颜色矩阵,您只需要将颜色矩阵保存,就拥有了独一无二的滤镜哦。