C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

VB.NETでSusieプラグインを使いたい

[トピック内 25 記事 (1 - 20 表示)]  << 0 | 1 >>

■85414 / inTopicNo.1)  VB.NETでSusieプラグインを使いたい
  
□投稿者/ K-1 (1回)-(2017/10/19(Thu) 15:35:32)

分類:[VB.NET/VB2005 以降] 

VB2015 Windows7です。
Susieプラグインを使って画像表示をしたいです。
VB6では動作していたコードを移植し、以下のように書きました。

'宣言
Private Declare Function GetPicture Lib "ifdds.spi" (ByVal FileSTR As String, ByVal OffSet As Integer, ByVal Mode As Short, ByRef MemoryHandle As Integer, ByRef InfoHandle As Integer, ByVal CallBack As Integer, ByVal CallBackLong As Integer) As Short

Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Dest As BITMAPINFOHEADER, ByVal Source As IntPtr, ByVal length As Integer)

Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As Integer) As Integer
Private Declare Function LocalLock Lib "kernel32" (ByVal MemHandle As Integer) As Integer

Private Declare Function SetDIBits Lib "gdi32" (ByVal Pic_hDC As Integer, ByVal hBitmap As Integer, ByVal nStartScan As Integer, ByVal nNumScans As Integer, ByRef lpBits As Integer, ByRef lpBi As Integer, ByVal wUsage As Integer) As Integer

Private Declare Function LocalUnlock Lib "kernel32" (ByVal MemHandle As Integer) As Integer

--------------------------------------------------------------------------------------------
'本体
Private Sub ddsView(ByRef temp_dds_name As String)
Dim BitMapMemoryHandle As Long
Dim BitMapInf As Long
Dim LocalMemoryBMP As Long
Dim LocalMemoryInf As Long
Dim BitMapHeader As BITMAPINFOHEADER
Dim ret As Integer

ret = GetPicture(temp_dds_name, 0, 0, BitMapMemoryHandle, BitMapInf, 0, 0) '画像の展開

LocalMemoryBMP = LocalLock(BitMapMemoryHandle) 'メモリのロック
LocalMemoryInf = LocalLock(BitMapInf)

ret = Len(BitMapHeader)
MoveMemory(BitMapHeader, LocalMemoryBMP, Len(BitMapHeader)) 'メモリ移動
Picture1.Width = VB6.TwipsToPixelsX(BitMapHeader.biWidth) 'ピクチャーボックスおよびフォームの大きさを整える
Picture1.Height = VB6.TwipsToPixelsY(BitMapHeader.biHeight)

Dim gp As Graphics = Picture1.CreateGraphics 'フォームのGraficsを作成
Dim hDC As IntPtr = gp.GetHdc() 'そのデバイスコンテキストを取得
SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, VB6.PixelsToTwipsY(Picture1.Height), LocalMemoryInf, LocalMemoryBMP, 0) 'ビットマップ表示

Picture1.Refresh() 'ピクチャーボックス更新

LocalUnlock(BitMapMemoryHandle) 'メモリロック解除
LocalUnlock(BitMapInf)

LocalFree(BitMapMemoryHandle) 'メモリ開放
LocalFree(BitMapInf)
End sub
ピクチャーボックス「Picture1」に画像が表示されることを期待していましたが、
変数Picture1.Width、Picture1.Heightに0が入っており、表示がされません。
GetPictureでファイル情報の吸い上げに失敗しているのか、メモリコピーに失敗しているのかもわかりません。

画像ファイル自体はビューアを使って表示ができることを確認してあります。

どなたかご教授をお願いいたします。
引用返信 編集キー/
■85427 / inTopicNo.2)  Re[1]: VB.NETでSusieプラグインを使いたい
□投稿者/ Azulean (885回)-(2017/10/20(Fri) 00:11:09)
とりあえず、VB6 のコードは捨てて、.NET らしく書き直した方が良いと思いますね。

少しぐぐると、クラスライブラリ用意してくれている方もいらっしゃるようなので。
http://elksimple.web.fc2.com/memo/spi_dot_net.html

No85414 (K-1 さん) に返信
> '宣言
> Private Declare Function GetPicture Lib "ifdds.spi" (ByVal FileSTR As String, ByVal OffSet As Integer, ByVal Mode As Short, ByRef MemoryHandle As Integer, ByRef InfoHandle As Integer, ByVal CallBack As Integer, ByVal CallBackLong As Integer) As Short

Integer 型で宣言しているところに Long 型を ByRef で渡すなど、熟練者としては信じがたいコードになっています。
Handle と名前がついているものや、Callback は、.NET らしく書くなら IntPtr 型ですね。
Mode は Short 型ではなく、Integer 型ではないかと。


> Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Dest As BITMAPINFOHEADER, ByVal Source As IntPtr, ByVal length As Integer)

ByVal は基本的にコピーを渡すという思想なので、コピーした場所に Move してくるとなると、呼び出し元の BitMapHeader に何も影響しません。
よって、Width = 0, Height = 0 のままになるのかと思います。


> Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As Integer) As Integer
> Private Declare Function LocalLock Lib "kernel32" (ByVal MemHandle As Integer) As Integer
> Private Declare Function LocalUnlock Lib "kernel32" (ByVal MemHandle As Integer) As Integer

この辺の Handle も IntPtr ですね。


> Private Declare Function SetDIBits Lib "gdi32" (ByVal Pic_hDC As Integer, ByVal hBitmap As Integer, ByVal nStartScan As Integer, ByVal nNumScans As Integer, ByRef lpBits As Integer, ByRef lpBi As Integer, ByVal wUsage As Integer) As Integer

hDC, hBitmap, lpBits lpBi はいずれも ByVal の IntPtr かと。


> SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, VB6.PixelsToTwipsY(Picture1.Height), LocalMemoryInf, LocalMemoryBMP, 0) 'ビットマップ表示

SetDIBits は Bits, bmi の順番のはずですが、コードを見る限り、逆に見える…?
引用返信 編集キー/
■85428 / inTopicNo.3)  Re[2]: VB.NETでSusieプラグインを使いたい
□投稿者/ PANG2 (192回)-(2017/10/20(Fri) 02:27:44)
ifdds.spi は32bitでしょうから、呼び出し側のアプリのターゲットCPUは「x86」にするべきでしょう。
https://msdn.microsoft.com/ja-jp/library/5b4eyb0k(v=vs.100).aspx

引用返信 編集キー/
■85429 / inTopicNo.4)  Re[2]: VB.NETでSusieプラグインを使いたい
□投稿者/ s6 (1回)-(2017/10/20(Fri) 07:34:13)
No85427 (Azulean さん) に返信
> とりあえず、VB6 のコードは捨てて、.NET らしく書き直した方が良いと思いますね。
>
> 少しぐぐると、クラスライブラリ用意してくれている方もいらっしゃるようなので。
> http://elksimple.web.fc2.com/memo/spi_dot_net.html
>
> ■No85414 (K-1 さん) に返信
>>'宣言
>> Private Declare Function GetPicture Lib "ifdds.spi" (ByVal FileSTR As String, ByVal OffSet As Integer, ByVal Mode As Short, ByRef MemoryHandle As Integer, ByRef InfoHandle As Integer, ByVal CallBack As Integer, ByVal CallBackLong As Integer) As Short
>
> Integer 型で宣言しているところに Long 型を ByRef で渡すなど、熟練者としては信じがたいコードになっています。
> Handle と名前がついているものや、Callback は、.NET らしく書くなら IntPtr 型ですね。
> Mode は Short 型ではなく、Integer 型ではないかと。
>
>
>> Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Dest As BITMAPINFOHEADER, ByVal Source As IntPtr, ByVal length As Integer)
>
> ByVal は基本的にコピーを渡すという思想なので、コピーした場所に Move してくるとなると、呼び出し元の BitMapHeader に何も影響しません。
> よって、Width = 0, Height = 0 のままになるのかと思います。
>
>
>> Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As Integer) As Integer
>> Private Declare Function LocalLock Lib "kernel32" (ByVal MemHandle As Integer) As Integer
>> Private Declare Function LocalUnlock Lib "kernel32" (ByVal MemHandle As Integer) As Integer
>
> この辺の Handle も IntPtr ですね。
>
>
>> Private Declare Function SetDIBits Lib "gdi32" (ByVal Pic_hDC As Integer, ByVal hBitmap As Integer, ByVal nStartScan As Integer, ByVal nNumScans As Integer, ByRef lpBits As Integer, ByRef lpBi As Integer, ByVal wUsage As Integer) As Integer
>
> hDC, hBitmap, lpBits lpBi はいずれも ByVal の IntPtr かと。
>
>
>> SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, VB6.PixelsToTwipsY(Picture1.Height), LocalMemoryInf, LocalMemoryBMP, 0) 'ビットマップ表示
>
> SetDIBits は Bits, bmi の順番のはずですが、コードを見る限り、逆に見える…?
引用返信 編集キー/
■85430 / inTopicNo.5)  Re[2]: VB.NETでSusieプラグインを使いたい
□投稿者/ 通りすがり (3回)-(2017/10/20(Fri) 07:35:32)
No85427 (Azulean さん) に返信

