このブログはアフィリエイト広告を利用しています

Swift

なぜキサマのCGContextコンストラクタはnilを返すのか

Core Graphicsフレームワークを使って描画処理をするためにCGContextインスタンスを作成しようとしたところ、コンストラクタに不適切なパラメータを渡すとnilが返ってしまうので、どんなパラメータを渡せばいいのかについて調べました。

ということで、キサマでも理解できるよう解説します。(キサマネタはタイトルとここだけで終わり。一度言ってみたかったけど満足。)

まずCGContextコンストラクタの各パラメータの基本説明をし、続いて正しいパラメータの組み合わせを判断する方法を説明します。最後にいくつか基本的なサンプルコードを紹介します。

CGContextコンストラクタの基本

CGContextコンストラクタは、パラメータを7つ指定する必要があります。

widthやheightなどの値は画像のサイズを指定するだけと簡単ですが、他のパラメータは何を指定していいのか見当も付かないことでしょう。

init?(
    data: UnsafeMutableRawPointer?,
    width: Int,
    height: Int,
    bitsPerComponent: Int,
    bytesPerRow: Int,
    space: CGColorSpace,
    bitmapInfo: UInt32
)

そこで、各パラメータの指定方法について言及しておきます。

bitsPerComponent, space, bitmapInfoについてデバイスが対応していない組み合わせを指定してしまうとコンストラクタはnilを返してしまうため注意が必要ですが、有効な組み合わせをどう把握するかについては次章のデバイスとピクセルフォーマットで解説します。

data: UnsafeMutableRawPointer パラメータ

元データが無い場合はnilを指定します。

ちなみにnil以外を指定する方法についてはケンヂまるは検証していないため言及しません。

width: Int パラメータ

作成したいビットマップの幅をピクセル数で指定します。

height: Int パラメータ

作成したいビットマップの高さをピクセル数で指定します。

bitsPerComponent パラメータ

「bits per component」とは、1つの色成分(赤、緑、青など)を表すビット数です。赤を0〜255で表現する場合、8bitsのデータ量を使うことになり、bitsPerComponentは8ということになります。

この値は5, 16, 32などを指定することができます。

bytesPerRow パラメータ

1行分のデータ量を指定します。

bitsPerComponentに8を指定すると、1ピクセルは32bitsになります。

1行分のデータ量は画像の幅を掛けることで求めることができるので、

32 * width = bytesPerRow

として求めることができます。

ただこのパラメータに0を指定すると適切な値を内部で算出してくれるので、0を指定しておけばいいでしょう。(必須パラメータにしないでくれよ…)

space パラメータ

色情報の持ち方をCGColorSpace形式で指定します。

RGBカラーを使いたい場合はCGColorSpace.sRGBCGColorSpaceCreateDeviceRGB()などを指定できます。

グレースケールの画像にしたい場合はCGColorSpaceCreateDeviceGray()などを指定できます。

CGColorSpace.sRGBは、そのままだとCFString型であるためspaceパラメータに指定することはできません。そのため、CGColorSpaceのinit(name:)コンストラクタに指定してCGColorSpaceインスタンス化してから指定する必要があります。

let color = CGColorSpace(name: CGColorSpace.sRGB)

bitmapInfo パラメータ

画像データのアルファチャンネルの持ち方などを指定します。

CGImageAlphaInfoCGBitmapInfoのrawValue(UInt32)の論理和を指定します。0を指定した場合は何も指定していないことを意味し、デフォルト値が適用されます。

デフォルト値が適用されるといっても、大抵の場合、アルファチャンネルの扱いについては指定しないとインスタンス作成に失敗します。

RGBカラーでアルファチャンネルに対応しない画像の場合、アルファチャンネル情報ビットは無視する必要があるため、次の値を指定することになります。

let bitmapInfo = CGImageAlphaInfo.noneSkipLast.rawValue

カラースペース・デバイス・ピクセルフォーマット

カラースペースとは、グレイスケール、RGB、CMYKなど色情報の持ち方のことです。

ピクセルフォーマットとは、画像の1ピクセルを表現するためのデータの持ち方のことです。

デバイスごとにサポートしているカラースペースとピクセルフォーマットの組み合わせは異なっています。MacOSではサポートされているものが、iOSではサポートされていなかったりします。

そしてCGContextのコンストラクタへのパラメータはデバイスがサポートしているものである必要があり、これに違反した場合はnilが返ることになります。

デバイスとサポートの関係性の情報はSupported Pixel Formatsドキュメントで網羅されています。

Supported Pixel Formatsドキュメントから抜粋

CS

Color Spaceのことで、CGContextコンストラクタのspaceパラメータに指定する情報です。

例えばCGContextコンストラクタのspaceパラメータにRGB系の値を指定した場合、上に示したテーブルより、対応しているbppとbpcの組み合わせは次のいずれかでなくてはなりません。

  • 16bpp 5bpc
  • 32bpp 8bpc
  • 64bpp 16 bpc

bpp

bppはbits per pixelのことで、1 pixelの色を表すのに何ビットのデータ量を使うかです。

赤・緑・青・アルファ情報を0〜255で表現するRGBカラーの場合、bppは32 bppになります。

32 bppは、8bpcとも言えます。

ややこしいかもしれませんが、アルファチャンネルを使わない8bpc RGBのbppは、24bppではなく32bppです。

