VBAで画像ファイルをサイズ変更し出力するには?

解決


やたこ  2008-03-05 08:59:05  No: 139165  IP: 192.*.*.*

Windows XP , Access 2003 で指定された画像ファイルを読み込み、
サイズの変更を行い、ファイルとして保存したいです。
現在ビットマップのファイルを読み込み、
WindowsAPI の StretchBlt を使って縮小した画像を
ウィンドウに表示することはできるのを確認しましたが、
ウィンドウではなくファイルに保存するための方法が分かりません。
http://madia.world.coocan.jp/cgi-bin/VBBBS2/wwwlng.cgi?print+200708/07080012.txt
にある紅閃光様のソースを参考に以下のように書いています。

    'デバイスコンテキストハンドル取得
    hdc = GetDC(0&)
    
    'メモリデバイスコンテキスト作成
    hSrcMem = CreateCompatibleDC(hdc)
    hDstMem = CreateCompatibleDC(hdc)
    
    hBmp = LoadImage(0, SourceFileName, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)
    hOldBmp = SelectObject(hSrcMem, hBmp)
    
    StretchBlt _
        hDstMem, _                        ←ここを hdc にしたらウィンドウに表示できました
        0, 0, 300, 300, _
        hSrcMem, _
        0, 0, 554, 787, _
        SRCCOPY
    
    
    ' GDIスタートアップ構造体初期化
    GdiPStartupInput.GdiplusVersion = 1
    ' GDI+ライブラリ初期化して失敗なら終了
    If GdiplusStartup(GDIPToken, GdiPStartupInput, 0&) <> 0 Then Exit Sub
    
    ' ピクチャーからGDI+BITMAPを作成
    Ret = GdipCreateBitmapFromHBITMAP(hDstMem, 0&, GdipBmpHdl)  ←  ここを hBmp にしたら、そのまま同じファイルを出力できました
    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(DestImageFile), ConvCLSID(CLSID_JPEG), VarPtr(EncodParameters))
        ' GDI+BITMAPを廃棄
        GdipDisposeImage GdipBmpHdl
    End If

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

編集 削除
やじゅ  2008-03-05 12:54:43  No: 139166  IP: 192.*.*.*

高度な技術を使う必要がなければ、下記リンクでもいいと思いますが
StretchBltで無いと駄目なんでしょうか? 

画像ファイル(Bmp)を拡大・縮小保存するには?
http://madia.world.coocan.jp/vb/vb_bbs2/200305/200305_03050049.html

編集 削除
もたこ  2008-03-05 13:11:17  No: 139167  IP: 192.*.*.*

アクセスのVBAなので、ピクチャーボックスがないのです。
それにフォームに表示するわけではないので、
コントロールに貼り付けて操作するよりはメモリ上で操作できてしまった方が
今後の利用においていろいろと応用も利くかと思いまして。

というわけで、引き続きアドバイスをお待ちしております。

編集 削除
K.J.K.  2008-03-05 14:24:46  No: 139168  IP: 192.*.*.*

# 回答ではありません。

そのような用途ならば、
http://www.imagemagick.org/script/index.php

ImageMagick COM+ Object
辺りを使うほうが無難では。

編集 削除
やたこ  2008-03-05 15:49:38  No: 139169  IP: 192.*.*.*

返事をいただきありがとうございます!!

ImageMagick は過去に使った経験があります。
無難なのですが、別環境でこのアクセスプログラムを使う場合、
別途 ImageMagick をインストールする必要がありますよね。
そのあたりがわずらわしくなるため、できれば WindowsAPI で
やってしまいたいのです。
多分、ビットマップとメモリのコンテキストデバイスを
うまく関係付けられればいいと思うんですが・・・。

COM+ Object については知らないので調べてみます。

編集 削除
熊谷隆史  2008-03-05 16:13:01  No: 139170  IP: 192.*.*.*

> ウィンドウに表示することはできるのを確認しましたが、
> ウィンドウではなくファイルに保存するための方法が分かりません。

jpegファイルでなくて、bmpファイルでいいなら
GDI+を使わなくてもいいのでは。

以下、追加で

BitBlt
CreateCompatibleBitmap
DeleteObject

SavePicture

とかで、何とかならないかな。

編集 削除
やたこ  2008-03-05 16:27:26  No: 139171  IP: 192.*.*.*

最初に書いてなくてすみません!!
最終的にやりたいのは jpeg ファイルなのです!

