最近工做之餘在作一個美圖秀秀的仿品 作到濾鏡這塊的時候 本身就參考了網上幾位博主(名字忘了記,很是抱歉)的博客,可是發現跟着他們的demo作的濾鏡處理,都會有很嚴重的內存泄漏,因而就本身按照大致的思路將代碼從新整理了下,並解決了內存泄漏問題。html
大致思路以下:git
根據圖片建立一個CoreGraphic的圖形上文->根據圖形上下文獲取圖片每一個像素的RGBA的色值數組->遍歷數組,按照顏色矩陣進行像素色值調整->輸出繪製新的圖片github
具體流程以下:數組
首先建立一個RGBA通道位圖上下文:注意在如下方法中,不要馬上釋放malloc方法生成的bitmapData內存空間指針,(可能有的朋友以爲已經把內存空間地址給了位圖上下文就能夠立馬釋放掉了,可是因爲位圖上下文在後來的圖像渲染時,仍然須要這一塊內存,所以不能在此處立馬釋放掉內存,以前拜讀的幾篇博客索性就不釋放內存了,所以會致使內存泄漏,處理一些高清圖像時,手機內存會輕易飆升到1G以上,而致使程序掛掉)否則會致使位圖上下文的內容數據不能正常存在而致使圖片生成失敗,在這裏須要一個全局內存指針來指向它,而且在合適的時候釋放內存,具體看以下代碼:ide
建立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-255spa
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對象也將不存在,樓主親測,是個大坑,讀者儘可能避免此類狀況。code
- (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便可打印一個完整的顏色矩陣,您只須要將顏色矩陣保存,就擁有了獨一無二的濾鏡哦。