Top > Programmingとか > VB / VB.NET > GDI+ in VB6

05 画像ファイルの読み込みと表示

先日までお伝えしたGDI+に関する資料群。
一応それなりのことは書いてあるんですが、やっぱ.NETからAPIに読み替えるのは難しいっす。
まして基本的な概念がわかんないまんまあれこれイジっているもんですからもー進捗悪い悪い。

頼りにしていたSteve McMahonさんの文献も、サンプルが動かない(T-T)。

こちらの理由は2つ。

ひとつは、GDI+ Type Libraryをさらにクラスへラップしちゃってるもんですからいまいち使う気になれないってこと。
中途半端にクラス化しても、VB6ではあまり使い勝手がよくないように思うんですね私。
複数のインスタンスを立てて別個に動作させるんでもなければ、言語仕様をクラスの概念で固めていないVB6は、かえってクラスであったりなかったりするとこんがらかりやすいんです。
勉強のために解析しようとすると、クラス内部って機能ぶつ切りで追っかけにくさひとしおですし。

もうひとつは、提供ファイルが足りない(T-T)。
ストリーム系のインターフェイスを実装するSTRM.tlbがないっぽいんです。
VB Acceleratorの中を探し回ればあるいはあるのかもしれませんが、もーめんどくさくなっちゃって。

てことで、少ない資料と格闘しながら、推測を交えて必死こいて調査しております。はい。

ウラ取れないまま進めてますので、間違ってたり手戻ったりもするかもしれません。
まぁそのへんはご勘弁ください、ってことでひとつ。



ソースを提示する前に、少々ご理解いただいとかないといけないことが。

GDIの頃からそぅでしたが、GDI+もオブジェクト作りまくりの考え方を取ります。
このへんをしっかり抑えておかないと、頭の中がこんがらかりますんで、ていねいに抑えておきましょう。

今回、ご理解いただきたいのはGraphicsオブジェクトとImageオブジェクト。

Graphicsオブジェクトは、線を引いたり色を塗ったり画像を貼ったりいろいろした結果がどんな絵になっているかを記録するオブジェクトです。
画用紙みたいなもんだと思ってください。何を塗っても書いても自由ですが、常にその結果だけをキープ。
ふつーオブジェクトはメモリの中に専用の領域を確保することで作成しますので見た目わからないんですが、GraphicsオブジェクトはVB6のコントロールと結びつけて作成できますので、書いた結果を常に表示させるようにすることが可能です。
GDIをご存じの方であれば、デバイスコンテキスト相当だと思っていただければおっけー。
hDCプロパティを引数に、Graphicsオブジェクトを作成することができます。

Imageオブジェクトは、ビットマップ画像を格納できるオブジェクトです。
ここで言う「ビットマップ」は拡張子bmpのファイル、ということではなく、ドットの集合、という意味で。
もっとも画像ファイルの違いは圧縮手法や記述手法の違いでしかありませんから、メモリ上に画像として取り込まれた時には全部同じ無圧縮・べた書きの状態になります。
GDI+では、GIF・JPEG・PNGファイルなどの取り扱いが可能になりました。
これは、主にImageオブジェクトでの対応という形で実現されています。

うーん。なんか書いてる端からちょっと違うような気もしますが。
まぁだいたいこのくらいのつもりで把握しておいていただければ、当面は大丈夫ではないかと。
この考え方ではうまく合わないような事象が出てきたら、またその時に考え方を修正していくということで。
とりあえず先へ進みます。



試行錯誤した結果。
画像ファイルを読み込んでPictureBoxに表示する手順は、以下のようになります。
  1. GDI+の使用開始宣言。

  2. 表示用PictureBoxを、Graphicsオブジェクトとして宣言。

  3. 読み込みたい画像ファイルを指定してImageオブジェクトを作成。

  4. Imageオブジェクトの画像をGraphicsオブジェクトに複写。

  5. 先に宣言したImageオブジェクトの解放。

  6. 先に宣言したGraphicsオブジェクトの解放。

  7. GDI+の使用終了宣言。

  8. PictureBoxの表示画像の定着。
GDI+の使用開始/終了宣言は以前説明しましたので割愛。

Graphicsオブジェクトは、変数の宣言上はただのLongです。
GDI+が勝手にメモリを確保しますので、VB側ではその一意なハンドルだけを取得しておけばいいんですね。
ここでは新しいメモリ領域ではなく、実際に結果を表示したいPictureBoxのhDCを引数にして、PictureBoxの描画領域をGDI+のGraphicsオブジェクトとして宣言します。

Ret = GdiPlus.GdiPlusExports.GdipCreateFromHDC(picTest.hDC, Graphics)
って感じですね。
picTestが描画用PictureBox、Graphicsがハンドル格納用Long変数です。
「GdiPlus.GdiPlusExports.」の部分はGDIPlus.tlbであることの枕詞で、ほんとは省略可能です。
私自身が勉強中ですので、出所を明確にしておきたく省略しないようにしてあるだけですね。


