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

2003年11月08日

06 画像ファイルの書き出し→頓挫→迷走

前回PictureBoxへの表示までできたわけですから、当然次の欲としてはPictureBoxからの保存ですよね。

SavePicture Picture1.Image。

いゃいゃ。これではなんのために苦労してGDI+を使っているのかわかりません。
これ、私にとってはVB6が現役の頃から使えねぇステートメントの代表格だったんです。保存形式も指定できない、色数(bit数)もそのマシンの現行の状態に依存してしまうって、何のコントロールもできないではありませんかい。

ので、

好きなbit数と好きな保存形式で保存したい。

ってのを目標に考えてみたかったわけでした。



となると、たぶん画像保存に使用できるAPIはGdipSaveImageToFile
これ、パラメータが全部INでImageオブジェクト、ファイルパス、エンコーダのCLSID、エンコーダのパラメータってことになっています。

えーと、エンコーダのCLSIDはGdipGetImageEncoders APIで引っ張り出せばいいのかな。
って、第3パラメータがEncoders As ImageCodecInfoってことになってる…これ、見つかったすべてのエンコーダ情報を配列にして返してくるはずなんですが…?え?

なんだかGPIPlus.tlbの宣言がおかしいなぁ…要素数不定の状態で返値格納用に変数指定して、APIから返ってきたら要素数が特定された配列になっているなんて芸当、VBからじゃできません(T-T)。

しょうがないのであらかじめGdipGetImageEncodersSize APIで返ってくるはずのImageCodecInfo構造体配列の総バイト数だけ取得して、バッファとしてそのサイズのByte配列を作っといて、それのアドレスをVarPtr関数で取得したものを第3引数としましょうか。
となるとタイプライブラリの宣言では合わなくなりますので、自分で別途第3引数をByVal Longにした形でDeclareし直し。

じゃあ第4パラメータのエンコーダパラメータ用構造体って…構造体の中に要素数不定の配列?引き渡したいパラメータ数に応じて構造体内部の配列を可変にするって…そんな芸当もVBじゃできません(T-T)(T-T)(T-T)。



あー、行き詰まりました。
なんかあちこちヘンです、このGDIPlus.tlb。

で、もぅ一度必死に調べ直してみると。
どぅも作者のDana Seamanさん、初期バージョンの1.05をvbAcceleratorに投稿した後、バージョンアップはご自分のサイトCyberActiveXでひっそりと行っていたみたいなんです。
最新版は1.31(2003.09.11)。
サイトに掲示されている変更履歴を読むと、

* Addition
(追加)
11-Sep-2003GdiAlloc and GdipFree
(GdiAllocとGdipFree。)
* Addition
(追加)
11-Sep-2003Misc. PointerNameStrings
(あちこち。PointerNameStringsとか。)
* Bug Fix
(バグフィックス)
11-Sep-2003Change EncoderParameter in GdipSaveImageToFile, GdipSaveImageToStream, Long pointer. See note to the right concerning this problem.
(GdipSaveImageToFileとGdipSaveImageToStreamのEncoderParameterをLong型のポインタに。この問題に関しては右のメモを参照のこと。)
* Bug Fix
(バグフィックス)
02-Aug-2003Fixed Declares which use IStream. Thanks to Zak Benjamin for pointing out this error.
(IStreamを使った時の宣言の修正。このエラーを指摘してくれたZak Benjamin氏に感謝。)
* Bug Fix
(バグフィックス)
03-Jul-2003Fixed GdipGetImageThumbnail Declare.
(GdipGetImageThumbnail宣言の修正。)
* Bug Fix
(バグフィックス)
03-Jul-2003Updated PictDesc UDT member names to comply with PlatformSDK.
(PictDesc UDTメンバの名前をPlatformSDKと統一。)

えと。2003.09.11のバグフィックス内容がジャストフィットっぽいですね。
じゃ、ってんで「右のメモを参照」してみると。

…あー、すいません、なんだか妙に構文が入り組んでて文意があまりよく辿れませんでした。限界っす(T-T)。
どぅも、「encoderParamsは可変配列を内部に持つ構造体であり、こんなものはVBでもタイプライブラリでも宣言できない。同等の内部イメージを持つメモリ領域を自力で作ってポインタで渡す、という方法が考えられる。」って感じで書いてあると思うんですが。