> 少しぐぐると、クラスライブラリ用意してくれている方もいらっしゃるようなので。
> http://elksimple.web.fc2.com/memo/spi_dot_net.html

あーそれバグ持ち
引用返信 編集キー/
■85431 / inTopicNo.6)  Re[3]: VB.NETでSusieプラグインを使いたい
□投稿者/ 通りすがり (4回)-(2017/10/20(Fri) 07:38:23)
すみませんNo85429はミスです
何で削除出来ないかなー…

引用返信 編集キー/
■85432 / inTopicNo.7)  Re[4]: VB.NETでSusieプラグインを使いたい
□投稿者/ 通りすがり (5回)-(2017/10/20(Fri) 07:55:25)
C#だけどこっちの方が
http://nive.jp/NiVE1/index.php?%A5%D7%A5%E9%A5%B0%A5%A4%A5%F3#BxSpi
引用返信 編集キー/
■85433 / inTopicNo.8)  Re[5]: VB.NETでSusieプラグインを使いたい
□投稿者/ K-1 (2回)-(2017/10/20(Fri) 10:13:08)
No85432 (通りすがり さん) に返信
ありがとうございます。

'宣言
Private Declare Function GetPicture Lib "ifdds.spi" (ByVal FileSTR As String, ByVal OffSet As Integer, ByVal Mode As Integer, ByRef MemoryHandle As IntPtr, ByRef InfoHandle As IntPtr, ByVal CallBack As IntPtr, ByVal CallBackLong As Integer) As Integer

Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Dest As BITMAPINFOHEADER, ByVal Source As IntPtr, ByVal length As Integer)

Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer
Private Declare Function LocalLock Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer

Private Declare Function SetDIBits Lib "gdi32" (ByVal Pic_hDC As IntPtr, ByVal hBitmap As IntPtr, ByVal nStartScan As Integer, ByVal nNumScans As Integer, ByVal lpBits As IntPtr, ByVal lpBi As IntPtr, ByVal wUsage As Integer) As Integer

Private Declare Function LocalUnlock Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer
'本体
Private Sub ddsView(ByRef temp_dds_name As String)
Dim BitMapMemoryHandle As Long
Dim BitMapInf As Long
Dim LocalMemoryBMP As Long
Dim LocalMemoryInf As Long
Dim BitMapHeader As BITMAPINFOHEADER
Dim ret As Integer

ret = GetPicture(temp_dds_name, 0, 0, BitMapMemoryHandle, BitMapInf, 0, 0) '画像の展開

LocalMemoryBMP = LocalLock(BitMapMemoryHandle) 'メモリのロック
LocalMemoryInf = LocalLock(BitMapInf)

MoveMemory(BitMapHeader, LocalMemoryBMP, Len(BitMapHeader)) 'メモリ移動
Picture1.Width = BitMapHeader.biWidth 'ピクチャーボックスおよびフォームの大きさを整える
Picture1.Height = BitMapHeader.biHeight

Dim gp As Graphics = Picture1.CreateGraphics 'フォームのGraficsを作成
Dim hDC As IntPtr = gp.GetHdc() 'そのデバイスコンテキストを取得
ret = SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, Picture1.Height, LocalMemoryBMP, LocalMemoryInf, 0) 'ビットマップ表示

Picture1.Refresh() 'ピクチャーボックス更新

LocalUnlock(BitMapMemoryHandle) 'メモリロック解除
LocalUnlock(BitMapInf)

LocalFree(BitMapMemoryHandle) 'メモリ開放
LocalFree(BitMapInf)

'Windowsにイベント渡し
System.Windows.Forms.Application.DoEvents()
End Sub

上記のように修正したところ、「BitMapHeader」へのデータコピーは動作するようになりました。
デバッカでみても、幅、高さなどは想定どおりの値が入ってきています。
ピクチャーボックスもそれらしい大きさに調整されています。
ですが、肝心の画像が表示されません。
ret = SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, Picture1.Height, LocalMemoryBMP, LocalMemoryInf, 0) 'ビットマップ表示
のLocalMemoryInfとLocalMemoryBMPを入れ替えてみたりしましたが、表示されませんでした。
retの値は0なので、正常終了はしているようです。

念のためコンパイルオプションを確認しましたが、CPUは「x86」でした。


引用返信 編集キー/
■85434 / inTopicNo.9)  Re[6]: VB.NETでSusieプラグインを使いたい
□投稿者/ 魔界の仮面弁士 (1432回)-(2017/10/20(Fri) 11:10:09)
No85433 (K-1 さん) に返信
> Private Sub ddsView(ByRef temp_dds_name As String)
何故 ByRef に?

確かに GetPicture API の引数は LPCSTR ではなく LPSTR ですが、
だからといって、呼び出し元にその結果を返す意味が思い当たりませんでした。


> Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer
何故 Integer 型に?

関数が失敗したときは、引数の値がそのまま返却される仕様だったはずなので、
引数と戻り値の型が異なるのは、すごく違和感があります。


> Dim LocalMemoryBMP As Long
何故 Long 型に?

他にも幾つか、暗黙の型変換で隠されている点がありますので、
「Option Strict On」にしておいたほうが良いですよ。


> ByVal CallBack As IntPtr
本来は ProgressCallback への Delegate にするべきではありますが、
どうせ使わないのなら IntPtr でも良いですね。


> Dim gp As Graphics = Picture1.CreateGraphics 'フォームのGraficsを作成
Dispose が漏れています。Using ブロックで囲みましょう。


> Dim hDC As IntPtr = gp.GetHdc()   'そのデバイスコンテキストを取得
ReleaseHdc メソッドも呼ばれていないようですが…。


> Picture1.Refresh() 'ピクチャーボックス更新
Refresh しているのは何故ですか?


Imports System.Runtime.InteropServices
Public Class Form1
 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

  Using bmp = Me.Icon.ToBitmap()
   Using g = PictureBox1.CreateGraphics()
    g.DrawImage(bmp, Point.Empty)
   End Using
   Using g = PictureBox2.CreateGraphics()
    g.DrawImage(bmp, Point.Empty)
   End Using
   Using g = PictureBox3.CreateGraphics()
    g.DrawImage(bmp, Point.Empty)
   End Using
   Using g = PictureBox4.CreateGraphics()
    g.DrawImage(bmp, Point.Empty)
   End Using
   'PictureBox1 : 何もしない
   PictureBox2.Refresh()
   PictureBox3.Invalidate()
   PictureBox4.Update()
  End Using
 End Sub
End Class


もし、PictureBox に表示することが目的なのであれば、
CreateGraphics するのではなく、
New Bitmap(w, h) で生成したビットマップを
Graphics.FromImage して描画し、最後に
PictureBox1 の BackgroundImage か Image に
セットする形でも良いかも知れません。
引用返信 編集キー/
■85435 / inTopicNo.10)  Re[7]: VB.NETでSusieプラグインを使いたい
□投稿者/ K-1 (4回)-(2017/10/20(Fri) 11:57:09)
2017/10/20(Fri) 11:58:20 編集(投稿者)
2017/10/20(Fri) 11:58:07 編集(投稿者)
2017/10/20(Fri) 11:57:58 編集(投稿者)

■No85434 (魔界の仮面弁士 さん) に返信
> ■No85433 (K-1 さん) に返信
>>Private Sub ddsView(ByRef temp_dds_name As String)
> 何故 ByRef に?
 特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。
 とりあえずByValには直しました。

>>Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer
> 何故 Integer 型に?
 特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。

>>Dim LocalMemoryBMP As Long
> 何故 Long 型に?
 特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。

>>ByVal CallBack As IntPtr
> 本来は ProgressCallback への Delegate にするべきではありますが、
> どうせ使わないのなら IntPtr でも良いですね。
 特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。

>>Dim gp As Graphics = Picture1.CreateGraphics  'フォームのGraficsを作成
> Dispose が漏れています。Using ブロックで囲みましょう。
> 
>>Dim hDC As IntPtr = gp.GetHdc()   'そのデバイスコンテキストを取得
> ReleaseHdc メソッドも呼ばれていないようですが…。
        Using gp As Graphics = Picture1.CreateGraphics  'フォームのGraficsを作成
            Dim hDC As IntPtr = gp.GetHdc()         'そのデバイスコンテキストを取得

            ret = SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, Picture1.Height, LocalMemoryBMP, LocalMemoryInf, 0) 'ビットマップ表示
            gp.ReleaseHdc()
        End Using
上記のように修正してみました。

>>Picture1.Refresh() 'ピクチャーボックス更新
> Refresh しているのは何故ですか?
 特に意識はありません。元になったVB6のコードで行っていたためです。

