質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.13%

Visual C++ で、HDC に表示された画像を PNG で保存したい

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 917

概要

いつも御世話になっております。Windows10 で Visual Studio 2019 の C++ を用いて、デクストップ・アプリを作りたいと考えています。以下に引用するようなコードで、次のような作業を行っています。

  1. PNGファイルを裏画面に Load する
  2. 裏画面に、正方形を作図する
  3. 裏画面を、表画面に表示する

このようにして表示した画面を、ふたたびPNGファイルに保存しようと考えています。「BMP画像をPNG画像に変換する」等の情報を参考にして、いろいろと考えているのですが、どうにもうまくいきません。

現在のコード

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;

            case IDM_SAVE:
                // ここで画面を保存したい
                break;

            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            RECT recDisp;
            GetClientRect(hWnd, &recDisp);    // クライアントを取得
            int cx = recDisp.right;        // クライアントのヨコ
            int cy = recDisp.bottom;    // クライアントのタテ

            // PNGファイルを読み込む
            CString filePath;
            CImage* img = new CImage();
            filePath = L"sample.png";
            img->Load(filePath);
            int width = img->GetWidth();
            int height = img->GetHeight();

            // ここから実際の描写
            HDC hdc = GetDC(hWnd);
            HDC mem = CreateCompatibleDC(hdc);                    //HDCを取得
            HBITMAP bmp = CreateCompatibleBitmap(hdc, cx, cy);    //HBITMAPを取得
            HBITMAP obmp = (HBITMAP)SelectObject(mem, bmp);        //HBITMAPを関連付ける

            HPEN hpen = CreatePen(PS_SOLID, 5, RGB(0x00, 0x7f, 0xff));

            // 裏画面に描写する
            FillRect(mem, &recDisp, br);
            img->Draw(mem, 0, 0, cx, ( cx * height ) / width, 0, 0, width, height);

            HPEN ohpen = (HPEN)SelectObject(mem, hpen);

            MoveToEx(mem, 50, 50,NULL);
            LineTo(mem, 150, 50);
            LineTo(mem, 150, 150);
            LineTo(mem, 50, 150);
            LineTo(mem, 50, 50);
            SelectObject(mem, ohpen);
            DeleteObject(hpen);

            TextOut(mem, 10, 10, wss.c_str(), (int)wss.length());

            // 表画面にコピー
            PAINTSTRUCT ps;
            BeginPaint(hWnd, &ps);
            BitBlt(hdc, 0, 0, cx, cy, mem, 0, 0, SRCCOPY);
            EndPaint(hWnd, &ps);

            // あれこれ削除する。
            SelectObject(mem, obmp);
            DeleteObject(bmp);
            DeleteDC(mem);
        }
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

エラー表示

上のコードの状態では、エラーは表示されません。しかし、「BMP画像をPNG画像に変換する」に掲載されている次のコード

CLSID   encoderClsid;
Status  stat;
GetEncoderClsid(L"image/png", &encoderClsid);
stat = image->Save(L"sample2.png", &encoderClsid, NULL);

を、冒頭の「// ここで画面を保存したい」の所に挿入すると、次のようなエラーが表示されます。

error C2065: 'Status': 定義されていない識別子です。
error C2146: 構文エラー: ';' が、識別子 'stat' の前に必要です。
warning C4551: 関数呼び出しに引数リストがありません。
error C3861: 'GetEncoderClsid': 識別子が見つかりませんでした
error C2065: 'image': 定義されていない識別子です。

お願い

case IDM_SAVE:
// ここで画面を保存したい
break;

のところで、画像を保存するには、どのようにすればよろしいでしょうか。なお、そもそも、WM_PAINT でない場所で「画像を保存する」ということは可能なのだろうか、という疑問もあります。よろしく御回答くださいますよう、お願い申し上げます。

追加

一番最後の「疑問」についてですが、

  1. 画面に表示させたら、その内容を Bitmap 領域に保存しておく
    (単なるコピー)。
  2. 必要な場合に、そのBitmapをPNGに変換して保存する