編集 削除
やじゅ  2008-03-05 18:15:26  No: 139172  IP: 192.*.*.*

ここらへんの組み合わせで・・・

GDI+を使用し、GIFなどを表示できるコントロールを作成する 
http://maglog.jp/zerry/Article194475.html
GDI+ を使ったJPEGファイルの作成
http://yaplog.jp/orator/archive/29

編集 削除
やたこ  2008-03-06 08:00:57  No: 139173  IP: 192.*.*.*

やじゅ様、ありがとうございます。
拝見させていただきました。

> GDI+を使用し、GIFなどを表示できるコントロールを作成する 
JPEG保存の方法が載っているのですが、ピクチャーコントロールから
デバイスコンテキストを渡す方法しかありません。

> GDI+ を使ったJPEGファイルの作成
画像ファイルを読み込んで、フォーム等に表示する方法ですね。

最初に書いたつもりなのですが、以下のことはできています。
1.ビットマップの読み込み
(最終的にはjpegも読めるように変更予定ですが)
2.メモリ上の編集
3.編集されたビットマップを JPEG 等で出力

私が分からないのは、2→3のところで、
メモリのデバイスコンテキストをビットマップのハンドルに
割り当てる(表現おかしいかもしれません)ところなのだと思います。

引き続きアドバイスいただけないでしょうか。

編集 削除
魔界の仮面弁士  2008-03-06 09:21:42  No: 139174  IP: 192.*.*.*

GDI+ でやるのだとすれば:

> 1.ビットマップの読み込み(最終的にはjpegも読めるように変更予定ですが)
ストリームからの読み込みなら、GdipLoadImageFromStream。
画像ファイルからなら、GdipLoadImageFromFile。<ロックに注意
Imageオブジェクトの複製なら、GdipCloneImage。

ちなみに破棄は、GdipDisposeImage。

bmp や jpeg が対象なら、これら GdipLoadImage系のかわりに、
GdipCreateBitmapFromStream
GdipCreateBitmapFromFile
GdipCreateBitmapFromDirectDrawSurface
GdipCreateBitmapFromHBITMAP
等を使って読み込むという手もあります。


> 2.メモリ上の編集
GDI+ なら、Graphics オブジェクトの操作になりますね。Graphics は、
Image オブジェクトからの生成なら、GdipGetImageGraphicsContext。
デバイスコンテキストハンドルからの生成なら、GdipCreateFromHDC。
ウィンドウハンドルからの生成なら、GdipCreateFromHWND。

あとは、GdipDrawLineI / GdipFillRectangleI / GdipDrawImage / GdipDrawString 等で画像加工、と。

あるいは、より高速な低レベル操作のために、
GdipBitmapLockBits / GdipBitmapUnlockBits を用いて、
ビットマップデータのバイナリを直接操作するとか。


> 3.編集されたビットマップを JPEG 等で出力
既に御存知のように GdipSaveImageToFile にて。

編集 削除
K.J.K.  2008-03-06 09:55:05  No: 139175  IP: 192.*.*.*

GDI+で行うのならば、最初から最後までGDI+だけで行ってしまうのが楽でしょう。
で、サイズの変更にはGdipGetImageThumbnailが有効なケースもあります。

編集 削除
熊谷隆史  2008-03-06 10:43:14  No: 139176  IP: 192.*.*.*

もう、全然関係ないけど。
2008/03/05(水) 16:13:01 の私のレスで

OleCreatePictureIndirect
ReleaseDC
DeleteDC
が、抜けてました。
お邪魔しました。

編集 削除
やたこ  2008-03-06 18:23:31  No: 139177  IP: 192.*.*.*

返信ありがとうございます!!!

画像ファイルを読み込んで画像ファイルを出力が目的なので、
GdipLoadImage  系になるのですかね。

こんな感じでやってみようと思います。
編集はひとまずおいておいて、読み込みと出力の確認をしようと思います。

1.GdipLoadImageFromFile  でファイルの読み込み。
 これは jpeg, bmp の他、png 等もOK?
2.GdipGetImageGraphicsContext でイメージをグラフィックに
グラフィックを編集(?)ここは後で
3.編集したグラフィックからイメージに?
4.GdipSaveImageToFile でファイルに

2. のGdipGetImageGraphicsContext の宣言部が分かりません。
Google で調べてもほとんどヒットしないのですが・・・
また、3では何を使用すればよいのでしょうか?

