GPUImage源碼閱讀(十)

概述

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),具體如下:

LUT導出.png
LUT格式.png

在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個大正方形。

8x64x8x64_lookup.png
LUT排列圖.png

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圖片:

lookup.png

除了8x64x8x64的LUT紋理外,還可以根據需要生成不同大小的LUT紋理:

8x8x8x8_lookup.png

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,然后在程序中使用。

  • 原圖
未處理的照片.png
未處理的LUT圖片.png
  • 處理后的圖片
潑辣修圖處理后的照片.png
潑辣修圖處理后的LUT圖片.png
  • 程序生成的濾鏡圖片
Simulator Screen Shot - iPhone 8 Plus.png
  • 程序代碼
//
//  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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容