【コラム】

実践! iPhoneアプリ開発

4 カメラアプリの作り方 (4) - 写真にエフェクトをかける

    木下誠  [2009/02/23]

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

    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

    関連したタグ

    新着記事

    特設サイトの情報

      人気記事

      一覧

        イチオシ記事

        新着記事

        特別企画

        マイナビニュースマガジン