次、Imageオブジェクトの作成。
Imageオブジェクトは非常にいろんな作成方法があるらしいんですが、今回は画像ファイルのデータを取得したいので、ファイルから起こす手法を採ります。
Ret = GdiPlus.GdiPlusExports.GdipLoadImageFromFile(strPath, Img)
strPathはファイル名(直接パス/間接パス/省略可能)、ImgはImageオブジェクトのハンドルを格納するLong変数です。

後はImageオブジェクトの内容をGraphicsオブジェクトに描けばいいわけです。

Ret = GdiPlus.GdiPlusExports.GdipDrawImageI(Graphics, Img, 0, 0)
第3・4引数の(0, 0)は、Graphics側の、描画開始左上座標です。(0, 0)で、PictureBox内側ぴったり左上からの描画になります。
座標系は、APIですので当然「ピクセル」ですね。座標変換がめんどくさい方は、PictureBox側のScaleModeプロパティも「3」(ピクセル)にしておくと楽ちんですね。


で、描画が終わったら後始末。
割り当てたり生成したオブジェクトはきっちり解放しておきましょう。
GdiPlus.GdiPlusExports.GdipDisposeImage Img
GdiPlus.GdiPlusExports.GdipDeleteGraphics Graphics
これもどちらもFunctionなんですが、解放を失敗しても今さら打つ手がないので、このへんはまぁいいかな、と。
Graphicsは「Delete」ですが、Imageは「Dispose」です。

あ、後、忘れちゃいけないのがVB側でのPictureBoxの後処理。
AutoRedrawプロパティをTrueにした上で、APIからの描画終了後に .Picture = .Image ってしとかないと、定着されません。
これを忘れると、他のウィンドウの下に回った時に重なった部分が欠けたり、そもそも描画しているはずなのに表示されなかったりします。
このへんは、従来のGDI Bitblt APIなんかを使っている時と同じ処理ですね。



以上のロジックをComanndButtonのClickイベントプロシージャに組み込みまして。
できあがったコードは、以下のような感じになります。
Private Sub cmdImageFile_Click()
Dim Graphics As Long
Dim Img As Long
Dim Ret As Long
Dim strPath As String
'画像ファイルからImageオブジェクトの生成
strPath = Trim$(txtPath.Text)
Ret = GdiPlus.GdiPlusExports.GdipLoadImageFromFile(strPath, Img)
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "イメージの取得に失敗しました", , Me.Caption
Exit Sub
End If
'PictureBoxのhDCからGraphicsオブジェクトの生成
Ret = GdiPlus.GdiPlusExports.GdipCreateFromHDC( _
picTest.hDC, Graphics)
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "表示領域の初期化に失敗しました", , Me.Caption
Exit Sub
End If
'Image → Graphics
Ret = GdiPlus.GdiPlusExports.GdipDrawImage(Graphics, Img, 0, 0)
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "イメージの描画に失敗しました", , Me.Caption
Exit Sub
End If
'生成したオブジェクトの解放
GdiPlus.GdiPlusExports.GdipDisposeImage Img
GdiPlus.GdiPlusExports.GdipDeleteGraphics Graphics
'表示画像の定着
picTest.Refresh
picTest.Picture = picTest.Image
End Sub
くどいぐらいにエラー処理を入れてあるのは、やはり私自身が勉強中だからですね(^^;)。


で、実際に実行してみました。

GdipLoadImageFromFileは、読み込む画像ファイルの保存形式を自動で判断します。
ので、ファイル名さえ指定すれば、.bmp、.jpg、.gif、.pngいずれの形式のファイルでも読み込めます。
(ほんとは.tiffとかまだ自動対応できる形式もあるようなんですが…私が使わないので、今回は調査の対象外とします。)

用意したサンプルは以下の4枚。
まったくおなじではどれがどのフォーマットなのかわからなくなるので、一応識別用に文字を入れてあります。

harborg.bmp harborg.jpg harborg.gif harborg.png
fig.1~4 動作検証用画像(.bmp/.jpg/.gif/.png)

ちなみに。
jpg画像の文字がにじんでいるのは、jpgの圧縮ロジックが元々そぅいぅ仕様だからです。
こーいぅラインのはっきりした画像に向いた圧縮形式ではありませんね。写真のような、境目のはっきりしない画像向けです。

実行結果はこちら。

031006-05.gif
031006-06.gif
fig.5・6 動作結果(.bmp/.jpg/.gif/.png)
でかっ。

gif以外のすべてのフォーマットが肥大して表示されてしまいました。なぜだ。

