カメラアプリの作り方は、今回で最終回となる。最後に説明するのは、撮影した写真にエフェクトをかける方法だ。このエフェクトの種類で、面白いカメラアプリになるかどうかが決まるだろう。
CGImageと画像情報の取得
画像にエフェクトをかけるには、画像をビットマップデータとして取り出す事が必要になるだろう。UIImagePickerControllerからは、画像はUIImageオブジェクトとして取得する事ができた。このクラスは、グラフィックシステムの中では上位に位置づけられており、簡単に画面に表示できる反面、ビットマップデータを取り出すような低レベルな操作を行うAPIは提供されていない。
そこで、Core Graphicsフレームワークを使おう。Core Graphicsは、Cocoaよりも低レイヤーに位置するグラフィックフレームワークで、直接画像を操作するためのAPIが色々とそろっている。ちなみに、Core GraphicsではすべてのAPIはC言語で提供されている。
Core Graphicsで画像を表すのが、CGImageと呼ばれるオブジェクトだ。UIImageオブジェクトから、CGImageオブジェクトを取り出す事ができる。UIImageが持っているCGImageというプロパティを使う。
UIImage.h
@property(nonatomic, readonly) CGImageRef CGImage;
CGImageを使うと、画像の詳細な情報を取得する事ができる。たとえば、画像の幅、高さ、ピクセルの要素毎のビット数、ピクセル毎のビット数、画像1行のバイト数、などだ。
次のようなソースコードで、UIImageからCGImageを取り出し、画像情報を調べる事ができる。
CameraViewController.m
...
// CGImageを取得する
CGImageRef cgImage;
cgImage = shrinkedImage.CGImage;
// 画像情報を取得する
size_t width;
size_t height;
size_t bitsPerComponent;
size_t bitsPerPixel;
size_t bytesPerRow;
CGColorSpaceRef colorSpace;
CGBitmapInfo bitmapInfo;
bool shouldInterpolate;
CGColorRenderingIntent intent;
width = CGImageGetWidth(cgImage);
height = CGImageGetHeight(cgImage);
bitsPerComponent = CGImageGetBitsPerComponent(cgImage);
bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
bytesPerRow = CGImageGetBytesPerRow(cgImage);
colorSpace = CGImageGetColorSpace(cgImage);
bitmapInfo = CGImageGetBitmapInfo(cgImage);
shouldInterpolate = CGImageGetShouldInterpolate(cgImage);
intent = CGImageGetRenderingIntent(cgImage);
ビットマップデータを取り出す
続いて、画像のビットマップデータを取り出す方法を説明しよう。これには、CGDataProviderと呼ばれるオブジェクトを使う。
Core Graphicsでは、画像を取り扱うために、画像そのものを表すCGImageに加えて、画像データを提供するCGDataProviderと、画像データの処理を行うCGDataConsumerというオブジェクトが用意されている。ビットマップデータのような、画像の元データを取得するときは、CGDataProviderを使うのだ。
CGImageからCGDataProviderを取り出すには、CGImageGetDataProviderというAPIを使う。
CGImage.h
CGDataProviderRef CGImageGetDataProvider(CGImageRef image);
そしてCGDataProviderからデータを取り出すために、CGDataProviderCopyDataというAPIがある。これが、画像のビットマップデータとなるのだ。
CGDataProvider.h
CFDataRef CGDataProviderCopyData(CGDataProviderRef provider);
ここまでのソースコードを紹介しよう。
CameraViewController.m
...
// データプロバイダを取得する
CGDataProviderRef dataProvider;
dataProvider = CGImageGetDataProvider(cgImage);
// ビットマップデータを取得する
CFDataRef data;
UInt8* buffer;
data = CGDataProviderCopyData(dataProvider);
buffer = (UInt8*)CFDataGetBytePtr(data);
ビットマップデータはCFDataの形で取得できる。プログラミングしやすいように、生データのポインタを取り出しておこう。これで、ついに画像のビットマップデータを直接触れるようになった。
エフェクトをかける
では、画像にエフェクトをかけていこう。ここでは、画像のモノクロ化を行ってみる。
モノクロ化を行うためによく使われる手法は、まず画像をRGBからYCCまたはYIQフォーマットへと変換する。そして、輝度であるY信号の値をRGB値として使う、というものだ。YCCフォーマットに変換する事で、人間の目に敏感な輝度の値を取り出せるようになり、これを使う事できれいなモノクロ画像を作る事ができる。
これにより、次のようなソースコードでエフェクトをかけてみよう。輝度値の計算の詳細は、画像処理プログラミングのテキストなどを参考にしてほしい。
CameraViewController.m
...
// ビットマップに効果を与える
NSUInteger i, j;
for (j = 0; j < height; j++) {
for (i = 0; i < width; i++) {
// ピクセルのポインタを取得する
UInt8* tmp;
tmp = buffer + j * bytesPerRow + i * 4;
// RGBの値を取得する
UInt8 r, g, b;
r = *(tmp + 3);
g = *(tmp + 2);
b = *(tmp + 1);
// 輝度値を計算する
UInt8 y;
y = (77 * r + 28 * g + 151 * b) / 256;
// 輝度の値をRGB値として設定する
*(tmp + 1) = y;
*(tmp + 2) = y;
*(tmp + 3) = y;
}
}
最後に、効果を与えたビットマップデータから、画像オブジェクトを作ろう。これは、いままでの作業の逆になる。まず、ビットマップデータを使ってCFDataオブジェクトを作る。それを使ってCGDataProviderを作り、さらにCGImageを作る。そして、CGImageからUIImageを作れば完成だ。
CameraViewController.m
...
// 効果を与えたデータを作成する
CFDataRef effectedData;
effectedData = CFDataCreate(NULL, buffer, CFDataGetLength(data));
// 効果を与えたデータプロバイダを作成する
CGDataProviderRef effectedDataProvider;
effectedDataProvider = CGDataProviderCreateWithCFData(effectedData);
// 画像を作成する
CGImageRef effectedCgImage;
UIImage* effectedImage;
effectedCgImage = CGImageCreate(
width, height,
bitsPerComponent, bitsPerPixel, bytesPerRow,
colorSpace, bitmapInfo, effectedDataProvider,
NULL, shouldInterpolate, intent);
effectedImage = [[UIImage alloc] initWithCGImage:effectedCgImage];
[effectedImage autorelease];
// 画像を表示する
_imageView.image = effectedImage;
これで、エフェクトをかけたUIImageオブジェクトの出来上がりだ。これを表示すれば、撮影した写真がモノクロになっているはずだ。
これで、カメラアプリとしての基本的な動作は実現できた。あとは、様々なエフェクトを追加すれば、もっと魅力的なカメラアプリに仕上がるだろう。
ここまでのソースコード: Camera-4.zip