> もし、PictureBox に表示することが目的なのであれば、
 とりあえずはPictureBoxに表示し、最終的には
Picture1.Image.Save(save_file_name)
 でBMP画像を生成したいのです。

引用返信 編集キー/
■85444 / inTopicNo.11)  Re[8]: VB.NETでSusieプラグインを使いたい
□投稿者/ 魔界の仮面弁士 (1434回)-(2017/10/20(Fri) 23:23:11)
2017/10/21(Sat) 03:40:17 編集(投稿者)

No85435 (K-1 さん) に返信
>>もし、PictureBox に表示することが目的なのであれば、
>  とりあえずはPictureBoxに表示し、最終的には
> Picture1.Image.Save(save_file_name)
>  でBMP画像を生成したいのです。

Picture1.Image (VB6) は、ReadOnly で、常に何かしらの画像を返しますが、
PictureBox1.Image (VB.NET) は Writable であり、その初期値は Nothing です。

元が Nothing であるという事は、まずは何かしらの画像をセットしなければ、
> Picture1.Image.Save(save_file_name)
を呼ぶことすらできない、ということです。


そして、それを行うための手段こそが、先の No85434 で回答した
> New Bitmap(w, h) で生成したビットマップを
> Graphics.FromImage して描画し、最後に
> PictureBox1 の BackgroundImage か Image に
> セットする形でも良いかも知れません。
という手順であるわけです。


具体的にはこんな感じです。

Dim bmp As New Bitmap(PictureBox1.Width, PictureBox1.Height)
Using g = Graphics.FromImage(bmp)
  '
  'g に対する描画処理をここに記述
  '
  Dim oldImage = PictureBox1.Image
  PictureBox1.Image = bmp
  If oldImage IsNot Nothing Then
    oldImage.Dispose()
  End If
End Using

またはこんな感じ。

Dim bmp As New Bitmap(PictureBox1.Width, PictureBox1.Height)
Dim hBitmap = bmp.GetHbitmap()
'
'hBitmap に対する描画処理をここに記述
'
DeleteObject(hBitmap);
Dim oldImage = PictureBox1.Image
PictureBox1.Image = bmp
If oldImage IsNot Nothing Then
  oldImage.Dispose()
End If



上記で oldImage.Dispose() しているのは、前に表示されていた古い画像を
処分するためのコードです。もし、一つの Bitmap を複数の場所で
共有利用している場合は、まだ使用中のものを Dispose しないように注意しましょう。
どこからも使用されなくなった時点で、Dispose を呼ぶようにしてください。



> ret = SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, Picture1.Height, LocalMemoryBMP, LocalMemoryInf, 0) 'ビットマップ表示

SetDIBits の第二引数には、HBITMAP を渡す必要があります。
第六引数(上記では LocalMemoryInf)にビットマップのデータを渡すと、
それが第二引数に指定したビットマップのピクセルへと描かれるわけです。


ここでいう HBITMAP とは、ビットマップオブジェクトのハンドルです。
VB6 で言えば、OLE_HANDLE または Long 型相当の値が要求されますし、
VBA7 で言えば、OLE_HANDLE または LongPtr 型相当の値です。
VB.NET で言えば、IntPtr 型の値が必要であることになりますね。

そして VB.NET においては、
> Dim bmp As New Bitmap(PictureBox1.Width, PictureBox1.Height)
で生成した Bitmap オブジェクトの GetHbitmap メソッドを使うことで、
簡単に HBITMAP を取得することができます。


ということで今回の場合、第二引数に渡している『CInt(CObj(Picture1.Image))』の部分が明らかにおかしいです。
ここに渡されている値がどのような内容になっているか、実際に確認してみましたか?

先に述べた通り、Image プロパティの初期値は Nothing のはずです。
PictureBox の Image プロパティに何の画像もセットしていないのであれば、
『CInt(CObj(Picture1.Image))』は『CInt(Nothoing)』と同義であり、
それは要するに、ハンドルとして「0」を指定していたということになります。

もし、Image が Nothing ではなく、何らかの画像が指定されていた場合はどうでしょうか?
その場合、画像を Integer 型に変換することはできませんので、CInt での変換が失敗し、
InvalidCastException の例外となるはずです。
もし現時点で例外になっていないのであれば、Image は恐らく Nothing のままだったのでしょうね。

そして変更すべきビットマップを渡していないのなら、画像が描画されるはずも無いわけで。


ちなみに VB.NET における PictureBox1.Image が返すデータの型は、Image 型(あるいはその継承クラス)ですが、
VB6 においては、Picture1.Image が返す型は StdPicture 型となっています。

StdPicture オブジェクトは、“既定のプロパティ”として Handle プロパティを持っていますので、
VB6 で CLng(Picture1.Image) とした場合は、Picture1.Image.Handle と同義となります。

そして VB6 における Picture1.Image.Handle の値は、HBITMAP (など)に相当します。
ちなみに Handle プロパティのデータ型は OLE_HANDLE 型です。(VB6 では Long と同義)
https://msdn.microsoft.com/en-us/library/aa267505.aspx



>>> Picture1.Refresh() 'ピクチャーボックス更新
>> Refresh しているのは何故ですか?
>  特に意識はありません。元になったVB6のコードで行っていたためです。

では質問を変えましょう。VB.NET の方の Refresh ではなく、移植元である
VB6 当時のコードにおいて、Refresh が記載されている理由を把握できていますか?


VB6 の PictureBox には、AutoRedraw プロパティというものがあります。これは
VB6 のヘルプに「継続表示属性を持つグラフィックス」にするためのものと記されています。

そしてこの「AutoRedraw プロパティ」の初期値は False であり、
これが False の状態で VB6 で描画処理を行っていく行為は、
VB.NET で CreateGraphics を用いて描画した場合に相当します。

試しに、AutoRedraw が True/False それぞれの状況でテストしてみます。

'--- VB6 ---
Option Explicit
Private Sub Form_Load()
 '継続表示属性の指定
 Picture1.AutoRedraw = True
 Picture2.AutoRedraw = False

 '背景色と背景画像
 Picture1.BackColor = vbYellow
 Set Picture1.Picture = Me.Icon
 Picture2.BackColor = vbYellow
 Set Picture2.Picture = Me.Icon
End Sub
Private Sub Command1_Click()
 Picture1.Line (1000, 10000)-(0, 0), vbRed
 Picture1.Print Timer
 Picture2.Line (1000, 10000)-(0, 0), vbRed
 Picture2.Print Timer
End Sub
Private Sub Command2_Click()
 Picture1.Refresh
 Picture2.Refresh
End Sub

上記を実行すると分かるのですが、Command1 を押すと、
Picture1/Picture2 双方に描画が行われます。
その後 Command2 を押すと、Picture2 側のみ描画内容がクリアされます。

Refresh は「強制再描画」を行うためのメソッドであり、
迂闊に呼び出すと、折角の描画内容が消えてしまいます。


ちなみに VB.NET の場合、再描画を行うために
>>> PictureBox2.Refresh()
>>> PictureBox3.Invalidate()
>>> PictureBox4.Update()
という 3 種類のメソッドがあります。詳しい説明はこちら。
https://dobon.net/vb/dotnet/control/refreshupdateinvalidate.html

CreateGraphics を使うなら、せめて Refresh ではなく、Update を使うのが良いでしょう。
ただし今回の要件では、そもそも CreateGraphics を使うべきでは無いと思います。



改めて、VB6 の時のコードに何故わざわざ Refresh があったかというと、
それが「API によって直接描画されたもの」であったからです。

VB のステートメントによって処理されたものではないために、
VB 側は描画されたことに気づくことができません。
そのため、API による描画処理を行うと、画面上へ直ちに反映されないという事象が生じます。

そこで、Refresh を呼び出して『強制再描画』を指示する必要があるわけです。

ところが Refresh を呼ぶと、先に示したように描画内容は消えてしまいます。
そこで VB6 では、AutoRedraw = True が使われています。上記の Picture1 がそれです。
これにより、そのイメージは「継続表示属性」を持つものとなります。このモードでは、
描画内容は常にメモリー上に蓄えられていますので、ここから自動的に描画内容が復元されます。

※ちなみに AutoRedraw = False の場合は、自動的に再描画はされないので、
 Paint イベントにて、再描画のための処理を用意する必要があります。


さて、VB.NET の場合はというと…こちらには AutoRedraw というプロパティは
ありませんが、それに相当するのが、今回の回答の冒頭に述べた
> PictureBox1.Image = bmp
ということになります。
この bmp が、VB6 でいうところの「継続表示属性を持つグラフィックス」にあたります。


VB.NET では、BackgroundImage が背景画像、Iamge が前景画像を意味します。
これらに画像をセットしておけば、Refresh などによる強制再描画を行っても
その内容が失われることはありません。


ここで注意すべきは、描画は「Image にセットされる Bitmap」に対して行うのであり、
「PictureBox 自体への CreateGraphics」で行うのでは無いということです。
CreateGraphics の方は、継続表示されるものでは無いため、容易に消えてしまいます。