私もそぅ思います。おーけぃ。
てことで、最新版1.31をDLしてみました。

DLできたのは1.30でした(T-T)。

直ってませんGdipSaveImageToFileの第3パラメータ。つかタイムスタンプが2003.08.02です。当然2003.09.11の修正内容が反映されているはずもなく。
リンクミスかとも思ったんですが、これアップミスですね。サーバに入ってないみたいですもん2003.11のタイムスタンプのファイルなんか。

さすがに、Danaさんに「GDIPlus.tlb 1.31をアップしてくださいなんてメールを打てるほどの英語力が私にはありません。
CyberActiveXにはどーいぅわけか、wpsjrさん(でいいのかな?…どぅも向こうの方は著作権放棄すると記名しない傾向があるので…)作った別のGDI+タイプライブラリもリンクされていますので。…いっそそっちを使っちゃおぅかしらもぅ。でもGraphicやらBitmapやらをわざわざクラスにしてあるあたり、私にはちょっとめんどいんですよねぇ。



ちょっと問題を整理しますと。

GDI+タイプライブラリのバグで画像の保存ができない。

てことですね。で、対応策としては、

  1. Dana Seamanさんにガンバってコンタクトを取り、最新Ver1.31を入手する。
    → 英文での会話ができないんですよね、私。

  2. wpsjrさんのタイプライブラリに乗り換える。
    → もぅ一度タイプライブラリの解析からやり直さなきゃならないかも。

  3. もぅ面倒なので自力Declareに方針を切り替える。
    → これはこれで大変ですし苦労も多そぅ。
のいずれか、といぅことになります。どぅしましょうか…少し考えてみたいと思います。

2003年10月06日

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

2003年09月24日

04 GDI+の開始と終了

まず最初に抑えておきたいことは、

GDI+は起動と終了を行い、各機能はその間に行う

といぅことです。

Bitbltに代表されるGDI APIは単発のAPIで、いきなりその命令だけをコールするスタイルでの使用です。
それに対しGDI+ APIは、最初にGDI+(ライブラリ?)を初期化し、各命令をコールし、作成したすべてのオブジェクトをきちんと解放した上でシャットダウンする、という手続きを執ります。

.NETのクラスライブラリでも、この手続きは直接関数として用意されていますので、必要な情報はmsdnからそのまま入手できます。



