当ブログでは、よく使う機能を独自のライブラリ化している話が何度も出てきます。独自なのは本当ですが、たいしたことはやっていません。小型プリンターRoltoの場合は深く考えて設計したので、特別に取り上げました。でも、他の機能に関しては、ほとんど普通の作り方だと思います。
ただし、少しずつですが改良はしています。最初のうちは、iOSのAPIに詳しくないので、自分にとっての使いやすさを前面に出して設計していませんでした。でも、APIの特徴を理解し始めるとともに、ライブラリにも改良を加えたくなります。
最近、オフスクリーン描画のライブラリを改良しました。自分なりに考えて設計したので、もしかしたら誰かの参考になるかもしれません。また、iOSの描画機能を理解する手助けにもなると思います。これから数回に分けて、要点を紹介します。
中身に入る前に、2次元描画APIに関する私の理解レベルについて触れます。ハッキリ言ってしまうと、iOSの2次元描画機能は分かりづらいです。まず最初に、Quartz 2DとかCore Graphicsとか複数の名前があって、その範囲を正確には理解できていません。もしかしたら、今使っているAPIよりも良いAPIがあり、本来はそちらで作るべきかもしれないとの不安も少し持っています。もし間違って理解している箇所を見付けたら、正しい理解や使い方を、ぜひ教えてください。
一応、いろいろと調べてみた結果として、次のような内容が得られました。名前がUIGraphicsで始まるAPIを使って描画の開始や終了、出力先などを管理します。具体的な描画は、名前がCGContextで始まるAPIを使い、細かな設定が可能であることも知りました。これら2つの組み合わせが、オフスクリーン描画機能を作るのに必要なAPIの全体像となります。
もう1つ、大事な点があります。描画内容を作っている処理中は、まったく描画していないことです。単に描画する内容を構築しているだけで、それが終わったときに得られるのが、描画内容を記したコマンド集のようなものです。最終的にUIImageの形で作ったとしても、やはり描画コマンド集です。もしビットマップ画像を貼り付けた場合は、その画像へのリンクが含まれた描画コマンド集となります。
この描画コマンド集は、解像度に依存しません。ですから、すべての座標に浮動小数点のCGFloatを用いています。実際に描画されるのは、画面に表示されたり、プリンタに印刷されるときです。画面に表示する際には、ハードウェアの解像度に応じて、描画するビットマップ画像を生成するのでしょう。ディスプレイがレチナの場合は、縦横とも2倍の画素数(全体では4倍の画素数)を持ったビットマップ画像を生成するはずです。
このような仕組みなので、オフスクリーンで描画していると思っている処理は、まったく描画していなくて、描画するコマンド集を作っているだけです。ですから非常に高速で処理され、そこで長い時間が取られることはありません。
実際に処理時間がかかるのは、画面に描画するときです。当然GPUを使うので、描画内容が単純であれば一瞬で終わります。描画内容が複雑になったとき(つまり、描画コマンドの数が非常に多いとき)、遅いと感じるほどの時間を使うのでしょう。それを少しでも速くするには、重なる部分で透明度を使わないとか、描画計算時間が短くなるように工夫する必要があります。その工夫を、描画内容を作る際に行なうことは、言うまでもありません。
実際に作ってみて、分かってきたこともあります。先に結論を行ってしまうと、制限が意外に多いということです。何でも自由に制御できる形にはなっていません。制限を理解して、上手に使う必要があります。
たとえば、CGContextです。線の色とか太さとか、描画内容を指定するときにCGContextを使います。このCGContextは、自由に保存して、自在に切り替えるようにはなっていません。保存して切り替えようと思いましたが、保存するための機能がなく、深いコピーもできませんでした。swiftでの深いコピーにはcopyメソッドを使いますが、そんなメドッソはないとコンパイラーに怒られました。
swift上ではCGContextという型の名前ですが、Objective-C上ではCGContextRefという違う名前で、参照を意味するRefが付いています。通常のオブジェクトもインスタンスへの参照ですが、わざわざRefとは付けません。おそらく、何か特殊な参照なのでしょう。
この辺の制限の話は、ライブラリを使う段階で詳しく取り上げたいと思います。もちろん、私が知っている範囲でですが。
制限が多い理由も、作っているうちに理解してきました。もっとも大事なのが、UIGraphicsやCGContextで作る描画コマンド集は、実際に描画するための部品を作るのが役割だという点です。最終的な表示内容ではなく、あくまで描画に使う「部品」です。その部品を組み合わせて、最終的な表示内容を画面上に構築します。部品ごとに配置を変え、実際に表示する際に描画するわけです。
アニメーションで使う場合が、一番理解しやすいでしょう。動かす最小単位を部品として作り、部品ごとに異なる動きを与えます。同じ動きをするものは1つの部品内に集めて、一緒に作ります。作った中身は、前述のように描画コマンド集です。
このような役割なので、並行して複数を作る必要はありません。あくまで描画する前の準備段階であり、描画で使う部品を作るわけですから、1つ1つの部品を順番に作っていくわけです。そのため、常に1つの部品だけを描画していて、「どの部品に対して描画するのか指定する機能」は持っていません。必要な描画が終わったら部品を出力し、別な部品の描画を開始します。あくまで「1つ1つの部品を順番に作る」なのです。
以上の内容ですが、最初はまったく理解できていませんでした。最初に作りたいと思った形(CGContextを自在に切り替えて描画する形)ではライブラリが実現できず、少しずつ別な方法へと変更していった中で、だんだんと分かってきたことです。
作成したライブラリを使い、複数のインスタンスを生成して動かした結果を見て、制限があることと、その制限の理由が理解できました。この辺の詳しい話は、ライブラリを使って動かした結果を知ることが、もっとも理解しやすい方法だと思います。そのため、一番最後に取り上げるつもりです。
ここまでの話から分かるように、オフスクリーン描画機能を作り進む中で、大事な点に気付きました。描画しておらず、描画で使う部品を作っていることです。これって「オフスクリーン描画機能」と呼んで構わないの?という素朴な疑問が生じました。当然ですね。でも、機能としては「オフスクリーン描画機能」が一番しっくり来るというか、中身の機能をイメージしやすいので、その名前で作り進めることにしました。将来は変更するかもしれませんが。
話が長くなりましたので、一旦ここで切ります。ライブラリを作る話は、次回からですね。まずは、全体設計の話になると思います。実際のソースコードが登場するのは、その後でしょう。
0 件のコメント:
コメントを投稿