今回提示したサンプルも、PictureBox1.CreateGraphics は使わず、
> Dim bmp As New Bitmap(PictureBox1.Width, PictureBox1.Height)
> Using g = Graphics.FromImage(bmp)
というコードになっていますよね。




>>> Private Sub ddsView(ByRef temp_dds_name As String)
>> 何故 ByRef に?
>  特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。

恐らくは、元のコードに ByRef/ByVal のいずれも書かれていなかったのでしょうね。
省略された場合、VB6 では ByRef 相当の意味となりますから。(VB.NET では、省略時は ByVal です)

ByRef にする必要があるのは、その引数が結果を返すための出力引数である場合だけです。
今回は出力引数にする理由が無いので、VB6 であっても、ByRef にしなければならない積極的な理由は無いはずです。

細かいことを言えば、引数がユーザー定義型または配列であった場合、VB6 においては ByVal を選択できないという
制限があるのですが、VB.NET においてはそれは当てはまりませんし、そもそも今回は配列等ではなく
String 型なのですから、ByVal にしておくのが適切であるということです。


>>> Dim LocalMemoryBMP As Long
>> 何故 Long 型に?
>  特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。

それは本当ですか?
もし本当なら、VB6 当時の宣言を教えて頂けるとありがたいです。

VB.NET にとっての Long は、8バイト(64bit)長の符号付き整数型(Int64 構造体)ですよね。

一方、VB6 で用意されている整数型は
 Byte: 1バイト長の符号無し整数型
 Integer: 2バイト長の符号付き整数型
 Long: 4バイト長の符号付き整数型
ぐらいのはずで、8 バイトな Int64 にコンバートされる理由が分からないのです。

VB6 で 8 バイトなのは、Currency と Date 型ぐらいなので、
どのような型から As Long になったのか興味があります。

これが VBA7 であれば、
 LongLong:8 バイト長の符号付き整数型
 LongPtr:x86環境では 4 バイト、x64環境では 8 バイトの整数型
がありましたが、今回は VB6 ですし…。
引用返信 編集キー/
■85450 / inTopicNo.12)  Re[9]: VB.NETでSusieプラグインを使いたい
□投稿者/ K-1 (5回)-(2017/10/21(Sat) 18:27:16)
No85444 (魔界の仮面弁士 さん) に返信
> 2017/10/21(Sat) 03:40:17 編集(投稿者)
> 
実験していただいてありがとうございます。

> >>> Dim LocalMemoryBMP As Long
> >> 何故 Long 型に?
>> 特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。
> 
> それは本当ですか?
> もし本当なら、VB6 当時の宣言を教えて頂けるとありがたいです。

下記がVB6のソースです。
'宣言
Private Declare Function GetPicture Lib "ifdds.spi" (ByVal FileSTR As String, ByVal OffSet As Long, ByVal Mode As Integer, MemoryHandle As Long, InfoHandle As Long, ByVal CallBack As Long, ByVal CallBackLong As Long) As Integer

Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (dest As Any, Source As Any, ByVal length As Long)
Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As Long) As Long
Private Declare Function LocalLock Lib "kernel32" (ByVal MemHandle As Long) As Long
Private Declare Function SetDIBits Lib "gdi32" (ByVal Pic_hDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBi As Any, ByVal wUsage As Long) As Long
Private Declare Function LocalUnlock Lib "kernel32" (ByVal MemHandle As Long) As Long

'本体
Private Sub ViewDds(filename As String)
Dim BitMapMemoryHandle As Long
Dim BitMapInf As Long
Dim LocalMemoryBMP As Long
Dim LocalMemoryInf As Long
Dim BitMapHeader As BITMAPINFOHEADER
Dim ret As Integer

ret = GetPicture(filename, 0, 0, BitMapMemoryHandle, BitMapInf, 0, 0) '画像の展開

LocalMemoryBMP = LocalLock(BitMapMemoryHandle) 'メモリのロック
LocalMemoryInf = LocalLock(BitMapInf)

MoveMemory BitMapHeader, ByVal LocalMemoryBMP, Len(BitMapHeader) 'メモリ移動
Picture1.Width = BitMapHeader.biWidth 'ピクチャーボックスおよびフォームの大きさを整える
Picture1.Height = BitMapHeader.biHeight

SetDIBits Picture1.hDC, Picture1.Image, 0, Picture1.Height, ByVal LocalMemoryInf, ByVal LocalMemoryBMP, 0 'ビットマップ表示
Picture1.Refresh 'ピクチャーボックス更新

LocalUnlock BitMapMemoryHandle 'メモリロック解除
LocalUnlock BitMapInf

LocalFree BitMapMemoryHandle 'メモリ開放
LocalFree BitMapInf
End Sub
これをVB2005のコンバータで変換して使用しています。
少なくともVB6では動作していました。

宣言の部分は「As Any」を型名に修正したくらいです。
本体は「Picture1.hDC」の部分をHDC取得処理を自分で付加しました。

引用返信 編集キー/
■85454 / inTopicNo.13)  Re[10]: VB.NETでSusieプラグインを使いたい
□投稿者/ 魔界の仮面弁士 (1436回)-(2017/10/22(Sun) 23:18:47)
No85450 (K-1 さん) に返信
>>>>> Dim LocalMemoryBMP As Long
>>>> 何故 Long 型に?
>>> 特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。
>> それは本当ですか?
>> もし本当なら、VB6 当時の宣言を教えて頂けるとありがたいです。
> 下記がVB6のソースです。
> Dim LocalMemoryBMP As Long

VB2005 は、VB6 の Long を Integer に置き換えるはずです。
VB2005 が、VB6 の Long を Long に置き換えることが、本当にあるのでしょうか?

実際に vbp ファイルごとコンバーターにかけてみましたが、
 Dim LocalMemoryBMP As Integer
にしか変換されなかったのですが…。

恐らくは、そちらの勘違いだと思いますので、念のため、
もう一度変換しなおしてみては頂けないでしょうか。


>>>Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer
>> 何故 Integer 型に?
> 特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。

私が言っているのは、
 『引数を IntPtr に直したのに、なぜ戻り値は Integer のままにしておいたのか』
という意味です。


No85450 (VB6) のオリジナルが
> Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As Long) As Long
だったのであれば、VB2005 による変換結果は
Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As Integer) As Integer
になっていたはずです。実際 No85414 のコードもそうなっていましたよね。

そしてこの段階では、VB6 の Long が .NET で Integer に置き換えられていたわけです。

それを No85433 の段階で『上記のように修正したところ』として、
> Private Declare Function LocalUnlock Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer
に変更したのですから、「変換したときの状態のまま」とは言いきるのは不自然というものでしょう。

確かに、戻り値が As Integer であるという点にだけ着目すれば、
「変換したときの状態のまま」であるとも言えなくは無いのですが、だとしても
先に説明したとおり、『関数が失敗したときは、引数の値がそのまま返却される仕様』なのですから、
この API の引数とその戻り値は、常に同じ型となるべきです。

そして何より、この変数は
>>>>> LocalMemoryBMP = LocalLock(BitMapMemoryHandle) 'メモリのロック
という使われ方をしているのですから、ますますもって型の不一致さが不自然に見えます。

LocalLock の引数を、(本来は IntPtr なのに) Integer のままにしていたのはまだ目を瞑るとしても、
それを受け取る LocalMemoryBMP の型を Long にしておく理由にはならないと思うのですが…。



>>>>> ByVal CallBack As IntPtr
>>>> 本来は ProgressCallback への Delegate にするべきではありますが、
>>>> どうせ使わないのなら IntPtr でも良いですね。
>>> 特に理由はありません。VB6のコードをVB2005のコンバータでVB.NETに変換したときの状態のままです。

『VB2005のコンバータでVB.NETに変換したときの状態のまま』ではないですよね?

VB.NET 2005 のコンバーター(アップグレードウィザード)は
IntPtr 型を使ったコードを生成しないはずです。
As OLE_HANDLE でさえ、As Integer に置き換わります。

No85433 で『上記のように修正したところ』と称する前の、最初の
No85414 の段階では IntPtr ではなく Integer だったわけですし…。


それはさておき、spi ファイルによっては、コールバックを指定する引数を
0 (あるいは IntPtr.Zero とか Nothing とか)を指定することは安全ではありません。

ifdds.spi の場合は大丈夫なのかも知れませんが、他の spi を使う場合には、
AddressOf なりラムダ式なりを渡しておいたほうが安全です。
http://home.netyou.jp/cc/susumu/progSusie.html#case9



> 宣言の部分は「As Any」を型名に修正したくらいです。

.NET で Any は使えませんものね。

複数の型を渡すことがある場合には、Any を使う代わりに、API 宣言をオーバーロードしておけば OK です。
不特定の型を渡すとなると、IntPtr を使った方がスマートになることもありますけれども。