例えアルファチャンネルに対応していなくても、アルファチャンネルの情報ビットは使われていないだけでデータとしては消費されるからです。

アルファチャネルなしのグレイスケールの場合、色の濃さを0〜255の範囲で表すだけでよいため、8bppになります。

bpc

bpcはbits per componentのことで、1つの色が表す階調の範囲を何ビットのデータ量で表現するかです。

0〜255の範囲で1色を表すARGBの場合、0〜255は8 bitsで表現するため、8 bpcになります。

階調を0〜255の範囲で表すグレースケールの場合でも、8 bpcになります。

この値はCGContextコンストラクタのbitsPerComponentパラメータに指定します。

余談

安定してSupported Pixel Formatsドキュメントに辿り着くには、次の手順を覚えおきましょう。

  1. Core GraphicsのSee AlsoセクションからQuartz 2D Programming Guideへジャンプ
  2. Quartz 2D Programming Guideの左側メニューTable Of ContentsからGraphics Contexts > Creating a Bitmap Graphics Context > Supported Pixel Formatsとたどる

CGContextコンストラクタがnilを返さない例

ここまでCGContextコンストラクタのパラメータへの値の指定方法と、その組み合わせの判断方法について解説しました。

これらを踏まえた、CGContextインスタンスを作るサンプルを作ってみました。

アルファチャンネルなしRGB

次のようにパラメータを指定すると、アルファチャンネルなしRGBイメージのCGContextを作成できます。

let context = CGContext(
    data: nil, // ベースデータなし
    width: 100, // 画像の幅は100pixel
    height: 50, // 画像の高さは50pixel
    bitsPerComponent: 8, // 8 bpc
    bytesPerRow: 0, // 1行あたりのデータサイズはコンストラクタに自動計算させる
    space: CGColorSpace(name: CGColorSpace.sRGB)!, // sRGBカラースペースを指定
    bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue // アルファチャンネル使用せず、25bit〜32bit目を読み飛ばす
)

アルファチャンネルありRGBイメージ

次のようにパラメータを指定すると、アルファチャンネルに対応したRGBイメージのCGContextを作成できます。

前の例に対して異なる部分はbitmapInfoパラメータだけです。

let context = CGContext(
    data: nil, // ベースデータなし
    width: 100, // 画像の幅は100pixel
    height: 50, // 画像の高さは50pixel
    bitsPerComponent: 8, // 8 bpc
    bytesPerRow: 0, // 1行あたりのデータサイズはコンストラクタに自動計算させる
    space: CGColorSpace(name: CGColorSpace.sRGB)!, // sRGBカラースペースを指定
    bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue // アルファチャンネル使用
)

bitmapInfoパラメータに指定しているpremultipliedFirstのpremultipliedは、色ブレンドの前アルファ値を掛けること指しています。

例えば赤(255, 0, 0)のアルファ0.5であれば、255 * 0.5として計算し、127になります。

黒色の上にこれを描画する場合、(0, 0, 0) + (255, 0, 0) * 0.5 = (127, 0, 0)といった感じで、最終的に薄暗い赤色が描画されます。

premultipliedFirstのFirstの部分は、ピクセルデータにおいて色情報より先にアルファ情報を格納することを示します。

例えば半透過の赤色の場合、(127, 255, 0, 0)のように、アルファの情報が最初に来ます。

アルファチャンネルありRGB64bitカラーイメージ

1ピクセルを表すのに32bitsではなく64bitsを使う、より広範囲な色を表現できるRGBイメージのCGContextを作成します。

let context = CGContext(
    data: nil, // ベースデータなし
    width: 100, // 画像の幅は100pixel
    height: 50, // 画像の高さは50pixel
    bitsPerComponent: 16, // 8 bpc
    bytesPerRow: 0, // 1行あたりのデータサイズはコンストラクタに自動計算させる
    space: CGColorSpace(name: CGColorSpace.sRGB)!, // sRGBカラースペースを指定
    bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue // アルファチャンネル使用
)

64bitsデータからはpremultipliedFirstは対応しなくなり、premltipliedLastのみ対応するようになります。

アルファチャンネルありRGB128bitsイメージ

bitsPerComponentに32を指定し、bitmapInfoにpremultipliedLastとfloatComponentsを指定して、1pixelあたり128bitsのデータ量の画像を作成は次のようにします。

32bpcからは、各色の情報の保持はfloat型のみサポートされているので、bitmapInfoにfloatComponentsを指定する必要があります。

let context = CGContext(
    data: nil, // ベースデータなし
    width: 100, // 画像の幅は100pixel
    height: 50, // 画像の高さは50pixel
    bitsPerComponent: 32, // 32 bpc
    bytesPerRow: 0, // 1行あたりのデータサイズはコンストラクタに自動計算させる
    space: CGColorSpace(name: CGColorSpace.sRGB)!, // sRGBカラースペースを指定
    // アルファチャンネル使用
    // またコンポーネント(各色情報)はFloat型で保持
    bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.floatComponents.rawValue
)

デバイスとピクセルフォーマットの対応表によると、iOSでbitsPerComponentに指定できる値は8までです。でも実際は、32を指定してもbitmapInfoとの組み合わせが適切なら問題なく動作するようです。対応表のページけっこう古いから、最新のデバイスの対応状況は実際のところもう少し改善されてるんだろなぁ。

-Swift

© 2024 ヂまるBlog