potisanのプログラミングメモ

プログラミング素人です。昔の自分を育ててくれたネット情報に少しでも貢献できるよう、情報を貯めていこうと思っています。Windows環境のC++やC#がメインです。

C++&Win API WICを使った画像ファイルからDIBセクションの作成

WIC(Windowsイメージングコンポーネント)を使って画像ファイルからDIBセクションを作成する方法です。WICはGDIでは扱いにくいアイコンやGIF、PNGを統一された方法でビットマップに変換できます。

以下のコードはC++20で動作するウィンドウアプリケーションで、ドロップした画像ファイルをグレースケールに変換してウィンドウ中央に表示します。MicrosoftのWILを使用しています。

WIC自体の使い方に不慣れなので間違えていたらご指摘ください。

#include <bit>
#include <string>
#include <vector>

#define STRICT
#define NOMINMAX
#include <Windows.h>
#include <wincodec.h>

#include "wil/com.h"

const WCHAR g_MainWindowClassName[] = L"MainWindow";
const WCHAR g_MainWindowTitle[] = L"Project1";

// COMの初期化・解放
auto coinit = wil::CoInitializeEx();

HINSTANCE g_hinstApp{};
wil::com_ptr<IWICImagingFactory> g_pwicImgFactory;
wil::unique_hbitmap g_hbmp{};
UINT g_bmpWidth{};
UINT g_bmpHeight{};
LPVOID g_bmpPixels{};

static inline constexpr UINT GetBitmapStride(UINT width, UINT bits) noexcept { return ((width * bits / 8 + 3) / 4) * 4; }

static HBITMAP CreateDIBitmapFromFileByWIC(IWICImagingFactory* pfactory, LPCWSTR filename, UINT frame, REFWICPixelFormatGUID formatGuid, UINT& w, UINT& h, LPVOID* pppixels);

static inline constexpr LONG GetWidth(const RECT& rc) noexcept { return rc.right - rc.left; }
static inline constexpr LONG GetHeight(const RECT& rc) noexcept { return rc.bottom - rc.top; }
static inline constexpr SIZE GetSize(const RECT& rc) noexcept { return { GetWidth(rc), GetHeight(rc) }; }
static inline constexpr RECT SetSize(RECT rc, const SIZE& size)
{
    rc.right = rc.left + size.cx;
    rc.bottom = rc.top + size.cy;
    return rc;
}

static inline constexpr SIZE FitSize(SIZE size, UINT maxWidth, UINT maxHeight) noexcept
{
    if (maxWidth == 0 || maxHeight == 0)
        return {};

    if (size.cx != maxWidth)
    {
        size.cy = (UINT)(size.cy / (double)size.cx * maxWidth);
        size.cx = maxWidth;
    }
    if (size.cy != maxHeight)
    {
        size.cx = (UINT)(size.cx / (double)size.cy * maxHeight);
        size.cy = maxHeight;
    }
    return size;
}

static inline constexpr SIZE FitSize(SIZE size, const SIZE& maxSize) noexcept
{
    return FitSize(size, maxSize.cx, maxSize.cy);
}

static inline constexpr RECT CenterRect(SIZE size, const RECT& whole) noexcept
{
    RECT rc;
    rc.left = (GetWidth(whole) - size.cx) / 2;
    rc.top = (GetHeight(whole) - size.cy) / 2;
    rc.right = rc.left + size.cx;
    rc.bottom = rc.top + size.cy;
    return rc;
}