単一型しか渡さない場合は、そもそも Any にする必要も無いので、API 宣言部を、
呼び出し時に渡すべきデータ型に書き換えましょう。(元の VB6 コード側も然り)



>> Picture1.Height = VB6.TwipsToPixelsY(BitMapHeader.biHeight)
> Picture1.Height = BitMapHeader.biHeight

この辺も、ちょっと怪しいですね。

VB6.PixelsToTwipsY や VB6.TwipsToPixelsY が必要なのか否か
きちんと確認しておいたほうが良いと思いますよ。

VB.NET では、PictureBox の Height プロパティはピクセル単位系ですが、
VB6 当時はそうであるとは限りません(既定では Twips 単位系です)。

VB6 当時、PictureBox の「親コントロール」の ScaleMode プロパティが
何に設定されていたのか、もう一度確認してみてください。
それにより、VB6 の PictureBox の Height プロパティの単位系が異なってきます。

ScaleMode プロパティは、既定では 1 (vbTwips) ですが、
もしここが、3(vbPixels) に変更されていたなら、ピクセル単位系として
扱うべきなので、Twips 単位系との相互変換はそもそも不要という事になります。


……ところで、BITMAPHEADER の biHeight メンバーが返す値は、
必ずしもプラスの値とは限らず、画像ファイルの内容によっては、
マイナス値になるものとプラス値になるものがあったと思うのですが、
*.dds ファイルの場合はどうなのでしょうね。




No85414
> SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, VB6.PixelsToTwipsY(Picture1.Height), LocalMemoryInf, LocalMemoryBMP, 0) 'ビットマップ表示

No85433
> ret = SetDIBits(hDC, CInt(CObj(Picture1.Image)), 0, Picture1.Height, LocalMemoryBMP, LocalMemoryInf, 0) 'ビットマップ表示

No85450
> SetDIBits Picture1.hDC, Picture1.Image, 0, Picture1.Height, ByVal LocalMemoryInf, ByVal LocalMemoryBMP, 0 'ビットマップ表示

ここら辺は、そもそも VB6 時点のコードが間違っていますね。
VB6 時点で動作していたように見えるのは、二重に間違えていたためです。


No85427 で Azulean さんが指摘されているとおり、SetDIBits API の仕様としては、
第 5 引数が画素情報で、第 6 引数が BITMAPINFO 構造体となっています。

その意味においては、LocalMemoryInf, LocalMemoryBMP ではなく、
LocalMemoryBMP, LocalMemoryInf の順で渡されるべきと言えます。
しかし元の VB6 コードは、LocalMemoryInf, LocalMemoryBMP の順で渡していますよね?

なぜそれで動いていたのかと言うと、その前の段階で取り違えを起こしていたからです。

> ret = GetPicture(filename, 0, 0, BitMapMemoryHandle, BitMapInf, 0, 0) '画像の展開
> LocalMemoryBMP = LocalLock(BitMapMemoryHandle) 'メモリのロック
> LocalMemoryInf = LocalLock(BitMapInf)

GetPicture API に対して、BitMapMemoryHandle, BitMapInf の順で渡していますが、
第 4 引数からは BITMAPINFO 構造体、第 5 引数からは画素情報が得られる仕様です。
ですから本来ここは BitMapInf, BitMapMemoryHandle の順で指定せねばならなかったのです。

さらに言えば、SetDIBits の呼び出し部についても、第 2 引数には
「Picture1.Image」ではなく、「Picture1.Image.Handle」が指定されるべきです。
この点については、 No85444 で既に指摘済みですね。


いずれにせよ、この部分は大幅に見直しが必要です。
VB6 当時の Picture1.Image と VB.NET の PictureBox1.Image の違いについては、先の回答で述べたとおり。


ここの SetDIBits は、PictureBox に描画しているわけではなく、
PictureBox の Image で示されているビットマップを書き換えるために使われています。

LocalMemoryInf と LocalMemoryBMP の色情報は、hDC に依存した色情報に直されるので、
この方法でセットされるビットマップは、Susie Plug-in から返される画像情報とは
もしかしたら、多少異なったものになるかも知れません。(確認はしていませんが)


それはさておき:

もしも SetDIBits を引き続き使うつもりなのであれば、
API 宣言側を「ByVal hBitmap As IntPtr」に直している以上、
呼び出し時に Integer 値を渡してはまずいわけです。

一応、Option Strict Off のままであれば、IntPtr な引数に Integer 値を渡せてしまいますが、
それに目を瞑ったとしても、そもそも Picture1.Image を基点に考えてはいけません。

No85444 でも書いていますが、VB6 の PictureBox とは異なり、.NET の Image は
自動的に Bitmap を作ってはくれません。渡すべきは Picture1.Image では無いのです。

SetDIBits に渡すべき HBITMAP 値を得たいなら、先にも述べたとおり、
まずは Bitmap クラスを New して、そこから GetHbitmap してください。

それを SetDIBits に渡せば、それで完成です。
あとはその Bitmap を Save するなり、PictureBox に表示するなりしましょう。


もしくは VB6 版で使っていた SetDIBits を使う手法をやめてしまい、
代わりに No85432 で通りすがりさんが紹介されていた手法を使うかですね。

あのコードの場合は、まずは GetPictureInfo API で画像の幅・高さ・色深度を取得し、
それを使って New Bitmap(幅, 高, PixelFormat) を呼び出す手順となっています。
画素情報を得るための GetPicture API を呼ぶのはその後。

この変換方法だと、SetDIBits の場合のように、hDC の色情報に変換されること無く、
本来のフォーマットに近い形で画像を生成できるかと思います。

New Bitmap した後は、Bitmap の LockBits メソッドで BitmapData クラスを取得し、
その Scan0 プロパティに対して画素情報を書き込んでいけば完成です。


どちらの方法を取るかはお好みで。
引用返信 編集キー/
■85455 / inTopicNo.14)  Re[11]: VB.NETでSusieプラグインを使いたい
□投稿者/ K-1 (6回)-(2017/10/23(Mon) 11:40:22)
No85454 (魔界の仮面弁士 さん) に返信
実験までしていただいて恐縮です。

> 『VB2005のコンバータでVB.NETに変換したときの状態のまま』ではないですよね?
 再度変換してみたところ、たしかにそうでした。

> VB6.PixelsToTwipsY や VB6.TwipsToPixelsY が必要なのか否か
> きちんと確認しておいたほうが良いと思いますよ。
 このへんも含め、動かないのであちこち修正してたのでその残滓が残っちゃっていたのでしょう。
 お騒がせしました。


> ここら辺は、そもそも VB6 時点のコードが間違っていますね。
> VB6 時点で動作していたように見えるのは、二重に間違えていたためです。
 なるほど。元ネタが間違っていたのですね。


それはともかく。
実際のところ必ずしもSetDIBitsにこだわりはありません。Susieプラグインで画像を展開したいだけですので。
No85432の通りすがりさんが紹介されていたコードも見てみましたが、C#のコードはいまいちわからず、関数をどう使えばいいのか見当がつかないので解析もままなりません。申し訳ないです。


> あのコードの場合は、まずは GetPictureInfo API で画像の幅・高さ・色深度を取得し、
> それを使って New Bitmap(幅, 高, PixelFormat) を呼び出す手順となっています。
> 画素情報を得るための GetPicture API を呼ぶのはその後。
> 
> この変換方法だと、SetDIBits の場合のように、hDC の色情報に変換されること無く、
> 本来のフォーマットに近い形で画像を生成できるかと思います。
> 
> New Bitmap した後は、Bitmap の LockBits メソッドで BitmapData クラスを取得し、
> その Scan0 プロパティに対して画素情報を書き込んでいけば完成です。

 ここらへんの具体的な解説をご教授おねがいできないでしょうか。
 後者の方法ですと、

    Private Sub Viewdds(ByVal temp_dds_name As String)
        Dim ret As Integer
        Dim BitMapMemoryHandle As Long
        Dim BitMapInf As Long
        Dim LocalMemoryBMP As Long
        Dim LocalMemoryInf As Long
        Dim BitMapHeader As BITMAPINFOHEADER

        ret = GetPicture(temp_dds_name, 0, 0, BitMapMemoryHandle, BitMapInf, 0, 0) '画像の展開

        LocalMemoryBMP = LocalLock(BitMapMemoryHandle) 'メモリのロック
        LocalMemoryInf = LocalLock(BitMapInf)
        MoveMemory(BitMapHeader, LocalMemoryBMP, Len(BitMapHeader)) 'メモリ移動

        Dim bmp As New Bitmap(BitMapHeader.biWidth, BitMapHeader.biHeight, System.Drawing.Imaging.PixelFormat.Format8bppIndexed)

        'BitmapDataクラスを取得しScan0を使ってLocalMemoryBMPからbmpへデータをコピーする
        '----↓---??????---↓---
        'BitmapData クラスを取得
        Dim _img As BitmapData = Nothing
        _img = bmp.LockBits(New Rectangle(0, 0, BitMapHeader.biWidth, BitMapHeader.biHeight), _
            System.Drawing.Imaging.ImageLockMode.WriteOnly, _
            System.Drawing.Imaging.PixelFormat.Format8bppIndexed)


        Dim pPtr As IntPtr
        Dim nStride As IntPtr
        pPtr = _img.Scan0
        nStride = _img.Stride

        Dim x, y As Long
        Dim pLine As IntPtr
        Dim color_data As Long = LocalMemoryBMP
        For y = 0 To BitMapHeader.biHeight - 1
            pLine = pPtr + y * nStride
            For x = 0 To BitMapHeader.biWidth - 1
                pLine = color_data
                pLine = pLine + 1
            Next x
        Next y
        '----↑---??????---↑---

        bmp.UnlockBits(_img)

        LocalUnlock(BitMapMemoryHandle) 'メモリロック解除
        LocalUnlock(BitMapInf)

        LocalFree(BitMapMemoryHandle) 'メモリ開放
        LocalFree(BitMapInf)

        '作成した画像を表示する
        Picture1.Image = bmp
    End Sub
