GDI+で作成するサムネイルの画質を上げるには?

解決


やたこ  2008-03-18 10:35:56  No: 139303  IP: 192.*.*.*

いつも質問させていただいて、ありがとうございます。
以前に写真を縮小する方法として、
GDI+ の GdipGetImageThumbnail の使い方を教えていただきました。

こちらを用いて、サムネイルを作成し、JPEG として保存しているのですが、
JPEG の保存時には、Quality は 100 に設定しても、
画質がよくありません。
Paint で同サイズに縮小した画像よりも明らかに画質が劣ります。

画質を改善する方法がありましたら、教えていただけないでしょうか。

' サムネイル作成のプロシージャ
Sub MakeThumbnail(ByVal SrcFileName As String, ByVal DstFileName As String _
                 , Optional ByVal MaxWidth As Integer = DefaultMaxWidth _
                 , Optional ByVal MaxHeight As Integer = DefaultMaxHeight _
                 , Optional ByVal JpegQuality As Integer = DefaultJpegQuality)
    Dim GdiPStartupInput    As GdiplusStartupInput
    Dim ret                 As GDIPlusStatusConstants
    Dim GDIPToken           As Long
    Dim GdipBmpHdl          As Long
    Dim EncodParameters     As EncoderParameters
    Dim lngImg              As Long
    Dim lngWidth            As Long
    Dim lngHeight           As Long
    Dim lngGraphics         As Long
    Dim lngBmp              As Long
    Dim lngNewBmp           As Long
    Dim dblWidthRatio       As Double
    Dim dblHeightRatio      As Double
    Dim lngNewWidth         As Long
    Dim lngNewHeight        As Long
    
    ' GDIスタートアップ構造体初期化
    GdiPStartupInput.GdiplusVersion = 1
    ' GDI+ライブラリ初期化して失敗なら終了
    If GdiplusStartup(GDIPToken, GdiPStartupInput, 0&) <> 0 Then Exit Sub
    
    ' 画像ファイルをビットマップとして読み込む
    ret = GdipCreateBitmapFromFile(StrPtr(SrcFileName), lngBmp)
    
    Do Until ret <> GDIPlusStatusConstants.Ok
    
        ' 幅、高さを取得
        If GdipGetImageWidth(lngBmp, lngWidth) <> 0 Then Exit Do
        If GdipGetImageHeight(lngBmp, lngHeight) <> 0 Then Exit Do
    
        ' 変換後画像の幅、高さを計算
        dblWidthRatio = IIf(MaxWidth / lngWidth > 1, 1, MaxWidth / lngWidth)
        dblHeightRatio = IIf(MaxHeight / lngHeight > 1, 1, MaxHeight / lngHeight)
        If dblWidthRatio < dblHeightRatio Then
            lngNewWidth = Round(lngWidth * dblWidthRatio)
            lngNewHeight = Round(lngHeight * dblWidthRatio)
        Else
            lngNewWidth = Round(lngWidth * dblHeightRatio)
            lngNewHeight = Round(lngHeight * dblHeightRatio)
        End If
    
        ' サイズ変換
        If GdipGetImageThumbnail(lngBmp, lngNewWidth, lngNewHeight, lngNewBmp, 0, 0) <> 0 Then Exit Do
    
        ' JPEG 保存
        If SavePictureJpg(lngNewBmp, DstFileName, JpegQuality) <> 0 Then Exit Do
        
        Exit Do
    Loop
    
    GdipDisposeImage lngBmp
    GdipDisposeImage lngNewBmp
    
    ' GDI+ライブラリ開放
    GdiplusShutdown GDIPToken
    
End Sub

' JPEG 保存のファンクション
Public Function SavePictureJpg(ByVal lngBmp As Long, ByVal FName As String, ByVal Quality As Long) As GDIPlusStatusConstants
    Dim GdiPStartupInput    As GdiplusStartupInput
    Dim GDIPToken           As Long
    Dim EncodParameters     As EncoderParameters
    
    ' 圧縮品質設定範囲のチェック
    If Quality > 100 Then Quality = 100
    
    ' GDIスタートアップ構造体初期化
    GdiPStartupInput.GdiplusVersion = 1
    ' GDI+ライブラリ初期化して失敗なら終了
    If GdiplusStartup(GDIPToken, GdiPStartupInput, 0&) <> 0 Then Exit Function
    
    ' エンコーダパラメータ設定
    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(lngBmp, StrPtr(FName), ConvCLSID(CLSID_JPEG), VarPtr(EncodParameters))

    ' GDI+ライブラリ開放
    GdiplusShutdown GDIPToken
End Function

