カメラアプリの作り方は、今回で最終回となる。最後に説明するのは、撮影した写真にエフェクトをかける方法だ。このエフェクトの種類で、面白いカメラアプリになるかどうかが決まるだろう。

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