いつも質問させていただいて、ありがとうございます。
以前に写真を縮小する方法として、
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
画質を重視したいのならば、GDI+の組み込みのサムネイル生成機能を使うのではなく、
GdipSetInterpolationModeなどを使って、InterpolutionModeをHighQualityBicubic
にしたGraphicsオブジェクトに元画像を描画し、それを保存することになるでしょう。
GdipCreateBitmapFromGraphicsかGdipCreateBitmapFromScan0やGdipCreateBitmapFromGdiDib
で保存用の元になるBitmapを作り、そこからGdipGetImageGraphicsContext
でGraphicsを得て、そこに対してGdipDrawImage系のいずれかで元画像を描画し、
最初に作ったBitmapを保存する、という手順になるでしょう。
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 を使うのかと思ったのですが、どうもうまくいかず・・・
もう少し教えていただけないでしょうか。
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をここで解放。)
# 足りなかった部分を追加。
GdipSetInterpolationModeは4の後、5の前でpGraphicsThumbnailに対して行います。
K.J.K 様
手順を教えていただき、ありがとうございます。
教えていただいたことを参考に、
SDKを見ながらなんとか自力で勧めようとしたのですが、
分からないことだらけで、1日かけても全然進みませんでした。
SDKに書かれているAPIをVBの宣言形式に直すルールも良く分かりません。
また、DrawImage系にはいくつか種類があるのは分かりましたが、
それぞれがどのように違い、どんなことができるのか、
など、googleで調べてもほとんど分かりません。
分かりやすい参照先などご存知でしたら教えていただけないでしょうか。
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
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
GdipCreateBitmapFromGraphicsを呼んでる位置はおかしいのでは。
「' 変換後画像の幅、高さを計算」のコードの後、「' サイズ変換」のコードの前で、
呼ぶべきですよね。
できました!!!
K.J.K様ありがとうございます!!
これで作られた写真、とてもキレイですね!
大満足です。すごく嬉しいです。
いつも教えていただいてばかりで申し訳ありません。
K.J.K様、本当にありがとうございました!!