くらいまでは想像つくのですが、途中の「BitmapDataクラスを取得しScan0を使ってLocalMemoryBMPからbmpへデータをコピー」という部分がピンとこなくて。
お手数おかけして申し訳ありません。

引用返信 編集キー/
■85456 / inTopicNo.15)  Re[12]: VB.NETでSusieプラグインを使いたい
□投稿者/ 魔界の仮面弁士 (1437回)-(2017/10/23(Mon) 12:27:16)
2017/10/23(Mon) 12:28:03 編集(投稿者)

No85455 (K-1 さん) に返信
> 実際のところ必ずしもSetDIBitsにこだわりはありません。Susieプラグインで画像を展開したいだけですので。

もう一つ別案として。

GetPicture API からは、「BITMAPINFO 構造体」と「ビットマップデータ」が
得られるようになっていますよね。

ということは、後はそれに BITMAPFILEHEADER 構造体さえ付与してしまえば、
そのデータを、*.BMP ファイル形式のビットマップとして扱うことができると思います。
http://www5d.biglobe.ne.jp/~noocyte/Programming/Windows/BmpFileFormat.html

BITMAPFILEHEADER さえ自作できるのなら、それらを並べて
ファイルとしてそのまま出力すれば、PictureBox1.ImageLocation を通じて
画面に表示することができます。

あるいはファイルではなく MemoryStream に蓄えておき、
Image.FromStream メソッドで得た画像を
PictureBox1.Image にセットしても良いでしょう。


>>あのコードの場合は、まずは GetPictureInfo API で画像の幅・高さ・色深度を取得し、
さらにこの前に IsSupported API を呼んで、ifdds.spi で読み込める形式なのかどうかを
確認しておくこともできます。

'対応しているファイル形式かどうかを確認する
Private Declare Unicode Function IsSupported Lib "ifdds.spi" (file As String, dw As UInteger) As <MarshalAs(UnmanagedType.I4)> Boolean

----
'API 呼び出し後に、文字列が書き換えられる可能性に対処
'http://home.netyou.jp/cc/susumu/progSusie.html#case1
Dim sFile As String = String.Copy(ddsFile & vbNullChar)
If Not IsSupported(sFile, &H0UI) Then
  Debug.WriteLine("対応していないファイル形式です:" & ddsFile)
  Return Nothing
End If



> ここらへんの具体的な解説をご教授おねがいできないでしょうか。

アプリケーションの開始パスの下に plugins というフォルダーを作って、
その中に *.spi ファイルを配置。

No85432 の DLL を参照設定に加えた上で、
  Dim bmp As Bitmap = Nothing
  Using susie As New BxSpi.SpiInput()
    If susie.LoadFile(ddsFilePath) Then
      bmp = susie.GetFrame(0)
    End If
  End Using
としてみれば読めるのかな…と思ったのですが、何故か化けてました。あれ?
引用返信 編集キー/
■85457 / inTopicNo.16)  Re[13]: VB.NETでSusieプラグインを使いたい
□投稿者/ K-1 (7回)-(2017/10/23(Mon) 13:44:07)
No85456 (魔界の仮面弁士 さん) に返信

>アプリケーションの開始パスの下に plugins というフォルダーを作って、
>その中に *.spi ファイルを配置。
 別のDLLを使うのはいまひとつスマートじゃないですし、ちょっと仕様的というかセキュリティ的というかマズイんです。

 ヘッダ情報を組み立てていきなりファイル出力もいいんですが、
 将来的に画像に加工を加える処理を追加する可能性があるので、なるべくメモリ内で処理してしまいたいのです。
 いままでご教授いただいた方法ですと、
「BitmapDataクラスを取得しScan0を使ってLocalMemoryBMPからbmpへデータをコピー。Bitmapを得る」
 というのが、一番希望的にバッチグーなんです。
 これなら最終的に得たBitmapに加工を加えてピクチャーボックスに表示したりがしやすそうですし、
 内部動作的にも納得がいくというか、流れがわかりやすいというか。

 よろしくお願いいたします。

引用返信 編集キー/
■85458 / inTopicNo.17)  Re[14]: VB.NETでSusieプラグインを使いたい
□投稿者/ 魔界の仮面弁士 (1438回)-(2017/10/23(Mon) 15:25:25)
2017/10/23(Mon) 16:35:29 編集(投稿者)

No85457 (K-1 さん) に返信
> 別のDLLを使うのはいまひとつスマートじゃないですし、ちょっと仕様的というかセキュリティ的というかマズイんです。

nuget で "dds" を検索すると、CSharpImageLibrary などが
ヒットしましたが、それも駄目なんですね。
(とはいえ、spi も DLL なのでは…)

dds のための追加のライブラリを導入できないなら、
.NET Framework 標準ということで
System.Windows.Media.Imaging.BitmapDecoder.Create あたりを
使うと、もしかしたら読めるかもしれません。
対応しているコーデックは多くなく、下記 URL によれば
 DXGI_FORMAT_BC1_UNORM
 DXGI_FORMAT_BC2_UNORM
 DXGI_FORMAT_BC3_UNORM
程度のようですし、Win10 と Win7 で成否が変わってきたりもしたので、
今回の要件を満たせるかどうかは分かりませんが。
https://msdn.microsoft.com/en-us/library/windows/desktop/dn424129.aspx
http://bbs.wankuma.com/index.cgi?mode=al2&namber=22767&KLOG=44


> 将来的に画像に加工を加える処理を追加する可能性があるので、なるべくメモリ内で処理してしまいたいのです。
ここでいう「メモリ内」が何を指すのかにもよりますが、言葉通りに捕らえるなら
New MemoryStream() に対して、BinaryWriter で書き込むのはどうでしょう。

Image.FromStream に渡せば、Bitmap として読み取れますし、
MemoryStream.ToArray を使えば、それらをバイナリ(Byte の配列)で取り出せます。


で、spi 以外の DLL を使いたくないのだとしても、たとえば
No85432 のサンプルで本当に正しい Bitmap が得られるのかどうかの
【事前検証】は、VB への移植前に試しておいた方がよろしいかと。

C# のソースコードが提供されている以上、そこから VB に翻訳することは
可能だとは思いますが、そこで使われている処理手順が、
今回の .spi + .dds に通用するのかどうかを見極めておかないと、
移植しても無駄になってしまう可能性もあるわけで…。



> これなら最終的に得たBitmapに加工を加えてピクチャーボックスに表示したりがしやすそうですし、

その点に関しては、当初の SetDIBits 案でも同じだと思います。

手順的には Bitmap インスタンスを New し、GetHbitmap で HBITMAP を得て
それを SetDIBits で書き換えてから利用する流れになりますから、
目的とする Bitmap インスタンスは、画面表示やファイル出力を
行う前に得ることができるかと思います。

つまり、VB6 で言うところの
 Picture1.Height = 高さ
 Picture1.Width = 幅
 Set bmp = Picture1.Image
 hBitmap = bmp.Handle
 SetDIBits hDC, hBitmap, …
という処理が、VB.NET で言うところの
 img = New Bitmap(幅, 高さ)
 hBitmap = img.GetHbitmap()
 SetDIBits(hDC, hBitmap, …)
 DeleteObject(hBitmap)
 PictureBox1.Image = img
になると思います。多分。


> Dim bmp As New Bitmap(BitMapHeader.biWidth, BitMapHeader.biHeight, System.Drawing.Imaging.PixelFormat.Format8bppIndexed)

お使いの dds ファイルのフォーマットにもよるかと思いますが、
指定する PixelFormat は Format8bppIndexed 固定で大丈夫なのでしょうか?

No85432 のサンプルを眺めてみましたが、GetPictureInfo API を呼んで PictureInfo 構造体を取得し、
それの colorDepth フィールドが示す 画素の bit 数 に応じた処理が行われているように見えます。

