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.jianshu.com/nb/11749791

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容