嘆き

全ては、内部処理が48bit-colorになったからって、それをスクショ時にそのまま書き出すiOSが悪い!
そんなあっさり48bit-colorのPNGなんて渡ってくると思わねえよ!

bitmapのpixel formatの基本

各ピクセルのRGBAのデータがひたすら並んでいる。
RGBAの順番については色々。
大概は24bit-color(RGB各8bit)+alpha(8bit)で32bit/pixel。

iOSでbitmap読むとき

CGBitmapContextGetAlphaInfo()CGBitmapContextGetBitmapInfo()の値を見比べてやる

AlphaInfo

typedef CF_ENUM(uint32_t, CGImageAlphaInfo) {
    kCGImageAlphaNone,               /* For example, RGB. */
    kCGImageAlphaPremultipliedLast,  /* For example, premultiplied RGBA */
    kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */
    kCGImageAlphaLast,               /* For example, non-premultiplied RGBA */
    kCGImageAlphaFirst,              /* For example, non-premultiplied ARGB */
    kCGImageAlphaNoneSkipLast,       /* For example, RGBX. */
    kCGImageAlphaNoneSkipFirst,      /* For example, XRGB. */
    kCGImageAlphaOnly                /* No color data, alpha data only */
};

↑ 元のヘッダファイルのkCGImageAlphaNoneSkipLastのコメント、「R B G X」は奴らのtypoなので注意。 (上の引用では修正済)

  • Aの位置がどこにあるか
  • Aはあるのか
  • premultiplied(アルファ乗算済み)かどうか

ByteOrderInfo

typedef CF_ENUM(uint32_t, CGImageByteOrderInfo) {
    kCGImageByteOrderMask     = 0x7000,
    kCGImageByteOrder16Little = (1 << 12),
    kCGImageByteOrder32Little = (2 << 12),
    kCGImageByteOrder16Big    = (3 << 12),
    kCGImageByteOrder32Big    = (4 << 12)
};
  • 区切りが16bitか32bitか。
  • byteorderがbig(そのまま)かlittle(逆順)か

BitsPer

size_t CGImageGetBitsPerComponent(CGImageRef image);
size_t CGImageGetBitsPerPixel(CGImageRef image);

これが今回のミソ。
普通に8bit/compoment、32bit/pixelが返ってくるものと思っていたが、いやいや。
ただスクショを撮って、それをImagePickerで読み込むだけで。
16bit/component、64bit/pixelのデータが来るわけですよ。まじかよ。さすが未来に生きてるな、Apple。

結論

0 1 2 3 4 5 6 7
24bit-color RGBA R G B A
24bit-color RGBA / LE32 A B G R
48bit-color RGBA R r G g B b A a
48bit-color RGBA / LE16 r R g G b B a A

UInt8のポインタでアクセスして、以下な感じで済ますのが良さげだった

#define u16(p,i,le) (le?p[2*i]+(p[2*i+1]<<8):(p[2*i]<<8)+p[2*i+1])

あとpremultipiledな場合は

CGFloat fa = p[3] / UINT8_MAX;
UInt8 r = p[0] / fa;
UInt8 g = p[1] / fa;
UInt8 b = p[2] /  fa;
UInt8 a = p[3];

な感じで使う前にalphaを逆算してあげてね

その他

他にも値がUnsigned intでなくfloating pointだったりすることもあるらしい(kCGBitmapFloatInfoMask)のだが、さすがに対応を割愛。