開発環境は VB6SP6+WinXPSP2です。
アクティブウインドウをスクリーンキャプチャしてGDI+でファイルに保存するプログラムを作っています。
まずPNG形式のほうがうまくいったので、Jpeg形式をエンコーダーパラメータで圧縮レベルを指定するようにしました。
統合開発環境上で動作させると、ちゃんと圧縮レベルが変わっているのですが、ネイティブコードコンパイルしてexeファイルで動作させると圧縮レベルの指定が無視されて常に最大になったファイルで保存されてしまいます。
ちなみにP-Codeコンパイルしても状況は変わりませんでした。
おそらくAPI呼び出しまわりのコードのどこかが悪いのではないかと思うのですが、VBのGDI+のサンプルも日本語では魔界の仮面弁士さまのぐらいのしか見当たらなく、洋物サンプルと比較しても問題となるような部分がわかりませんでした。
どこかおかしいのでしょうか?
ここからコード
Public Enum GDIPlusStatusConstants
Ok = 0
GenericError = 1
InvalidParameter = 2
OutOfMemory = 3
ObjectBusy = 4
InsufficientBuffer = 5
NotImplemented = 6
Win32Error = 7
WrongState = 8
Aborted = 9
FileNotFound = 10
ValueOverflow = 11
AccessDenied = 12
UnknownImageFormat = 13
FontFamilyNotFound = 14
FontStyleNotFound = 15
NotTrueTypeFont = 16
UnsupportedGdiplusVersion = 17
GdiplusNotInitialized = 18
PropertyNotFound = 19
PropertyNotSupported = 20
ProfileNotFound = 21
End Enum
Private Type UUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Type GdiplusStartupInput
GdiplusVersion As Long
DebugEventCallback As Long
SuppressBackgroundThread As Long
SuppressExternalCodecs As Long
End Type
Private Type EncoderParameter
Guid As UUID
NumberOfValues As Long
Type As Long
Value As Long
End Type
Private Type EncoderParameters
Count As Long
Parameter(15) As EncoderParameter
End Type
Private Declare Function GdiplusStartup Lib "gdiplus.dll" _
(ByRef token As Long, ByRef inputBuf As GdiplusStartupInput, ByVal outputBuf As Long) As GDIPlusStatusConstants
Private Declare Sub GdiplusShutdown Lib "gdiplus.dll" (ByVal token As Long)
Private Declare Function GdipCreateBitmapFromHBITMAP Lib "gdiplus.dll" _
(ByVal hbm As Long, ByVal hpal As Long, ByRef bitmap As Long) As GDIPlusStatusConstants
Private Declare Function GdipDisposeImage Lib "gdiplus.dll" _
(ByVal Image As Long) As GDIPlusStatusConstants
Private Declare Function GdipSaveImageToFile Lib "gdiplus.dll" _
(ByVal Image As Long, ByVal filename As Long, ByRef clsidEncoder As UUID, ByVal encoderParams As Long) As GDIPlusStatusConstants
Private Declare Function CLSIDFromString Lib "ole32" _
(ByVal lpszCLSID As Long, ByRef pclsid As UUID) As Long
const CLSID_PNG = "{557CF406-1A04-11D3-9A73-0000F81EF32E}"
const CLSID_JPEG = "{557CF401-1A04-11D3-9A73-0000F81EF32E}"
const CLSID_QUALITY = "{1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB}"
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
'┃ 名称 :SavePicturePng
'┃ 説明 :指定されたピクチャーをPNG形式で保存する
'┃ 引数 [値渡]PicObj :Pictureオブジェクト
'┃ [値渡]FName :保存先ファイル名。既存のファイルは上書き
'┃ 戻値 :GDI+のステータスに関する列挙体
'┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Public Function SavePicturePng(ByVal PicObj As IPictureDisp, ByVal FName As String) As GDIPlusStatusConstants
Dim GdiPStartupInput As GdiplusStartupInput
Dim Ret As GDIPlusStatusConstants
Dim GDIPToken As Long
Dim GdipBmpHdl As Long
' ピクチャーオブジェクトが無い
If PicObj Is Nothing Then
SavePicturePng = GDIPlusStatusConstants.UnknownImageFormat
Exit Function
End If
' GDIスタートアップ構造体初期化
GdiPStartupInput.GdiplusVersion = 1
' GDI+ライブラリ初期化して失敗なら終了
If GdiplusStartup(GDIPToken, GdiPStartupInput, 0&) <> 0 Then Exit Function
' ピクチャーからGDI+BITMAPを作成
Ret = GdipCreateBitmapFromHBITMAP(PicObj.Handle, 0&, GdipBmpHdl)
' 変換成功
If Ret = GDIPlusStatusConstants.Ok Then
' PNG変換で保存
SavePicturePng = GdipSaveImageToFile(GdipBmpHdl, StrPtr(FName), ConvCLSID(CLSID_PNG), 0)
' GDI+BITMAPを廃棄
GdipDisposeImage GdipBmpHdl
End If
' GDI+ライブラリ開放
GdiplusShutdown GDIPToken
End Function
'┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
'┃ 名称 :SavePictureJpg
'┃ 説明 :指定されたピクチャーをJPEG形式で保存する
'┃ 引数 [値渡]PicObj :Pictureオブジェクト
'┃ [値渡]FName :保存先ファイル名。ファイルが存在すれば上書き
'┃ [値渡]Quality :圧縮品質設定(0〜100) 0:高圧縮低画質、100:低圧縮高画質
'┃ 戻値 :GDI+のステータスに関する列挙体
'┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Public Function SavePictureJpg(ByVal PicObj As IPictureDisp, ByVal FName As String, ByVal Quality As Byte) As GDIPlusStatusConstants
Dim GdiPStartupInput As GdiplusStartupInput
Dim Ret As GDIPlusStatusConstants
Dim GDIPToken As Long
Dim GdipBmpHdl As Long
Dim EncodParameters As EncoderParameters
' 圧縮品質設定範囲のチェック
If Quality > 100 Then Quality = 100
' ピクチャーオブジェクトが無い
If PicObj Is Nothing Then
SavePictureJpg = GDIPlusStatusConstants.UnknownImageFormat
Exit Function
End If
' GDIスタートアップ構造体初期化
GdiPStartupInput.GdiplusVersion = 1
' GDI+ライブラリ初期化して失敗なら終了
If GdiplusStartup(GDIPToken, GdiPStartupInput, 0&) <> 0 Then Exit Function
' ピクチャーからGDI+BITMAPを作成
Ret = GdipCreateBitmapFromHBITMAP(PicObj.Handle, 0&, GdipBmpHdl)
If Ret = GDIPlusStatusConstants.Ok Then
' エンコーダパラメータ設定
EncodParameters.Count = 1
With EncodParameters.Parameter(0)
.Guid = ConvCLSID(CLSID_QUALITY)
.NumberOfValues = 1
' 4=EncoderParameterValueTypeLong
.Type = 4
' 圧縮品質
.Value = VarPtr(Quality)
End With
' JPG変換で保存
SavePictureJpg = GdipSaveImageToFile(GdipBmpHdl, StrPtr(FName), ConvCLSID(CLSID_JPEG), VarPtr(EncodParameters))
' GDI+BITMAPを廃棄
GdipDisposeImage GdipBmpHdl
End If
' GDI+ライブラリ開放
GdiplusShutdown GDIPToken
End Function
'┌───────────────────────────────────────
'│ 名称 :ConvCLSID
'│ 説明 :クラスID文字列から128BitクラスID値に変換する
'│ 引数 [値渡]sGuid :クラスID文字列
'│ 戻値 :128BitクラスID値
'└───────────────────────────────────────
Private Function ConvCLSID(ByVal sGuid As String) As UUID
CLSIDFromString StrPtr(sGuid), ConvCLSID
End Function
ここまでコード
補足
このコードを組み込んでいるソフトでは、DirectInputでマウスの挙動をトレースする機能をつけています。
Implements DirectXEvent8
Private Sub DirectXEvent8_DXCallback(ByVal eventid As Long)
関係ないとは思っていますけど。
また、ピクチャーはPictureBoxではなく、スクリーンからメモリデバイスコンテキストにBitBltしたものをOleCreatePictureIndirectでPictureObjectにしたものを指定しています。
一度PictureBoxで中継しても結果は同じでした。
IDE上で実行≒P-Codeコンパイル≠ネイティブコードコンパイル だと理解していたんですが違うんでしょうか・・・
なんかレスまったくつかないので、解決してないけど、解決とします・・・
ほとんど上記のコードだけでプロジェクト作ったら、ちゃんとネイティブexeで圧縮レベル指定されたファイルが作成されました。
以上のことを踏まえると、上記のコードは問題ないようです。
サンプルコードの動作確認ぐらい自分でやれや、ということですね。
ごめんなさい。
なんにしろ、現在のプロジェクトのどこかで干渉しているということになるんですけど、理由がさっぱりです。
ま、地道にプロジェクトをぶつ切りにして自分で調べます。
聞いている内容がはなから板違い?(ノ∀`)ペチ
次からはもっとレスのつきやすいネタにしますね。
解決にチェックしときながら追加情報
>ほとんど上記のコードだけでプロジェクト作ったら、ちゃんとネイティブexeで圧縮レベル指定されたファイルが作成されました。
と書きましたが、ごめんなさい。間違えてました。
P-codeコンパイルでは圧縮レベル指定出来ましたが、
ネイティブコンパイルでは圧縮レベル指定出来ませんでした。
魔界の仮面弁士さまのコードでもダメでした。
英語圏のもふたつほど試しましたがやっぱりだめです。
もしかして、わたしのパソコンがダメなんでしょうかね。
GdipSaveImageToFile の encoderParams の渡しかたが以下のようなのと2種類あるみたいですね。
どちらも動きますが(P-codeで)、どちらのほうが適当なんでしょうか?
Private Type EncoderParameters
Count As Long
Parameter As EncoderParameter
End Type
Private Declare Function GdipSaveImageToFile Lib "gdiplus.dll" _
(ByVal Image As Long, ByVal filename As Long, ByRef clsidEncoder As UUID, ByRef encoderParams As Any) As GDIPlusStatusConstants
SavePictureJpg = GdipSaveImageToFile(GdipBmpHdl, StrPtr(FName), ConvCLSID(CLSID_JPEG), EncodParameters)
> Public Function SavePictureJpg(ByVal PicObj As IPictureDisp, ByVal FName As String, ByVal Quality As Byte) As GDIPlusStatusConstants
ByVal Quality As Byte
↓
ByVal Quality As Long
だとどうでしょう?
Σ(゜д゜lll)
やった!うまくいきました!
ということは、
GdipSaveImageToFile は、渡されたパラメータのポインタアドレスから4Byteを読み込んでるっぽいですね。
アライメントの関係で、Byte型だと1バイトしか使わないので隣の3バイトは不定データですかね。
よく考えれば妥当な動作です。
修正
Public Function SavePictureJpg(ByVal PicObj As IPictureDisp, ByVal FName As String, Optional ByVal Quality As Long=100) As GDIPlusStatusConstants
追加
If Quality < 25 Then Quality = 25 ' 圧縮品質設定範囲のチェック
IDE(P-code)実行とバイナリExeでは変数で確保される場所が違うのは知ってましたが、IDEではひょっとしたら不定データ部がクリアされてるのか、1バイトしか読み込まないように介入があるのか、まさかアライメントの仕方が違うとか・・・はないか。
少し、変数宣言の順番とアドレスの位置関係を見てみたら、IDEだと割と宣言順通りに並ぶことが多いようですが、バイナリのときは宣言順とあまり関係ないようです。
なんにしろ、APIに変数をアドレスで渡すときには要注意ということですね。
他にも注意することはいろいろありますけど・・・(;‾3‾)
最初からLong型にしとけばよかった><
わたしはLong派(?)なので、LongかByteかちょっと迷ったんです。
駄菓子菓子。
貴重な勉強になりました。
魔界の仮面弁士さま、ありがとうございました。
こういう経験を楽しいと感じるから人生はやめられないんですよね。(←?)
今度こそ解決。
ツイート | ![]() |