2014年12月6日土曜日

オフスクリーン描画をライブラリ化(本編1)

(注:数回後の投稿で行なった実験が終わったので、ソースコードの一部を修正しました。修正後の現状が正式版です)

オフスクリーン描画機能をライブラリ化する話の続きです。いよいよSwiftでソースコードを作る段階です。大まかな構造は決まっていますから、swiftだと簡単に作れます。ライブラリ用のクラスとして実現するので、クラス内に配列を用意して、CGContextで使う色設定を保存する必要があります。

 

まずは入れ物となるクラスを作ります。クラス名は少し長めですが、DrawImageOffScreenとしました。オフスクリーン以外の描画ライブラリも作ると予想し、DrawImageシリーズとして統一しようと思っての命名です。

クラス内には配列を用意して、CGContextの色設定を保存する必要があります。線色(StrokeColor)と塗りつぶし色(FillColor)の両方を。また、それぞれの色の初期値も先頭で定義しておくべきでしょう。Swiftコードは、次のようになります。

// オフスクリーン描画機能をライブラリ化したクラス
class DrawImageOffScreen {
    // 色のデフォルト値
    let COLOR_DFAULT_STROKE : [CGFloat] = [0.0, 0.0, 0.0, 1.0]  // 黒で不透明
    let COLOR_DFAULT_FILL : [CGFloat] = [1.0, 1.0, 1.0, 1.0]    // 白で不透明
    // CGContextに設定する値の入れ物
    var numContext : Int = 0
    var arySColor : [[CGFloat]] = []   // StrokeColor
    var aryFColor : [[CGFloat]] = []   // FillColor
    // 描画可能フラグ
    var enabledDraw : Bool = false
}

色の初期値としては、線色が黒の不透明、塗りつぶし色は白の不透明にしました。中を塗りつぶさない場合の指定は、図形を描く命令で可能なので、中を塗りつぶす場合のデフォルト値として白を選びました。

配列の要素数を示す変数名は、numContextとしました。今は色情報だけですが、他の情報も加えたとき、変数名を変更しなくて済むようにとの配慮です。

さらに、描画可能状態かを示すフラグenabledDrawも加えます。これがtrueのときだけ、設定や描画を可能とします。

このクラスの中に、いろいろなメソッドを追加することになります。

 

最初は、クラスの初期化です。できるだけ少ない行数でクラスを使いたいので、開始に必要な値を初期化に含めています。イメージの大きさ(幅と高さ)、背景の透明不透明、使用する色数だけです。

入れるかどうか迷ったのが、UIGraphicsBeginImageContextWithOptionsの引数scaleです。自動判定を意味する0.0のまま使い続けそうなので、今回は外しました。必要になった時点で入れればよいとの判断です。

// サイズを指定して、空のイメージを生成する
init(_ rWidth:CGFloat, _ rHight:CGFloat, _ rOpaque:Bool, _ rNumContext:Int) {
    if (rNumContext < 1) { println("ERROR:DIOS_IN:rNumContextが小さすぎ"); return }
    // コンテキストのデフォルト値を設定
    numContext = rNumContext
    for i in 0..<rNumContext {
        arySColor.append(COLOR_DFAULT_STROKE)
        aryFColor.append(COLOR_DFAULT_FILL)
    }
    // オフスクリーン描画の開始(rOpaqueがfalseで背景が透明に,scaleが0.0で自動)
    let iCGSize : CGSize = CGSizeMake(rWidth, rHight)
    UIGraphicsBeginImageContextWithOptions(iCGSize, rOpaque, 0.0)
    enabledDraw = true   // 描画可能に設定
}

ここでも「色のデフォルト値を設定」ではなく、「コンテキストのデフォルト値を設定」としました。色以外の情報を追加したとき、コメントを修正しなくて済むようにとの配慮です。

一番最初に、最低限のエラーチェックを入れいています。rNumContextに1より小さい値を指定した場合は、エラーメッセージを出し、何もしないで終わります。

エラーメッセージのフォーマットは以前からほぼ固定です。必ず「ERROR:」で始まり、エラーを出したクラス名の省略形と「_」が続き、その中の場所を示す省略形と「:」が加わります。最後にエラー内容を示す文字列を付けるという、ありがちな形式です。今回の場合は、「DrawImageOffScreen」の省略形が「DIOS」で、「init」の省略形が「IN」ですね。

 

続いて、初期化とペアになる終了処理を書きます。描いた画像をUIImegeとして返し、描画処理を終了するメソッドです。処理内容は非常に簡単で、次のようなSwiftコードになります。

