お世話になっております。よろしくお願いします。VC6使っております。
独自クラスでイメージ管理をしようとしています。
ファイルからの読込みや保存は完成しているのですが、クリップボード(HBITMAP)からイメージ
を取得したいと思っています。
インターネット上でHBITMAP→LPPICTUREの変換方法はみつけたのですが、ファイルサイズがわ
かりません。
ファイルサイズ(m_dwFileSize)は保存の時にGlobalAllocするためのサイズに使用しているの
で、ファイルから開いたときは、そのファイルサイズを格納しているのですが、クリップボード
からだとファイルサイズが取得できなくて困っています。
何か良い方法はありますでしょうか。
一応、そのクラスと、m_dwFileSizeを使って保存している関数と、HBITMAPからLPPICTUREに変
換している関数をのせてみました。よろしくお願い致します。
	//エラーチェックは省略しています
class CMyImage 
{
public:
	DWORD		m_dwFileSize;	//ファイルファイズ
	LPPICTURE	m_pPicture;		//IPictureのポインタ
public:
	BOOL _SaveIPicture( CArchive& ar );		//アーカイブ保存
	BOOL CreateFromHBITMAP( HBITMAP hBitmap );	//HBITMAPから変換
//その他省略
}
// CArchiveにIPictureを保存
BOOL CMyImage::_SaveIPicture( CArchive& ar ) 
{
BOOL rtv = FALSE;
	HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, m_dwFileSize );
	LPVOID pvData = GlobalLock(hGlobal);
	LPSTREAM pstm = NULL;
	HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pstm);
	if(!(SUCCEEDED(hr) && pstm))
		::AfxThrowMemoryException();
	LONG lBytes=0;
	hr = m_pPicture->SaveAsFile(pstm, FALSE, &lBytes);
	ar << (DWORD)lBytes;
	ar.Write(pvData, (UINT)lBytes );
	GlobalUnlock(hGlobal);
	pstm->Release();
	GlobalFree(hGlobal);
	rtv = TRUE;
exit:
	return rtv;
}
BOOL CMyImage::CreateFromHBITMAP( HBITMAP hBitmap )
{
	HRESULT hr = S_OK;
	LPPICTURE	pRtvPic=0;
    	PICTDESC pd;
 	::ZeroMemory(&pd, sizeof(PICTDESC));
    	pd.cbSizeofstruct = sizeof(PICTDESC);
	pd.picType = PICTYPE_BITMAP;
	pd.bmp.hbitmap = hBitmap;
	pd.bmp.hpal = NULL;
   	hr = ::OleCreatePictureIndirect(&pd, IID_IPicture, FALSE, (LPVOID*)
&pRtvPic);
	if(!(SUCCEEDED(hr) && pRtvPic))
		AfxThrowMemoryException();
	m_pPicture = pRtvPic;
	m_dwFileSize = ???? /////←ここに保存時にAllocするファイルサイズをいれたい
	return TRUE;
}
ビットマップファイルのサイズは、基本的には
(1画素のバイト数 * 幅)を4の倍数に切り上げたもの * 高さ + ヘッダのバイト数
で算出できます。
ただし、圧縮されている場合や、BITMAPV5HEADERを使う場合は、この限りではありませ
ん。
あ、パレットサイズとかビットフィールドのサイズもあるか。必要ならばそれも足して
ください。
ごめん、前言は完全に撤回。
IPicture::SaveAsFile の仕様なんて知らねーや。
SaveAsFileなんて名前のくせに、IStreamにしか吐き出せないのか。
ファイルサイズの算出はGlobalAllocのために必要としてるんだろうから、そもそも
CreateStreamOnHGlobalじゃなくて、SHCreateStreamOnFileを使ってみるとか、ファイル
に直接吐き出すIStreamの実装を作ってみるとかしたらどうだろう。
CreateStreamOnHGlobalに渡すhGlobalはサイズ0でかまいません。
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, 0);
としておけばよいです。
# GlobalAlloc にサイズ 0 を渡せるってのは初めて知ったけど…
> サイズ0でかまいません。
ほんと?
GlobalAlloc
 http://msdn2.microsoft.com/en-us/library/aa366574.aspx 