質問ばかりですみません。始めてGDI+というものを知ったので
全然分かっていません。

編集 削除
魔界の仮面弁士  2008-03-06 19:20:14  No: 139178  IP: 192.*.*.*

> 1.GdipLoadImageFromFile  でファイルの読み込み。
>  これは jpeg, bmp の他、png 等もOK?
はい。読み込みも書き込みも対応しています。

GDI+ が対応している画像形式については、こちらをご覧ください。
http://msdn.microsoft.com/library/ja/cpguide/html/_gdiplus_Images_Bitmaps_and_Metafiles_about.asp

ただし GdipLoadImageFromFile の場合、読み込まれたファイルが
ロックされてしまいますので、できれば、GdipLoadImageFromStream を
使った方が良いでしょう。
http://www.vb-user.net/junk/replySamples/2007.10.21.12.01/DrawFromStream.txt


> 2.GdipGetImageGraphicsContext でイメージをグラフィックに
> グラフィックを編集(?)ここは後で
この「編集」で何をしたいのかによって、手順が異なってきます。

たとえば、最初に書かれていた
> サイズの変更を行い、ファイルとして保存したいです。
というのが、画像の縦横のサイズを意図しているのであれば、K.J.K.さんが
書かれているように、GdipGetImageThumbnail を利用するのが簡単かと。

> 3.編集したグラフィックからイメージに?
GdipGetImageThumbnail によるサイズ変更だけならば、
Image から 別の Image が作られるため、Graphics は使用しません。

複数の画像を合成したり、文字列を描画したりといった加工を行う場合は、
Graphics オブジェクトが必要になります。


なお、GDI+ の Image オブジェクトとは、画像データ(ファイル,メモリ等)そのものを表します。
一方、Graphics オブジェクトは、その Image に対する描画ツール(ペン、ブラシ、フォントなど)を表します。

また、Image から作成した Graphics の場合、描画した結果は
元になった Image にそのまま反映されていますので、
> 編集したグラフィックからイメージに?
といった作業は行いません。


> 4.GdipSaveImageToFile でファイルに
ですね。

編集 削除
やたこ  2008-03-06 19:42:42  No: 139179  IP: 192.*.*.*

魔界の仮面弁士様、ありがとうございます!!

はい。主にJPEG画像のファイルサイズの縮小が目的なので、
縦横サイズの縮小と、圧縮率の指定ができて保存できればOKです。

試行錯誤している間に以下の様にしてビットマップで読み込み、
JPEGのQuality(圧縮率?)を指定して出力することはできました!

    Ret = GdipCreateBitmapFromFile(StrPtr(SourceFileName), lngBmp)
    
    EncodParameters.Count = 1
        With EncodParameters.Parameter(0)
            .Guid = ConvCLSID(CLSID_QUALITY)
            .NumberOfValues = 1
            ' 4=EncoderParameterValueTypeLong
            .Type = 4
            ' 圧縮品質
            .Value = VarPtr(Quality)
        End With
    Ret = GdipSaveImageToFile(lngBmp, StrPtr(DestFileName), ConvCLSID(CLSID_JPEG), VarPtr(EncodParameters))

Graphicsは不要とのことなので、あとはサイズ縮小ができれば
いいのかな、と思うのですが、
ビットマップとImageというのは何が異なるのでしょうか?
また、サイズ縮小について  GdipGetImageThumbnail を調べたのですが、
宣言部も使い方も情報が見つかりませんでした。
(GDI+というのは何でこんなに情報が少ないのでしょう??
  使い方さえ分かれば非常に便利なものに見えるのですが・・・)

申し訳ありませんが、もう少し手ほどきいただけないでしょうか。

編集 削除
魔界の仮面弁士  2008-03-06 20:19:42  No: 139180  IP: 192.*.*.*

> ビットマップとImageというのは何が異なるのでしょうか?
Bitmap オブジェクトは Image オブジェクトの一種です。

Image の機能に加えて、bmp, jpg, gif, png 等の
ビットマップ系画像を操作するための機能が追加されています。


> 宣言部も使い方も情報が見つかりませんでした。
SDK の [GDI+ Reference] - [GDI+ Flat API] の項に、関数の宣言が
載っているので(C++用)、それを VB6 の Declare 文に翻訳してみてください。
http://msdn2.microsoft.com/en-us/library/ms533969.aspx


