JPEG画像の表示と印刷

[戻る]
Ti Amo 2008/12/25(木) 09:38:36
VC2005, MFC, SDI, CFormView アプリケーションです。

子ダイアログ上にJPEG画像を表示し、それを印刷しようと考えています。
このページを参考にしてやってみました。
http://www.athomejp.com/goldfish/mfc/cobject/jpegview.asp

COMのIStreamとIPictureを使い、Renderメソッドで描く手法となっています。
表示は問題なく出来ました。
印刷が出来ません。
OnPrint(CDC* pDC, CPrintInfo* pInfo) の中に、上記とほぼ同じコードを書きました。
Render関数の中の、次の2点が問題と思っています。
m_pJpeg->Render(dc, 0, 0, size.cx, size.cy,
                0, hmHeight, hmWidth, -hmHeight, &rc);
1.第1引数の dc の代わりに pDC とすると「HDCではない」と叱られるので、
    HDC hDC = ::GetDC(m_hWnd);
    とやって、この hDC を使いました。
2.最後の引数 &rc は GetClientRect ですから、これはいけないと思って
    NULL にしました。

初心者で理屈がよく判っていません。
Render関数も MSDN Library で出てきません。
OnPrint の中ではどのようなコードにしたらいいか、どうぞご教授ください。
よろしくお願いいたします。

aetos 2008/12/25(木) 10:00:25
画面に表示するときはウィンドウのデバイスコンテキストに描画するのと同じように、
印刷するときはプリンタのデバイスコンテキストに描画してやります。

> 1.第1引数の dc の代わりに pDC とすると「HDCではない」と叱られるので、
>     HDC hDC = ::GetDC(m_hWnd);
>     とやって、この hDC を使いました。

これではやはりダイアログのデバイスコンテキストになってしまいます。
pDC->GetSafeHdc() で取得したものを渡してください。

> 2.最後の引数 &rc は GetClientRect ですから、これはいけないと思って
>     NULL にしました。

NULL で構いませんが、その理由は MSDN を見て確認しておいてください。
http://msdn.microsoft.com/en-us/library/ms682202(VS.85).aspx

Ti Amo 2008/12/25(木) 13:21:03
aetos さん、有り難うございます。

HDC hDC = pDC->GetSafeHdc();
とやって、Render の第1引数にこの hDC を置いたら、一応うまくいきました。
hDC の解放の仕方が判りません。
hDC->unused;
としたのですが、これでいいでしょうか?

また、OnPaint のマップモードを都合があって LOENGLISH にしているので、
HIMETRIC から LOENGLISH に換算するためサイズを 25.40 で割りました。つまり、
size.cx, size.cy のところを、(int)(hmWidth/25.40), (int)(hmHeight/25.40)
と書きました。
しかし、印刷結果は約2割小さくなります。
何故でしょう?

よろしくお願いいたします。

aetos 2008/12/25(木) 13:39:43
> hDC->unused;
> としたのですが、これでいいでしょうか?

ダメです。
その hDC は解放する必要はありません。

> しかし、印刷結果は約2割小さくなります。

size、hmWidth、hmHeight を取得しているコードと、OnPaint のコードを全部載せられま
すか?
長すぎるようなら適度に省略してください。

Ti Amo 2008/12/25(木) 14:06:22
一つ前の発言中、OnPaint は OnPrint の書き間違いでした。

> その hDC は解放する必要はありません。
判りました。

void CJPEGPrint::OnPrint(CDC *pDC, CPrintInfo* pInfo)
の中に、以下のように記述しています。