GDI+の初期化はGdiplusStartup、終了はGdiplusShutdownといぅAPIで行います。
msdnでの説明は以下のよぅな感じです。
相変わらずないい加減な超訳(便利だなこの言葉は)ですので、正確に知りたい方は原本をご参照ください。
GdiplusStartup Function
原本Online
凡例API
Status GdiplusStartup(ULONG_PTR token *token, const GdiplusStartupInput *input, GdiplusStartupOutput *output);
TypeLibrary GdiPlus.GdiPlusExportsのメンバ
Function GdiplusStartup(token As Long, Input As GdiplusStartupInput, [Output]) As GpStatus
説明The GdiplusStartup function initializes Microsoft® Windows® GDI+. Call GdiplusStartup before making any other GDI+ calls, and call GdiplusShutdown when you have finished using GDI+.
GdiplusStartupファンクションはMicrosoft® Windows® GDI+を初期化します。他の何らかのGDI+をコールする前に、GdiplusStartupをコールしてください。そしてGDI+の使用を終了する時にはGdiplusShutdownをコールしてください。
パラメータtoken
[out]
Pointer to a ULONG_PTR that receives a token. Pass the token to GdiplusShutdown when you have finished using GDI+.
トークンを返すULONG_PTR型のポインタです。
GDI+の使用を終了させる時、GdiplusShutdownにこのトークンを渡す必要があります。
input
[in]
Pointer to a GdiplusStartupInput structure that contains the GDI+ version, a pointer to a debug callback function, a Boolean value that specifies whether to suppress the background thread, and a Boolean value that specifies whether to suppress external image codecs.
GdiplusStartupInput構造体のポインタです。
これはGDI+のバージョン、デバッグ用コールバック関数のポインタ、バックグラウンドスレッドでの動作を抑制するかどうかを指定するBoolean値、外部イメージのコーデックを抑制するかどうかを指定するBoolean値で構成されています。
output
[out]
Pointer to a GdiplusStartupOutput structure that receives a pointer to a notification hook function and a pointer to a notification unhook function. If the SuppressBackgroundThread data member of the input parameter is FALSE, then this parameter can be NULL.
GdiplusStartupOutput構造体のポインタです。
通知をフックする関数のポインタとフックしない関数のポインタを含みます。
inputパラメータで「バックグラウンドスレッドでの動作を抑制するかどうかを指定するBoolean値」がFALSEであれば、この値はNULLでかまいません。
戻り値If the function succeeds, it returns Ok, which is an element of the Status enumeration.
成功すれば、Status列挙体の要素「Ok」が戻ります。
If the function fails, it returns one of the other elements of the Status enumeration.
失敗すれば、Status列挙体の他の要素が戻ります。
訳注:TypeLibraryでは、Const Ok = 0(GdiPlus.GpStatusのメンバ)として定数宣言されています。
解説You must call GdiplusStartup before you create any GDI+ objects, and you must delete all of your GDI+ objects (or have them go out of scope) before you call GdiplusShutdown.
何らかのGDI+オブジェクトを作成する前に、GdiplusStartupをコールしなければなりません。
そしてGdiplusShutdownをコールする前にすべてのGDI+オブジェクトを削除(あるいはスコープ外へ追いやる)しておかなければなりません。
You can call GdiplusStartup on one thread and call GdiplusShutdown on another thread as long as you delete all of your GDI+ objects (or have them go out of scope) before you call GdiplusShutdown.
あるスレッドでGdiplusStartupをコールし、すべてのGDI+オブジェクトを削除(あるいはスコープ外へ追いやる)した後であれば、別のスレッドからでもGdiplusShutdownをコールすることができます。
Do not call GdiplusStartup or GdiplusShutdown in DllMain or in any function that is called by DllMain. If you want to create a dynamic-link library (DLL) that uses GDI+, you should use one of the following techniques to initialize GDI+:
DllMainまたはDllMainからコールされるいかなる関数の中からも、GdiplusStartupあるいはGdiplusShutdownをコールしてはいけません。
もしGDI+を使ったDLLを作りたいなら、GDI+の初期化についての補足テクニックのひとつを使うべきです。
Require your clients to call GdiplusStartup before they call the functions in your DLL and to call GdiplusShutdown when they have finished using your DLL.
Export your own startup function that calls GdiplusStartup and your own shutdown function that calls GdiplusShutdown. Require your clients to call your startup function before they call other functions in your DLL and to call your shutdown function when they have finished using your DLL.
Call GdiplusStartup and GdiplusShutdown in each of your functions that make GDI+ calls.

(このへん補足テクニックなんでしょうけれども、VB6からほんとの意味でのDLLを作ることはできません。今回の作業にはなんぼなんでも無関係でしょうし、めんどくさいので訳しません。)

GdiplusShutdown Function
原本CD/DVD(2003.04版) Online
凡例API
void GdiplusShutdown(ULONG_PTR token);
TypeLibrary GdiPlus.GdiPlusExportsのメンバ
Sub GdiplusShutdown(token As Long)
説明The GdiplusShutdown function cleans up resources used by MicrosoftR WindowsR GDI+. Each call to GdiplusStartup should be paired with a call to GdiplusShutdown.
GdiplusShutdownファンクションはMicrosoft® Windows® GDIによるリソースの使用状態をクリーンアップします。
GdiplusStartupをコールしたら、必ずGdiplusShutdownをコールしてください。
(「複数のGdiplusStartupをコールしている場合には、対を成す形でそれぞれGdiplusShutdownをコールしてください」かもしれません。)
パラメータtoken
[in]
Token returned by a previous call to GdiplusStartup.
GdiplusStartupをコールした時に戻されたトークンを指定します。
戻り値No return value.
ありません。
訳注:戻り値がないため、TypeLibraryではSubで宣言されています。
解説You must call GdiplusStartup before you create any GDI+ objects, and you must delete all of your GDI+ objects (or have them go out of scope) before you call GdiplusShutdown.
何らかのGDI+オブジェクトを作成する前に、GdiplusStartupをコールしなければなりません。
そしてGdiplusShutdownをコールする前にすべてのGDI+オブジェクトを削除(あるいはスコープ外へ追いやる)しておかなければなりません。