> dwBytes
> If this parameter is zero and the uFlags parameter specifies GMEM_MOVEABLE,
> the function returns a handle to a memory object that is marked as 
> discarded.
日本語訳
> dwBytes
> dwBytes パラメータに 0、uFlags パラメータに GMEM_MOVEABLE フラグを
> 指定すると、この関数は、破棄マークが付けられたメモリオブジェクトの
> ハンドルを返します。
CreateStreamOnHGlobal
 http://msdn2.microsoft.com/en-us/library/aa378980.aspx 
> hGlobal
> The handle must be allocated as movable and nondiscardable. 
日本語訳
> hGlobal
> このハンドルは、移動可能、かつ、破棄不可能として確保しなければなりません。
ダメそうな気がするけど。
CreateStreamOnHGlobal の hGlobal に NULL を渡すのはいいみたい。その場合、内部的
にメモリを割り当ててくれるとある。
その場合は、IStream::Write で書きこみ終わってから、GetHGlobalFromStream で内容
を取得できるのかな。
>CreateStreamOnHGlobal
> http://msdn2.microsoft.com/en-us/library/aa378980.aspx 
New handles should be allocated with a size of zero.
のとこでGlobalAllocの引数を0にすると解釈していました。
nondiscardableの部分は単にGMEM_DISCARDABLEが指定されていないこと
という程度にしか思わなかったのですがどうなんでしょうね?
> New handles should be allocated with a size of zero.
ほんとだ。そう書いてあるわ。見落としてた。
だとすると、GlobalAlloc のページ
 http://msdn.microsoft.com/library/ja/default.asp? 
url=/library/ja/jpmemory/html/_win32_globalalloc.asp
にある、
> dwBytes 
> 最初にハンドルだけを作成しておき、後で実際にメモリを割り当てる場合に
> この方法を利用できます。
のケースに当てはまるわけか。
この文が英語版には見当たらないのは気になるけれど…
ま、DISCARDABLE というステータスは Win32 には無いのだから、気にしなくてもいいか
もしれない。
ただ、CreateStreamOnHGlobal のページには、
> The initial contents of the stream are the current contents of the
> memory block provided in the hGlobal parameter.
とか
> The initial size of the stream is the size of the memory handle
> returned by the Microsoft Win32 GlobalSize function.
というように、初期サイズを与えてもいいような記述があるのは気になる。
うーん…
> New handles should be allocated with a size of zero.
New handles というのも気になる。
これはひょっとして、「サイズ0で確保しておくべきだ」ではなく「新しいハンドルは、
サイズ0で確保されるでしょう」と読むべきなのではないか?
# 英語は難しいなぁ。
ありがとうございます。レス遅くなってすみません。
試しに、保存のところでGlobalAllocでサイズ0渡してみました。
やはりエラーになってしまいました。
	HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, 0/*m_dwFileSize*/ );
	if(NULL == hGlobal)
		::AfxThrowMemoryException();
	LPVOID pvData = GlobalLock(hGlobal);
	if(NULL == pvData)
		::AfxThrowMemoryException();	//←このエラーに来てしまい