編集 削除
K.J.K.  2008-03-18 11:19:29  No: 139304  IP: 192.*.*.*

画質を重視したいのならば、GDI+の組み込みのサムネイル生成機能を使うのではなく、
GdipSetInterpolationModeなどを使って、InterpolutionModeをHighQualityBicubic
にしたGraphicsオブジェクトに元画像を描画し、それを保存することになるでしょう。

GdipCreateBitmapFromGraphicsかGdipCreateBitmapFromScan0やGdipCreateBitmapFromGdiDib
で保存用の元になるBitmapを作り、そこからGdipGetImageGraphicsContext
でGraphicsを得て、そこに対してGdipDrawImage系のいずれかで元画像を描画し、
最初に作ったBitmapを保存する、という手順になるでしょう。

編集 削除
やたこ  2008-03-18 15:51:36  No: 139305  IP: 192.*.*.*

K.J.K 様、ありがとうございます。

いただいた情報を元に以下などを見つけて、修正しているのですが・・・
http://madia.world.coocan.jp/cgi-bin/VBBBS2/wwwlng.cgi?print+200706/07060013.txt
Graphics を作るところが良く分かりません。

1.GdipLoadImageFromFile  でファイルからイメージ lngImg を作成
2. GdipGetImageGraphicsContext で lngImg からグラフィックス g を作成
3. GdipDrawImageRectI で g から lngNewImg を作成
4. GdipSaveImageToFile で lngNewImg をファイルに保存

で、できるのかと思うのですが、あっていますか?
2〜3のところで、gに対して、GdipSetInterpolationMode すればよいのかと。
ただ、2 の部分が良く分かりません。
Graphics の作成には、
ピクチャーボックスの hdc を使っているものが見つかりますが、
当方の環境はAccessのためピクチャーボックスがなく、
また、メモリ上で(画面に表示せず)操作をしたいので、
メモリの hdc を使うのかと思ったのですが、どうもうまくいかず・・・

もう少し教えていただけないでしょうか。

編集 削除
K.J.K.  2008-03-18 17:27:42  No: 139306  IP: 192.*.*.*

GDI+でもGDIでも大体手順は同じです。
GDI+のBitmapとGDIのBitmapを対応させ、GDI+のGraphicsとGDIのDCを対応させて考えてみては。

1,GdipLoadImageFromFileで、ファイルからイメージpImageSourceを作成。
2,GdipCreateFromHWNDで、グラフィックスpGraphicsDesktopを作成。
(HWND=0で多分OKかと。もしダメならばGetDesktopWindowの戻り値。)
3,GdipCreateBitmapFromGraphicsで、pGraphicsDesktopを元にしたイメージpImageThumbnailを作成。
(pGraphicsDesktopはここで解放。)
4,GdipGetImageGraphicsContextで、pImageThumbnailからグラフィクスpGraphicsThumbnailを作成。
5,GdipDrawImage系のいずれかで、pGraphicsThumbnailにpImageSourceを描画。
(pGraphicsThumbnailをここで解放。)
(pImageSourceをここで解放。)
6,GdipSaveImageToFileで、pImageThumbnailをファイルに保存。
(pImageThumbnailをここで解放。)

編集 削除
K.J.K.  2008-03-18 17:31:01  No: 139307  IP: 192.*.*.*

# 足りなかった部分を追加。
GdipSetInterpolationModeは4の後、5の前でpGraphicsThumbnailに対して行います。

編集 削除
やたこ  2008-03-19 19:21:54  No: 139308  IP: 192.*.*.*

K.J.K 様
手順を教えていただき、ありがとうございます。
教えていただいたことを参考に、
SDKを見ながらなんとか自力で勧めようとしたのですが、
分からないことだらけで、1日かけても全然進みませんでした。
SDKに書かれているAPIをVBの宣言形式に直すルールも良く分かりません。
また、DrawImage系にはいくつか種類があるのは分かりましたが、
それぞれがどのように違い、どんなことができるのか、
など、googleで調べてもほとんど分かりません。

分かりやすい参照先などご存知でしたら教えていただけないでしょうか。

編集 削除
K.J.K.  2008-03-19 20:17:31  No: 139309  IP: 192.*.*.*

GDI+の関数の記述は、こんなもんでは。直打ちしてるのでミスがあったら直して下さい。

Enum InterpolationMode
    InterpolationModeInvalid = -1,
    InterpolationModeDefault = 0&,
    InterpolationModeLowQuality = 1&,
    InterpolationModeHighQuality = 2&,
    InterpolationModeBilinear = 3&,
    InterpolationModeBicubic = 4&,
    InterpolationModeNearestNeighbor = 5&,
    InterpolationModeHighQualityBilinear = 6&,
    InterpolationModeHighQualityBicubic = 7&
End Enum

Declare Function GdipCreateFromHWND Lib "gdiplus.dll" _
(ByVal hWnd As Long, ByRef RetGpGraphics As Long) As Long

Declare Function GdipCreateBitmapFromGraphics Lib "gdiplus.dll" _
(ByVal Width As Long, ByVal Height As Long, _
 ByVal GpGraphics As Long, ByRef RetGpBitmap As Long) As Long

Declare Function GdipGetImageGraphicsContext Lib "gdiplus.dll" _
(ByVal GpImage As Long, ByRef RetGpGraphics As Long) As Long

Declare Function GdipSetInterpolationMode Lib "gdiplus.dll" _
(ByVal GpGraphics As Long, ByVal InterpolationMode As Long) As Long

Declare Function GdipDrawImageRectI Lib "gdiplus.dll" _
(ByVal GpGraphics As Long, ByVal GpImage As Long, _
 ByVal Left As Long, ByVal Top As Long, _
 ByVal Width As Long, ByVal Height As Long) As Long

編集 削除
やたこ  2008-03-20 11:20:36  No: 139310  IP: 192.*.*.*

K.J.K 様、ありがとうございます。
おかげさまで、ぐっと進みました!!
今のところ、画像を縮小することはできたのですが、
たとえば1200*1600の画像を240*320のサイズに縮小すると、
サイズは1200*1600のままで、240*320の部分以外は真っ黒になってしまいます。
おそらくGdipDrawImageRectIの使い方をきちんと
把握できていないためだとは思うのですが・・・。
どのように修正したらよいか教えていただけないでしょうか。

    ' GDIスタートアップ構造体初期化
    GdiPStartupInput.GdiplusVersion = 1
    ' GDI+ライブラリ初期化して失敗なら終了
    If GdiplusStartup(GDIPToken, GdiPStartupInput, 0&) <> 0 Then Exit Sub
    
    ' 画像ファイルをイメージとして読み込む
    ret = GdipLoadImageFromFile(StrPtr(SrcFileName), pImageSource)
    
    Do Until ret <> GDIPlusStatusConstants.Ok
    
        If GdipCreateFromHWND(0, pGraphicsDesktop) <> 0 Then Exit Do
    
        ' 幅、高さを取得
        If GdipGetImageWidth(pImageSource, lngWidth) <> 0 Then Exit Do
        If GdipGetImageHeight(pImageSource, lngHeight) <> 0 Then Exit Do
    
        If GdipCreateBitmapFromGraphics(lngWidth, lngHeight, pGraphicsDesktop, pImageThumbnail) <> 0 Then Exit Do
        If GdipGetImageGraphicsContext(pImageThumbnail, pGraphicsThumbnail) <> 0 Then Exit Do
        
        ' 変換後画像の幅、高さを計算
        dblWidthRatio = MaxWidth / lngWidth
        dblHeightRatio = MaxHeight / lngHeight
        If dblWidthRatio > 1 And dblHeightRatio > 1 Then
        ElseIf dblWidthRatio < dblHeightRatio Then
            lngWidth = MaxWidth
            lngHeight = Round(lngHeight * dblWidthRatio)
        Else
            lngWidth = Round(lngWidth * dblHeightRatio)
            lngHeight = MaxHeight
        End If
    
         ' サイズ変換
        If GdipDrawImageRectI(pGraphicsThumbnail, pImageSource, 0, 0, lngWidth, lngHeight) <> 0 Then Exit Do
        GdipDisposeImage pImageSource
        GdipDisposeImage pGraphicsThumbnail
        
        ' JPEG 保存
        If SavePictureJpg(pImageThumbnail, DstFileName, JpegQuality) <> 0 Then Exit Do
        GdipDisposeImage pImageThumbnail
        Exit Do
    Loop
        
    ' GDI+ライブラリ開放
    GdiplusShutdown GDIPToken

編集 削除
K.J.K.  2008-03-20 17:53:17  No: 139311  IP: 192.*.*.*

GdipCreateBitmapFromGraphicsを呼んでる位置はおかしいのでは。
「' 変換後画像の幅、高さを計算」のコードの後、「' サイズ変換」のコードの前で、
呼ぶべきですよね。

編集 削除
やたこ  2008-03-21 08:37:03  No: 139312  IP: 192.*.*.*

できました!!!
K.J.K様ありがとうございます!!
これで作られた写真、とてもキレイですね!
大満足です。すごく嬉しいです。
いつも教えていただいてばかりで申し訳ありません。
K.J.K様、本当にありがとうございました!!

編集 削除