えー、とりあえずこんなところで。

GdiplusStartupInput/GdiplusStartupOutput構造体についても一応訳してみたんですが、ほとんどバックグラウンドスレッドの取り扱いとコールバック関数のセットに関する記述でした。

VB6はAPIなどを使って明示的にスレッドを起こしでもしない限り、基本的にシングルスレッド(OCX等で独立したスレッドを持つ場合を除きます)アプリケーションしか生成できませんので、スレッドをまたがる場合のGDI+の設定はあまり意味を持ちません。
GDI+側でも「バックグラウンドスレッドを考慮しない」デフォルトとなっていますので、何も設定しない状態でVB6にマッチした設定状態になりますね。

コールバック関数のセットは、主にpaintなどのシステムメッセージに呼応した形での再描画を目的として用意されている機能です。
VC++などでは再描画(他のウィンドウの裏に隠れて一部/全部が見えなくなった状態から再度前面に出て、隠れていた部分の表示が必要になるなど)は基本的にアプリケーション側で行う必要があります。ので、再描画を促すシステムメッセージが流れてきた場合にどの関数を作動させるかをあらかじめ登録しておくのが普通です。

それに対してVBは、基本的には再描画を必要とするような描画を行えるオブジェクトはFormとPictureBoxくらいです。
こかもこれらはAutoRedrawプロパティをTrueにしておくだけで、描画したイメージをメモリ上にバックアップして、再描画時にはそのイメージを再表示するような動作をします。
ので、オーナードローでも行わない限り再描画のための機能をこしらえる必要はほとんどないんですよね。

従って、GdiplusStartupInput構造体はGdiplusVersionメンバを1にセットする(使用するGDI+のバージョンを指定。現在Ver1/1.1しか存在していませんので、必ず「1」を指定することになります)くらいで、他のメンバは0(つまり構造体を定義した時の初期値のまんま)で構いません。
GdiplusStartupOutput構造体に至ってはまったく使いませんし、第一GdiplusStartup関数の中でもオプショナルの扱いになっていますので、引数として用意する必要すらありません。

以上より、GDI+の開始と終了は、以下のよぅな記述となります。

Private Sub Command1_Click()
Dim typGPInput As Gdiplus.GdiplusStartupInput
Dim lngToken As Long
typGPInput.GdiplusVersion = 1
If (GdiplusStartup(lngToken, typGPInput) _
<> GdiPlus.GpStatus.Ok) Then
MsgBox "GDI+の起動に失敗しました", vbCritical
Exit Sub
End If
Call GdiplusShutdown (lngToken)
End Sub
いぇ別に起動と終了だけなので、実行してもなんにならないんですけど。
まずは基本形といぅことで、ここまで。


記述サンプルとして解析に使っているGDIPlusLOGOプロジェクトでは、プロシージャごとに起動-終了を行っているんですよね。
これは途中でバグって強制終了になってしまった場合にGDI+のインスタンスが残りにくい、といぅ開発者側の都合での記述のよぅに思えます。
実際に操作してみると、やはりけっこぅ重めの動作に感じられます。これは、あるいはプロシージャごとの起動-終了にかかるオーバーヘッドのせいなのかなぁといぅ気もしています。
「猫でもわかるプログラミング」の方では、
  1. プログラムの最初にGdiplusStartup関数を実行する。

  2. プログラムが終了する直前にGdiplusShutdown関数を実行する。
といぅ説明になっており、動作効率から考えるとこちらの方が正当な手法のよぅにも思えます。
このへんは、もぅ少し動作するよぅなところまで作り込んでから、動作時間計測でもしてみよぅかと思っています。

2003年09月22日

03 調べてみました3改々

人情が身にしみているさるですこんばんは。

