ExcelVBAゲームプログラミング?

初心者でもきっとできる!
Excelさえ持っていれば特別なソフトは不要!
すぐにでも始められる簡単ゲームプログラミング!
今すぐ始めよう!

サンプルやゲームのダウンロードができる別館も好評運営中です。
ご意見やご質問、ゲームの感想等は掲示板までお気軽に。是非、皆さんの声を聞かせてください。運営、開発の励みになります。



各種ダウンロードはコチラ ↓ 意見・感想・質問はコチラ ↓
影倉庫 Shadow warehouse サポート掲示板
ブログの全体像はコチラ ↓ リンクのページはコチラ ↓
サイトマップ 自分本位なリンク


スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。






Chapter.134 [ APIによる描画処理6:図形描画実践編 ]

■前回のおさらい

API による図形描画処理も、いよいよ本丸に近づいてきました。
前回は準備編と題して、標準モジュールに記述するコードを解説しましたが、様々な API の宣言を始め、なかなかわかりにくい内容だったのではないかと思います。
今回はフォームモジュールに記述するコードを解説することになりますが、前回の内容がきちんと理解できていれば、さほど難しくはないと思います。API を使う場合に常に言えることですが、API は扱いが難しいというより面倒であることが多いです。とにかく複雑というか、独特のルールに従って正しく記述を行なわないと動作しないわけです。

今回は、図形描画系の代表的な API である矩形・円形・直線を描画するそれぞれの API について詳しく解説します。文章量というか、コードの文量は前回と比較すればはるかに少なくなりますので、じっくり読み解いていただければと思います。

それではさっそく、フォームモジュールに記述するコードを見ていきましょう。


■ユーザーフォームの起動と終了

ユーザーフォームの起動と終了に、それぞれ処理を行なうようにコードを記述します。

VBA で利用できるユーザーフォームは、起動と同時にいくつかのイベントを発生させますが、今回はユーザーフォームが起動しアクティブになった瞬間に発生するイベント、Activate イベントを使います。

Private Sub UserForm_Activate()

    get_handle Me.Caption

End Sub

ユーザーフォームの起動関連イベントには、Activate 以外にも、もうひとつ Initialize がありますね。ただし今回は、絶対に Activate イベントを利用する必要があります。その理由、想像がつくでしょうか。

Initialize イベントは、実際にユーザーフォームが表示される前の段階、つまり、メモリ上にユーザーフォームのデータが展開される段階で発生します。ユーザーフォームの Initialize イベント中にコードを中断してみればわかりますが、イベントが発生した段階ではまだユーザーフォームは画面上に表示されていないわけです。

一方で、Activate イベントが発生するタイミングは、ユーザーフォームが表示されてから、実際にアクティブになったその瞬間です。画面上には既にユーザーフォームが表示されているわけですから、その点が Initialize イベントとは異なります。

先ほどのコードは、前回解説したウィンドウハンドルを取得するためのプロシージャを呼び出しているだけの簡素なものですが、肝心のユーザーフォームが表示されていない状態でウィンドウハンドルを取得しようとしても、うまくいくはずがないですよね。
ユーザーフォーム、つまりウィンドウが画面上にしっかり表示されてからハンドル取得の処理が実行されるようにするわけですから、あえて Activate イベントを使う必要があるのですね。

さて、続いてユーザーフォームの終了時の処理です。

Private Sub UserForm_Terminate()

    release_obj

End Sub

終了時の処理も、前回解説した終了処理プロシージャを呼び出しているだけですので、コードの量は非常に少ないです。呼び出しているプロシージャが実際にどんな処理をしているのかは、前回の記事を参照していただければと思います。


■やっと描画処理

さて、前回も含めて、長い長い前置きになりましたが、やっと実際の描画処理を行なっている部分に到達です。

以下に掲載するコードはユーザーフォームのクリックイベントを利用して、いくつかの図形を API で描画しているものです。一見すると何をやっているのかわからないかもしれませんが、そこはしっかり解説しますので、まずはコードを見てみましょう。

Private Sub UserForm_Click()

    Dim pen As Long
    Dim brush As Long
    Dim lpPoint As POINTAPI
    
    pen = p_create(1, PS_SOLID, RGB(255, 0, 0)) '①
    brush = b_create(BS_SOLID, RGB(255, 255, 0), 0) '②
    dPen = SelectObject(hdc, pen) '③
    dBrush = SelectObject(hdc, brush) '④
    Rectangle hdc, 10, 10, 60, 60 '⑤
    Ellipse hdc, 70, 10, 120, 60 '⑥
    MoveToEx hdc, 130, 35, lpPoint '⑦
    LineTo hdc, 180, 35 '⑧

End Sub

上から順番に見ていきます。

まず、変数の宣言ですが、このプロシージャのなかではみっつの変数を宣言しています。変数 pen と、変数 brush は、その名の通りペンとブラシのハンドルを格納するために使います。わかりにくいのは最後に宣言されている lpPoint という変数だと思いますが、こちらはエラー回避のために後々利用することになりますので、そこで詳しく解説します。

①と②は、前回解説した自前のオブジェクト生成プロシージャですね。
①では、ペンオブジェクトを生成していますが、今回の引数の指定では、1 ピクセルの赤い実線を指定してペンを生成しています。変数 pen に、生成されたペンオブジェクトのハンドルが入ります。
②はブラシですね。通常の塗り潰しブラシ(ソリッドブラシ)で、色は黄色です。変数 brush にブラシオブジェクトのハンドルが入りますね。

続いて③と④ですが、ここではデバイスコンテキストにオブジェクトを割り当てています。イメージとしては、ペンとブラシを持ち替えさせている感じですね。
前回の講座でも詳しく解説しましたが、SelectObject 関数はデバイスコンテキストにオブジェクトを選択させる機能があり、戻り値として、それまで選択されていたオブジェクトのハンドルを返してきます。重要なのは、プログラムが終了する最後の最後、プログラム内部で生成したオブジェクトを削除する処理を行なう際、一番最初にデバイスコンテキストがデフォルトで持っていたオブジェクトのハンドルが必要になる点です。なぜデフォルトのオブジェクトのハンドルが必要なのかと言えば、削除しようとするオブジェクトがデバイスコンテキストに選択状態になっていると、エラーが起こる可能性があるからです。
今回のプログラムでは、ペンとブラシをそれぞれひとつずつ生成していますが、これらのオブジェクトを削除する際には、デバイスコンテキストに別のオブジェクトが選択されている必要があります。このときに、デフォルトのオブジェクトのハンドルが必要になるのですね。
③と④の処理では、それぞれデフォルトのオブジェクトのハンドルを、変数 dPen と変数 dBrush に保持するように処理しています。このふたつの変数は、標準モジュールのほうで Public な変数として宣言してありますので、最後の終了処理で活用することができます。

さて、続いては⑤です。ここでは、Rectangle 関数という API が登場していますね。この関数の詳細は前回詳しく解説していますが、要するに矩形を描画する API です。
同様に⑥では Ellipse 関数という API を使っています。これは円(楕円)を描画する API ですね。

⑦はどうでしょうか。ここでは、MoveToEx 関数が登場していますね。この関数は現在の原点を設定する API で、直線を描画しようとする際などによく登場します。このあと出てくる⑧の LineTo 関数という API は、直線を描画することができる API ですが、この API は引かれる直線の終点しか指定することができません。直線の始点は、現在の原点と同義です。つまり、⑦の MoveToEx で直線の始点(現在の原点)を設定し、⑧の LineTo で直線の終点を指定するわけです。
直線の描画は、このように若干面倒な処理手順になりますが、こればかりは API の仕様なので、諦めるより他ありません。

