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

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

ただいまの
回答率

89.23%

WinAPI AlphaBlendの再描画

解決済

回答 2

投稿

  • 評価
  • クリップ 2
  • VIEW 1,008

Weapon

score 87

 前提・実現したいこと

前回の質問:WinAPI AlphaBlendのDIB定義を参考に私なりに書いてみました。しかし背景の再描画がうまくいかない問題が解決できません。
下記のコードでWNDCLASSEXで背景を(HBRUSH)WHITE_BRUSHとすると初期表示は
初期表示
となりますがリサイズすると再描画後
周囲は再描画されないうえ、画像(透過の赤格子)がほかの画像(黄色の非透過)と重なっていない部分は背景の白ではなくおそらく黒とブレンドされます。
(HBRUSH)GRAY_BRUSHや(HBRUSH)BLACK_BRUSHの場合は正常にその背景色にブレンドされ、周囲の再描画もされます。
FillRect(hdc, &ps.rcPaint, WHITE_BRUSH);
でWM_PAINT内で塗れば正常には動きますが仕様等的に正しい使い方ではないのでしょうか?それともWM_PAINT内の解放等に誤りがあるのでしょうか?

コーディング上の指摘もありましたらお願いします。

 該当のソースコード

#define UNICODE

#include <windows.h>
#pragma comment(lib, "msimg32.lib")


HINSTANCE hInst;

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

const wchar_t CLASS_NAME[] = L"class name";
const wchar_t TITLE[] = L"Alpha transparent window";

struct ARGB
{
    BYTE Blue;
    BYTE Green;
    BYTE Red;
    BYTE Alpha;
};

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR nCmdline, int nCmdShow) {
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(nCmdShow);

    hInst = hInstance;

    WNDCLASSEX wc = {
    sizeof(WNDCLASSEX),CS_HREDRAW | CS_VREDRAW,WindowProc,
    0,0,hInstance,
    0,0,(HBRUSH)WHITE_BRUSH,
    0,CLASS_NAME,0 };

    RegisterClassEx(&wc);

    HWND hwnd = CreateWindowEx(
        0, CLASS_NAME, TITLE, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 960, 540,
        0, 0, hInstance, 0
    );

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg = {};

    while (GetMessage(&msg, 0, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_CREATE:
        break;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        //FillRect(hdc, &ps.rcPaint, WHITE_BRUSH);

        BITMAPINFO bmpio;

        bmpio.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        bmpio.bmiHeader.biWidth = 160;
        bmpio.bmiHeader.biHeight = 90;
        bmpio.bmiHeader.biPlanes = 1;
        bmpio.bmiHeader.biBitCount = 32;
        bmpio.bmiHeader.biCompression = BI_RGB;
        bmpio.bmiHeader.biSizeImage = 160 * 90 * sizeof(ARGB);

        DWORD *dib = (DWORD*)calloc(sizeof(DWORD) * 160 * 90, sizeof(sizeof(DWORD) * 160 * 90));

        for (int i = 0; i < 160; i++)for (int j = 0; j < 90; j++)dib[i + j * 160] = 0xffff00;
        StretchDIBits(
            hdc, 10, 10, 160, 90, 0, 0, 160, 90, dib, &bmpio, DIB_RGB_COLORS, SRCCOPY
        );

        StretchDIBits(
            hdc, 200, 200, 160*2, 90*2, 0, 0, 160, 90, dib, &bmpio, DIB_RGB_COLORS, SRCCOPY
        );

        BITMAPINFO bmpi;
        void* pv;

        bmpi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        bmpi.bmiHeader.biWidth = 160;
        bmpi.bmiHeader.biHeight = 90;
        bmpi.bmiHeader.biPlanes = 1;
        bmpi.bmiHeader.biBitCount = 32;
        bmpi.bmiHeader.biCompression = BI_RGB;
        bmpi.bmiHeader.biSizeImage = 160 * 90 * sizeof(ARGB);

        HDC hdcimg = CreateCompatibleDC(hdc);
        HBITMAP hbmp = CreateDIBSection(hdc, &bmpi, DIB_RGB_COLORS, &pv, NULL, 0);
        SelectObject(hdcimg, hbmp);

        GdiFlush();

        BYTE* pbits = reinterpret_cast<BYTE*>(pv);
        for (size_t x = 0; x < 160; x++)
        {
            for (size_t y = 0; y < 90; y++)
            {
                ARGB* line = reinterpret_cast<ARGB*>(pbits + y * 160 * sizeof(ARGB) + x * sizeof(ARGB));
                if ((x / 10) % 2 == 0) {
                    line->Alpha = 0x7f;
                    line->Red = 0xff;
                    line->Blue = 0x0;
                    line->Green = 0x0;
                }
                    else
                    {
                        line->Alpha = 0x0;
                        line->Red = 0xff;
                        line->Blue = 0x0;
                        line->Green = 0x0;
                    }
            }
        }


        BLENDFUNCTION bf;
        bf.BlendOp = AC_SRC_OVER;
        bf.BlendFlags = 0;
        bf.SourceConstantAlpha = 0xff;
        bf.AlphaFormat = AC_SRC_ALPHA;

        AlphaBlend(hdc, 10, 10, 160*3, 90*3, hdcimg, 0, 0, 160, 60, bf);

        SelectObject(hdcimg, hbmp);
        DeleteDC(hdcimg);
        DeleteObject(hbmp);
        EndPaint(hwnd, &ps);
    }

        break;

    case WM_COMMAND:
        InvalidateRect(hwnd, NULL, TRUE);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    }
    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}

 補足情報