その手順を VB の文法で書くと、こんな感じです。
掲示板に直接書いたものなので、検証はしていませんが。

'※info は、GetPictureInfo API で得た PictureInfo 構造体
Dim bmp As Bitmap = Nothing
Dim dataSize As Integer = info.width * info.height
Select Case info.colorDepth
 Case 32
  bmp = new Bitmap(info.width, info.height, PixelFormat.Format32bppArgb)
  dataSize *= 4
 Case 24
  bmp = new Bitmap(info.width, info.height, PixelFormat.Format24bppRgb)
  dataSize *= 3
 Case 16
  bmp = new Bitmap(info.width, info.height, PixelFormat.Format16bppRgb565)
  ' 2 倍な気もするのだけれど、元の SpiPicture.cs が
  ' 3 となっていたので、とりあえずそのままにしておく
  dataSize *= 3
 Case 8
  bmp = new Bitmap(info.width, info.height, PixelFormat.Format8bppIndexed)
  'dataSize *= 1
 Case 4
  bmp = new Bitmap(info.width, info.height, PixelFormat.Format4bppIndexed)
  dataSize \= 2
 Case Else
  bmp = new Bitmap(info.width, info.height, PixelFormat.Format1bppIndexed)
  dataSize \= 8
End Select

'カラーパレットを持つ PixelFormat 形式の場合は、
'RGBQUAD(パレット情報)も転写しておく
Dim cp = bmp.Palette
For i = 0 To cp.Entries.Length - 1
 '※bpi は、getPicture API で得た BITMAPINFO 構造体
 cp.Entries(i) = Color.FromArgb(bpi.bmiColors(i).rgbRed, bpi.bmiColors(i).rgbGreen, bpi.bmiColors(i).rgbBlue)
Next
bmp.Palette = cp

'※bmpData は getPicture で得たビットマップデータ
Dim data(Math.Max(0, dataSize - 1)) As Byte
Marshal.Copy(Marshal.ReadIntPtr(bmpData), data, 0, data.Length)

'Bitmap 本体を表す配列のデータを Scan0 に転送
Dim bitmapdata As BitmapData = bmp.LockBits(New Rectangle(New Point(), bmp.Size), ImageLockMode.ReadWrite, bmp.PixelFormat)
Marshal.Copy(data, 0, bitmapdata.Scan0, data.Length)
bmp.UnlockBits(bitmapdata)

'出力イメージを上下反転
bmp.RotateFlip(RotateFlipType.RotateNoneFlipY)

Return bmp
引用返信 編集キー/
■85459 / inTopicNo.18)  Re[15]: VB.NETでSusieプラグインを使いたい
□投稿者/ K-1 (8回)-(2017/10/23(Mon) 17:07:10)
No85458 (魔界の仮面弁士 さん) に返信

サンプルコードをありがとうございます。
とりあえず、コンパイルエラーをとるために、以下のように手直しして動かしてみました。
'---宣言
    'BITMAPINFO構造体
    Private Structure BITMAPINFO
        Dim bmiHeader As BITMAPINFOHEADER
        Dim bmiColors() As RGBQUAD
    End Structure
'---本体
    Private Sub Viewdds(ByVal temp_dds_name As String)
        Dim ret As Integer
        Dim BitMapMemoryHandle As Long
        Dim BitMapInf As Long
        Dim LocalMemoryBMP As Long
        Dim LocalMemoryInf As Long
        Dim BitMapHeader As BITMAPINFOHEADER
        Dim bpi As BITMAPINFO
        Dim info As PICTUREINFO

        ret = GetPictureInfo(temp_dds_name, temp_dds_name.Length, 0, info) '画像の展開

        ret = GetPicture(temp_dds_name, 0, 0, BitMapInf, BitMapMemoryHandle, 0, 0) '画像の展開

        LocalMemoryBMP = LocalLock(BitMapMemoryHandle) 'メモリのロック
        LocalMemoryInf = LocalLock(BitMapInf)
        MoveMemory(BitMapHeader, LocalMemoryInf, Len(BitMapHeader)) 'メモリ移動

        Dim bmp As Bitmap = Nothing
        Dim dataSize As Integer = info.width * info.height
        Select Case info.colorDepth
            Case 32
                bmp = New Bitmap(info.width, info.height, PixelFormat.Format32bppArgb)
                dataSize *= 4
            Case 24
                bmp = New Bitmap(info.width, info.height, PixelFormat.Format24bppRgb)
                dataSize *= 3
            Case 16
                bmp = New Bitmap(info.width, info.height, PixelFormat.Format16bppRgb565)
                dataSize *= 3
            Case 8
                bmp = New Bitmap(info.width, info.height, PixelFormat.Format8bppIndexed)
                'dataSize *= 1
            Case 4
                bmp = New Bitmap(info.width, info.height, PixelFormat.Format4bppIndexed)
                dataSize \= 2
            Case Else
                bmp = New Bitmap(info.width, info.height, PixelFormat.Format1bppIndexed)
                dataSize \= 8
        End Select

        'カラーパレットを持つ PixelFormat 形式の場合は、
        'RGBQUAD(パレット情報)も転写しておく
        If (info.colorDepth = 1) Or (info.colorDepth = 4) Or (info.colorDepth = 8) Then
            ReDim bpi.bmiColors(255)
            MoveMemory(bpi, LocalMemoryInf, Len(bpi)) 'メモリ移動

            Dim cp As System.Drawing.Imaging.ColorPalette
            cp = bmp.Palette
            Dim i As Long
            For i = 0 To cp.Entries.Length - 1
                '※bpi は、getPicture API で得た BITMAPINFO 構造体
                cp.Entries(i) = Color.FromArgb(bpi.bmiColors(i).rgbRed, bpi.bmiColors(i).rgbGreen, bpi.bmiColors(i).rgbBlue)
            Next
            bmp.Palette = cp
        End If

        '※bmpData は getPicture で得たビットマップデータ
        Dim data(Math.Max(0, dataSize - 1)) As Byte
        Marshal.Copy(Marshal.ReadIntPtr(LocalMemoryBMP), data, 0, data.Length)

        'Bitmap 本体を表す配列のデータを Scan0 に転送
        Dim bitmapdata As BitmapData = bmp.LockBits(New Rectangle(New Point(), bmp.Size), ImageLockMode.ReadWrite, bmp.PixelFormat)
        Marshal.Copy(data, 0, bitmapdata.Scan0, data.Length)
        bmp.UnlockBits(bitmapdata)

        LocalUnlock(BitMapMemoryHandle) 'メモリロック解除
        LocalUnlock(BitMapInf)

        LocalFree(BitMapMemoryHandle) 'メモリ開放
        LocalFree(BitMapInf)

        '出力イメージを上下反転
        bmp.RotateFlip(RotateFlipType.RotateNoneFlipY)

        '作成した画像を表示する
        Picture1.Image = bmp
    End Sub

「Marshal.Copy(Marshal.ReadIntPtr(LocalMemoryBMP), data, 0, data.Length)」
ここで「保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます。」エラーとなってしまいました。
data.Lengthの値は「&H100000」でした。


> nuget で "dds" を検索すると、CSharpImageLibrary などが
> ヒットしましたが、それも駄目なんですね。
> (とはいえ、spi も DLL なのでは…)
 うう、そうなんですが、いろいろ事情が・・・

> その点に関しては、当初の SetDIBits 案でも同じだと思います。
 先に書いたSetDIBitsでは動作せず「CInt(CObj(Picture1.Image))」が問題ありそうと、いろいろ教えていただいたものの、どう修正していいものやら分からなかったもので。面目ありません。
一応、下記のようにつくってはみたのですが、結局ダメでした。
        Using gp As Graphics = Picture1.CreateGraphics  'フォームのGraphicsを作成
            Dim hDC As IntPtr = gp.GetHdc()         'そのデバイスコンテキストを取得

            Dim bmp As New Bitmap(BitMapHeader.biWidth, BitMapHeader.biHeight, System.Drawing.Imaging.PixelFormat.Format8bppIndexed)

            Dim _bmp As System.IntPtr
            _bmp = bmp.GetHbitmap

            ret = SetDIBits(hDC, _bmp, 0, Picture1.Height, LocalMemoryBMP, LocalMemoryInf, 0) 'ビットマップ表示

            Picture1.Image = Image.FromHbitmap(_bmp)

            Picture1.Refresh() 'ピクチャーボックス更新

            gp.ReleaseHdc()
        End Using

引用返信 編集キー/
■85460 / inTopicNo.19)  Re[16]: VB.NETでSusieプラグインを使いたい
□投稿者/ 魔界の仮面弁士 (1439回)-(2017/10/23(Mon) 20:06:56)
2017/10/23(Mon) 20:20:34 編集(投稿者)