補足コラム:MoveToEx関数の不思議
もうひとつ、⑦の部分でひとつ不可解な部分があるかと思います。
MoveToEx関数の第三引数で、lpPointという変数を使っていますね。しかし、lpPointという変数が、どこかで別の処理に使われている形跡はありませんね。一見すると、どうしてこの変数が必要なのか、よくわからないと思います。
実は、MoveToEx関数には、現在の原点を指定する機能のほかに、それまでの原点を調査する機能もあります。その際に利用されるのが、問題の第三引数、つまり今回の例で言えば変数lpPointがあてがわれている部分なのです。
MoveToEx関数は、原点を設定する際に、それまでの原点の座標を第三引数で指定されたPOINTAPI型の変数に格納してくれます。引数として与えた変数の内容が、関数によって実行と同時に書き換えられるわけです。
VBAを使っていると、関数が返すデータはイコールを使って代入するやり方が全てのように考えてしまいがちですが、APIでは、今回のMoveToEx関数のように引数から受け取った参照先にデータを格納する仕様が多く見られます。最初は理解しにくいかもしれませんが、今後、APIプログラミングやDirectXを用いた処理に本格的に取り組もうと考えているなら、このような仕様にも慣れておく必要があります。それほど、この手の仕様は当たり前で一般的なものです。
少しだけ難しい話をすれば、引数として変数が渡されるとき、それが参照渡しされていることが重要です。そうでなければ、MoveToEx関数が変数の中身に干渉することができなくなってしまうからですね。


さて、少し長くなりましたが、簡単にまとめてみましょう。
①と②で、ペンとブラシを生成しました。
③と④で、生成したオブジェクトをデバイスコンテキストに選択させました。
⑤では、矩形を描画。⑥では、円を描画しています。そして⑦と⑧の合わせ技で、直線を描画しています。

つまり、この一連の処理が実行されると、ユーザーフォーム上にはふたつの図形と直線が描画されます。このプロシージャはユーザーフォームのクリックイベントプロシージャですので、ユーザーフォーム上をクリックしたときに実行されます。

実行されたときのイメージは、以下のような感じですね。

・ユーザーフォームが表示される。
790.png



・クライアント領域をクリックすると図形が描画される。
791.png


■ペンやブラシをいろいろ変化させてみる

さて、ここからはオマケになりますが、ペンやブラシを生成する際に、いろいろ引数を変化させるとどんなことが起こるのか、いくつか例を挙げてみたいと思います。
ここでは、自作のプロシージャである p_create b_create に、それぞれどのような引数を与えたのか併せて掲載しますので、参考にしてみてください。


792.png

pen = p_create(1, PS_SOLID, RGB(0, 255, 0))
brush = b_create(BS_HATCHED, RGB(0, 0, 255), HS_VERTICAL)
・1ピクセル、実線、緑のペン
・縦線ハッチ、青のブラシ



793.png

pen = p_create(1, PS_DASHDOT, RGB(125, 0, 125))
brush = b_create(BS_HATCHED, RGB(0, 125, 125), HS_DIAGCROSS)
・1ピクセル、一点鎖線、濃い紫のペン
・斜め交差線ハッチ、青緑のブラシ



794.png

pen = p_create(3, PS_SOLID, RGB(125, 125, 0))
brush = b_create(BS_HOLLOW, 0, 0)
・3ピクセル、実線、黄土色のペン
・塗り潰し無しブラシ



このような感じで、いろいろとパラメーターを変化させてみることで、ある程度多彩な表現ができることがわかると思います。
ただ、このようなペンやブラシによる表現は、言い方は悪いですがそれほど表現豊かなものとはお世辞にも言えません。それに、実際にやってみるとわかりますが、実線以外のペンを生成する際、ペンの太さを 1 ピクセルよりも太くした場合に、思ったとおりの表現にならないケースも存在します。やはり、この辺は融通が利かないので、若干使いにくく感じてしまうと思います。

また、よーく観察するとわかりますが、ハッチブラシを使っている場合や、実線以外の線形を指定したペンで描画した場合などに、不思議な現象が起こっていることに気が付いたでしょうか。


793.png


この画像、特に一番右側の直線(一点鎖線)が引かれているところをよく見てください。線と線の切れ目のところは、背景となるユーザーフォームの色が透けているのではなく、白い色になってしまっているのがわかるでしょうか。
同様に、ハッチブラシの隙間の部分も、真っ白になってしまっていますね。

実は、これも API を使った図形や線の描画のやっかいな部分で、デバイスコンテキストの背景色が原因で、このようなことが起こってしまっているのです。
デバイスコンテキストには、背景色という概念があり、点線の隙間の部分や、ハッチブラシの隙間の部分などにはこの背景色が描画されるようになっています。もちろん、API を用いれば背景色に関する挙動についても変更することは可能なのですが、それはまた別の機会に、おいおい説明していくことにしましょう。


■まとめ

さて、簡単な図形を二・三描画するだけなのに、随分と長いテキストになってしまいましたね。何度も書いていることではありますが、本当に API 関連の処理は冗長になりがちですし、なんともわかりにくいので説明も比例して冗長になってしまいます。

しかし、ここはあえて考え方を変えてみましょう。
本当のことを言ってしまえば、いかに、普段何気なく使っている VBA が使いやすい言語であるか、ということなんですね。本来のウィンドウズプログラミングとは、このような複雑で面倒なものなのです。API を用いるということは、それなりに敷居が高いことですし、当然のように高いレベルの知識を要求されます。難しいからと投げ出してしまうのか、それとも難しいなりに理解しようと努力するのか。ここは正直な話、プログラマひとりひとりの、それぞれの個人の自由なのです。

多少難しいですが、全く理解できないほど強烈に難解である……というものでもないと私は思っています。それに、誰にでも、できる限り理解しやすいように、講座のテキストは書いているつもりです。せっかく VBA から API を利用することができるのですから、少しだけ我慢して、習得してしまうことを私はオススメします。

この API プログラミングという世界の先に、DirectX プログラミングというさらに果てしない可能性が広がっているなら、尚更です。こんなところで挫折してしまうのは、なんとも勿体無い。それに、厳しいことを言ってしまえば、このレベルの話についてこれないようでは、DirectX を扱うのは 無理 です。高みを目指すのならば、このような比較的簡単な API による描画処理などは、小さなステップのひとつとして乗り越えてほしいと思います。
前回と今回の講座で解説したコードをそのまま実装したサンプルファイルを、別館にアップしておきます。参考にするなり、パラメータをいろいろ変更して実験してみるなり、活用していただければと思います。

ゆっくりでいいのです。
焦らず、じっくり、取り組んでいきましょう。

サンプルダウンロード ⇒ コチラよりダウンロードできます。(別館)



■格言

イベントの発生順序に注意してコードを記述
API 独特の引数の使い方を理解する


簡単な図形を描画するだけでも大変ですが、こんなのは API 描画処理の序の口です。


スポンサーサイト






Chapter.135 [ APIによる描画処理7:画像描画の仕組み編 ]

■いよいよ画像描画処理へ

前回までの講座では、API を用いた図形描画処理を扱いました。
既存の API だけで、矩形や楕円、直線などが描画できます。他にも扇形や多角形など、紹介しきれなかった図形描画関数もありますが、それはそれで、前回までの講座内容を理解できた人であれば、自分で調べて使いこなすことができるでしょう。基本さえわかってしまえば、それほど難しいものではありません。

さて、今回からは、いよいよ画像データを用いた描画処理へと内容が移っていきます。単なる図形の描画処理と比較すると、画像データを描画する一連の処理は非常に難易度が高くなり、且つ、複雑な処理を要求されます。

始めからこんなことを書いてしまうと脅しているのかと勘違いされてしまうかもしれませんが、API を用いた画像描画は、単なる図形描画に比べると多少難しい内容であることは間違いありません。しかしそこは怖気づくことなく、むしろ気合を入れて、がんばりましょう。

今回はまず、画像描画の仕組み編と題して、基本的な部分から解説していくことにします。実際問題、画像を画面上に描画すると言っても、それがどのような仕組みで、またどのような手順で行なわれるものなのか、その概念をまずはしっかり理解していただければと思います。


■様々な画像形式

画像データと一口に言っても、世の中には様々な画像データが存在します。
最も代表的なのは、拡張子が bmp で表されるウィンドウズビットマップ形式でしょう。他にも、写真データなどでよく使われる jpeg 形式や、アニメーションなどにも利用される gif 形式、web サイトなどで使われることが多くなってきた png 形式などもありますね。

API による画像の描画処理では、上記の様々な画像形式のうち bmp 形式のデータを使うことを基本とします。それ以外の形式が利用不可能なわけではありませんが、それはそれで敷居が高くなります。まずは、基本的なビットマップの使い方をしっかりマスターすることが最初の課題になるわけです。本講座でも、原則として bmp 形式の画像ファイルを用いて解説していきます。


■デバイスコンテキストとビットマップ