ました
となると、ファイルサイズはやはり計算しかないのかしら。
クリップボードのCF_BITMAPで取得できるHBITMAPだけに対応できればよいのですが、圧縮とか
そういう種類みたいなものもあるのでしょうか。
とりあえず、計算もやってみます。
どなたかのサイトを参照させていただき、HBITMAPのファイルサイズを計算する関数
作ってみました。
UINT GetBitmapFileSize(HBITMAP hbmp)
{
	BITMAP       bmp;   // ビットマップの情報
	int          iclrbits; // 画面の色のビット数
	UINT		palsize;
	UINT		filesize=0;
	// ビットマップの情報を取得する
	if( GetObject( hbmp, sizeof(BITMAP), &bmp ) == 0 )
		return 0;
	// ビット数計算
	iclrbits = bmp.bmPlanes * bmp.bmBitsPixel;
	// (1,4,8,16,24,32のどれか)
	if ( iclrbits == 1 )      iclrbits = 1;
	else if( iclrbits <= 4 )  iclrbits = 4;
	else if( iclrbits <= 8 )  iclrbits = 8;
	else if( iclrbits <= 16 ) iclrbits = 16;
	else if( iclrbits <= 24 ) iclrbits = 24;
	else                      iclrbits = 32;
	if( iclrbits != 24 )
	{
		palsize = sizeof(RGBQUAD) * iclrbits;
	}else{
		palsize = sizeof(RGBQUAD) * 0;
	}
	// ファイルのサイズを計算する
	filesize = (DWORD)( sizeof(BITMAPFILEHEADER) 
		+ sizeof(BITMAPINFOHEADER) 
		+ palsize
		+ (bmp.bmWidth + 7) / 8 * bmp.bmHeight * iclrbits );
	return filesize;
}
ペイントブラシ経由でペーストしてみると、ファイルから直接開いたときの
サイズと全然桁の違う大きい値になってしまいました。(bmp.bmBitsPixel=32みたいです)
(他のアプリ経由でのテストはまだしていませんので、後でしたいと思います。)
が、別の問題がでてきました!
クリップボードから、最初に書いた自作の変換関数
BOOL CMyImage::CreateFromHBITMAP( HBITMAP hBitmap )
で、LPPICTUREに変換し、表示まで普通に成功しているのですが、保存時に SaveAsFile()の
ところで失敗してしまいます。
GlobalAllocのサイズは、計算で出た桁違いの大きな値を指定しているので、サイズの問題では
ないような気がします。
「ペースト&保存」がなかなか上手くいきません。何か良い方法はありますでしょうか(;_;)
よろしくお願い致します。
> New handles should be allocated with a size of zero.
>
>New handles というのも気になる。
>これはひょっとして、「サイズ0で確保しておくべきだ」ではなく「新しいハンドルは、
>サイズ0で確保されるでしょう」と読むべきなのではないか?
これはメモリ確保済みのハンドルを再利用せずに新規に作成する場合
という意味ではないでしょうか。
GlobalAllocに与えるのは初期サイズなのでたぶんいくらでもよいです。
>試しに、保存のところでGlobalAllocでサイズ0渡してみました。
>やはりエラーになってしまいました。
こちらは単にGlobalLockするタイミングが悪いだけです。
SaveAsFileが成功してからGlobalLockしてください。
いろいろありがとうございます。
下のようにGlobalLockの位置を変えるとAllocサイズ0で成功しました。
ありがとうございました。
BOOL CMyImage::_SaveIPicture( CArchive& ar )
{
	BOOL rtv = FALSE;
	HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, 0/*m_dwFileSize*/ );
	 //←サイズ0にする
	if(NULL == hGlobal)
		::AfxThrowMemoryException();
	/* これをSaveAsFileのあとに移動
	LPVOID pvData = GlobalLock(hGlobal);
	if(NULL == pvData)
		::AfxThrowMemoryException();
	*/
	LPSTREAM pstm = NULL;
	HRESULT hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pstm);
	if(!(SUCCEEDED(hr) && pstm))
		::AfxThrowMemoryException();
	LONG lBytes=0;
	hr = m_pPicture->SaveAsFile(pstm, FALSE, &lBytes);
	if(hr != S_OK)
		::AfxThrowMemoryException();
	//ここに移動してきた
	LPVOID pvData = GlobalLock(hGlobal);
	if(NULL == pvData)
		::AfxThrowMemoryException();
	ar << (DWORD)lBytes;
	//ar.Write(pstm, (UINT)m_dwFileSize);
	ar.Write(pvData, (UINT)lBytes );
	GlobalUnlock(hGlobal);
	pstm->Release();
	GlobalFree(hGlobal);
	rtv = TRUE;
exit:
	return rtv;
}
まだ、クリップボードからBITMAPを拾ってLPPICTUREに変換して保存するときにSaveAsFileで
エラーになる問題は残っていますが、問題がこのスレのタイトルとずれそうなのでここではやめ
ておいたほうがいいですよね。
このスレの「ファイルサイズを取得したい」については、”ファイルサイズを取得する必要がな
い”という結論で「解決」しました。
みなさん、大変ありがとうございました!!
CreateStreamOnHGlobalの第2引数がTRUEの場合は
最後にGlobalFreeを呼ぶ必要はありません。
>クリップボードからBITMAPを拾ってLPPICTUREに変換して保存するときにSaveAsFileで
>エラーになる問題
クリップボードからだとSaveAsFileの第2引数はTRUEでなければダメみたいですね。
(ただし、TRUEにするとファイルから読んだ場合でも常にビットマップ形式で保存されます)
ありがとうございます!
SaveAsFileの第2引数TRUEで成功しました。
ヘルプ見てみても第2引数の意味が分からなかったので、参考にしたサンプルソースがFALSEだ
ったのをそのままにしておりました。
クリップボードから読込みしたときと使い分けるようにフラグ管理しておこうと思います。
ありがとうございました。

 
  
  
  
   
                        