No85459 (K-1 さん) に返信
> 'BITMAPINFO構造体
> Private Structure BITMAPINFO
>  Dim bmiHeader As BITMAPINFOHEADER
>  Dim bmiColors() As RGBQUAD
> End Structure
> ReDim bpi.bmiColors(255)
> MoveMemory(bpi, LocalMemoryInf, Len(bpi)) 'メモリ移動

可変長であることが考慮されていないように見えます。
これだとメモリアクセスエラーを引き起こしてしまうでしょう。

確かに 256 色ビットマップなら、256 個(1024 バイト)の RGBQUAD が必要ですが、
16 色ビットマップなら、RGBQUAD は 16 個(64 バイト)しか持ちません。
モノクロなら 2 個ですし、そもそもパレットを持たない
フルカラービットマップなら、RGBQUAD は 0 個です。
範囲外のエリアを転送しないようご注意ください。

なお、RGBQUADの個数は、BITMAPINFOHEADER.biBitCount が示すビット数によって定まりますが、
今回は biBitCount を元に PixelFormat を指定しているので、カラーパレットの個数も
 colorDepth = 8 (Format8bppIndexed)なら、bmp.Palette.Entries.Length = 256
 colorDepth = 4 (Format4bppIndexed)なら、bmp.Palette.Entries.Length = 16
 colorDepth = 2 (Format1bppIndexed)なら、bmp.Palette.Entries.Length = 2
になっているはずです。
PixelFormat が "Indexed" で無いもの、すなわち colorDepth が 16, 24, 32 の場合は
パレットを持たないため、bmp.Palette.Entries.Length は 0 を返します。


> Dim BitMapInf As Long
だから何で Long にするんですか……。orz
(Option Strict On を宣言していますか?)

*.spi (32bit Susie Plug-in) のために、Long 型が必要になることはありません。
*.sph (64bit Susie Plug-in) だったとしても、Long の出番はほぼ無いでしょう。


> ret = GetPictureInfo(temp_dds_name, temp_dds_name.Length, 0, info) '画像の展開

モード 000 (ファイルインターフェイス)指定と、
モード 001 (メモリインターフェイス)指定が
ごっちゃになっているようです。

ret = GetPictureInfo(画像ファイルのパス, 読み込み開始位置, 0, 出力先のPictureInfo)
ret = GetPictureInfo(画像バイナリのポインタ, バイナリのデータ長, 1, 出力先のPictureInfo)

どちらの方式でも呼び出せますので、お好きなほうでどうぞ。
両方を使い分けたいのなら、オーバーロードで宣言しておけば OK。

また、Declare 宣言での最後の引数は、出力引数となとり、 API 宣言については
 ByRef IntPtr
 ByRef PictureInfo構造体
 ByVal PictureInfoクラス
のいずれを使っても OK です。ただし PictureInfo 型の定義については、
『<StructLayout(LayoutKind.Explicit, Pack:=1)>』を付与しておく必要があります。
既定の Pack サイズだと、hInfo のアライメントがずれてしまいますので。

そしてその hInfo メンバーですが、呼び出し後の解放処理も必要なのでお忘れなく。
hInfo に 0 以外の値がセットされてきた場合、それを解放するのは呼び出し側の責任です。
ifdds.spi が hInfo をセットするのかどうかは調べていませんが、
一応、対処しておいた方が無難でしょう。


> If (info.colorDepth = 1) Or (info.colorDepth = 4) Or (info.colorDepth = 8) Then
Or ではなく、OrElse を使いましょう。本題とは無関係ですが。


>  Dim i As Long
>  For i = 0 To cp.Entries.Length - 1
VB.NET 2002 までは、ループカウンタを For の外で宣言する必要がありましたが、
VB.NET 2003 以降では、
 For i As Integer = 0 To cp.Entries.Length - 1
のように記述するべきです。なお、VB2008 以降では型推論が使えるため、
 For i = 0 To cp.Entries.Length - 1
という表記にするのが一般的です。


> Using gp As Graphics = Picture1.CreateGraphics 'フォームのGraphicsを作成
>  Dim hDC As IntPtr = gp.GetHdc() 'そのデバイスコンテキストを取得
Graphics.FromImage(bmp) から GetHdc した方が良いんじゃないかな…。(自信無し)


> Picture1.Image = Image.FromHbitmap(_bmp)
ここは
 Picture1.Image = bmp
ですね。
SetDIBits を呼び終わった段階で、_bmp は DeleteObject してください。


> Picture1.Refresh() 'ピクチャーボックス更新
これは不要かと思います。
引用返信 編集キー/
■85461 / inTopicNo.20)  Re[17]: VB.NETでSusieプラグインを使いたい
 
□投稿者/ K-1 (9回)-(2017/10/23(Mon) 22:07:02)
No85460 (魔界の仮面弁士 さん) に返信

>>Dim BitMapInf As Long
> だから何で Long にするんですか……。orz
> (Option Strict On を宣言していますか?)
 す、すみません。何度も。きちんと「Option Strict On」宣言も先頭に入れました;

教えていただいたことを踏まえて、再度コードを整理しました。
'---宣言
	Private Declare Function GetPicture Lib "ifdds.spi" (ByVal FileSTR As String, ByVal OffSet As Integer, ByVal Mode As Integer, ByRef MemoryHandle As IntPtr, ByRef InfoHandle As IntPtr, ByVal CallBack As IntPtr, ByVal CallBackLong As IntPtr) As Integer

	Private Declare Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (ByRef Dest As BITMAPINFOHEADER, ByVal Source As IntPtr, ByVal length As Integer)

	Private Declare Function LocalFree Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer
	Private Declare Function LocalLock Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer

	Private Declare Function SetDIBits Lib "gdi32" (ByVal Pic_hDC As IntPtr, ByVal hBitmap As IntPtr, ByVal nStartScan As Integer, ByVal nNumScans As Integer, ByVal lpBits As IntPtr, ByVal lpBi As IntPtr, ByVal wUsage As Integer) As Integer

	Private Declare Function LocalUnlock Lib "kernel32" (ByVal MemHandle As IntPtr) As Integer

'---本体
	Private Sub Viwedds(ByVal temp_dds_name As String)
		Dim BitMapMemoryHandle As IntPtr
		Dim BitMapInf As IntPtr
		Dim LocalMemoryBMP As IntPtr
		Dim LocalMemoryInf As IntPtr
		Dim BitMapHeader As BITMAPINFOHEADER
		Dim ret As Integer


		ret = GetPicture(temp_dds_name, 0, 0, BitMapInf, BitMapMemoryHandle, Nothing, Nothing) '画像の展開

		LocalMemoryInf = CType(LocalLock(BitMapInf), IntPtr)
		LocalMemoryBMP = CType(LocalLock(BitMapMemoryHandle), IntPtr) 'メモリのロック

		MoveMemory(BitMapHeader, LocalMemoryInf, Len(BitMapHeader)) 'メモリ移動
		Picture1.Width = BitMapHeader.biWidth 'ピクチャーボックスおよびフォームの大きさを整える
		Picture1.Height = BitMapHeader.biHeight

		Using gp As Graphics = Picture1.CreateGraphics  'フォームのGraphicsを作成
			Dim hDC As IntPtr = gp.GetHdc()         'そのデバイスコンテキストを取得

			Dim bmp As New Bitmap(BitMapHeader.biWidth, BitMapHeader.biHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb)

			Dim _bmp As System.IntPtr
			_bmp = bmp.GetHbitmap

			'Graphicsのデバイスコンテキストを取得
			'Dim g As Graphics = Graphics.FromImage(bmp)
			'Dim hDC As IntPtr = g.GetHdc()

			ret = SetDIBits(hDC, _bmp, 0, Picture1.Height, LocalMemoryInf, LocalMemoryBMP, 0) 'ビットマップ表示

			Picture1.Image = bmp

			gp.ReleaseHdc()
			'g.ReleaseHdc()

			'DeleteObject(_bmp)
		End Using

		LocalUnlock(BitMapMemoryHandle) 'メモリロック解除
		LocalUnlock(BitMapInf)

		LocalFree(BitMapMemoryHandle) 'メモリ開放
		LocalFree(BitMapInf)
	End Sub

「DeleteObject(_bmp)」は「'DeleteObject' は宣言されていません。アクセスできない保護レベルになっています。」になってしまうので、
とりあえずコメントにしておきました。

結果は真っ黒な画面が表示されるだけでした。


>>Using gp As Graphics = Picture1.CreateGraphics  'フォームのGraphicsを作成
>> Dim hDC As IntPtr = gp.GetHdc()         'そのデバイスコンテキストを取得
> Graphics.FromImage(bmp) から GetHdc した方が良いんじゃないかな…。(自信無し)
「Picture1.CreateGraphics」をnew bmpの後、SetDIBitsの前に行ってみましたが、結果は変わらず。

変数「BitMapHeader」をみると、それらしい値は入っているようなんですが・・・

引用返信 編集キー/

このトピックをツリーで一括表示

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -