[Objective-C] GPUImageで簡単フィルタ


[GPUImage](https://github.com/BradLarson/GPUImage)という、GPUで画像に簡単にフィルタをかけられるライブラリがあります。
今回はこれを使ってツールを作ろうと思っているので、使い方なんかをメモしておこうと思います。


## ライブラリとしてプロジェクトに追加する

これは単純に自分のiOS開発の経験不足ですが、ライブラリとして追加するにあたって、調べて出てくる情報がXcodeのバージョン違いなどでなかなか解決できなかったのでメモです。

*ちなみに、`.xcodeproj` ファイルをプロジェクトにDrag & Dropする方法での追加です。*


#### ドラッグアンドドロップで `.xcodeproj` を追加

GitHubのGPUImageリポジトリからファイルを落としてきたら、その中の「framework」内にある `GPUImage.xcodeproj` ファイルを、自分のプロジェクトにドラッグアンドドロップで追加します。


#### 依存関係を解決する

ただこのままだとまだ依存関係が解決されておらず、ビルドできません。
いくつか調べていたらターゲットに追加する、みたいなことを見かけたんですが、プロジェクトファイルを選択すると出てくるターゲットではなく、 **Scheme設定 > Build > Targets** のほうっぽいです。
こちらに追加したらビルドができました。

##### 余談

余談ですが、実行ボタンをAltキーを押しながらクリックするとすぐSchemeの設定ウィンドウを表示できるので便利です。

#### Link Binary With LibrariesにGPUImage.frameworkを追加する

Build Phasesにある **Link Binary With Libraries** にも **GPUImage.framework** を追加します。

こうしてGPUImageのプロジェクトを追加することができたら、いよいよGPUImageを使っていきます。


## GPUImageでHello Worldしてみる

GPUImageにはいくつかのインターフェースが準備されていて、画像だけでなく、動画などカメラの入力に対してフィルタをかける、というようなこともできるようです。

が、今回は普通の静止画に対してフィルタをかける方法です。

### GPUImagePictureを使う

```objc
UIImage *image = [UIImage imageNamed:@"logo.jpg"];
GPUImagePicture *imageSource = [[GPUImagePicture alloc] initWithImage:image];
GPUImageSepiaFilter *imageFilter = [[GPUImageSepiaFiler alloc] init];

[imageSource addTarget:imageFilter];
[imageFilter useNextFrameForImageCapture];
[imageSource processImage];

UIImage *filteredImage = [imageFilter imageFromCurrentFramebuffer];

UIImageView *imageView = [[UIImageView alloc] initWithImage:filteredImage];
[aView addSubview:imageView];
```

上記の例は、読み込んだ画像に対してセピアフィルタをかけているところです。
`GPUImagePicture` で画像を準備し、`GPUImageFilter` を追加して出力しています。
GPUImageではこんなふうに、フィルタを追加していく形で実装するようです。

なので、いくつかのフィルタを重ねてかけることもできるようになっています。


## 複数フィルタを重ねがけしてみる

最初、フィルタを複数作って `addTarget` を繰り返していけばいいのかな、と思っていたんですが、どうやらもう少し複雑なようです。

具体的には以下のように指定します。まずはおおまかな流れを示します。


### 複数フィルタを追加するフロー

1. `GPUImagePicture` インスタンスを画像を指定して生成
2. 必要な数のフィルタを生成する
3. `GPUImageFilterGroup` インスタンスを生成
4. フィルタグループに、必要なだけフィルタを追加（`addFilter:`）する
5. 初期化用のフィルタを配列で指定する（`setInitialFilters:`）
6. 終端（最後）のフィルタを指定する（`setTerminalFilter:`）
7. フィルタをつなげる（フィルタをかける順番を指示する）→ これには数珠つなぎになるように、前のフィルタの `addTarget:` メソッドに、次のフィルタを引数として渡してやります。
8. (1)で生成した `GPUImagePicture` インスタンスに、セットアップしたフィルタグループを `addTarget:` する。
9. `useNextFrameForImageCapture` メソッドを実行する
10. フィルタを実行する（`processImage`）


### コード

さて、なにはともあれコードを見たほうが早いと思うので例を。

```objc
// 画像を読み込み
UIImage *image = [UIImage imageNamed:@"logo.jpg"];

// 静止画用のオブジェクトを作る
GPUImagePicture *stillImageSource = [[GPUImagePicture alloc] initWithImage:image];

// フィルタ生成
GPUImageSobelEdgeDetectionFilter *sobelFilter      = [[GPUImageSobelEdgeDetectionFilter alloc] init];
GPUImageSepiaFilter              *sepiaImageFilter = [[GPUImageSepiaFilter alloc] init];

// フィルタグループを作成
GPUImageFilterGroup *filterGroup = [[GPUImageFilterGroup alloc] init];

// ソーベルフィルタを追加
[filterGroup addFilter:sobelFilter];

// セピアフィルタを追加
[filterGroup addFilter:sepiaImageFilter];

// 初期化用フィルタを指定
[filterGroup setInitialFilters:@[sepiaImageFilter]];

// 最後のフィルタを指定
[filterGroup setTerminalFilter:sobelFilter];

// フィルタはフィルタ同士追加する
[sepiaImageFilter addTarget:sobelFilter];

// GPUImagePictureにフィルタグループ追加
[stillImageSource addTarget:filterGroup];

// フィルタ実行
[filterGroup useNextFrameForImageCapture];
[stillImageSource processImage];

UIImage *filterdImage = [filterGroup imageFromCurrentFramebuffer];
UIImageView *imageView = [[UIImageView alloc] initWithImage:filterdImage];
[self.view addSubview:imageView];
```

基本的な処理は、ひとつのフィルタを追加するのとあまり代わりません。
違いは、単体のフィルタに対して行っていた処理を `GPUImageFilterGroup` に行う点でしょう。
（グループもひとつのフィルタと考えると分かりやすいと思います）


### 気をつける点

`GPUImageFilterGroup` の `addFilter:` を実行するだけではなく、生成したフィルタ同士を連結させないとならない点に注意が必要です。
（`addFilter:` した順番での処理ではなさそう）

後述しますが、画面にフィルタ結果を出すだけなら `GPUImageView` を使いますが、`UIImage` として書き出したい場合、`useNextFrameForImageCapture` メソッドを実行しておかないと出力されません。
想像ですが、フィルタの連結と関係あるのかなと。（出力用バッファに接続するイメージ？）

設定が済んだら、あとは `processImage` でフィルタを実行し、`imageFromCurrentFramebuffer` で画像を取得します。
画像は `UIImage` なので、あとは普通に画像を使う要領で使用することができます。


### GPUImageViewを使って画面にフィルタ結果を表示する

ちなみに画面に表示するだけであれば、`GPUImageView` を使うのが簡単です。
これはおそらく、内部的にバッファへの接続などをよしなにやってくれるものだと思います。
使い方は以下の通り。

#### コード例

```objc
GPUImageView *imageView = [[GPUImageView alloc] initWithFrame:self.view.bounds];
imageView.fillMode = kGPUImageFillModePreserveAspectRatio;
[self.view addSubview:imageView];

[filterGroup forceProcessingAtSize:imageView.sizeInPixels];
[filterGroup addTarget:imageView];
```

ちなみに上記の場合は `useNextFrameForImageCapture` の実行は必要ありません。


## オリジナルのフィルタを追加する

GPUImageでは、オリジナルのフィルタを作成する方法も準備されています。
オリジナルフィルタを生成するには **フラグメントシェーダ** を自分で書く必要があります。
ただ、それ以外のセットアップはGPUImage側で行ってくれ、さらに必要最小限の記述でフィルタを作成することが可能になっています。

### GLSLを記述する

まずはGLSLでフラグメントシェーダを書きます。

```glsl:ColorSwapFilter
/**
 *  赤と緑を入れ替えるフィルタ
 */

uniform sampler2D inputImageTexture;
varying highp vec2 textureCoordinate;

void main() {
	vec4 textureColor = texture2D(inputImageTextre, textureCoordinate);
	vec4 outputColor;
	
	outputColor.r = textureColor.g;
	outputColor.g = textureColor.r;
	outputColor.b = textureColor.b;
	outputColor.a = 1.0;
}
```


### GPUImageでフィルタ登録する

次に、今書いたGLSLをGPUImageのフィルタとして機能させます。

```objc
// 前略（フィルタ生成部分以外は他と同じ）

GPUImageFilter *customFilter = [[GPUImageFilter alloc] initWithFragmentShaderFromFile:@"ColorSwapFilter"];

[imageSource addTarget:customFilter];
[customFilter useNextFrameForImageCapture];
[imageSource processImage];

// 後略（フィルタから画像を生成する手順は同じ）
```

以上の手順で、簡単にカスタムフィルタを使うことができます。

#### テキストからGLSLを生成する

`initWithFragmentShaderFromFile:` は、内部では `initWithFragmentShaderFromString:` を呼んでおり、テキスト情報を使ってGLSLを生成することも可能です。
そしてGPUImageには便利なマクロが準備されています。

```objc
#define STRINGIZE(x) #x
#define STRINGIZE2(x) STRINGIZE(x)
#define SHADER_STRING(text) @ STRINGIZE2(text)
```

`SHADER_STRING` マクロを使うと、煩わしダブルクォーテーションなどの記述が不要になり、以下のようにGLSLをそのまま書けるようになります。

```objc
// GPUImageのFilterから一部抜粋
NSString *const kGPUImage3x3ConvolutionFragmentShaderString = SHADER_STRING
(
uniform sampler2D inputeImageTexture;
// ... 略
);
```

ちょっとしたGLSLなどは文字列で保持しておいたほうが分かりやすいケースもあるかと思います。
そんなときに便利なマクロですね。


## FiltersでGLSLを書こう！

ちなみに[Filters](http://filters.kayac.com/)とはiOSアプリとWebサイト上にあるエディタと接続することで、PCで **リアルタイム** にカメラのフィルタを作成できるサービスです。（もちろんそれを保存してあとで使うこともできます）

[![Filters](https://qiita-image-store.s3.amazonaws.com/0/5616/8b929291-2e54-5b2c-f2bf-4b043eddb73c.png "Filters")](https://itunes.apple.com/jp/app/filters-wu-xianni-zengerufiruta/id926621140)

ここで色々試してみたり、あるいはここで公開されているものを、GPUImage向けに変更して使うと面白いフィルタがすぐに使えると思います。



## 参考記事

- [画像処理のデファクトスタンダードライブラリGPUImageの基本と応用](http://qiita.com/yimajo/items/e38e8bafc17144b7af79)