// 出来上がった合成イメージを返す
func getFinalImageF() -> UIImage {
    if !enabledDraw { println("ERROR:DIOS_GI:Disabled"); return nil }
    // 描き終わった内容をUIImageに保存する
    let iImage : UIImage = UIGraphicsGetImageFromCurrentImageContext()
    // 描画の終了
    enabledDraw = false   // 描画不可能に設定
    UIGraphicsEndImageContext()
    return iImage
}

処理内容に関しては、とくに説明する必要はないでしょう。最初に描画可能かどうか調べて、可能なら処理を進めます。最後に、描画不可能に設定してから、コンテキストを終了してます。

迷ったのはメソッド名ですね。このメソッドには、2つの役割が含まれます。出来上がったUIImageをゲットする役割と、描画機能を終了する役割です。ゲットのほうを重視しながら、終わりを意味する「Final」を入れてみました。イマイチですね。

ここでは2つの役割を1つのメソッドに含めましたが、別々のメソッドとして作るという選択肢もあります。別々に作った場合の短所は、描画処理全体の最後に、余計なメソッドを呼ぶ必要があることです。逆に長所は、途中まで描いた段階でもUIImageを取得できることです。途中の段階と最終段階の両方をUIImageで得たい処理のとき、無駄な手間が不要となります。実際には小さい違いなので、好きな方を選んで構わないと思います。

 

次は、CGContextに設定するための色を指定するメソッドです。色には、線色と塗りつぶし色があり、それぞれ透明度まで含めると4つずつの値が含まれます。それを一気に指定すると、多すぎる引数となります。また、線を描く場合などは、線色しか使いません。やはり、別々のメソッドとして作るほうが無難でしょう。

そうして作ったSwiftコードは、次のとおりです。

// コンテキストの線色を設定する
func setContextStrokeColorF(rNum:Int, _ rRed:CGFloat, _ rGreen:CGFloat, _ rBlue:CGFloat, _ rAlpha:CGFloat) {
    if !enabledDraw { println("ERROR:DIOS_SC:Disabled"); return }
    if (rNum >= numContext || rNum < 0) { println("ERROR:DIOS_SC:rNumが範囲超え"); return }
    arySColor[rNum][0] = rRed
    arySColor[rNum][1] = rGreen
    arySColor[rNum][2] = rBlue
    arySColor[rNum][3] = rAlpha
}
// コンテキストの塗りつぶし色を設定する
func setContextFillColorF(rNum:Int, _ rRed:CGFloat, _ rGreen:CGFloat, _ rBlue:CGFloat, _ rAlpha:CGFloat) {
    if !enabledDraw { println("ERROR:DIOS_FC:Disabled"); return }
    if (rNum >= numContext || rNum < 0) { println("ERROR:DIOS_FC:rNumが範囲超え"); return }
    aryFColor[rNum][0] = rRed
    aryFColor[rNum][1] = rGreen
    aryFColor[rNum][2] = rBlue
    aryFColor[rNum][3] = rAlpha
}

どちらのメソッドでも、描画可能かどうかと、配列の範囲を超えてないかだけはチェックしています。エラーメッセージはほとんど同じですが、それぞれのメソッドを示す省略形だけが違っています。

4つの値のチェックも入れるべきか迷いましたが、入れないことにしました。表示された色を確認しながら開発するはずなので、間違えたら気付くだろうと思ったからです。また、値が範囲を超えても、異常終了したりせず、超えた分を無視して処理されるからです。たとえば、1.0を超えた値を指定すると、1.0として処理されます。

 

このままだと配列の要素数が、最初に指定したまま固定になっています。使う側の処理内容によっては、どれだけ使うのか途中で判断し、追加しながら使いたい場合もあるでしょう。

そこで、配列を追加するメソッドだけを加えました。追加する個数を指定する形にしてます。次のように、Swiftコードは非常に簡単です。

// 指定された個数だけコンテキストを追加する
func addContextF(rNumAdd:Int) {
    if !enabledDraw { println("ERROR:DIOS_AC:Disabled"); return }
    numContext += rNumAdd
    for _ in 1...rNumAdd {
        arySColor.append(COLOR_DFAULT_STROKE)
        aryFColor.append(COLOR_DFAULT_FILL)
    }
}

このメソッドでコンテキストを追加した後、追加した数の分だけ、前述の色設定メソッドを使って、線色と塗りつぶし色を登録することになります。正しく使っているか確認する意味で、描画可能かどうかもチェックしています。

配列から削除する処理は面倒なので作りませんでした。追加があるだけでも便利に使えますし、イメージを作り終わったらメモリーを開放するでしょうから、削除が無くても困らないと考えました。

 

ここまでで、描画を担当するメソッド以外は作り終えました。このまま続けると、さらに長くなりそうなので、ここで一旦切ります。残りのメソッドは、次回ということで。

 

(使用開発ツール:Xcode 6.0.1, SDK iOS 8.0)

0 件のコメント:

コメントを投稿