前回までの図形を描画する処理のなかで、ペンやブラシといったオブジェクトが登場しました。そして、ペンやブラシはデバイスコンテキストに含まれているオブジェクトであり、必要に応じて持ち替えながら処理を行なっていましたね。

実は、ビットマップも、ペンやブラシと同じように、デバイスコンテキストに選択させることで初めて利用できるようになるオブジェクトの一種です。
ペンオブジェクトを新しく生成し、デバイスコンテキストに選択させて線を引いたのと同じように、ビットマップもまた、生成してからデバイスコンテキストに関連付け、そこから次の処理を行ないます。

少し話が遡りますが、デバイスコンテキストは画材がワンセットになったようなものです……という話をしたのを憶えているでしょうか。ビットマップとは、その画材のなかの、言うなれば既に描き上がっている絵のようなものです。
デバイスコンテキストという画用紙の上に、ビットマップという絵を貼り付けることで、画面上に画像が描画されるわけですね。注意しなければならないのは、デバイスコンテキストに選択させられるペンやブラシがそれぞれひとつだけだったのと同様に、ビットマップもまた、関連付けられるのはひとつのビットマップオブジェクトのみです。もしも、複数の画像ファイルから、複数のビットマップを扱いたいと思うのなら、少なくとも複数のデバイスコンテキストを使いこなす必要があります。


■ビットブロック転送

API による画像の描画処理は、ビットブロック転送と呼ばれる技術によって実現することができます。
と、そんなふうにいきなり言われても、ビットブロック転送とはなんぞや! と思う人が大半ではないかと思います。

ビットブロック転送の詳細な定義を説明すると非常に長くなってしまうので、わかりやすくイメージしやすいように解説すると、ビットブロック転送とは言うなれば貼り絵のようなものです。
そして、先ほども書いたように、ビットマップオブジェクトは、常にデバイスコンテキストと関連付けられることによって利用できるものです。要は、元となるデバイスコンテキストから、別のデバイスコンテキストに対して、貼り絵の要領でビットマップデータを切り取って貼り付けていく処理こそがビットブロック転送だと考えてください。

実際の画用紙などに描かれた絵であれば、切り取って別のところに貼り付けてしまったらぽっかりと穴が開いてしまいますが、そこはパソコンの中にあるデータですから、当然のようにコピーすることが可能です。

画用紙の中に描かれた絵の、そのなかの一部だけ(あるいは全て)をコピーして、別の画用紙に貼り付ける……そんなイメージでビットブロック転送は行なわれます。また、これらの処理は非常に高速に動作します。仮に、1 秒間に 60 回の描画を行なうプログラムを組んだとして、貼り付ける作業を 100 回や 200回 程度、ループするたびに毎回行なったところで、まったく問題ない程度の速さです(もちろん動作するパソコンのスペックにもよりますが)。

この速さは、従来ユーザーフォーム上でイメージオブジェクトを使って行なっていた処理に比べると、とてつもない速さと言えます。そしてこの速さこそが、モード 4 、つまり API による描画処理を行なう最大の利点になるわけです。


■ビットブロック転送の基本

ビットブロック転送を行なうためには、BitBlt 関数という API を使います。

この API では、転送先のデバイスコンテキストはどれなのか、転送先の座標はどこなのか、また転送の元となるデバイスコンテキストはどれなのか、その座標はどの位置なのかなど、多くの引数を指定する必要があります。
引数が非常に多いので、一見すると使いこなすのが難しいように感じてしまうかもしれませんが、これは慣れてさえしまえばどうということのないレベルの話です。以下に、その宣言文と引数の意味を掲載しますので、とりあえず参考までに見てみてください。

Declare Function BitBlt Lib "gdi32" _
(ByVal hDestDC As Long, _ 転送先デバイスコンテキスト
ByVal x As Long, _ 転送先の X 座標(左上角の横位置)
ByVal y As Long, _ 転送先の Y 座標(左上角の縦位置)
ByVal nWidth As Long, _ 転送する横幅
ByVal nHeight As Long, _ 転送する縦幅
ByVal hSrcDC As Long, _ 転送元のデバイスコンテキスト
ByVal xSrc As Long, _ 転送元の X 座標(左上角の横位置)
ByVal ySrc As Long, _ 転送元の Y 座標(左上角の縦位置)
ByVal dwRop As Long) _ ラスタオペレーション
As Long


随分たくさんの引数があるということがわかると思います。
位置や、幅といった部分は、全てピクセル単位です。そして、転送先と転送元の都合ふたつのデバイスコンテキストのハンドルが必要になります。最もわかりにくいのが、一番最後の引数で指定されているラスタオペレーションだと思われますが、これについては、実際に使う段階になったらあらためて解説したいと思います。

詳細な使い方はさておき、この BitBlt 関数を使うことによって、デバイスコンテキストから、別のデバイスコンテキストに対してビットマップを転送することが可能です。

前回までの講座で、ユーザーフォームのデバイスコンテキストのハンドルを取得するところまではやりましたね。今後は、デバイスコンテキストを自前で複数用意して、そこにビットマップを関連付けたり、そこから画像データを転送することでユーザーフォーム上に画像を描画させたりといった処理を行なっていきます。

BitBlt 関数を駆使することによって、最終的にはマスク処理(余白部分の透過処理)など、高度な描画処理を実現することができます。まずは、API による画像の描画処理とはどういうことを指すのか、また、それはどのように実現されるのかを、なんとなくで構いませんのでイメージできるようにしておきましょう。


■まとめ

文章ばかりの味気ない講座になりましたが、内容を少しまとめてみましょう。

・画像描画に利用する画像形式は bmp 形式
・ビットマップはデバイスコンテキストに含まれるオブジェクト
・ビットマップはビットブロック転送によって描画される
・ビットブロック転送はデバイスコンテキスト間で行なわれる
・ビットブロック転送を行なうには BitBlt 関数を利用する

こんな感じですね。

重要なことは、ビットマップは、それ単体では描画処理に利用できないということです。少しわかりにくいかもしれませんが、ビットマップは、あくまでもデバイスコンテキストとワンセットで初めて利用することが可能になるオブジェクトなのですね。

ビットマップオブジェクトに画像データを読み込ませたあと、それを自前で用意したデバイスコンテキストに関連付けます。そして、そのビットマップを関連付けたデバイスコンテキストから、ユーザーフォームのデバイスコンテキストにビットブロック転送を行なうことで、初めて画像が画面上に現れます。

この大筋の流れが理解できていれば、今回の講座は修了したと言っていいでしょう。
一見すると面倒な処理のように見えますが、これこそが API による画像描画処理の基本になりますので、しっかり概念を理解しておいてください。

次回はいよいよ具体的なコードを掲載して解説していくことになりますが、新手の API もいくつか登場しますので、今回の講座の内容をいかに理解できているかが重要になってきます。

何度も言っていることですが、焦る必要はありません。しっかりと、そしてじっくりと、諦めずに取り組んでいきましょう。



■格言

ビットマップとデバイスコンテキストの関係について理解する
ビットブロック転送とはどんな処理なのかイメージできるようにしておく


今回は文章ばかりの内容となりましたが、次回からはガリガリと進めていきますので乞うご期待。







Chapter.136 [ APIによる描画処理8:ビットブロック転送編 ]

■画像を転送せよ

前回の講座では、API によって画像を描画する仕組みについて、ざっくりとですが解説しました。今回は、実際にコードを掲載しつつ、描画が行なわれるところまでをきっちりと解説します。

前回、ビットブロック転送という言葉についても解説しましたね。デバイスコンテキストから、別のデバイスコンテキストに対してビットマップを転送すること、それこそがビットブロック転送に他なりません。そして、ビットブロック転送を実現するための API が BitBlt 関数だということも解説しました。

今回は実際に BitBlt 関数を使ってビットブロック転送を行なうところまでやりますが、そのためには様々な事前準備が必要になります。毎度のことながら、API を用いる際にはこの事前の準備が非常に面倒なのですが、こればっかりはどうしようもないことですので、焦らずじっくり、取り組んでいきましょう。

さて、前回の講座でも解説しましたが、ビットマップオブジェクトは、デバイスコンテキストに含まれるオブジェクトの一種です。ペンやブラシと同じように、デバイスコンテキストに選択させることで利用できます。
ペンやブラシがそうであったように、ビットマップもまた、SelectObject 関数によって選択させて利用するわけですね。