VisualStudio2017 Community
Windows10

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 2

checkベストアンサー

+1

かなり混乱されているようですが、原因は一般的な GDI 関数の HBRUSH 値と RegisterClassEx で登録された HBRUSH 値の扱いが異なることにあります。

tagWNDCLASSEXW structure の説明にあるように hbrBackgroud には COLOR_??? 定数に +1 した値を指定することが可能です。

#define COLOR_SCROLLBAR         0
#define COLOR_BACKGROUND        1
#define COLOR_ACTIVECAPTION     2
#define COLOR_INACTIVECAPTION   3
#define COLOR_MENU              4
#define COLOR_WINDOW            5
#define COLOR_WINDOWFRAME       6
#define COLOR_MENUTEXT          7
#define COLOR_WINDOWTEXT        8
#define COLOR_CAPTIONTEXT       9
#define COLOR_ACTIVEBORDER      10
#define COLOR_INACTIVEBORDER    11
#define COLOR_APPWORKSPACE      12
#define COLOR_HIGHLIGHT         13
#define COLOR_HIGHLIGHTTEXT     14
#define COLOR_BTNFACE           15
#define COLOR_BTNSHADOW         16
#define COLOR_GRAYTEXT          17
#define COLOR_BTNTEXT           18
#define COLOR_INACTIVECAPTIONTEXT 19
#define COLOR_BTNHIGHLIGHT      20
#define COLOR_3DDKSHADOW        21
#define COLOR_3DLIGHT           22
#define COLOR_INFOTEXT          23
#define COLOR_INFOBK            24
#define COLOR_HOTLIGHT          26
#define COLOR_GRADIENTACTIVECAPTION 27
#define COLOR_GRADIENTINACTIVECAPTION 28
#define COLOR_MENUHILIGHT       29
#define COLOR_MENUBAR           30
#define COLOR_DESKTOP           COLOR_BACKGROUND
#define COLOR_3DFACE            COLOR_BTNFACE
#define COLOR_3DSHADOW          COLOR_BTNSHADOW
#define COLOR_3DHIGHLIGHT       COLOR_BTNHIGHLIGHT
#define COLOR_3DHILIGHT         COLOR_BTNHIGHLIGHT
#define COLOR_BTNHILIGHT        COLOR_BTNHIGHLIGHT


GDI 関数のうち HBRUSH を受け取るものも基本的にはこの動作(COLOR_??? + 1を指定可能)になります。
FillRect

COLOR_定数は 0 から始まるため、+1 したものを指定するため、一番最小の値は 1 となります。一方、Stock Object の定数は以下のようになっており

#define WHITE_BRUSH         0
#define LTGRAY_BRUSH        1
#define GRAY_BRUSH          2
#define DKGRAY_BRUSH        3
#define BLACK_BRUSH         4

WHITE_BRUSH が最小の 0 となります。WNDCLASSEX の hbrBackground に 0 つまりは NULL を指定した場合、先ほどのページに以下のように記載されています。

When this member is NULL, an application must paint its own background whenever it is requested to paint in its client area. To determine whether the background must be painted, an application can either process the WM_ERASEBKGND message or test the fErase member of the PAINTSTRUCT structure filled by the BeginPaint function.

この場合、WM_ERASEBKGND によって背景が再描画されないため、提示されている事象が発生しています。一方、ドキュメントには記載されていませんが、FillRect の HBRSUH に NULL を指定すると白で塗りつぶされます。この動作の差が WHITE_BRUSH 値を指定した際の動作の差となって表れています。

自動的に背景色を白く塗りつぶすには yominet さんが挙げられているように GetStockObject を使用するか、(HBRUSH)(COLOR_WINDOW + 1) を指定するようにしてください。


他の指摘点としては・・・

 1.メモリリーク

DWORD *dib = (DWORD*)calloc(sizeof(DWORD) * 160 * 90, sizeof(sizeof(DWORD) * 160 * 90));

calloc で割り当てた dib を free していません。

 2.GdiFlush の使い方

GdiFlush が必要なのは GDI 操作が完了していることを保証するためです。
今回の場合、作成した DIB セクションに対して GDI の描画処理を呼び出していないので、
このタイミングで呼び出すことは不要です。

 3.呼び出す関数が統一されていない

DispatchMessageW
DefWindowProcW

UNICODE 版の関数を直接呼び出している部分とそうではない部分があります。
ネットに公開されているソースでは W を付けないことが多いかと思いますが、それに合わせる必要はなく自分のルールとして付けるか付けないかのどちらかに統一するようにすることをお勧めします。VC++ のコード補完では A 付、W 付、何もなしの 3 種類が候補に挙がるはずですので、選択するのはそれほど難しくはないと思います。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+1

WM_PAINTの前にウィンドウの生成に変なところがあります

    WNDCLASSEX wc = {
    sizeof(WNDCLASSEX),CS_HREDRAW | CS_VREDRAW,WindowProc,
    0,0,hInstance,
    0,0,(HBRUSH)WHITE_BRUSH,
    0,CLASS_NAME,0 };

ここの
(HBRUSH)WHITE_BRUSH

(HBRUSH)::GetStockObject(WHITE_BRUSH)
に変えてみてください

#「WHITE_BRUSH」はただの数値であって、HBRUSHではない

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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