という方法で可能かな、と思いました。

追加その2

CImage で、あれこれ作業するよりも、openCV を使って作業した方が楽なのかなという気がしてきました。(しかし、OpenCV は、まだまったく使ったことがありません)。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+1

既にMFCのCImageクラスを利用しているのですから、これを使ってもPNGファイルで保存できます。

ご提示のコードのWM_PAINT中の処理でBitBltで画面にビットマップ転送していますが、この段階で描画したい内容がHBITMAP bmpに完成しているということですから、このHBITMAP bmpを利用し、CImageに一時的にAttachした後、CImage.SaveでPNGファイル指定して保存してしまえば良いです。

CImage Class - Microsoft Docs

// WM_PAINT中の処理
...省略
    // 表画面にコピー
    PAINTSTRUCT ps;
    BeginPaint(hWnd, &ps);
    BitBlt(hdc, 0, 0, cx, cy, mem, 0, 0, SRCCOPY);
    EndPaint(hWnd, &ps);

    // C:\temp\sampleout.png にPNGファイルとして保存
    const TCHAR outFileName[] = _T("C:/temp/sampleout.png");
    CImage imageForSave;

    // HBITMAP bmp = CreateCompatibleBitmap(hdc, cx, cy);
    // で作成したビットマップハンドルをアタッチ
    imageForSave.Attach(bmp);
    imageForSave.Save(outFileName, Gdiplus::ImageFormatPNG);
    imageForSave.Detach();

    // あれこれ削除する。
    SelectObject(mem, obmp);
    DeleteObject(bmp);
    DeleteDC(mem);

...省略

Windows 7 (64ビット)上で、Visual Studio 2019 で動作確認しましたが、描画内容がPNGファイルで保存されました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/14 17:07

    御回答いただき、ありがとうございました。
    実際のところ、

    CImage saveImage;
    saveImage.Attach(bmp);
    saveImage.Save(L"test.bmp");
    saveImage.Save(L"test.jpg");
    saveImage.Save(L"test.png");
    saveImage.Detach();

    このようにして、画像を保存することができました。
    ありがとうございました。

    キャンセル

+1

ソースが一部しかないので推測ですが

#include <gdiplus.h>
using namespace Gdiplus;


これがちゃんとヘッダなり、ソースに入っていないのではないでしょうか。StatusはMicrosoft Dev Centerのサイトに説明がありますが、GDI+で使用するenumです。
タイプミスも含めてチェックしてみてください。

GDI+を使うときにはほかにもいろいろお作法があります。それが足りないとビルドが通っても実行時にうまくいかなかったりしますので、そちらも合わせて調査されることをお勧めします。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/14 10:18

    御回答いただき、ありがとうございます。
    ソースをすべて示すには、どうすればよいのでしょうか。cpp ファイルだけでなく、リソースファイルもあるので、「どういう書き方をすればよいのか」にいつも悩んでいます。

    キャンセル

  • 2019/12/14 11:12

    インクルードしているヘッダファイル部分の情報や、独自に定義しているヘッダファイル(*.h)があればそれも一緒に提示されると良いと思います。

    キャンセル

  • 2019/12/14 11:17

    御回答いただき、ありがとうございました。
    独自に定義しているヘッダファイルは、ありません。インクルードしているヘッダファイル部分は、以下のとおりです

    #include "framework.h"
    #include "BitBlt_CImage.h"
    #include "strconv.h"
    #include "atlimage.h"
    #include "gdiplus.h"
    #include <string>
    using namespace Gdiplus;
    using namespace std;

    キャンセル

+1

error C2065: 'Status': 定義されていない識別子です。

gdiplustypes.h を include しては如何かと。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/14 10:41

    御回答いただき、ありがとうございます。
    Status については、どうやら解決したようです。
    しかし、肝心の Save ができません。もう少し、頑張ってみます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.13%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る