Search code examples
c++winapidirect2dwic

Windows API Rendering an Image From File (Beginner)


I am trying to understand how to load and render an image from a file using the Windows API, Direct2D, and Visual C++. I have been more or less attempting to follow an MSDN article on this topic. I am new to both C++ (experienced in C) and the Windows API.

I wrote 3 functions.

HRESULT imagefactorysetup(IWICImagingFactory * pImageFactory)
{
    HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, (LPVOID *) &pImageFactory);

    return hr;
}

HRESULT imageload(LPCWSTR filename, IWICImagingFactory * pImageFactory, IWICBitmapFrameDecode * pFrame)
{
    IWICBitmapDecoder * pDecoder = NULL;
    HRESULT hr = pImageFactory->CreateDecoderFromFilename(filename, NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder);

    if (SUCCEEDED(hr))
        hr = pDecoder->GetFrame(0, &pFrame);

    //Format convert the frame to 32bppPBGRA
    IWICFormatConverter * pFormatConverter = NULL;
    if (SUCCEEDED(hr))
    {
        SafeRelease(&pFormatConverter);
        hr = pImageFactory->CreateFormatConverter(&pFormatConverter);
    }

    if (SUCCEEDED(hr))
        hr = pFormatConverter->Initialize(pFrame, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.f, WICBitmapPaletteTypeCustom);

    return hr;
}

HRESULT imagerender(HWND hWnd, IWICBitmapFrameDecode * pFrame, int x, int y)
{
    //Create a D2D render target properties
    D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties = D2D1::RenderTargetProperties();

    //Set the DPI to be the default system DPI to allow direct mapping
    //between image pixels and desktop pixels in different system DPI settings
    renderTargetProperties.dpiX = DEFAULT_DPI;
    renderTargetProperties.dpiY = DEFAULT_DPI;

    //Create a D2D render target
    D2D1_SIZE_U sz = D2D1::SizeU(MAINWINDOWWIDTH, MAINWINDOWHEIGHT); //Change size

    ID2D1Factory * pD2DFactory;

    HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory1), NULL, (LPVOID *) &pD2DFactory);

    ID2D1RenderTarget * pRenderTarget;
    //renderTargetProperties, D2D1::HwndRenderTargetProperties(hWnd, sz), &pRenderTarget);
    hr = pD2DFactory->CreateHwndRenderTarget(&renderTargetProperties, D2D1::HwndRenderTargetProperties(hWnd, sz), &pRenderTarget);


    ID2D1Bitmap * pD2DBitmap = NULL;

    pRenderTarget->CreateBitmapFromWicBitmap(pFrame, NULL, &pD2DBitmap);

    D2D1_SIZE_F size = pD2DBitmap->GetSize();
    D2D1_POINT_2F origin = D2D1::Point2F((float) x, (float) y);

    if (pD2DBitmap)
        pRenderTarget->DrawBitmap(pD2DBitmap, D2D1::RectF(origin.x, origin.y, origin.x + size.width, origin.y + size.height));

    return hr;
}

Question:

1) The following line gives me an error. I tried reading some documentation on MSDN but am unsure what the issue is.

hr = pD2DFactory->CreateHwndRenderTarget(&renderTargetProperties, D2D1::HwndRenderTargetProperties(hWnd, sz), &pRenderTarget);

Error:

IntelliSense: no instance of overloaded function "ID2D1Factory::CreateHwndRenderTarget" matches the argument list
            argument types are: (D2D1_RENDER_TARGET_PROPERTIES *, D2D1_HWND_RENDER_TARGET_PROPERTIES, ID2D1RenderTarget **)
            object type is: ID2D1Factory 68 18

2) In general, is there a more idiomatic / efficient way of approaching the problem of rendering an image from a file onto a window than what I have attempted? My previous programming experience has been strictly in C.


