Jpegファイルの圧縮レベルの指定をするには

解決


紅閃光  URL  2007-08-08 03:20:43  No: 137103

開発環境は 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コンパイル≠ネイティブコードコンパイル だと理解していたんですが違うんでしょうか・・・


紅閃光  URL  2007-08-25 19:39:39  No: 137104

なんかレスまったくつかないので、解決してないけど、解決とします・・・

ほとんど上記のコードだけでプロジェクト作ったら、ちゃんとネイティブexeで圧縮レベル指定されたファイルが作成されました。

以上のことを踏まえると、上記のコードは問題ないようです。

サンプルコードの動作確認ぐらい自分でやれや、ということですね。
ごめんなさい。

なんにしろ、現在のプロジェクトのどこかで干渉しているということになるんですけど、理由がさっぱりです。
ま、地道にプロジェクトをぶつ切りにして自分で調べます。

聞いている内容がはなから板違い?(ノ∀`)ペチ

次からはもっとレスのつきやすいネタにしますね。


紅閃光  URL  2007-08-31 03:44:24  No: 137105

解決にチェックしときながら追加情報

>ほとんど上記のコードだけでプロジェクト作ったら、ちゃんとネイティブ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)


魔界の仮面弁士  2007-08-31 07:34:38  No: 137106

> 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

だとどうでしょう?


紅閃光  URL  2007-09-01 08:15:49  No: 137107

Σ(゜д゜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かちょっと迷ったんです。

駄菓子菓子。
貴重な勉強になりました。

魔界の仮面弁士さま、ありがとうございました。

こういう経験を楽しいと感じるから人生はやめられないんですよね。(←?)

今度こそ解決。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加