さいばざる(35) しょぼんで「9.22分の「今日の猿」、間違って消してしまいました。」と書いたところ、意外に多くの反応をいただきました
日頃ほとんど反応のない(クレームはあります)中で「誰かの役に立ってるんかしら?」などと自信なげなことを考えながら好き勝手書いている私としては、おおいに元気づけられました。

ありがとうございました。

日付しか書かなかったせいで別の日のコラムと間違えて送ってくださった方、自分のところのキャッシュには残っていなかったがなんとか復旧できるかもしれない方法を思いつく限り考えてくださった方、自分のところのキャッシュにはなかったとご報告くださった方などなど。
私も自分の回りのすべてのパソコンをあさったんですが。
自宅のマシン(にょーぼのも入れて3台)、職場のマシンともアウトでした。
Google等独自にキャッシュを取る検索エンジン等もアウトでした。

この削除してしまった「GDI+(3)改」は、書きかけ推敲前の文章を一度間違ってアップしてしまったいきさつもあって、表示期間も短く初回アップはまったく違う文章だったりでなおさらややこしい状態になっています。
私のサブマシンに残っていたキャッシュは、初回アップのものでした。

今さらもう一度同じ文章を書き起こすことはできませんが、伝えたかったのはGDI+を学ぶ際に参考にできる(かもしれない)URLとmsdnアドレスであったわけです。
ここさえハズさなければ、私のトークが変わるだけでほぼ同価値の文章になるのではないかと。

てゅうことで、以下 GDI+(3)改々(三度目の正直エディション) です。



えー、前回の情報は少し舌足らずだったよぅな気がします。
書いた後で見つけた情報なんかもあったりして。
てことで、今一度プログラミングの参考になる情報の所在をメモとして残しておきたいと思います。

なかなか具体的なプログラミングに入れないでじりじりしているんですが、もぅちょい我慢。

まず、Microsoftから提供されているGDI+の公式情報は、msdn Library(US版はこちら)で提供されています。
msdn(Libraryを含む)は有料で提供契約をした個人/団体に対し、CD/DVDで定期的に提供されます。
また、Microsoftのサイトでオンライン版が無償で提供されています。
msdn Libraryはオリジナルの英語版と、翻訳されて別の言語ネイティブの方に読みやすいように再編集された各国語版があります。
ただし各国語版(日本語版を含む)は翻訳作業が入りますので、英語版とは時差があります。
ひらったく言うと、日本語訳が間に合っておらず、英語版でしか読めない情報があるということです。

CD/DVD版はこのへんを考慮し、日本語版Libraryに、翻訳の間に合っていない英語のままの情報が混ざっています。
対してオンライン版は、日本語サイトでは日本語版のみ、USサイトでは英語版のみになっています。

また、オンライン版は起稿/翻訳がされた情報が、日々更新追加されていきます。
CD/DVD版は基本的に年4回、3ヶ月に一度送付されていますので、最新情報についてはタイムラグがあることになります。

以上より。

基本的にはCD/DVD版を読めば事足りますが、最新情報・最新翻訳情報を確認するためにはオンライン版も合わせて参照する必要がある、ということです。
実際、意外なほどいろんなところが日々修正更新されていますので、古い情報を当たって終わりにしてしまうと、重要な情報を取り逃がしてしまう場合も少なくありません。

で、私のマシンには、現在2003.04版のCDからインストールされたmsdn Libraryが入っています。
その後にさらに最新版が届いているはずなんですが、忙しさにかまけてまだインストールしておりません。

まぁ以上のようなスタンダードな情報のありかと特性を把握いただいた上で、GDI+の公式な使用のありか。

【msdn CD/DVD 2003.04版】 (インストールしてない方はクリックしてもエラーになります。)
  [グラフィックおよびマルチメディア]-[Graphics and Multimdia(英語)]-[GDI+]

【msdn オンライン英語版】
  [Graphics and Multimdia]-[GDI+]-[SDK Documentation]-[GDI+]

この資料は英語で、日本語版はありません。のでCD/DVD版も英語ですし、オンライン日本語版では項目自体がありません。

なお、CD/DVD版の最新ではどうなっているかわかりませんが、2003.04版にはなくオンライン英語版にのみある情報がけっこう多いです。
CD/DVD版をお持ちでも、一度オンライン版をチェックすることを強くお勧めします。
(ってなんだか高い金払ってCD/DVD版を入手している必要がないよぅな…T-T)