また、何度も書いているように、ビットブロック転送はデバイスコンテキストからデバイスコンテキストへの転送を行なう処理になりますので、ユーザーフォームのデバイスコンテキストのほかに、別のデバイスコンテキストを自前で用意しなければなりません。
というわけで、まずは自前のデバイスコンテキストを用意するところから、解説していくことにしましょう。


■メモリデバイスコンテキストを生成する

デバイスコンテキストを自前で用意するには、それ専用の API である CreateCompatibleDC 関数を使います。
この関数は、引数に、元となるデバイスコンテキストのハンドルを必要とします。これはどういうことかと言うと、デバイスコンテキストと一口に言っても、その実行環境によって様々な性能の差が起こる可能性が考えられます。そこで、元となるデバイスコンテキストを引数として渡すことによって、それと互換性のある同等のデバイスコンテキストを生成させるようにするわけです。

ユーザーフォームのデバイスコンテキストを取得しておき、それを引数として渡すことによって、それと同等の(それと互換性のある)デバイスコンテキストを生成することができるので、結果としてその後の処理で不具合が起こらないようにすることができるわけです。

また、この CreateCompatibleDC 関数によって生成されるデバイスコンテキストは、一般に『 メモリデバイスコンテキスト 』と呼ばれます。これは、メモリ上にのみ存在するデバイスコンテキストだから、というそのまんまの理由です。

さておき、実際に CreateCompatibleDC 関数の宣言文を見てみましょう。

Declare Function CreateCompatibleDC Lib "gdi32" _
(ByVal hdc As Long) As Long

上記の hdc という引数の部分に、ユーザーフォームのデバイスコンテキストのハンドルを渡します。戻り値として返されてくるのが、新しく生成されたメモリデバイスコンテキストのハンドルになります。

ここで生成されたメモリデバイスコンテキストは、ビットマップを選択させた状態にしないと描画処理には利用できませんので注意しましょう。また、メモリデバイスコンテキストが必要なくなったときには、DeleteDC 関数を使って解放しなければなりません。

Declare Function DeleteDC Lib "gdi32" _
(ByVal hdc As Long) As Long

DeleteDC 関数の引数には、解放したいメモリデバイスコンテキストのハンドルを渡します。

ここで注意してほしい点が一点あります。
前回までの講座で、ReleaseDC 関数を使っていたのを憶えているでしょうか。ReleaseDC 関数と DeleteDC 関数は非常に似ていますが、全く別の関数です。
既存のデバイスコンテキスト、つまり、ユーザーフォームのデバイスコンテキストを解放するのが ReleaseDC 関数。自前で生成したメモリデバイスコンテキストを解放するのが DeleteDC 関数です。非常にややこしいですが、どちらも必ずプログラムの終了前に正しく実行する必要がありますので、注意しましょう。


■ビットマップの生成

ビットマップを生成するには、いくつかの方法があります。
それらの複数の方法のうち、比較的利用する頻度が高い方法として、ふたつの方法があります。
具体的には、先ほどのメモリデバイスコンテキストと同じように、ビットマップオブジェクトをメモリ上に単体で生成する方法と、画像ファイルからデータを読み込むと同時にビットマップオブジェクトとして生成してしまう方法のふたつです。

まず、メモリデバイスコンテキストのときと同じように、メモリ上にビットマップだけを生成する方法から見てみましょう。これには、 CreateCompatibleBitmap 関数を使います。

