目前好多APP都具備濾鏡美顏效果功能,作為iOS開發者,多少也要懂一點圖片的處理效果,本文章只是簡單介紹基本的濾鏡效果:
一、圖片儲存原理
要對圖片進行修改,那首先就要了解他的儲存原理,在iOS開發中接觸最多的主要的圖片格式:jpg、png、webp,不管是什么格式的圖片,可以看成是一個個像素點構成,這一個個點就是RGBA(jpg格式沒有A通道)。當我們儲存圖片時,系統都會有對應的一個緩沖池(buffer)進行存儲,buffer裝載的信息大致可以分為兩個部分:
一個是Info,這個是圖片的描述信息,
另外一個就是Data,這里面就是圖片數據信息,但是這不是最原始的RGB數據,而是經過壓縮編碼后的數據,我們要修改的也是這個數據。
簡單畫個圖理解下:
二、濾鏡效果
實現濾鏡效果的步驟:
1、將UIImage轉化為Data數據
2、Data數據進行灰度、色彩、美白等處理
3、將處理后的Data數據載轉化回UIImage
根據以上三步,做如下操作:
1、將UIImage轉化為Data數據
這里需要介紹一個方法,參數說明詳見
//Bitmap上下文(位圖結構)
CGBitmapContextCreate(<#void * _Nullable data#>, <#size_t width#>, <#size_t height#>, <#size_t bitsPerComponent#>, <#size_t bytesPerRow#>, <#CGColorSpaceRef _Nullable space#>, <#uint32_t bitmapInfo#>)
//image ---> data
- (unsigned char*)p_imageToData:(UIImage *)image {
//使用的是框架是CoreGraphics,所以要把image ——> CGImage
CGImageRef imageRef = [image CGImage];
CGSize imageSize = image.size;
//顏色空間
CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
//分配bit級空間大小,一個像素點分為 R G B A 為4byte,像素點個數 = 寬*高
void *data = malloc(4*imageSize.width*imageSize.height);
//Bitmap上下文
//kCGImageAlphaPremultipliedLast 當前顏色的排列順序
//kCGBitmapByteOrder32Big 位數 4*8
CGContextRef context = CGBitmapContextCreate(data, imageSize.width, imageSize.height, 8, 4*imageSize.width, colorSpaceRef, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast);
//渲染
//參數1:bitmap上下文
//參數2:需要渲染的空間大小
//參數3:原始圖片
CGContextDrawImage(context, CGRectMake(0, 0, imageSize.width, imageSize.height), imageRef);
//釋放
CGColorSpaceRelease(colorSpaceRef);
CGContextRelease(context);
return (unsigned char*)data;
}
2、Data數據進行灰度、色彩、美白等處理
a、灰度處理
上面幾組顏色,他們的RGB值就是顯示的對應的值,可見當RGB值都相等時,表現出來的都是灰色,只是程度不同而已。灰度處理也是這個原理,將像素點的RGB設置成相等的,這里提供一個灰度處理公式:
Gray = 0.299*Red +0.587*Green +0.114*Blue
- (unsigned char*)p_grayDataWithData:(unsigned char*)imageData imageSize:(CGSize)imageSize {
unsigned char* data = malloc(4*sizeof(unsigned char)*imageSize.width*imageSize.height);
//初始化data 內存
//參數1:內存地址
//參數2:填充的值
//參數3:需要填充的內存空間大小
memset(data, 0, 4*imageSize.width*imageSize.height);
for (int h = 0; h < imageSize.height; h++) {
for (int w = 0; w < imageSize.width; w++) {
//當前像素點的位置
unsigned int index = h*imageSize.width+w;
//取出原始的RGBA
//imageData+index*4: imageData地址加上偏移量(每個像素4byte)
unsigned char red = *(imageData+index*4);
unsigned char green = *(imageData+index*4+1);
unsigned char blue = *(imageData+index*4+2);
//灰度處理
unsigned int newRGB = red*0.299+green*0.587+blue*0.114;
//可能算出來的值大于255
newRGB = newRGB > 255? 255:newRGB;
memset(data+index*4, newRGB, 1);
memset(data+index*4+1, newRGB, 1);
memset(data+index*4+2, newRGB, 1);
}
}
return data;
}
上面代碼提到的有關內存的,畫了個簡單的圖幫助理解:
b、簡單美白處理
本文采用映射表的方法處理美白效果,0-255之間有256個點,按每組32個,分8組,節點為55,110,155,185,225,240,250,255,然后再將段按照32等分分割,畫個圖說明下:
- (unsigned char*)p_skinWhiteWithData:(unsigned char*)imageData imageSize:(CGSize)imageSize {
void *data = malloc(4*imageSize.width*imageSize.height);
memset(data, 0, imageSize.width*imageSize.height);
NSArray *array = @[@"55",@"110",@"155",@"185",@"225",@"240",@"250",@"255"];
NSMutableArray *colorArray = [[NSMutableArray alloc] init];
int last = 0;//記錄前一次的值,一開始為 0
for (int i = 0; i < 8; i++) {
int num = [array[i] intValue];
float step = (num - last)/32.0;//步長,每次增加多少
for (int j = 0; j < 32; j++) {
float newNum = last+step*j;//上一次的值加上步長*j 得到對應位置的值
NSString *newNumStr = [NSString stringWithFormat:@"%lf",newNum];
[colorArray addObject:newNumStr];
}
last = num;
}
for (int h = 0; h < imageSize.height; h++) {
for (int w = 0; w < imageSize.width; w++) {
unsigned int index = h*imageSize.width+w;
//取出原始的RGBA
unsigned int red = *(imageData+index*4);
unsigned int green = *(imageData+index*4+1);
unsigned int blue = *(imageData+index*4+2);
//美白處理
unsigned int newRed = [colorArray[red] floatValue];
unsigned int newGreen = [colorArray[green] floatValue];
unsigned int newBlue = [colorArray[blue] floatValue];
memset(data+index*4, newRed, 1);
memset(data+index*4+1, newGreen, 1);
memset(data+index*4+2, newBlue, 1);
}
}
return data;
}
c、彩色底版處理
同樣給出一個處理彩色底版的算法:
newValue = 255 - oldValue
- (unsigned char*)p_colorDataWithData:(unsigned char*)imageData imageSize:(CGSize)imageSize {
unsigned char* data = malloc(4*sizeof(unsigned char)*imageSize.width*imageSize.height);
//初始化data 內存, 1:內存地址 2:填充的值 3:需要填充的內存空間大小
memset(data, 0, 4*imageSize.width*imageSize.height);
for (int h = 0; h < imageSize.height; h++) {
for (int w = 0; w < imageSize.width; w++) {
unsigned int index = h*imageSize.width+w;
//取出原始的RGBA
unsigned char red = *(imageData+index*4);
unsigned char green = *(imageData+index*4+1);
unsigned char blue = *(imageData+index*4+2);
//彩色處理
unsigned int newRed = 255-red;
unsigned int newGreen = 255-green;
unsigned int newBlue = 255-blue;
memset(data+index*4, newRed, 1);
memset(data+index*4+1, newGreen, 1);
memset(data+index*4+2, newBlue, 1);
}
}
return data;
}
3、將處理后的Data數據載轉化回UIImage
- (UIImage *)p_dataToImage:(unsigned char*)imageData imageSize:(CGSize)imageSize {
//原始數據 4*imageSize.width*imageSize.height 數據空間大小
CGDataProviderRef dataProRef = CGDataProviderCreateWithData(NULL, imageData, 4*imageSize.width*imageSize.height, NULL);
CGImageRef imageRef = CGImageCreate(imageSize.width, imageSize.height, 8, 32, 4*imageSize.width, CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault, dataProRef, NULL, NO, kCGRenderingIntentDefault);
UIImage *imageNew = [UIImage imageWithCGImage:imageRef];
//釋放
CFRelease(imageRef);
CGDataProviderRelease(dataProRef);
return imageNew;
}
四、結果
直接上效果圖:
五、總結
以上是簡單的處理效果,如果想要多樣的處理效果就是計算新的RGB值的算法優化問題了,你們也可以隨便改,會出現不同的效果呦。最后送上本Demo的鏈接地址:https://github.com/xyzcwb/ImageDemo