Solution

  • No problem. You can use GDI+ to load any image that windows supports natively. You can then use GDI to draw it.

    Here's a short example of drawing a (transparent) PNG to the background of a dialog. I've built it using MinGW32 and Code::Blocks. You'll need to link the msimg32 and gdiplus libraries to make use of AlphaBlend and the Bitmap class (and the functions to init/shutdown GDI+).

    Points that may be worth mentioning are:

    • mLoadImage will load anything that windows will show in Windows Photo Viewer (// BMP, GIF, JPEG, PNG, TIFF, Exif, WMF, and EMF) - it uses the Bitmap class, as found in Gdiplus.
    • The WM_ERASEBKGND message comes with wParam holding a device context that you can draw straight into - that's why there's no need to get one by usig BeginPaint, as we do in response to a WM_PAINT message.
    • You can use BitBlt or StretchBlt for images that dont contain transparent areas.

    Main.cpp

    #define WINVER 0x0500       // for AlphaBlend
    #include <windows.h>
    #include <commctrl.h>
    #include <stdio.h>
    #include <gdiplus.h>
    #include "resource.h"
    
    using namespace Gdiplus;
    HINSTANCE hInst;
    
    
    void setClientSize(HWND mHwnd, int width, int height)
    {
        RECT wndRect, clientRect, mRect;
        int clientX, clientY, windowX, windowY, difX, difY;
    
        GetWindowRect(mHwnd, &wndRect);
        GetClientRect(mHwnd, &clientRect);
        clientX = clientRect.right - clientRect.left;
        clientY = clientRect.bottom - clientRect.top;
    
        windowX = wndRect.right - wndRect.left;
        windowY = wndRect.bottom - wndRect.top;
    
        difX = windowX - clientX;
        difY = windowY - clientY;
    
    //    GetWindowRect(mHwnd, &mRect);
        POINT topLeft = {wndRect.left, wndRect.top};
        //  ScreenToClient(mParentHwnd, &topLeft);
    
        SetWindowPos(mHwnd, HWND_TOP, topLeft.x, topLeft.y, width+difX, height+difY, SWP_NOZORDER);
    }
    
    HBITMAP mLoadImg(wchar_t *filename)
    {
        Bitmap mBitmap(filename,false);
        HBITMAP result;
        mBitmap.GetHBITMAP(0x00000000, &result);
        return result;
    }
    
    void onPaint(HWND hwnd, HBITMAP bkg)
    {
        HDC memDC, hdc;
        PAINTSTRUCT ps;
        HBITMAP old;
        RECT clientRect;
        int width, height;
    
        hdc = BeginPaint(hwnd, &ps);
    
        GetClientRect(hwnd, &clientRect);
        width = clientRect.right - clientRect.left;
        height = clientRect.bottom - clientRect.top;
    
        memDC = CreateCompatibleDC(NULL);
        old = (HBITMAP)SelectObject(memDC, bkg);
    
    
        byte alpha = 255;
        BLENDFUNCTION bf = {AC_SRC_OVER,0,alpha,AC_SRC_ALPHA};
        AlphaBlend(hdc, 0,0,width,height, memDC, 0,0, width,height, bf);
    // try the below instead of AlphaBlend - they each rely on the fact I've resized the
    // client area to the same size as the image I'll draw on it.
    //    BitBlt(hdc, 0,0, clientRect.right,clientRect.bottom, memDC, 0,0, SRCCOPY);
    
        SelectObject(memDC, old);
        DeleteDC(memDC);
        EndPaint(hwnd, &ps);
    }
    
    BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        static HBITMAP mBkg;
        switch(uMsg)
        {
            case WM_INITDIALOG:
            {
                mBkg = mLoadImg(L"wiki.png");
                BITMAP bm;
                GetObject(mBkg, sizeof(bm), &bm);
                setClientSize(hwndDlg, bm.bmWidth, bm.bmHeight);
            }
            return TRUE;
    
            case WM_ERASEBKGND:
            {
                RECT clientRect;
                HBRUSH bkgBrush = CreateSolidBrush( RGB(255,0,0) );
                GetClientRect(hwndDlg, &clientRect);
                FillRect( (HDC)wParam, &clientRect, bkgBrush);
                DeleteObject(bkgBrush);
            }
            return 1;
    
            case WM_PAINT:
                onPaint(hwndDlg, mBkg);
                return 0;
    
            case WM_CLOSE:
            {
                EndDialog(hwndDlg, 0);
            }
            return TRUE;
    
            case WM_COMMAND:
            {
                switch(LOWORD(wParam))
                {
                }
            }
            return TRUE;
        }
        return FALSE;
    }
    
    
    
    int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
    {
        GdiplusStartupInput gdiplusStartupInput;
        ULONG_PTR gdiplusToken;
        GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    
        hInst=hInstance;
        InitCommonControls();
        int retVal = DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgMain);
    
        GdiplusShutdown(gdiplusToken);
    
        return retVal;
    }
    

    resource.h

    #ifndef IDC_STATIC
    #define IDC_STATIC (-1)
    #endif
    
    #define DLG_MAIN                                100
    

    resource.rc

    // Generated by ResEdit 1.6.2
    // Copyright (C) 2006-2014
    // http://www.resedit.net
    
    #include <windows.h>
    #include <commctrl.h>
    #include <richedit.h>
    #include "resource.h"
    
    
    
    
    //
    // Dialog resources
    //
    DLG_MAIN DIALOG 0, 0, 186, 95
    STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_THICKFRAME | WS_SYSMENU
    EXSTYLE WS_EX_WINDOWEDGE
    CAPTION "Dialog"
    FONT 8, "Ms Shell Dlg"
    {
    }
    

    Wiki.png

    enter image description here

    Result enter image description here