概述
GPUImage是一個著名的圖像處理開源庫,它讓你能夠在圖片、視頻、相機上使用GPU加速的濾鏡和其它特效。與CoreImage框架相比,可以根據GPUImage提供的接口,使用自定義的濾鏡。項目地址:https://github.com/BradLarson/GPUImage
這篇文章主要是閱讀GPUImage框架中的 GPUImageLookupFilter 類的源碼。GPUImageLookupFilter 是GPUImage中的顏色查找濾鏡,在一般的相機應用中使用得最廣泛,它的作用是通過顏色變換從而產生出新風格的圖片。接下來就看一下顏色超招標如何在GPUImage實現的。以下是源碼內容:
GPUImageLookupFilter
簡介
LUT (Lookup Tables)即查找表 。LUT是個非常簡單的數值轉換表,不同的色彩輸入數值“映射”到一套輸出數值,用來改變圖像的色彩。例如:紅色在LUT中可能被映射成藍色,于是,應用LUT的圖像中每個紅的地方將由藍色取代。不過,LUT的實際應用要比這種情況更微妙一些。LUT通常用來矯正域外色的問題。例如,一個以RGB色彩空間存儲的圖像要打印到紙上時,必須首先將文件轉換為CMYK色彩空間。這可以用一個LUT將每個RGB色彩轉換為等效的CMYK色彩,或者與域外色最接近的色彩(LUT還能夠用縮放的方法在一定程度改變所有的色彩,因此可使圖像在視覺上與原來相同)。 另外需要注意的一個問題是,盡管查找表經常效率很高,但是如果所替換的計算相當簡單的話就會得不償失,這不僅僅因為從內存中提取結果需要更多的時間,而且因為它增大了所需的內存并且破壞了高速緩存。如果查找表太大,那么幾乎每次訪問查找表都會導致高速緩存缺失,這在處理器速度超過內存速度的時候愈發成為一個問題。
LUT的生成
在生成LUT的時候,如果要把所有色彩的處理結果都存起來,那這張表勢必會相當大,占用非常多的內存。比如存儲RGB顏色空間的LUT表,我們需要的空間為256x256x256=16777216 字節。雖然保存成圖片的時候會有壓縮,但是如果我們使用多種不同的LUT占用的空間也是相當大的。實際上,我們并不會記錄所有的顏色值,而是只記錄部分顏色值,其它不在LUT里的顏色則用插值的方式來獲取相應的顏色值。LUT有許多存儲方式,PS中支持導出4中LUT(3DL、CUBA、CSP、ICC),具體如下:
在GPUImage中使用的是二維的LUT圖片,它是一張512x512大小的圖片。它由8x8個大正方形格子組成,每個大格子又是由64x64個小正方形組成。因此,LUT圖片的寬度:8*64=512,高度:8*64=512。對于每個小正方形,它的寬度值即為R通道值,高度即為G通道值。由于R通道的范圍為0~255,因此,從寬度方面來看,每個小正方形的R通道的差為:256/64=4,R通道的集合為[0,4,8,12,16,...,255],同理從高度上看,每個小正方形的G通道的差為:256/64=4,G通道的集合為[0,4,8,12,16,...,255],由于RG通道值存放在每個64x64的大正方形中,那么B通道的值存放在哪里呢?答案就是在8x8=64個大正方形中,不難看出每個RGB通道都是用64個點來存放,占用空間為:64x64x64=262144。不同于RG通道的是,B通道存放時使用了寬度和高度分別為8個大正方形。
LUT生成代碼如下所示,(在這里為了方便,我直接在棧區生成了512x512x4的數組):
typedef struct {
char r,g,b,a;
} RGBA;
- (void)generateLoockupTexture
{
RGBA rgba[8*64][8*64];
for (int by = 0; by < 8; by++) {
for (int bx = 0; bx < 8; bx++) {
for (int g = 0; g < 64; g++) {
for (int r = 0; r < 64; r++) {
// 將RGB[0,255]分成64份,每份相差4個單位,+0.5做四舍五入運算
int rr = (int)(r * 255.0 / 63.0 + 0.5);
int gg = (int)(g * 255.0 / 63.0 + 0.5);
int bb = (int)(((bx + by * 8.0) * 255.0 / 63.0 + 0.5));
int aa = 255;
int x = r + bx * 64;
int y = g + by * 64;
rgba[y][x] = (RGBA){rr, gg, bb, aa};
}
}
}
}
UIImage *image = [QMImageHelper convertBitmapRGBA8ToUIImage:(unsigned char *)rgba withWidth:8*64 withHeight:8*64];
[UIImagePNGRepresentation(image) writeToFile:@"/Users/qinmin/Desktop/lookup.png" atomically:YES];
}
以下是代碼生成的LUT圖片:
除了8x64x8x64的LUT紋理外,還可以根據需要生成不同大小的LUT紋理:
GPUImageLookupFilter
GPUImageLookupFilter 就是根據LUT獲取相應的變換顏色,我們可以使用PS直接改變lookup顏色表,從而改變圖片的風格。常用的PS工具有「曲線」,「飽和度」、「色彩平衡」、「可選顏色」,但是GPUImageLookupFilter的局限在于只能進行顏色上的調整(曲線,色彩平衡等),其它效果調整也只限于利用圖層間混合模式的更改,例如做暗角、漏光等效果。GPUImageLookupFilter繼承自GPUImageTwoInputFilter,接受LUT紋理和待濾鏡的圖片輸入。
頂點著色器
NSString *const kGPUImageTwoInputTextureVertexShaderString = SHADER_STRING
(
attribute vec4 position;
attribute vec4 inputTextureCoordinate;
attribute vec4 inputTextureCoordinate2;
varying vec2 textureCoordinate;
varying vec2 textureCoordinate2;
void main()
{
gl_Position = position;
textureCoordinate = inputTextureCoordinate.xy;
textureCoordinate2 = inputTextureCoordinate2.xy;
}
);
片段作色器
NSString *const kGPUImageLookupFragmentShaderString = SHADER_STRING
(
varying highp vec2 textureCoordinate;
varying highp vec2 textureCoordinate2; // TODO: This is not used
uniform sampler2D inputImageTexture;
uniform sampler2D inputImageTexture2; // lookup texture
uniform lowp float intensity;
void main()
{
highp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
highp float blueColor = textureColor.b * 63.0;
// 根據B通道獲取小正方形格子(64x64格子)
highp vec2 quad1;
quad1.y = floor(floor(blueColor) / 8.0);
quad1.x = floor(blueColor) - (quad1.y * 8.0);
highp vec2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0);
quad2.x = ceil(blueColor) - (quad2.y * 8.0);
// 根據小正方形格子和RG通道,獲取紋理坐標,每個大格子的大小:1/8=0.125,每個小格子的大小:1/512
highp vec2 texPos1;
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
highp vec2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
lowp vec4 newColor1 = texture2D(inputImageTexture2, texPos1);
lowp vec4 newColor2 = texture2D(inputImageTexture2, texPos2);
lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), intensity);
}
);
LUT使用
LUT的使用一般都比較簡單,主要是對原Lookup Table做一些特效處理生成新的Lookup Table在程序中使用,一般的使用步驟如下:
1、用PS或其它濾鏡軟件打開待濾鏡的圖片。
2、對待濾鏡的圖片做曲線,色彩平衡、色調等調整。
3、調整適合后,對Lookup Table做相同的操作。
4、調整好Lookup Table后,導出新的Lookup Table,然后在程序中使用。
- 原圖
- 處理后的圖片
- 程序生成的濾鏡圖片
- 程序代碼
//
// ViewController.m
// GPUImageFilter
//
#import "ViewController.h"
#import <GPUImage.h>
#import "QMImageHelper.h"
typedef struct {
char r,g,b,a;
} RGBA;
#define DOCUMENT(path) [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:path]
@interface ViewController ()
@property (nonatomic, strong) GPUImageLookupFilter *filter;
@property (nonatomic, strong) GPUImagePicture *picture;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 設置背景色
[_imageView setBackgroundColorRed:1.0 green:1.0 blue:1.0 alpha:1.0];
[_imageView setFillMode:kGPUImageFillModePreserveAspectRatio];
[self setupFilter];
// [self generateLoockupTexture];
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
#pragma mark - PrivateMethod
- (void)setupFilter
{
self.filter = [[GPUImageLookupFilter alloc] init];
[self.filter setIntensity:0.65f];
[self.filter addTarget:self.imageView];
self.picture = [[GPUImagePicture alloc] initWithImage:[UIImage imageNamed:@"3.jpg"]];
[self.picture addTarget:self.filter];
[self.picture processImage];
GPUImagePicture *loockup = [[GPUImagePicture alloc] initWithImage:[UIImage imageNamed:@"lookup.jpg"]];
[loockup addTarget:self.filter];
[loockup processImage];
}
- (void)generateLoockupTexture
{
RGBA rgba[8*64][8*64];
for (int by = 0; by < 8; by++) {
for (int bx = 0; bx < 8; bx++) {
for (int g = 0; g < 64; g++) {
for (int r = 0; r < 64; r++) {
// 將RGB[0,255]分成64份,每份相差4個單位,+0.5做四舍五入運算
int rr = (int)(r * 255.0 / 63.0 + 0.5);
int gg = (int)(g * 255.0 / 63.0 + 0.5);
int bb = (int)(((bx + by * 8.0) * 255.0 / 63.0 + 0.5));
int aa = 255;
int x = r + bx * 64;
int y = g + by * 64;
rgba[y][x] = (RGBA){rr, gg, bb, aa};
}
}
}
}
UIImage *image = [QMImageHelper convertBitmapRGBA8ToUIImage:(unsigned char *)rgba withWidth:8*64 withHeight:8*64];
[UIImagePNGRepresentation(image) writeToFile:@"/Users/qinmin/Desktop/lookup.png" atomically:YES];
}
@end
總結
GPUImageLookupFilter 可以做出很多風格濾鏡效果,它的原理比較簡單,但是功能卻很強大。除了使用GPUImageLookupFilter外,還可以綜合使用各種混合濾鏡,做出許多很有意思的濾鏡效果。在GPUImage中還內置了幾種LookupFilter,它們分別是GPUImageAmatorkaFilter, GPUImageMissEtikateFilter, GPUImageSoftEleganceFilter它們的原理都是使用Color Lookup Tables,只是使用的紋理不同而已。
源碼地址:GPUImage源碼閱讀系列 https://github.com/QinminiOS/GPUImage
系列文章地址:GPUImage源碼閱讀 http://www.lxweimin.com/nb/11749791