さて、この公式な資料は、GDI+を.NETで使用するために用意されたクラスライブラリについての説明になっています。
クラスライブラリは、Windows OS側で用意されているAPIを.NETで使えるようにするためのクラスとか関数とか構造体とかの集合です。
ですので、API直接の説明ではありません。.NET用に用意されているルールですので、APIと1対1対応になっているわけでもありません。

で、GDI+ .NETクラスライブラリとGDI+ APIの対比表があります。
もちろん英語ですけど。

【msdn CD/DVD 2003.04版
  [グラフィックおよびマルチメディア]-[Graphics and Multimdia(英語)]-[GDI+]-[GDI+ Reference]-[GDI+ Flat API]

【msdn オンライン英語版】
  [Graphics and Multimdia]-[GDI+]-[SDK Documentation]-[GDI+]-[GDI+ Reference]-[GDI+ Flat API]

私の探した限り、直接APIに言及しているのはここだけです。
クラスライブラリでは用意していない機能などは、この対比表の中で引数等の説明が記述されていたりします。



さて、公式な資料の限界がここまでなら、非公式な資料としてはどぅか。

日本語で何とか説明してくれているサイトを探してみたんですけど、…見つかりませんでした。
.NET、もぅちょい近いところでVC++6くらいまでの基本的な使い方のところであればちらほらと見かけたんですが、全体的な包括した説明になっているところはありませんでした。
ましてやVB6での説明など。VB6など。おおおおお(T-T)

VB6はすでに失われてしまった言語なのか?

いゃいゃ、単にそーいぅツッコんで調べてくれる主力層が.NETとかlinuxとかに分散していっちゃったんだと思っています。

で、どーしてこんなにこってりしてるんだってくらいに精力的に独自の情報を提供しまくってくれるのはやはり海外のサイトの方が強いですね。

でまず、先日ご紹介したVB6用GDI+タイプライブラリを提供してくれているVB Acceleratorから。
GDI+ Type Libraryからリンクをたぐると、Steve McMahonさんといぅ方が「Reading and Writing JPG, PNG, TIF and GIF Files」「Reading EXIF and Other Image Properties Using GDI+」「Scale, Rotate, Skew and Transform Images using GDI+」を提供してくださっています。
もちろん動作するサンプルプログラムも提供されていますが、どのような概念で捉えていけばいいかの説明が秀逸です。

もぅひとつ。VB Helperから。

これはVB.NETでの資料なので直接的にはあれなんですけれども。
えと、イギリスにVBUG UKって団体があるみたいなんですね。URLをみていると、なんか企業(co.uk)みたいな気もするんですけど。ついでに言っちゃうと、日本でのVBユーザーグループVBUGとの関係も私は知らないんですけど。
で、VBUG UKがカンファレンス2003(せ、正式名称がわからない…T-T)を開催してまして、その際にGDI+のトークもあったらしいんですよ。
そのときのプレゼンテーション資料(PowerPoint)と、その資料の中で使用されているものすごい量のサンプルプログラム(VB.NET)がこちら

VB.NETがどぅの、ってところは本コンテンツの趣旨に合わないんですけど、私が見た中で一番GDI+の個々の機能が把握できて、しかも実際に動作を検証できる資料がこれだったんですよ。

以上、興味のある方は是非。



以上、紛失してしまった「GDI+(3)改」のリライトです。
…なんだか元のものより資料としてしっかりしてしまったよぅな。
あれを書いた時にはまだ見つけていなかった資料なんかも追加で書いてしまいましたし。

なんだかかえって最初からこちらを読んでいただいたほぅが、話が早かったかもしれません。

…ひょうたんから駒?

2006.09.12 追記 投稿日を2003.09.22に直しました。やっぱ前後のエントリと順番がずれると読みにくいので。 でもこのエントリは、ほんとは2003.09.29に書いてます。

2003年09月16日

02 調べてみました2

さて、もぅちょい楽に何とかできる方法はないものかと。

手持ちのMSDN(2003.04版)をざっくり探してみると、ありました。詳細なGDI+についての記述が。

