読者です 読者をやめる 読者になる 読者になる

定食屋おろポン

おろしポン酢と青ネギはかけ放題です

CoreGraphicsでハマる

基礎固め

実践! iPhoneアプリ開発 (4) カメラアプリの作り方 (4) - 写真にエフェクトをかける | マイナビニュース
こちらの連載を見ながらiPhone開発のお勉強をしている時のお話です。

4ページ目で早くも詰まった/(^o^)\

ハマったポイント

imagePickerで取得した画像をモノクロに変換するところですね。
左がオリジナル画像、右が変換後。ちなみにこの画像は我が家の愛猫、という訳ではなくてGoogleイメージ検索で適当に拾った画像です。



なんだか変換に失敗しています。
やけに薄い。そして黄色い。

とはいえ画像処理なんてやったこともないから、何が悪いのかさっぱり分からん。
まずはログを吐かせてみました。

// Monochrome effect
NSUInteger i, j;
for (j = 0; j < height; j++) {
    for (i = 0; i < width; i++) {
        // Get pixel pointer
        UInt8*  tmp;
        tmp = buffer + j * bytesPerRow + i * 4;
        
        // Get RGB value
        UInt8 r, g, b;
        r = *(tmp + 3);
        g = *(tmp + 2);
        b = *(tmp + 1);
        
        if (i < 3 && j < 3) {
            
            NSLog(@"\n \
                  *tmp:\t%d\n \
                  *tmp+1:\t%d\n \
                  *tmp+2:\t%d\n \
                  *tmp+3:\t%d\n \
                  *tmp+4:\t%d\n \
                  *tmp+5:\t%d\n \
                  *tmp+6:\t%d\n \
                  (77 * r + 28 * g + 151 + b) / 256:%d",
                  *tmp, *(tmp+1), *(tmp+2), *(tmp+3), *(tmp+4), *(tmp+5), *(tmp+6),
                  (77 * r + 28 * g + 151 + b) / 256);
        }
        
        // Convert RGB -> YUV
        UInt8   y;
        y = (77 * r + 28 * g + 151 + b) / 256;
        
        // Set Y value to RGB value
        *(tmp + 3) = y;
        *(tmp + 2) = y;
        *(tmp + 1) = y;

すると以下のような結果に。

// 1px目
*tmp:	58
*tmp+1:	92
*tmp+2:	137
*tmp+3:	255
*tmp+4:	43
*tmp+5:	74
*tmp+6:	120
(77 * r + 28 * g + 151 + b) / 256:92

// 2px目
*tmp:	43
*tmp+1:	74
*tmp+2:	120
*tmp+3:	255
*tmp+4:	44
*tmp+5:	74
*tmp+6:	122
(77 * r + 28 * g + 151 + b) / 256:90

// 3px目
*tmp:	14
*tmp+1:	48
*tmp+2:	94
*tmp+3:	255
*tmp+4:	11
*tmp+5:	41
*tmp+6:	88
(77 * r + 28 * g + 151 + b) / 256:87
.
.
.

*(tmp+4)からは次のピクセルのデータを取得している模様。つまり、値は4つセット。これはRGBAの値ですね。4番目:*(tmp+3)の値が常に255であることから、まあこれがアルファ値でしょう
サンプルコードでは、ABGRの並びであることを想定している模様ですが、今回のログを見るにBGRAの順番で並んでいます。
これは一体どういうことだろう、と探してみたらこんな記事が。
iOS4からCFDataGetBytePtrの戻りが以前と異なる問題について - 最遅メンヘル研究会
なるほどわからん。しかし、とりあえずRGBAの並び順を勝手に想定して書いたらダメらしいということは分かった。
CoreGraphics.frameworkのCoreImage.hや、CoreImageのドキュメントを眺めていると、RGBAの並び順などの情報はCGImageAlphaInfoが持っていて、取得するにはCGImageAlphaInfo CGImageGetAlphaInfo(CGImageRef image)を使えばよい模様。具体的には

CGImageAlphaInfo alphaInfo;
alphaInfo = CGImageGetAlphaInfo(cgImage);

となりますね。
CGImageAlphaInfoの定義は

enum CGImageAlphaInfo {
   kCGImageAlphaNone,
   kCGImageAlphaPremultipliedLast,
   kCGImageAlphaPremultipliedFirst,
   kCGImageAlphaLast,
   kCGImageAlphaFirst,
   kCGImageAlphaNoneSkipLast,
   kCGImageAlphaNoneSkipFirst
};
typedef enum CGImageAlphaInfo CGImageAlphaInfo;

となっていて、今回シミュレータではkCGImageAlphaPremultipliedLastの画像として読み込まれていました。
Premultipliedってのはアルファ値を事前に掛けておくってことだろうけど詳しいことはCore Image Programming Guideに書いてあるみたいなので今度読みます。
ともかく

  • kCGImageAlphaFirst
  • kCGImageAlphaPremultipliedFirst

はARGB(つまりBGRA)

  • kCGImageAlphaLast
  • kCGImageAlphaPremultipliedLast

はRGBA(つまりABGR)

  • kCGImageAlphaNone
  • kCGImageAlphaNoneSkipLast

はアルファ値がなく、RGBー(つまりーBGR)

  • kCGImageAlphaNoneSkipFirst

はアルファ値がなく、ーRGB(つまりBGRー)

  • kCGImageAlphaOnly

はアルファ値のみ(つまり…どういうことだってばよ?)
といった感じだろうことが推察されます。ARGBの順である画像を実際に見てみるとBGRAと逆になっているのは

CFByteOrderLittleEndian
Multi-byte values are stored with the least-significant bytes stored first. Pentium CPUs are little endian.

とのこと。cf.RE: Byte Order in CGBitmapContextCreate
さて、厳密にやるならばアルファ値がない場合やアルファ値のみの場合、CMY等RGB以外の場合etc..についても処理を考える必要があるんだろうけど、当座は以下のような感じで良いのではないですかね。

.
.
.
CGImageAlphaInfo alphaInfo;
alphaInfo = CGImageGetAlphaInfo(cgImage);

// 変な変数名ですみません。
int adjustAlphaInfo;
if (alphaInfo == kCGImageAlphaFirst ||
    alphaInfo == kCGImageAlphaPremultipliedFirst ||
    alphaInfo == kCGImageAlphaNoneSkipFirst) {
    adjustAlphaInfo = 0;
}
else if (alphaInfo == kCGImageAlphaLast ||
         alphaInfo == kCGImageAlphaPremultipliedFirst ||
         alphaInfo == kCGImageAlphaNone ||
         alphaInfo == kCGImageAlphaNoneSkipLast) {
    adjustAlphaInfo = 1;
}
else {
    return;
}
.
.
.
// Get RGB value
UInt8 r, g, b;
r = *(tmp + 2 + adjustAlphaInfo);
g = *(tmp + 1 + adjustAlphaInfo);
b = *(tmp + 0 + adjustAlphaInfo);
.
.
.
// Set Y value to RGB value
*(tmp + 2 + adjustAlphaInfo) = y;
*(tmp + 1 + adjustAlphaInfo) = y;
*(tmp + 0 + adjustAlphaInfo) = y;

ようやく成功。長かった。