LRESULT CALLBACK MainWindow_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CREATE:
        DragAcceptFiles(hwnd, TRUE);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_DROPFILES:
        {
            auto hdrop = std::bit_cast<HDROP>(wParam);

            auto required = DragQueryFileW(hdrop, 0, nullptr, 0);
            std::wstring s(required, L'\0');
            DragQueryFileW(hdrop, 0, s.data(), required + 1);
            try {
                g_hbmp.reset(CreateDIBitmapFromFileByWIC(g_pwicImgFactory.get(), s.data(), 0,
                    GUID_WICPixelFormat24bppBGR, g_bmpWidth, g_bmpHeight, &g_bmpPixels));

                // TODO:以下をコメントアウトするとグレースケール処理を無効化できます。
                auto stride = GetBitmapStride(g_bmpWidth, 24);
                UINT yx = 0;
                auto ppixels = std::bit_cast<BYTE*>(g_bmpPixels);
                for (UINT y = 0; y < g_bmpHeight; y++)
                {
                    UINT z = yx;
                    for (UINT x = 0; x < g_bmpWidth; x++)
                    {
                        auto r = ppixels[z + 2];
                        auto g = ppixels[z + 1];
                        auto b = ppixels[z + 0];
                        auto gray = (r + g + b) / 3;

                        ppixels[z + 2] = gray;
                        ppixels[z + 1] = gray;
                        ppixels[z + 0] = gray;

                        z += 3;
                    }
                    yx += stride;
                }
            }
            catch (const std::exception&) {
                g_hbmp.reset();
                g_bmpPixels = nullptr;
                MessageBoxW(hwnd, L"JPEGファイルの読み込みに失敗しました。", L"エラー", MB_ICONINFORMATION);
            }
            DragFinish(hdrop);

            InvalidateRect(hwnd, nullptr, TRUE);
        }
        return 0;

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

            // ウィンドウの大きさに合わせて描画する。
            wil::unique_hdc hdcSrc(CreateCompatibleDC(ps.hdc));
            SelectObject(hdcSrc.get(), g_hbmp.get());

            RECT rcClient;
            GetClientRect(hwnd, &rcClient);
            SIZE bmpSize = FitSize(SIZE(g_bmpWidth, g_bmpHeight), GetSize(rcClient));
            auto center = CenterRect(bmpSize, rcClient);
            SetStretchBltMode(ps.hdc, HALFTONE); // 画質をきれいにする。
            StretchBlt(ps.hdc, center.left, center.top, GetWidth(center), GetHeight(center),
                hdcSrc.get(), 0, 0, g_bmpWidth, g_bmpHeight, SRCCOPY);

            EndPaint(hwnd, &ps);
        }
        return 0;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int cmdShow)
{
    g_hinstApp = hInstance;

    // WICの準備
    g_pwicImgFactory = wil::CoCreateInstance<IWICImagingFactory>(CLSID_WICImagingFactory);

    // メインウィンドウの登録と作成
    WNDCLASSEXW wcex{};
    wcex.cbSize = sizeof(WNDCLASSEXW);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = MainWindow_WndProc;
    wcex.hInstance = g_hinstApp;
    wcex.hIcon = LoadIconW(nullptr, IDI_APPLICATION);
    wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
    wcex.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
    wcex.lpszClassName = g_MainWindowClassName;
    if (RegisterClassExW(&wcex) == 0)
        return -1;

    auto hwndMain = CreateWindowExW(
        WS_EX_OVERLAPPEDWINDOW,
        g_MainWindowClassName,
        g_MainWindowTitle,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        nullptr,
        nullptr,
        g_hinstApp,
        nullptr);
    ShowWindow(hwndMain, cmdShow);
    UpdateWindow(hwndMain);

    MSG msg;
    int iret = 0;
    while ((iret = GetMessage(&msg, nullptr, 0, 0) != 0 && iret != -1))
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return iret;
}

static HBITMAP CreateDIBitmapFromFileByWIC(IWICImagingFactory* pfactory, LPCWSTR filename, UINT frame, REFWICPixelFormatGUID formatGuid, UINT& w, UINT& h, LPVOID* pppixels)
{
    THROW_IF_NULL_ALLOC(pfactory);

    // ビットマップの読み込み
    wil::com_ptr<IWICBitmapDecoder> pwicBmpDecoder;
    wil::com_ptr<IWICBitmapFrameDecode> pframe;
    THROW_IF_FAILED(pfactory->CreateDecoderFromFilename(
        filename,
        nullptr,
        GENERIC_READ,
        WICDecodeMetadataCacheOnDemand,
        &pwicBmpDecoder));
    THROW_IF_FAILED(pwicBmpDecoder->GetFrame(frame, &pframe));

    // formatGuid形式への変換
    wil::com_ptr<IWICFormatConverter> pconverter;
    wil::com_ptr<IWICBitmap> pbmp2;
    THROW_IF_FAILED(pfactory->CreateFormatConverter(&pconverter));
    THROW_IF_FAILED(pconverter->Initialize(
        pframe.get(),
        formatGuid,
        WICBitmapDitherTypeNone,
        nullptr,
        0,
        WICBitmapPaletteTypeCustom));
    THROW_IF_FAILED(pfactory->CreateBitmapFromSource(pconverter.get(), WICBitmapCacheOnDemand, &pbmp2));

    // 上下反転
    wil::com_ptr<IWICBitmapFlipRotator> protator;
    THROW_IF_FAILED(pfactory->CreateBitmapFlipRotator(&protator));
    THROW_IF_FAILED(protator->Initialize(pbmp2.get(), WICBitmapTransformFlipVertical));

    // DIBセクションの作成
    THROW_IF_FAILED(protator->GetSize(&w, &h));
    auto stride = GetBitmapStride(w, 24);
    BITMAPINFO bmi = { {sizeof(BITMAPINFOHEADER), w, h, 1, 24, BI_RGB} };
    auto hbmp = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, pppixels, nullptr, 0);
    WICRect rc{ 0, 0, w, h };
    auto hr = protator->CopyPixels(&rc, stride, stride * h, std::bit_cast<BYTE*>(*pppixels));
    if (SUCCEEDED(hr)) {
        return hbmp;
    }
    else {
        DeleteObject(hbmp);
        THROW_HR(hr);
    }
}

参考