画像ファイルをいろいろ調べてみると、bmp/jpg/pngには、解像度といぅ属性が埋め込まれていることがわかりました。
私の手持ちのグラフィックソフトでてきとーに描いた画像だったんですが、全部きちんと72ピクセル/インチになっていました。
私のマシンは96ピクセル/インチなんですよねぇ…。gifが原寸で表示されているのは、解像度が「不明」だからなのでした。
このへんの指定解像度の違いまで吸収対応するとは。やるな、GDI+。

つか、よけいなことを(T-T)。

あー。なんとか解像度属性に関わらず、原寸で表示できるような方法を探さなくてはなりません。 第一Macじゃないんだから、インチあたりのドット数ったってディスプレイのサイズが変われば何にもならないじゃないすか。何をわざわざしちめんどくさい動作をしますかね。 …いゃ、わかってんですけど。DTPなんかでは解像度まで考慮して画像を展開していかないとマズい場合が確かにあるんですが。 画面上での画像のみの処理を考えた場合には、あまり意味がないんですよ。 第一、画像の解像度と自分のマシンの表示解像度を把握しているユーザがどれほどいるもんですか。

愚痴はともかく。

えーと。たぶん、

  • 読み込んだ画像の縦横のドット数を取得する。

  • Graphicsオブジェクトへ書き込む際に、縦横のドット数を指定する。
あたりでうまくいくんではないかと。
オブジェクトブラウザとmsdnを必死に眺めても、そのくらいしか使えそうな機能がないんで。

Imageオブジェクトの縦横ドット数を取得するFunctionはそれぞれGdipGetImageHeightとGdipGetImageWidth。
ドット数を指定しての描画FunctionはGdipDrawImageRectI、といったところです。

それらの機能を盛り込んで書き直したコードはこちら。

Private Sub cmdImageFile_Click()
Dim Graphics As Long
Dim Img As Long
Dim Ret As Long
Dim strPath As String
Dim lngImgHeight As Long
Dim lngImgWidth As Long
'画像ファイルからImageオブジェクトの生成
strPath = Trim$(txtPath.Text)
Ret = GdiPlus.GdiPlusExports.GdipLoadImageFromFile( _
strPath, Img)
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "イメージの取得に失敗しました", , Me.Caption
Exit Sub
End If
'Image高さの取得
Ret = GdiPlus.GdiPlusExports.GdipGetImageHeight( _
Img, lngImgHeight)
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "イメージの高さの取得に失敗しました", , Me.Caption
Exit Sub
End If
'Image幅の取得
Ret = GdiPlus.GdiPlusExports.GdipGetImageWidth( _
Img, lngImgWidth)
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "イメージの幅の取得に失敗しました", , Me.Caption
Exit Sub
End If
'PictureBoxのhDCからGraphicsオブジェクトの生成
Ret = GdiPlus.GdiPlusExports.GdipCreateFromHDC( _
picTest.hDC, Graphics)
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "表示領域の初期化に失敗しました", , Me.Caption
Exit Sub
End If
'Image → Graphics
Ret = GdiPlus.GdiPlusExports.GdipDrawImageRectI( _
Graphics, Img, 0, 0, lngImgWidth, lngImgHeight)
If (Ret <> GdiPlus.GpStatus.Ok) Then
MsgBox "イメージの描画に失敗しました", , Me.Caption
Exit Sub
End If
'生成したオブジェクトの解放
GdiPlus.GdiPlusExports.GdipDisposeImage Img
GdiPlus.GdiPlusExports.GdipDeleteGraphics Graphics
'表示画像の定着
picTest.Refresh
picTest.Picture = picTest.Image
End Sub
実行結果はこちら。

031006-07.gif
031006-08.gif
fig.7・8 動作結果2(.bmp/.jpg/.gif/.png)

ふぅ。なんとかなったよぅな気がします。
今日のところは、ここまでといぅことで。

動作検証用Pgはこちら→
ただし、別途GDIPlus.tlbを入手する必要があります。

bn_Survive.gif
上のVBアイコンは、SHIN-ICHIさんのサイトSurviveplus.netよりお借りしました。ソフトもアイコンも3DCGもサイトそのものもむちゃくちゃカッコイイサイトです。
(C)copyright 2001-2003 Sony Communication Network Corporation
Harbot(ハーボット)は、ソニーコミュニケーションネットワーク株式会社の商標です。
Harbot(ハーボット)に関わる著作権その他一切の知的財産権は、
ソニーコミュニケーションネットワーク株式会社に属します。
Harbotについて知りたい方はこちら→hb-small.gif

トラックバック

このエントリーのトラックバックURL:
http://salv.miscnotes.com/mt/mt-tb.cgi/453

コメント

VB6にてTIFを表示させる処理が必要だったのですが、こちらを参考にさせて頂きながら、作成することが出来ました。
処理の流れの説明も分かり易く、内部の動きも理解できました。
これからも参考にさせていただきます!

コメントを投稿