Declare Function CreateCompatibleBitmap Lib "gdi32" _
(ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long

引数をよく見てみればわかりますが、この関数もまたデバイスコンテキストのハンドルを引数として受け取ります。メモリデバイスコンテキストのときと同じように、互換性をとるためにこのような仕様になっているわけですね。

ですから、この CreateCompatibleBitmap 関数を利用する際には、必ず元となるユーザーフォームのデバイスコンテキストのハンドルを渡すようにしましょう。
また、第二引数と第三引数には、それぞれ生成するビットマップの幅と高さを指定します。単位はピクセルです。どの程度のサイズの画像を扱いたいのかによって、これらの幅や高さなどは適宜調節する必要があります。


さて、次に画像ファイルから直接ビットマップオブジェクトを生成する方法を見てみましょう。この場合には、LoadImage 関数という API を利用します。

Declare Function LoadImage Lib "user32" Alias "LoadImageA" _
(ByVal hinst As Long, _
ByVal lpsz As String, _
ByVal un1 As Long, _
ByVal n1 As Long, _
ByVal n2 As Long, _
ByVal un2 As Long) As Long

この関数も非常に引数が多くて、なんじゃこりゃとなってしまいがちですが、ここはもう決め打ちで使い方を覚えてしまったほうが簡単です。

ハッキリと言います。
第二引数は、引数の型が String 型になっていますね。ここに、読み込みたい画像ファイルのパスを渡します。
また、第六引数には、数値の 16 を指定します。
そして、それ以外の引数には、全て数値の 0 を指定します。

この LoadImage という関数は非常に多機能な関数で、いろいろな機能が搭載されています。その分、引数の数も、それらが持つ意味も、非常に多岐に渡っており、とても難解な内容となっています。
実際に API の描画処理で、画像ファイルを読み込んで利用する用途であれば、上で書いたように特定の引数にだけ気をつければ、あとはまったく同じように指定してしまって問題ありません。

例えば、次のようにすればいいわけですね。

Dim BMP As Long
BMP = LoadImage(0, ファイルのパス, 0, 0, 0, 16)

はい、簡単ですね。
この関数の使い方に関しては、細かいことはさておき、使い方を覚えてしまったほうが得です。もし、どうしても気になるという人は、MSDN などで調べてみるといいでしょう。調べなければよかったとゲンナリすること請け合いです。

とまぁ冗談はさておき、ビットマップオブジェクトを生成することについては、まずはこの二種類の方法さえ知っていれば、現段階ではまったく問題ありません。今後、講座を進めていくうちに、別のビットマップ生成関連 API を紹介することもあるでしょうが、まずはこの二種類の方法をしっかり理解しておいてください。

生成したビットマップは、その生成方法がどのような方法であったにせよ、必要なくなった時点で必ず解放しなければなりません。これには、ペンやブラシのときに使ったのと同じ DeleteObject 関数を使います。
この API については以前にも詳しく解説していますので、ここでは割愛しますが、必ず最後に解放しなければならない点だけは、漏れのないように注意しましょう。



■ビットブロック転送のラスタオペレーション

さて、前回の講座でも登場した BitBlt 関数についても、さらに補足します。まずは、こちらも宣言文から見ていきます。

Declare Function BitBlt Lib "gdi32" _
(ByVal hDestDC As Long, _
ByVal x As Long, _
ByVal y As Long, _
ByVal nWidth As Long, _
ByVal nHeight As Long, _
ByVal hSrcDC As Long, _
ByVal xSrc As Long, _
ByVal ySrc As Long, _
ByVal dwRop As Long) As Long

それぞれの引数の詳細については、前回の記事で載せましたのでそちらを見ていただくとして、今回解説するのは最後の引数に指定するラスタオペレーションについてです。

ラスタオペレーションとは、簡単に言ってしまうと、ビットブロック転送をどのようなルールで行なうかを定義するための要素です。
単純にコピーして転送するのか、それとも特殊な効果を与えて転送するのか、ラスタオペレーションを使えば様々な転送方法が指定できます。

そして、このラスタオペレーションには、たくさんの種類があります。それらのひとつひとつについてこの場で解説してしまうと、ものすごく膨大な文章量になってしまいますので、今回だけは、我慢してひとつだけ覚えてください。

Public Const SRCCOPY = &HCC0020

はい、これです。

これは、Const が登場していることからもわかるとおり、定数を宣言しているコードですね。そしてこの SRCCOPY という定数は、全てをそのままコピーして転送するためのラスタオペレーションを定義するものです。

BitBlt 関数を実際に利用する際には、最後の引数にこの SRCCOPY を指定して実行します。そうすることで、ビットマップがそのままなんの加工もされることなく、単純にコピーされて転送されることになるわけです。

まずは、普通にそのまま転送する処理をマスターしてください。そのあと、順を追ってその他のラスタオペレーションについても解説していきますので、楽しみにしていてください。


■実際にコードを記述していく

さて、ここから実際にコードを記述していきますが、API 関連の処理ではお馴染みの大量の宣言文なども含まれるため、これから掲載するコードはすごく膨大に見えてしまうかもしれません。気後れすることなく、じっくり見ていってください。

それではまず、標準モジュールに記述するコードからです。

'■各種宣言文
Declare Function FindWindow Lib "user32.dll" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Declare Function GetDC Lib "user32.dll" _
(ByVal hwnd As Long) As Long

Declare Function DeleteDC Lib "gdi32" _
(ByVal hdc As Long) As Long

Declare Function ReleaseDC Lib "user32.dll" _
(ByVal hwnd As Long, ByVal hdc As Long) As Long

Declare Function SelectObject Lib "gdi32.dll" _
(ByVal hdc As Long, ByVal hgdiobj As Long) As Long

Declare Function DeleteObject Lib "gdi32.dll" _
(ByVal hObject As Long) As Long

Declare Function CreateCompatibleDC Lib "gdi32" _
(ByVal hdc As Long) As Long

Declare Function LoadImage Lib "user32" Alias "LoadImageA" _
(ByVal hinst As Long, _
ByVal lpsz As String, _
ByVal un1 As Long, _
ByVal n1 As Long, _
ByVal n2 As Long, _
ByVal un2 As Long) As Long

Declare Function BitBlt Lib "gdi32" _
(ByVal hDestDC As Long, _
ByVal x As Long, _
ByVal y As Long, _
ByVal nWidth As Long, _
ByVal nHeight As Long, _
ByVal hSrcDC As Long, _
ByVal xSrc As Long, _
ByVal ySrc As Long, _
ByVal dwRop As Long) As Long

Public Const SRCCOPY = &HCC0020

Public hwnd As Long
Public hdc As Long
Public cDCObj As New Collection
Public cBMPObj As New Collection
Public hbufDC As Long
Public hbufBMP As Long

事前に宣言するものだけでもかなりの量がありますね。
各種 API に関しては、今回の講座も含めて既に解説したものしか登場していませんので、もしわからない部分がある場合には、過去の講座などを読み返してみてください。

今回の宣言文では、定数はひとつだけ宣言していますね。BitBlt 関数のラスタオペレーションである SRCCOPY がそれにあたります。

public で宣言している変数は、お馴染みのウィンドウハンドル格納用変数である hwnd や、ユーザーフォームのデバイスコンテキストハンドル格納用の hdc を始め、プログラムの終了処理の際にオブジェクトをまとめて解放するためのコレクションなどが宣言されています。

ペンやブラシのときにも同様のことをやりましたが、オブジェクトを大量に生成する可能性のあるプログラムの場合、コレクションをうまく活用することで解放処理が確実に、且つ、スムーズに行なえる場合があります。
今回は、メモリデバイスコンテキストと、ビットマップオブジェクトの、二種類のオブジェクト用にふたつのコレクションが宣言されています。cDCObj cBMPObj のふたつがそれですね。

さらに、今回はメモリデバイスコンテキストのハンドルを保持するための hbufDC と、そのデバイスコンテキストに割り当てるビットマップオブジェクトのハンドルを保持するための hbufBMP を宣言しています。

さて、続いては各種プロシージャを見ていきます。

'■各種プロシージャ

'ウィンドウハンドル取得用

Sub get_handle(w_caption As String)

    hwnd = FindWindow(vbNullString, w_caption)
    hdc = GetDC(hwnd)
    
End Sub

'各種オブジェクトの生成
Sub create_obj()

    hbufDC = CreateCompatibleDC(hdc) '①
    hbufBMP = LoadImage(0, ThisWorkbook.Path & "\test.bmp", 0, 0, 0, 16) '②
    cDCObj.Add hbufDC '③
    cBMPObj.Add hbufBMP '④
    SelectObject hbufDC, hbufBMP '⑤
    
End Sub

'ビットブロック転送
Sub blt_image()

    BitBlt hdc, 10, 10, 80, 80, hbufDC, 0, 0, SRCCOPY '⑥

End Sub

'終了処理
Sub release_obj()

    Dim v As Variant
    
    For Each v In cDCObj
        DeleteDC v
    Next
    For Each v In cBMPObj
        DeleteObject v
    Next
    
    ReleaseDC hwnd, hdc
    
End Sub

ウィンドウハンドルの取得と、終了処理に関しては、ここまで順番に講座を進めてきていればそれほど難しくないはずです。終了処理でもペンやブラシのときにやっていたことを、そのままメモリデバイスコンテキストとビットマップで行なっているだけですね。

わかりにくいのは、オブジェクトの生成と、ビットブロック転送の処理になると思いますので、そこは詳しく解説します。

①では、メモリデバイスコンテキストを新しく生成しています。変数 hbufDC にハンドルが格納されるようになっていますね。
②では、LoadImage 関数を使って、ファイルから画像データを読み込んでビットマップオブジェクトを生成しています。先ほども書いたように、この API は非常に引数が多いですが、第二引数にファイルのパス、そして最後の引数に数値の 16 を指定する以外は、全て数値の 0 を指定すれば OK です。
今回のケースでは、このコードが実行される Excel ブックと同じディレクトリに存在する test.bmp というファイルを読み込むように指定しています。

③と④では、生成したオブジェクトをコレクションに追加しています。
そして⑤の部分で、メモリデバイスコンテキストにビットマップを選択させています。ここではペンやブラシのときと同じように、SelectObject 関数を使っていますね。

さて、最後に⑥ですが、ここが今回最大の肝となる、ビットブロック転送を行なっているところですね。ここは若干ややこしいので、コードを再度掲載して、細かく見ていきましょう。

Sub blt_image()

    BitBlt hdc, 10, 10, 80, 80, hbufDC, 0, 0, SRCCOPY
           ①   ②  ③  ④  ⑤  ⑥      ⑦ ⑧ ⑨
End Sub

BitBlt 関数は非常に引数が多いのでわかりにくいですが、慣れてしまえばどうということはありません。
①が転送先のデバイスコンテキストのハンドルです。今回はユーザーフォーム上に画像を転送するわけですから、ここは hdc ですね。
②と③が、転送先の座標を指定する引数です。クライアント領域内の、どこに画像を転送するのか、ふたつの引数を使って指定します。②は左端からの距離、③は上端からの距離ですね。

④と⑤はどうでしょうか。
ここは、転送する幅と高さを指定します。今回のコードでは、幅と高さがそれぞれ 80 ピクセルの画像ファイルを読み込んで、そのデータをそのまま転送します。ですから、ここでは画像ファイルの大きさと全く同じ 80 という指定になっているわけですね。
仮に、大きな画像ファイルを読み込んで、一部分だけ転送したいという場合には、この幅と高さをうまく調節することで特定の部分だけを転送することも可能です。④が横幅で、⑤が高さになります。

続いて⑥には、転送する元データを保持しているデバイスコンテキストのハンドルが入ります。

⑦と⑧はどうでしょうか。ここでは、転送元となるデバイスコンテキストの転送座標を指定しています。これもやはり、⑦が横位置、⑧が縦位置です。

⑨には、ラスタオペレーションを指定しますが、今回はそのままコピーするという一番単純な処理を行ないますので、あらかじめ定数として宣言していた SRCCOPY が入っています。

今回のサンプルでは、以下の画像をビットマップオブジェクトに読み込んで、転送してみます。先ほども書いたように、この画像は幅と高さが 80 ピクセルです。(bmp形式のファイルがアップロードできないので、以下の画像はpng形式になっています)

802.png

そして、ユーザーフォームには、画像がちゃんと転送されているのかわかりやすいように、あらかじめ背景色を設定しておきます。

800.png

随分と長くなってしまいますが、最後にユーザーフォームのフォームモジュールに記述するコードを掲載します。これでコードは最後なので、もう少し踏ん張ってください。

Private Sub UserForm_Activate()

    get_handle Me.Caption
    create_obj
    
End Sub

Private Sub UserForm_Terminate()

    release_obj
    
End Sub

Private Sub UserForm_Click()

    blt_image
    
End Sub

はい、ここではさほど難しいことはやっていませんね。
理解しておいてほしいのは、今回も、前回までと同様に、ユーザーフォームのクリックイベントをトリガーとして、描画処理が行なわれるようになっているという部分くらいです。

それ以外の部分では、ハンドルを取得するプロシージャや、オブジェクトを生成するプロシージャ、あるいは終了処理のプロシージャを適宜呼び出しているだけですので、焦らず見ていけば理解できるはずです。



■まとめ

いやー、しかし随分と長文になりましたね。内容もてんこ盛りでしたが、理解できたでしょうか。

一度にかなりたくさんのことを書いたので、一発で理解することは難しいかもしれませんが、講座の最後にサンプルファイルへのリンクも張っておきますので、それらを活用しながらじっくり理解していっていただければと思います。

ちなみに、今回のサンプルを実行すると次のような感じになります。

・ユーザーフォームを表示
800.png



・クライアント領域のクリックで画像が転送される
801.png


たったこれだけのことをやるために、随分と膨大な準備が必要になるんですね。
しかし、何度も書いているように、API を使った処理というのはこういうものです。こればっかりは慣れるしかありませんし、基本となる部分が出来上がってしまえば、あとは継ぎ足しながらいろいろと拡張できます。

そして、この BitBlt 関数を使った描画処理の最大の利点として忘れてはならないのが、その高速な描画速度です。この素晴らしい恩恵を受けるためには、それなりに準備が必要だということで、割り切ってがんばりましょう。

ちなみにこれは余談ですが、DirectX はさらに速いです。恐いですね……。



サンプルダウンロード ⇒ コチラよりダウンロードできます。(別館)



■格言

メモリデバイスコンテキストやビットマップオブジェクトについて理解する
BitBlt関数の多数ある引数の意味を落ち着いて理解する


ついに高速描画の世界への入り口に立ちました。次回からは、ラスタオペレーションについてさらに詳細に見ていきます。






Chapter.137 [ APIによる描画処理9:ラスタオペレーション ]

■ラスタオペレーション

前回は、ビットマップのビットブロック転送について、多少本格的に解説しました。
簡単な画像一枚描画するだけなのに、随分と大変な準備が必要でしたね。まぁこればっかりは、何度も言っているように、API による処理の宿命でもありますので、慣れるより他ありません。

前回の講座の内容がかなり盛りだくさんだったので、全てをくまなく理解するのは大変かもしれませんが、厳しいことを言ってしまえば、前回の内容をクリアできないようでは、この先の講座内容にはついてこれないかもしれません。
もし、少しでも不安が残るというかたは、面倒に感じるかもしれませんが、サンプルファイルなどを手直ししたりしつつ前回の内容をしっかり理解してから先に進むことをオススメします。

さて、今回は前回、前々回と利用した BitBlt 関数についてさらに詳細に解説していきます。というより、もっとはっきり言ってしまえば、BitBlt 関数の最終引数に指定するラスタオペレーションについて、詳しく見ていきます。

BitBlt 関数は非常に多機能な API で、本当に様々な描画処理をこの関数ひとつで実現することができます。そしてその多彩な描画方法を指定する方法が、他ならぬラスタオペレーションなのですね。

前回は単純にメモリデバイスコンテキストに割り当てたビットマップをそのまま転送しました。要は、単にコピーしただけの処理ですね。今回は、代表的なラスタオペレーションを紹介すると共に、それぞれどんな描画になるのか、解説したいと思います。

今回は、かなり画像が多くなりました。いやー、大変でした。
まぁ、苦労した分、有意義な講座になっているといいんですが。


■まずはざっくり見てみよう

それでは、まず最初にラスタオペレーションの一覧を見てみましょう。
結構、これを見るだけでもゾッとするかもしれませんね……。

SRCCOPY = &HCC0020
SRCPAINT = &HEE0086
SRCAND = &H8800C6
SRCINVERT = &H660046
SRCERASE = &H440328
NOTSRCCOPY = &H330008
NOTSRCERASE = &H1100A6
MERGECOPY = &HC000CA
MERGEPAINT = &HBB0226
PATCOPY = &HF00021
PATPAINT = &HFB0A09
PATINVERT = &H5A0049
DSTINVERT = &H550009
BLACKNESS = &H42
WHITENESS = &HFF0062

随分たくさんありますね。しかし、驚くなかれ、これで全てではありません。まぁでも、ここに紹介したものであっても、全てを覚える必要は正直言ってないです。代表的なところだけ覚えているだけで十分なのですが、せっかくなので一通り紹介します。

さて、それでは各ラスタオペレーションの意味を見てみましょう。

定数名詳細
SRCCOPYコピー元からコピー先へそのままコピーする
SRCPAINTOR演算子でコピー元の色とコピー先の色を組み合わせる
SRCANDAND演算子でコピー先の色とコピー元の色を組み合わせる
SRCINVERTXOR演算子でコピー先の色とコピー元の色を組み合わせる
SRCERASEAND演算子で、コピー先の色を反転した色とコピー元の色を組み合わせる
NOTSRCCOPY色を反転してコピーする
NOTSRCERASEOR演算子でコピー先の色とコピー元の色を組み合わせ反転する
MERGECOPYAND演算子でコピー元の色とコピー先の色を組み合わせる
MERGEPAINTOR演算子でコピー元の色を反転した色とコピー先の色を組み合わせる
PATCOPY指定したパターンをコピーする
PATINVERTXOR演算子で指定したパターンの色とコピー元の色を組み合わせる
PATPAINTOR演算子で指定したパターンの色とコピー元の色を反転した色を組み合わせ、さらにOR演算子でそれとコピー先の色を組み合わせる
DSTINVERTコピー先の矩形を反転する
BLACKNESS物理的パレットのインデックス0に関連してる色(規定値では黒)でコピー先矩形を塗りつぶす
WHITENESS物理的パレットのインデックス1に関連してる色(規定値では白)でコピー先矩形を塗りつぶす


はい、こちらもかなり盛りだくさんですね。
しかも、パッと見ただけでは、意味がよくわからないと思います。ただ、AND や OR などの論理演算の意味をゼロから解説するよりも、まずは実際に描画した場合にどうなるのかを、見た目でイメージしてもらったほうが理解しやすいと思います。

というわけで、ここからは描画イメージを一気に載せていきます。
また、理由は後述しますが、背景が黒の場合と白の場合を掲載します。

ラスタオペレーションに関しては、なるべくカラフルな画像を描画したほうが違いを把握しやすいと思われますので、描画する画像ファイルは次のものを使います。

856.png

ちなみに、この画像は png 形式ですが、実際に描画に使っているのは bmp 形式の画像ファイルです。

それでは、一気にいきます。

黒背景白背景
811.png826.png
812.png827.png
813.png828.png
814.png829.png
815.png830.png
816.png831.png
817.png832.png
818.png833.png
819.png834.png
820.png835.png
821.png836.png
822.png837.png
823.png838.png
824.png839.png
825.png840.png


さて、どうですか。
背景が黒の場合と、白の場合で随分結果が違っていると感じたのではないでしょうか。もし、随分違っているなぁと感じなかったとしたら、むしろそこは、違うなぁと感じて欲しいところです。

実は、この背景色の違いによって描画結果が大きく変化するというのがポイントで、このような仕組みがあるからこそ、BitBlt 関数ひとつで背景透過処理などを実装することができます。

どういうことかと言えば、先ほどのラスタオペレーションの詳細を思い出してみてください。SRCPAINT と、SRCAND のふたつのラスタオペレーションは、どのように説明されていたでしょうか。

 SRCPAINT = OR 演算子でコピー元の色とコピー先の色を組み合わせる
 SRCAND = AND 演算子でコピー元の色とコピー先の色を組み合わせる

AND と OR と言えば、プログラマーにはお馴染みの論理演算ですよね。
双方共に真のときに解が真となる AND、いずれかが真であれば解も真となる OR。先ほどの描画結果をよーく見ると、この演算の仕組みが少しだけ理解できるかもしれません。

812.png827.png
813.png828.png


これを見てもまだピンとこない方もいらっしゃるでしょうから、ここはズバリ、核心を言ってしまいましょう。

これらの SRCPAINT SRCAND といったラスタオペレーションを用いることによって、背景色を透過しての描画処理を行うことが可能です。

そしてさらにわかりやすく言ってしまえば、SRCPAINT で描画した場合には黒が透過されます。逆に、SRCAND で描画した場合には白が透過されます。
このラスタオペレーションの特徴をうまく活用することで、描画能力は飛躍的にアップし、キャラチップなどを描いた一枚のビットマップから、特定の色だけを透過してキャラクターのみを画面上に描画したりすることも可能です。

まぁその話は今後、別の講座でしっかり取り上げますのでいったん置いておくとして、もう少し、各ラスタオペレーションの描画結果について詳細に見ていきましょう。


■背景によってこんなにも結果が変わる

先ほどは、背景が黒一色の場合と、白一色の場合の描画結果を載せました。
微妙に描画結果が違っていたりしてましたが、それでは、白黒以外の色が背景だった場合はどうなるのでしょう。

何も描画されなくなる?

それとも半透明になってしまうとか?

なんとなく、気になりますね。

まぁ、これは先ほどのラスタオペレーションの説明(一覧表にしたやつです)をよく見ればわかりますが、ラスタオペレーションの種類によっては、色が混ざり合った状態で描画されたり、あるいは、全くなにも描画されなかったりします。

正直なところ、白黒背景よりも、背景がカラフルだった場合のほうが、描画結果は興味深い状態になります。せっかくなので、ここはそれも載せちゃいましょう。

今回は、背景にまず、以下の画像を敷き詰めます。

857.png

そのあと、その上から先ほどの画像を同じようにラスタオペレーションを変えながら描画してみます。白黒背景の場合とは打って変わってなかなか面白いことが起こります。


SRCCOPY841.png
SRCPAINT842.png
SRCAND843.png
SRCINVERT844.png
SRCERASE845.png
NOTSRCCOPY846.png
NOTSRCERASE847.png
MERGECOPY848.png
MERGEPAINT849.png
PATCOPY850.png
PATPAINT851.png
PATINVERT852.png
DSTINVERT853.png
BLACKNESS854.png
WHITENESS855.png


はい、どうですか。
かなり興味深いことになっていますね。

半透明になったり、色が反転していたり、あるいは何も描画されていなかったり。

ラスタオペレーションの意味を正しく理解していると、これらの描画結果にも、きちんと納得できます。ただ個人的には、そこはあんまり深く考えなくてもいいと思っています。全てのラスタオペレーションについて完璧に理解する必要はないと思うわけです。

なぜかと言えば、実際にゲームで使用することがあるのは、せいぜい SRCCOPY SRCPAINT SRCAND NOTSRCCOPY くらいだからです。

先ほども書いたように、透過処理を行なうためには、ただ単純にビットマップをコピーして転送する SRCCOPY 以外に、いくつかのラスタオペレーションを組み合わせて使う必要があります。しかし、逆に言ってしまえば、さっき挙げたほんのいくつかのラスタオペレーション以外のものを使う機会は、現実的にはほとんどありません。

予備知識として、このラスタオペレーションで描画すればこんなことが起こるんだなぁと、ざっくりと知っていることは損にはならないでしょう。あまり深く考えすぎず、軽い気持ちで見ていただければそれでいいと思います。

補足コラム:その他のラスタオペレーション

今回、白黒とカラーの背景で、各ラスタオペレーションの描画結果を掲載しましたが、いくつかのラスタオペレーションでは、背景云々ではなく、ずっと描画結果が変わってないものがあることに気が付いた人もいるでしょう。ここでは本編で解説しきれなかったいくつかのラスタオペレーションについて、簡単にですが、補足します。

:PATCOPY・PATPAINT・PATINVERT
これらの PAT から始まるラスタオペレーションでは、画像を転送しても何も描画されません。描画されるのは、デバイスコンテキストに割り当てられているパターン、つまり、パターンブラシです。そう、ブラシが描画されるラスタオペレーションなんですね。
ブラシとは言っても、あくまでもパターンブラシですので、もし使ってみたいという場合にはパターンブラシの生成方法から理解する必要があります。本講座では取り扱いませんが、自分でもやってみたことがなかったので試しにやってみたら、ちゃんとできました。ちなみに、ブラシは転送元のメモリデバイスコンテキストではなく、ユーザーフォームの側のデバイスコンテキストに割り当てたものが使われます。

:DSTINVERT
転送先の指定領域の色を全て反転させます。これも、転送元は関係ないラスタオペレーションですね。

:BLACKNESS・WHITENESS
これは定数名からも、なんとなく想像付きますね。単純に、黒や白で指定領域を塗り潰します。少し話が飛躍しますが、APIを用いた描画処理においては、ループするたびに背景を一度初期化(全ての領域を一度黒などで塗り潰しリセットすること)をしなければならない場合があります。そういった場合に活躍するラスタオペレーションと言えるでしょう。こちらのラスタオペレーションも、やはり転送元が関係しないタイプですね。

上記のいくつかの転送元が影響を与えないラスタオペレーションでは、BitBlt関数の引数に、転送元を指定する必要がありませんね。実際問題を書いてしまうと、仮に転送元のデバイスコンテキストを指定しても無視されるわけですが、基本的にはvbNullというVBの組み込み定数を与えるのが正しい記述と言えます。



■まとめ

さて、いかがでしたでしょうか。
BitBlt 関数とラスタオペレーションの不思議を、じっくり堪能できたのではないかと思います。

正直、ちょっと難しい話なので、いまいちよくわからないよという人もいるのではないかと思っています。ただ、ラスタオペレーションに関しては、特殊な処理でもしない限りは、それほど理解が深くなくてもいいと思っています。
ラスタオペレーションにおいて重要なのは、意味よりもむしろ使い方です。どのラスタオペレーションを用いると、いったいなにが実現できるのか。その答えさえ知っていれば、それでいいのではないかと個人的には思います。

途中で少し話題にしましたが、BitBlt 関数を駆使すれば、背景を透過してキャラクターなどを描画する、いわゆるマスク処理を行なうことが可能です。これには、いくつかのラスタオペレーションを組み合わせて用いる必要がありますが、 BitBlt 関数一本で実現できる処理です。

AND だの OR だの、よくわかんねーよ! という人でも、やり方さえわかってしまえば、BitBlt 関数ひとつでマスク処理はできます。もちろん、理解しているほうが良いに決まっていますが、そこは割り切って覚えてしまうというのもひとつの手です。
私個人は、意味まで理解したくなってしまうタイプなのですが、別に、理解はあとからでもいいと思いますし、まずは使い方を理解し、何度か使ううちに理解するのだって、間違いではないでしょう。

焦らず、じっくりと、取り組んでいただけたらと思います。



■格言

ラスタオペレーションを変えることで様々な描画結果が得られる
背景の状態によって描画結果は様々な影響を受ける


次回はいよいよマスク描画をやります。難しい話になるかもしれませんが、今回までの内容をしっかり理解していることが重要になりますので、がんばりましょう。







メールフォーム

影斬に物申すという方はこちら

名前 :
メール:
件名 :
本文 :

可能な限り要望には応えますが、必ず返信や回答ができることを、保障するものではありません。
ご了承ください。

Chapters

コンテンツ一覧


■Chapter 一覧■
    全てのChapterの一覧です。
    直接アクセスしたい方はこちらをご利用下さい。

    Chapter.1 [ 知っておくべき心得 ]
    Chapter.2 [ Excelってなんだろう ]
    Chapter.3 [ Excelの基本画面 ]
    Chapter.4 [ VBAとは? ]
    Chapter.5 [ モジュールについて ]
    Chapter.6 [ 変数 ]
    Chapter.7 [ 変数の型と宣言 ]
    Chapter.8 [ プロシージャとスコープ ]
    Chapter.9 [ ゲームつくる様々な手法 ]
    Chapter.10 [ ユーザーフォーム ]
    Chapter.11 [ プロパティウィンドウ ]
    Chapter.12 [ 乱数 ]
    Chapter.13 [ 条件分岐 ]
    Chapter.14 [ ゲーム画面のデザイン ]
    Chapter.15 [ コード記述の基本作法 ]
    Chapter.16 [ じゃんけんゲーム:1 名前をつける ]
    Chapter.17 [ じゃんけんゲーム:2 フォームの起動 ]
    Chapter.18 [ じゃんけんゲーム:3 乱数の種 ]
    Chapter.19 [ じゃんけんゲーム:4 イベント ]
    Chapter.20 [ じゃんけんゲーム:5 引数 ]
    Chapter.21 [ じゃんけんゲーム:6 役判定 ]
    Chapter.22 [ じゃんけんゲーム:7 予測と制限 ]
    Chapter.23 [ Withステートメント ]
    Chapter.24 [ 画像を表示させる ]
    Chapter.25 [ 画像表示の発展形 ]
    Chapter.26 [ 繰り返し処理 For文 ]
    Chapter.27 [ 繰り返し処理 Do~Loop文 ]
    Chapter.28 [ Exitステートメント ]
    Chapter.29 [ フォーム上の位置情報 ]
    Chapter.30 [ API基礎知識 ]
    Chapter.31 [ API補足知識 ]
    Chapter.32 [ メインループを考える ]
    Chapter.33 [ 同期処理の概念 ]
    Chapter.34 [ 移動処理その1:画面設定と考え方 ]
    Chapter.35 [ 移動処理その2:DoEvents ]
    Chapter.36 [ 移動処理その3:キー入力判定API ]
    Chapter.37 [ 条件分岐のさらなる探求 Select Case ]
    Chapter.38 [ アニメーション ]
    Chapter.39 [ 配列変数 ]
    Chapter.40 [ ゲームの初期化 ]
    Chapter.41 [ シューティングゲーム1:ゲーム設計 ]
    Chapter.42 [ シューティングゲーム2:メインプロセス ]
    Chapter.43 [ シューティングゲーム3:構造体 ]
    Chapter.44 [ シューティングゲーム4:定数 ]
    Chapter.45 [ シューティングゲーム5:プレイヤーキャラクター ]
    Chapter.46 [ シューティングゲーム6:ショットを撃つ① ]
    Chapter.47 [ シューティングゲーム7:ショットを撃つ② ]
    Chapter.48 [ シューティングゲーム8:Mod演算子の活用 ]
    Chapter.49 [ シューティングゲーム9:敵キャラクター登場 ]
    Chapter.50 [ シューティングゲーム10:衝突判定 ]
    Chapter.51 [ シューティングゲーム11:衝突の実体 ]
    Chapter.52 [ シューティングゲーム12:敵の攻撃 ]
    Chapter.53 [ シューティングゲーム13:爆発エフェクト ]
    Chapter.54 [ シューティングゲーム14:残機数表示① ]
    Chapter.55 [ シューティングゲーム15:残機数表示② ]
    Chapter.56 [ シューティングゲーム16:スコアの表示 ]
    Chapter.57 [ シューティングゲーム17:タイトル画面 ]
    Chapter.58 [ シューティングゲーム18:ボスキャラクター ]
    Chapter.59 [ シューティングゲーム19:最後の仕上げへ ]
    Chapter.60 [ シューティングゲーム20:いよいよ完成STG ]
    Chapter.61 [ カードゲームで使えるめくり効果 ]
    Chapter.62 [ ラジアンと角度 ]
    Chapter.63 [ ラジアンの活用:円運動 ]
    Chapter.64 [ ラジアンの活用:任意の角度へ移動する ]
    Chapter.65 [ APIによるサウンド再生:基礎 ]
    Chapter.66 [ APIによるサウンド再生:MIDIと多重再生 ]
    Chapter.67 [ APIによるサウンド再生:MCIコマンドとループ再生 ]
    Chapter.68 [ Function プロシージャ ]
    Chapter.69 [ 値渡しと参照渡し ]
    Chapter.70 [ デバッグ1:イミディエイトウィンドウ ]
    Chapter.71 [ デバッグ2:ローカルウィンドウ ]
    Chapter.72 [ デバッグ3:コード実行の中断 ]
    Chapter.73 [ オブジェクトってなんだ ]
    Chapter.74 [ プロパティ・メソッド・イベント ]
    Chapter.75 [ オブジェクト変数 ]
    Chapter.76 [ オブジェクトとコレクション ]
    Chapter.77 [ 特殊な繰り返し:For Each ]
    Chapter.78 [ エラー処理 ]
    Chapter.79 [ On Error と GoTo文 ]
    Chapter.80 [ Resumeステートメント ]
    Chapter.81 [ バイトとビット ]
    Chapter.82 [ ウィンドウメッセージとイベント ]
    Chapter.83 [ 文字列の基礎 ]
    Chapter.84 [ 文字列操作① ]
    Chapter.85 [ 文字列操作② ]
    Chapter.86 [ タイピングゲーム1:仕様を決める ]
    Chapter.87 [ タイピングゲーム2:キー入力検知 ]
    Chapter.88 [ タイピングゲーム3:文字列照合 ]
    Chapter.89 [ タイピングゲーム4:判定関数 ]
    Chapter.90 [ タイピングゲーム5:ゲーム画面設計 ]
    Chapter.91 [ タイピングゲーム6:問題文のソート ]
    Chapter.92 [ タイピングゲーム7:動的配列 ]
    Chapter.93 [ タイピングゲーム8:キーダウンイベント ]
    Chapter.94 [ タイピングゲーム9:正打数の表示 ]
    Chapter.95 [ タイピングゲーム10:タイムの表示 ]
    Chapter.96 [ クリックゲーム1:イベントの種類 ]
    Chapter.97 [ クリックゲーム2:画面設計 ]
    Chapter.98 [ クリックゲーム3:クリック座標検知 ]
    Chapter.99 [ クリックゲーム4:キャラクター準備 ]
    Chapter.100 [ クリックゲーム5:キャラクターの配置 ]
    Chapter.101 [ クリックゲーム6:キャラクター移動とNot演算子 ]
    Chapter.102 [ クリックゲーム7:クリックのヒット判定 ]
    Chapter.103 [ クリックゲーム8:ヒットマークエフェクト ]
    Chapter.104 [ クリックゲーム9:サウンド処理の実装 ]
    Chapter.105 [ クリックゲーム10:マウスカーソルの変更 ]
    Chapter.106 [ ブロック崩しゲーム1:仕様と概要を決める ]
    Chapter.107 [ ブロック崩しゲーム2:基本概念の確認 ]
    Chapter.108 [ ブロック崩しゲーム3:ベクトルとは ]
    Chapter.109 [ ブロック崩しゲーム4:変数や定数の宣言 ]
    Chapter.110 [ ブロック崩しゲーム5:初期化処理の実装 ]
    Chapter.111 [ ブロック崩しゲーム6:ブロックの配置 ]
    Chapter.112 [ ブロック崩しゲーム7:根幹処理とバーの処理 ]
    Chapter.113 [ ブロック崩しゲーム8:線分と線分の交差を判定 ]
    Chapter.114 [ ブロック崩しゲーム9:線分同士の交点 ]
    Chapter.115 [ ブロック崩しゲーム10:ボールの処理 ]
    Chapter.116 [ ブロック崩しゲーム11:最終調整して完成へ ]
    Chapter.117 [ テキストファイル操作基礎 ]
    Chapter.118 [ テキストファイル操作:読み込み編 ]
    Chapter.119 [ テキストファイル操作:CSV読み込み編 ]
    Chapter.120 [ テキストファイル操作:様々な読込編 ]
    Chapter.121 [ テキストファイル操作:バイナリ編 ]
    Chapter.122 [ テキストファイル操作:暗号化編 ]
    Chapter.123 [ テキストファイル操作:復号化編 ]
    Chapter.124 [ クラスモジュールとは ]
    Chapter.125 [ クラスモジュール:メソッド編 ]
    Chapter.126 [ クラスモジュール:プロパティ編 ]
    Chapter.127 [ クラスモジュール:イベント拡張編 ]
    Chapter.128 [ クラスモジュール:イベント自作編 ]
    Chapter.129 [ APIによる描画処理1:ハンドル ]
    Chapter.130 [ APIによる描画処理2:デバイスコンテキスト ]
    Chapter.131 [ APIによる描画処理3:ペン オブジェクト ]
    Chapter.132 [ APIによる描画処理4:ブラシ オブジェクト ]
    Chapter.133 [ APIによる描画処理5:図形描画準備編 ]
    Chapter.134 [ APIによる描画処理6:図形描画実践編 ]
    Chapter.135 [ APIによる描画処理7:画像描画の仕組み編 ]
    Chapter.136 [ APIによる描画処理8:ビットブロック転送編 ]
    Chapter.137 [ APIによる描画処理9:ラスタオペレーション ]
    Chapter.138 [ APIによる描画処理10:マスク描画 概念編 ]
    Chapter.139 [ APIによる描画処理11:マスク描画 実践編 ]


    コードやVBAに関する質問などはサポート掲示板(別館)までお気軽にどうぞ。




fc2 seotool Excel VBA ゲーム プログラミング 講座

Counter

twitter


Shadow BBS - 影掲示板

VBA 関連書籍



上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。