>(GDI+というのは何でこんなに情報が少ないのでしょう??
SDK に細かく書かれていますよ。英語かつ C++ 向けですけれども。


>  使い方さえ分かれば非常に便利なものに見えるのですが・・・)
C++ 用には、ラッパーとなるクラスがすでに提供されていますし、
VB 向けにも、.NET Framework には System.Drawing 名前空間の下に、
GDI+ のクラスが用意されています。

このような事情もあってか、実際には、Flat API の出番はさほど多くありません。
SDK には、Flat API 関数を直接呼ぶ事を推奨していないとさえ書かれていますし。
> It is recommended that you do not directly call the functions in the flat API.


もっとも VBA や VB6 においては、Flat API を Declare 宣言(またはタイプライブラリ)で
呼び出すほか無いのですけれども。

編集 削除
魔界の仮面弁士  2008-03-06 20:28:59  No: 139181  IP: 192.*.*.*

> また、サイズ縮小について  GdipGetImageThumbnail を調べたのですが、
> 宣言部も使い方も情報が見つかりませんでした。

元の定義が

GpStatus WINGDIPAPI GdipGetImageThumbnail(
    GpImage *image,
    UINT thumbWidth,
    UINT thumbHeight,
    GpImage **thumbImage,
    GetThumbnailImageAbort callback,
    VOID * callbackData)

だから、VBA だとこうかな。

Private Declare Function GdipGetImageThumbnail Lib "gdiplus" _
   (ByVal baseImage As Long, _
    ByVal thumbWidth As Long, _
    ByVal thumbHeight As Long, _
    ByRef thumbImage As Long, _
    ByVal callback As Long, _
    ByVal callbackData As Long) As Long


引数1: 元となる Image オブジェクト
引数2: 縮小後の幅
引数3: 縮小後の高さ
引数4: ここに、処理結果の Image が返される

最後の 2 つの引数は、0 を渡しておきましょう。

編集 削除
やたこ  2008-03-07 08:31:29  No: 139182  IP: 192.*.*.*

画像のサイズ縮小、できました!!!!!
魔界の仮面弁士様はじめ皆様、
たくさんのご指導いただきありがとうございました!!

感謝感謝です!
今後とも勉強して参ります!!

編集 削除
今更ジロー  2008-05-30 17:51:46  No: 139183  IP: 192.*.*.*

はじめて質問させていただきます。

私もやたこ様と同様の問題をかかえて、過去の内容を元にトライしておりました。やりたかった内容はjpegファイルを読み込んでリサイズして保存。
これだけです。

GdipGetImageThumbnailで縮小するところまでは問題なさそうですが、GdipSaveImageToFileで保存ができません。

というよりコンパイルエラーで「パブリック オブジェクト モジュールで定義されたユーザー定義型に限り、変数に割り当てる事ができ、実行時バインディングの関数に渡すことができます。」となります。

開発環境はVistaSP1+ACCESS2007(VBA)です。

コンパイルエラーは次の箇所で発生します。

 EncodParameters.Count = 1
        With EncodParameters.Parameter(0)
            .Guid = ConvCLSID(CLSID_QUALITY) <ここのConCLSIDでエラー
            .NumberOfValues = 1
            .Type = 4
            .Value = VarPtr(Quality)
        End With
    Ret = GdipSaveImageToFile(lngBmp, StrPtr(DestFileName), ConvCLSID(CLSID_JPEG), VarPtr(EncodParameters))

ConvCLSIDは

Private Function ConvCLSID(ByVal sGuid As String) As UUID
    CLSIDFromString StrPtr(sGuid), ConvCLSID
End Function

としております。

VBAに限らずプログラム自体が我流でサンプルコードを頼りに作成できるぐらいのレベルなのでここで行き詰まってしまいました。

便乗質問で申し訳ございません。

何か有効な情報がありましたらお願いいたします。m(_ _)m

編集 削除
魔界の仮面弁士  2008-05-30 19:14:09  No: 139184  IP: 192.*.*.*

解決済みのスレッドに便乗質問するのは避けてください。
新規に質問を投稿し、そこに関連スレッドの URL を載せてください。

> パブリック オブジェクト モジュールで定義されたユーザー定義型に限り、
> 変数に割り当てる事ができ、実行時バインディングの関数に渡すことができます。
ユーザー定義型、および、それを引数に受け取るプロシージャ等を、
すべて Private で宣言してみてください。

編集 削除