[グラフィックおよびマルチメディア]
  -[Graphics and Multimedia(英語)]
    -[GDI+]

うわぁ全編英語。
いぇ必要とあれば読み下しますが、なんぼなんでも概念の把握のレベルから各関数の実装まで全部英語のままで読み下すのはエラい作業で。
すいませんいくらなんでもここに人生をかけるわけにはいきません。

あ、2003.04の段階では日本語訳が間に合っていないだけなのかも。
オンラインのMSDNライブラリなら最新の翻訳状況で提供されているはずだから、そちらも参照してみましょう。

…そもそも項目がないし(T-T)。

あ、抽象的な概念の理解であれば、GDI+ グラフィックといぅコンテンツがありました。
2003.04版とオンライン版では格納位置が違うので、最新のMSDNライブラリはオンライン版に近い位置に格納されているのかもしれません。

2003.04版
[.NET開発]
  -[.NET開発]
    -[Visual Studio .NET]
      -[製品ドキュメント]
        -[Visual Studio .NETによる開発]
          -[Windowsアプリケーションの作成]
            -[Windowsフォーム]
              -[GDI+ グラフィック]
オンライン版
[.NET開発]
  -[Visual Studio .NET]
    -[製品ドキュメント]
      -[Visual BasicとVisual C#]
        -[アプリケーションの作成]
          -[Windowsアプリケーションの作成]
            -[Windowsフォーム]
              -[GDI+ グラフィック]

いちおぅ読んでみましたけど。どぅも抽象的すぎていまいちピンと来ません。
まぁないよりマシな資料ではあります。

で、結局一番参考になったのが、LaLa Moo-Mooさん(でいいのかな?)のサイトThe Laughing Kettle内のページ[Developer's Lab.]-[GDI+の導入]のあたり。感謝。

このページ、説明に採用している言語こそVC++.netですが、開発環境の導入からGDI+関数の開始・終了のお約束まで、短いながらツボを外さないシンプルな文章でまとめられており、大変わかりやすいです。

ちなみに。
Developer's Lab.のページからGDI/GDI+のビットマップ転送能力とかそのへんのコンテンツまで読み込んでいくと思わぬディープな世界にハマり込んでいきます。
とことんがお好きな方はそちらもどぅぞ。…D言語ってなんだ。(日本語ポータルはこちら)

さらにちなみに。
C/C++学びサイトの定番、Yasutaka Kumeiさんの猫でもわかるプログラミングでも、第335336章「GDI+の基礎」のあたりで説明されています。
ただしこちらはC++ですらなくWindows SDKでの実装の説明ですので、初心者向けとは言いながら根性入れないと訳がわからないかも。



で、肝心なVB6上での実装手法はどぅするんだ、といぅと。
こちらも具合のよさそぅなものを見つけました。
VB AcceleratorといぅサイトのGDI+ Type Library (GDIPlus.TLB)

GDIPlus.TLBそのものの入手は上記ページの左上[download]-[GDIplus Type Library]から。
使用サンプルはGDI+ TypeLib TLB - Unicode Logo GeneratorGDI+ Wrapperの2つがあります。

とりあえず、Unicode Logo Generatorの方をDLして動作検証。
おぉ、日本語フォントにも対応してさくさく動くよ。これはイキかも。

と思って同梱のReadMe(これ、ファイル名が「@PSC_ReadMe_42861_1.txt」で、LHUT32 Ver.1.41 & UNZIP32.DLL Ver5.40ではエラーで展開できませんでした(T-T))を読んだら。

Requires Forms Object 2.0 which is part of Office.
MS Officeが提供するFM20.DLLが必要です。

え。
FM20.DLLって、再配布はできないしあちこち動作にボロが出るんでVB6からは使っちゃいけません的伝説を持つ悪名高いDLLじゃないですか。

If you don't have Office you can download FM20.Dll as part of SetupPad.Exe at ~
もしMS Officeを持っていないならActiveX Control Padに含まれるFM20.DLLを使うことができます。

ってむりやり入手しても。
つかFM20.DLLて無料で入手できるんだ。へぇ。ちなみに上記ReadMeに記述されているURLはなんでだか途中で切れていますが、入手先はこちら。でそれの日本語版パッチがこちら。さらにそれの解説がこちら(日本語版の解説は、ちょっと趣旨が違うけれどもこちら)。

いゃツッコみどころはそこではなく。
私としてはFM20.DLLは使いたくないので、もしGDI+タイプライブラリがFM20.DLLを必須とするなら採用をあきらめなければならず。

でも、GDI+にOfficeフォームって必要なのかなぁ…?

半べそをかきながら調べてみたら、なんのことはない、表示テキストの入力TextBoxをUniCode対応にしたくてtxtLogoをMSForms.TextBoxで作成していただけなのでした。ほっ。



以上で調査のための準備と使い物になる(かもしれない)パーツは揃いました。
次回から、実際にVB6 & GDI+でグラフィックの取り扱いができるかどぅかの検証をしてみたいと思います。

01 調べてみました

さいばざる(30) GIFにおいて、

…VB.NETはGIF変換の機能を最初から持っているんでした…(T-T)

と書いたところ、K.J.Kさんから

あれは.NETの機能ではなく、GDI+の機能です。

とツッコみをいただきましたありがとうございますGDI+ってなんですかわかりません(T-T)。



てことで調べてみましたGDI+。

このへん、どぅもぴしっとわかりやすく説明してくれている資料が見あたりません。
わかった分を列挙してみると、

  • 従来の描画制御機能であるGDIと完全に置き換わる、新しい描画制御機能であること。

  • Jpeg、Gif、Png等の圧縮画像データを取り扱えること、アルファブレンド(不完全透過)をサポートしていること。

  • 対応しているOSはWin98/SE/ME、NT/2000/XP。
    ただしWinXPでは標準で搭載されているが、他OSでは別途DLLを用意する必要があること。

  • DLL名はGdiPlus.dll。MicrosoftサイトのPlatform SDK Redistributable: GDI+ RTMのページから入手できること。
    また、GdiPlus.dllは自作Pgに同梱して再配布することが可能であること。
って感じでしょうか。

試しに上記URLから実際にGdiPlus.dllをDLしてみると、自動展開CABファイルで1,097,832Bytes、展開してみると出てくるのが自動展開ZIPファイルで1,026,440Bytes、さらにこれを展開するとドキュメントファイルと共に現れたGdiPlus.dllが1,706,800Bytes。でかっ。
自作Pgと一緒に配布する想定で、これまた試しにLZH(lh5)で圧縮してみると987,296Bytesに。
…いゃぁでかっ。このへんはちょっとやりにくいかもしれませんね。

でもまぁ各種形式の画像をそのまま使えるのは、プログラマ側のメリットとしてはむちゃくちゃでかいです。
ので、使えるかどぅか調べてみることにしましょう。



過日お話ししたよぅに、言語仕様としてさくっと使えるようになっているのはVS.netにおいてのみです。
本コンテンツではやはりVB6で何とかしたいと思いますので、例のごとく各APIをDeclareで宣言してひとつずつCallしていくか、と。

となると、GDI+のAPI関数なんかVB6のAPIビューアに入っているわけがありませんから、やはりSDKに格納されているVC++用のヘッダファイルを参照してDeclare宣言文を書き上げていくのがスタンダードな手法でしょう。

GDI+ API関数の宣言が書かれているヘッダファイルは、GdiPlus.hを始めとするGdi*.h 30ファイル。VS.netがインストールされている環境なら(VS.netフォルダ)\Vc7\PlatformSDK\Includeにあります。
…量の多さにうんざり。中身も今まで見たこともないよぅな宣言になっているし。ってVC++.net用だから当たり前か。どぅやってVB6で通るよぅに書き換えればいいんだ?

VS.netをお持ちでない方は、Microsoftのサイトから.NET Framework SDK Version 1.1をDL→インストールすればいいんですが。ダウンロードサイズが161,185KB。162MBってあんた…。

だめだ、これはいくらなんでも環境の構築手法が非現実的だ。
「やればできる」のかもしれませんが、べらぼうな手間暇がかかってしまいます。

それでも俺はやる、って方は止めませんが。
もぅ少し現実的な手法を考えてみることにしましょう。