pDC->SetMapMode(MM_LOENGLISH);
CFile f;
if (f.Open(_T("Sample.jpg"), CFile::modeRead) == FALSE)
{
    AfxMessageBox(_T("画像ファイルが見つかりません"));
    return;
}
DWORD dwSize;
HGLOBAL h;
void *pBuf;
LPSTREAM pstm;
HRESULT hr;
dwSize = (DWORD)f.GetLength();
h = ::GlobalAlloc(GMEM_MOVEABLE, dwSize);
if (h == NULL)
{
    f.Close();
    AfxMessageBox(_T("メモリが確保できないので\n画像の表示・印刷を中止しま
す"));
    return;
}
pBuf = ::GlobalLock(h);
if (pBuf == NULL)
{
    f.Close();
    ::GlobalFree(h);
    AfxMessageBox(_T("GlobalLockが失敗したので\n画像の表示・印刷を中止しま
す"));
    return;
}
if (f.Read(pBuf, dwSize) != dwSize)
{
    f.Close();
    ::GlobalUnlock(h);
    ::GlobalFree(h);
    AfxMessageBox(_T("読み込めないので\n画像の表示・印刷を中止します"));
    return;
}
f.Close();
::GlobalUnlock(h);
pstm = NULL;
hr = ::CreateStreamOnHGlobal(h, TRUE, &pstm);
if (SUCCEEDED(hr) == FALSE)
{
    pstm->Release();
    AfxMessageBox(_T("IStream失敗のため\n画像の表示・印刷を中止します"));
    return;
}
hr = ::OleLoadPicture(pstm, dwSize, FALSE, IID_IPicture, (LPVOID*)&m_pGazo);
if (SUCCEEDED(hr) == FALSE || m_pGazo == NULL)
{
    pstm->Release();
    AfxMessageBox(_T("画像ロード失敗のため\n画像の表示・印刷を中止します"));
    return;
}
pstm->Release();
HDC hDC = pDC->GetSafeHdc();
long hmWidth;
long hmHeight;
if (m_pGazo != NULL)
{
    m_pGazo->get_Width(&hmWidth);
    m_pGazo->get_Height(&hmHeight);
    m_pGazo->Render(hDC, 0, 0,
        (int)(hmWidth/25.40), -(int)(hmHeight/25.40),
        0, hmHeight, hmWidth, -hmHeight, NULL);
}

Ti Amo 2008/12/25(木) 14:15:13
訂正です。
Render 関数の第4,5引数の (int) は間違いで (long) です。
結果は同じです。

Ti Amo 2008/12/25(木) 18:36:58
よろしくお願いいたします。

Ti Amo 2008/12/26(金) 11:09:15
原因が判りました!
自分のPC環境は、Windows XP で、画面のプロパティの「DPI設定」を「大きなサイズ
(120DPI)」で使っていました。
これを「通常のサイズ(96DPI)」に変更したところ、画像の表示と印刷がほぼ同じ大きさ
となりました。

pDC->BitBlt() や pDC->TextOut() 等の場合、DPI設定には無関係に画像や文字の大きさ
は表示と印刷で一致します。
IPicture の場合は、Windows環境のDPI設定を取得して補正しなければならないのでしょ
う。

らら 2008/12/26(金) 13:40:35
 >   m_pGazo->Render(hDC, 0, 0,
 >      (int)(hmWidth/25.40), -(int)(hmHeight/25.40),

サイズを固定値で割っているのが少しきになりまして・・・。
HIMETRIC 単位をデバイス単位に変換する
 CDC::HIMETRICtoDP(CSize& size) 
とかいう関数もあったりします。
私は実際にテストしてみていませんが、「JPEGファイルを表示する」サンプルコードを
作ってくれているサイトがありましたので、参考にされてみてはいかがでしょうか。

http://athomejp.com/goldfish/mfc/cobject/jpegview.asp

らら 2008/12/26(金) 13:44:49
すみません、引数の型間違えてたので訂正です。
ついでに、ちゃんと書きました・・^^;

void CDC::HIMETRICtoDP(LPSIZE lpSize) const;

Ti Amo 2008/12/26(金) 15:51:38
らら さん、レス有り難うございます。

「参考に・・・」とご提示戴いたサイトは、元々参考にしたサイトで、この質問の冒頭
に掲げております。

 > void CDC::HIMETRICtoDP(LPSIZE lpSize) const;

もちろん、これでもやってみました。

CSise size;
pDC->HIMETRICtoDP(&size);
・・・・
m_pGazo->Render(hDC, 0, 0,
    size.cx, -size.cy,
    0, hmHeight, hmWidth, -hmHeight, NULL);

これでも、WindowsデスクトップのDPI設定によって、印刷される画像の大きさが違って
きます。

(int)(hmWidth/25.40), -(int)(hmHeight/25.40), と 25.40 で割っているのは、前に
も書いていますが、OnPrint() の中でマップモードを pDC->SetMapMode(LOENGLISH) と
しているためです。
hmWidth, hmHeight が HIMETRIC なので、LOENGLISH との比が 25.40 になります。

要するに、WindowsデスクトップのDPI設定によって印刷される画像の大きさが違ってく
るので、困っています。
DPI の取得の仕方を研究していますが、まだ判りません。
どなたかお解りでしたらご教授お願いします。

aetos 2008/12/26(金) 16:21:32
GetDeviceCaps に LOGPIXELSX / LOGPIXELSY を渡せば取得できたかも。

Ti Amo 2008/12/26(金) 17:04:18
aetos さん、ありがとうございます。
やってみました。
pDC->GetDeviceCaps(LOGPIXELSX);
結果は、DPI=96 のときも、DPI=120 のときも「360」という値が取得されました。
自分の環境での値だと思うのですが、Capacity(能力)ということだろうと納得してお
る次第です。

Ti Amo 2008/12/26(金) 17:12:44
やはり能力ですね。
画面のプロパティ → 設定 → 詳細設定 → DPI設定のコンボボックスで
「カスタム設定」を開くと、スケールが 360 まであります。

Ti Amo 2008/12/26(金) 19:47:04
こんなページがありました。
http://msdn.microsoft.com/en-us/library/ms701681(VS.85).aspx
「DPIを意識したアプリケーショの作り方」というんでしょうか。
文中に、「GetSystemMetricsで好みのユーザーインターフェースのサイズを取得し、そ
れをスケールにしろ。」とあります。
そこで、
int cycaption = ::GetSystemMetrics(SM_CYCAPTION); //タイトルバーの高さを取得
double cyratio = cycaption / 27.08;
m_pGazo->Render(hDC, 0, 0,
    (long)(hmWidth/25.40*cyratio), -(long)(hmHeight/25.40*cyratio),
    0, hmHeight, hmWidth, -hmHeight, NULL);
としたら、デスクトップのDPI設定を変えても画像の印刷と表示の大きさが一致し、一定
となりました。

また、こんなページもありました。
http://msdn.microsoft.com/ja-jp/library/ms229605(VS.80).aspx
Win32 SDK 関数の GetDialogBaseUnits というのがあるそうです。
上記と同じことのようです。戻り値がlongでややこしい。

もっと直接的にデスクトップのDPI設定値を取得できないものでしょうか。

gak 2008/12/27(土) 02:38:48
> 画面のプロパティの「DPI設定」を「大きなサイズ(120DPI)」...
ここで設定している値はスクリーンDC の dpi。

> pDC->GetDeviceCaps(LOGPIXELSX);
> 結果は、DPI=96 のときも、DPI=120 のときも「360」という値が取得されました。
pDC はプリンタDC。なので、この時の「360」はプリンタDCの dpi。
画面のプロパティで設定した dpi を取得したいのならば対象を間違っている。

> もっと直接的にデスクトップのDPI設定値を取得できないものでしょうか。
”スクリーンDC”に対して GetDeviceCaps() をかませば良い。
もっと言えば、今回の場合は”スクリーンDC”より
http://msdn.microsoft.com/en-us/library/ms690468(VS.85).aspx
で取得できる DC が適当か。


> DPI設定によって印刷される画像の大きさが違ってくるので、困っています。
JPEG 等の画像ファイルは pixel 単位(デバイス単位)でサイズ情報を持っている。
つまり、mm / inch のような、どんな環境でも同じ大きさを表現できる単位では無い。

例えば「1000 pixel」というサイズの場合、
「96 dpi」の環境では
 1000 * 25.40 / 96  = 264.583mm
だが、「120 dpi」の環境では
 1000 * 25.40 / 120 = 211.667mm
となり、同じ「1000 pixel」でも論理単位変換時に差異が出る。

> pDC->SetMapMode(MM_LOENGLISH);
>  :
> m_pGazo->Render()
「JPEGオリジナルサイズの ○○mm で印刷して」という意図の処理だと思うが、↑で述べた
通りデバイス単位→論理単位の変換は環境に依存する。
つまり、○○の部分を(如何なる環境においても)一意に決める、という事は基本困難。

# それでも○○の部分を一意に決めたいケースはやはりある。
# なので、論理単位変換時に必要な情報(dpi 等)も一緒に保存する画像形式もある
#(JPEG も保存可能)のだが…、残念ながら IPicture はその情報を利用してなかった
# ので意味無しだったと思う


お休みモードに入るので、以降レス途絶えます…

ISLe 2008/12/27(土) 02:40:34
> int cycaption = ::GetSystemMetrics(SM_CYCAPTION); //タイトルバーの高さを取得
> double cyratio = cycaption / 27.08;

デスクトップPCだと画面のプロパティからデザイン変更できますよね。
モバイルPC用のテクニックは使えないのでは?


HIMETRICtoDP → HIMETRICtoLP だったらどうなります?
#25.04なんてマジックナンバー使わずに済むと思う。

96dpiでも120dpiでも画面に描画されるJPEG画像の大きさは同じでしたっけ?
HIMETRICtoDPとHIMETRICtoLPの比を印刷時に考慮すれば良いかも。

Ti Amo 2008/12/27(土) 09:50:52
[[解決]]
出来ました!
gak様、ありがとうございました。

> ここで設定している値はスクリーンDC の dpi。
> pDC はプリンタDC。なので、この時の「360」はプリンタDCの dpi。
> 画面のプロパティで設定した dpi を取得したいのならば対象を間違っている。

その通りです。間違っていました。恥ずかしい。

CDC *pScreenDC = GetDC();
int dpi = pScreenDC->GetDeviceCaps(LOGPIXELSX);
m_pGazo->Render(hDC, 0, 0,
    (long)(hmWidth/25.40*dpi/100), -(long)(hmHeight/25.40*dpi/100),
    0, hmHeight, hmWidth, -hmHeight, NULL);
ReleaseDC(pScreenDC);

これでバッチリOKです。
画像の大きさが画面と印刷で一致しました。jpg, gif, bmp・・・確認しました。

> http://msdn.microsoft.com/en-us/library/ms690468(VS.85).aspx
こちらは、取得するのが HDC なので GetDeviceCaps(LOGPIXELSX) が出来ません。

>「JPEGオリジナルサイズの ○○mm で印刷して」という意図の処理だと思うが、
> ↑で述べた通りデバイス単位→論理単位の変換は環境に依存する。
> つまり、○○の部分を(如何なる環境においても)一意に決める、という事は
> 基本困難。

この作成中のアプリで、ダイアログ上に表示した画像・文字・線などを、
同じ配置関係で印刷出来ればいいんです。
解決です。有り難うございました。

ISLeさん、レスありがとうございました。

subaru 2008/12/27(土) 13:32:53
情報が断片的でわかりにくいけど、
IPicture::get_Width
IPicture::get_Height
がディスプレイの論理解像度に依存して報告してくるのが問題になっているということかな?

gakさんの、IPictureはJPEGの解像度を利用しないという情報から
画面側はピクセル単位での描画が正確なサイズと考えられます。


以下はVC6のOLESTD.Hに定義されているピクセル値への変換マクロです。(ppliは解像度)

#define HIMETRIC_PER_INCH   2540      // number HIMETRIC units per inch
#define MAP_LOGHIM_TO_PIX(x,ppli)   MulDiv((ppli), (x), HIMETRIC_PER_INCH)

Ti Amoさんの
>CDC *pScreenDC = GetDC();
>int dpi = pScreenDC->GetDeviceCaps(LOGPIXELSX);
>m_pGazo->Render(hDC, 0, 0,
>    (long)(hmWidth/25.40*dpi/100), -(long)(hmHeight/25.40*dpi/100),
>    0, hmHeight, hmWidth, -hmHeight, NULL);
>ReleaseDC(pScreenDC);

のコードと比べてみると実は全く同じことをしているようです。
ということは、画面の方は少なくともこの部分は結局MM_TEXTモードでの描画に変更して
ますね?


印刷側も同じコードだと解像度が高いプリンタではつらい気もするのですが、
参考までに印刷側もどのように修正して解決したのか教えていただけるとありがたいです。

Ti Amo 2008/12/27(土) 15:06:33
>Ti Amoさんの
>>CDC *pScreenDC = GetDC();
>>int dpi = pScreenDC->GetDeviceCaps(LOGPIXELSX);
>>m_pGazo->Render(hDC, 0, 0,
>>    (long)(hmWidth/25.40*dpi/100), -(long)(hmHeight/25.40*dpi/100),
>>    0, hmHeight, hmWidth, -hmHeight, NULL);
>>ReleaseDC(pScreenDC);

これが印刷部分です。

質問の冒頭に書いた目的です。
> 子ダイアログ上にJPEG画像を表示し、それを印刷しようと考えています。

改めて、印刷部分と子ダイアログ表示部分のコードを記します。

【印刷部分】
void CJPEGPrint::OnPrint(CDC *pDC, CPrintInfo* pInfo) の中です。

pDC->SetMapMode(MM_LOENGLISH);    // 理由があってこのモードにしています
CFile f;
if (f.Open(_T("Sample.jpg"), CFile::modeRead) == FALSE)
{
    AfxMessageBox(_T("画像ファイルが見つかりません"));
    return;
}
DWORD dwSize;
HGLOBAL h;
void *pBuf;
LPSTREAM pstm;
HRESULT hr;
dwSize = (DWORD)f.GetLength();
h = ::GlobalAlloc(GMEM_MOVEABLE, dwSize);
if (h == NULL)
{
    f.Close();
    AfxMessageBox(_T("メモリが確保できないので\n画像の表示・印刷を中止"));
    return;
}
pBuf = ::GlobalLock(h);
if (pBuf == NULL)
{
    f.Close();
    ::GlobalFree(h);
    AfxMessageBox(_T("GlobalLockが失敗したので\n画像の表示・印刷を中止"));
    return;
}
if (f.Read(pBuf, dwSize) != dwSize)
{
    f.Close();
    ::GlobalUnlock(h);
    ::GlobalFree(h);
    AfxMessageBox(_T("読み込めないので\n画像の表示・印刷を中止します"));
    return;
}
f.Close();
::GlobalUnlock(h);
pstm = NULL;
hr = ::CreateStreamOnHGlobal(h, TRUE, &pstm);
if (SUCCEEDED(hr) == FALSE)
{
    pstm->Release();
    AfxMessageBox(_T("IStream失敗のため\n画像の表示・印刷を中止します"));
    return;
}
hr = ::OleLoadPicture(pstm, dwSize, FALSE, IID_IPicture, (LPVOID*)&m_pGazo);
if (SUCCEEDED(hr) == FALSE || m_pGazo == NULL)
{
    pstm->Release();
    AfxMessageBox(_T("画像ロード失敗のため\n画像の表示・印刷を中止します"));
    return;
}
pstm->Release();
if (m_pGazo != NULL)
{
    CDC *pScreenDC = GetDC();
    int dpi = pScreenDC->GetDeviceCaps(LOGPIXELSX);
    ReleaseDC(pScreenDC);
    long hmWidth, hmHeight;
    m_pGazo->get_Width(&hmWidth);
    m_pGazo->get_Height(&hmHeight);
    HDC hDC = pDC->GetSafeHdc();
    m_pGazo->Render(hDC, 0, 0,
        (long)(hmWidth/25.40*dpi/100), -(long)(hmHeight/25.40*dpi/100),
        0, hmHeight, hmWidth, -hmHeight, NULL);
}

【子ダイアログに表示している部分】
CFile f; から pstm->Release(); までは上記と同じコードを OnInitDialog() の中に
書いています。

void CDlgHyouji::OnPaint() の中です。

if (m_pGazo != NULL)
{
    long hmWidth, hmHeight;
    m_pGazo->get_Width(&hmWidth);
    m_pGazo->get_Height(&hmHeight);
    CSize size(hmWidth, hmHeight);
    CDC* pDC = GetDC();
    pDC->HIMETRICtoDP(&size);
    ReleaseDC(pDC);
    HDC hDC = ::GetDC(m_hWnd);
    m_pGazo->Render(hDC, 0, 0,
        size.cx, size.cy,
        0, hmHeight, hmWidth, -hmHeight, NULL);
    ::ReleaseDC(m_hWnd, hDC);
}

お気づきのことがありましたら、教えてください。

subaru 2008/12/27(土) 18:49:22
ダイアログのコードの方の
pDC->HIMETRICtoDP(&size);
は内部的に印刷側と同じ計算が行われていると思います。

画面側のm_pGazo->Renderと印刷側のm_pGazo->Render
に渡している画像サイズは同じになっていませんか?

そうであれば単にピクセル値をMM_LOENGLISHモードで
描画しているということになります。

画面側の物理解像度がたまたま100DPIに近い値だから、
ほぼ同サイズで印刷されるのではないでしょうか。

Ti Amo 2008/12/27(土) 20:37:19
> 画面側のm_pGazo->Renderと印刷側のm_pGazo->Render
> に渡している画像サイズは同じになっていませんか?

その通りです。同じ値となっています。

最初は印刷側でも
pDC->HIMETRICtoDP(&size);
として、これを Render に使いましたが、サイズが表示側と一致しないので
困った訳です。
そのときの pDC がプリンタDCであったことに気づいていなかったのです。

今や
pScreenDC->HIMETRICtoDP(&size);
とすれば何も問題ありません。
物凄い遠回りをしました。
お陰で勉強になりました。

subaru様のご指導で、すっきりしたコードになりました。
ありがとうございました。

古いスレッドには追加発言できません。