ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Direct2D] 비트맵 이미지 그리기
    C++/미분류 2024. 2. 26. 17:52

    지난 글 ([Direct2D] 도형 그려보기)를 통해 윈도우에 도형을 그려보았습니다.

    특정 컨텐츠를 표현하고자 할 때, 이와 같은 방법으로는 섬세한 표현에 어려움이 있습니다.

    비트맵은 2D 그래픽 프로그램에서 컨텐츠를 표현하는 효과적인 방법 중 하나입니다.

     

    이번 글에서는 png, jpg등 이미지 파일을 읽어와 윈도우에 표시하는 방법을 살펴보도록 하겠습니다.

    이번 글은 지난 글 ([WinAPI] 키보드 입력)에서 코드가 이어집니다.

     


     

    0. 시작하기 전에 (COM Initialize)

     

    더보기

    COM이란 Component Object Model의 약어로, Microsoft에서 개발한 컴포넌트 모델입니다.

    서로 다른 환경, 프로그래밍 언어 간의 상호작용이 가능하게 하는 인터페이스를 제공하는 기술로, 여러가지 라이브러리가 COM을 기반으로 만들어졌습니다.

     

    이번 글에서 사용되는 객체는 COM기반 객체입니다.

    따라서, 프로그램에서 COM라이브러리를 초기화하는 작업이 필요합니다.

     

    WinMain함수의 윈도우 객체 초기화 및 메시지 루프 실행 이전에, COM 초기화 코드를 추가하도록 하겠습니다.

    int WinMain(
        HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        PSTR lpCmdLine,
        int nShowCmd
    ) {
        MyApp* app = new MyApp();
        if (SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
            if (SUCCEEDED(app->initialize(hInstance))) {
                app->runMessageLoop();
            }
        }
    
        return 0;
    }

    CoInitializeEx함수를 추가로 호출했습니다.

     

    1, 변수 및 함수 선언

     

    더보기

    비트맵을 로드하는 함수와, 그에 필요한 변수들을 선언하도록 하겠습니다.

     

    우선 함수입니다.

    class MyApp {
    private:
        ...
        HRESULT LoadBitmapFromFile(
            PCWSTR uri,
            ID2D1Bitmap** ppBitmap);
        ...
    };

    LoadBitmapFromFile은 지정한 uri에 대응하는 이미지 파일을 읽어와서 비트맵 객체를 초기화합니다.

     

    다음과 같이 변수도 선언하도록 하겠습니다.

    class MyApp {
    private:
        ...
        IWICImagingFactory* myWICFactory = nullptr;
        ID2D1Bitmap* myBitmap = nullptr;
        ...
    };

    IWICImagingFactory객체와 ID2D1Bitmap객체를 선언했습니다.

     

    IWICImagingFactory는 이미지 코덱 프레임워크의 팩토리 객체입니다.

    ID2D1Factory객체와 유사하게, 이 팩토리 객체를 통해 다른 객체를 초기화 할 수 있습니다.

     

    ID2D1Bitmap객체는 비트맵을 저장할 객체입니다.

    이 객체를 Render target을 통해 화면에 그릴 수 있습니다.

     

    2. 객체 초기화 및 해제

     

    더보기

    객체가 추가되었으므로, 우선 생성자의 초기화 리스트에 추가하도록 하겠습니다.

    MyApp::MyApp() :
        ...
        myWICFactory(nullptr),
        myBitmap(nullptr) {
    }

     

    이미지 팩토리(IWICImagingFactory)객체는 장치 독립적 자원입니다.

    이전에 구현한 CreateDeviceIndependentResources함수에 이미지 팩토리 초기화 코드를 추가하겠습니다.

    HRESULT MyApp::CreateDeviceIndependentResources() {
        HRESULT hr = S_OK;
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &myDirect2dFactory);
        if (SUCCEEDED(hr)) {
            hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, 
                IID_PPV_ARGS(&myWICFactory));
        }
    
        return hr;
    }

    결과값이 누락되지 않고, 결과를 확인할 수 있도록 조건문을 추가하여 초기화했습니다.

     

    로드된 이미지는 ID2D1Bitmap객체를 통해 조작됩니다.

    이 객체는 Render target에 의존적인 장치 의존적 자원이므로, Render target 해제 이전에 해제되어야 합니다.

    이전에 구현한 DiscardDeviceResources함수에 비트맵 객체 해제 함수를 추가하겠습니다.

    void MyApp::DiscardDeviceResources() {
        SAFE_RELEASE(myBitmap);
        SAFE_RELEASE(myRenderTarget);
        ...
    }

    Render target 해제 이전에 해제하여, 문제가 발생하지 않도록 했습니다. 

     

    3. LoadBitmapFromFile함수 구현

     

    더보기

    이미지 파일을 읽어 비트맵 객체를 초기화 하는 역할은 LoadBitmapFromFile함수가 수행합니다.

    이 함수는 MSDN에서 제공하는 가이드를 기반으로 작성되었습니다.

    How to Load a Bitmap from a File - Win32 apps | Microsoft Learn

     

    함수 내부에서 사용될 지역변수를 선언합니다.

    IWICBitmapDecoder* pDecoder = NULL;
    IWICBitmapFrameDecode* pSource = NULL;
    IWICStream* pStream = NULL;
    IWICFormatConverter* pConverter = NULL;
    IWICBitmapScaler* pScaler = NULL;

     

    LoadBitmapFromFile함수에서는 이미지 팩토리와 Render target을 참조합니다.

    해당 객체들이 초기화되어있지 않을 경우에 대한 예외 처리를 수행합니다.

    if (!myWICFactory) CreateDeviceIndependentResources();
    if (!myRenderTarget) CreateDeviceResources();

    각각의 객체에 대한 참조가 불가할 경우, 객체를 초기화하는 함수를 호출했습니다.

     

    사용할 객체에 대한 초기화가 완료되었습니다.

    첫 번째로 URI에 해당하는 파일을 읽어옵니다.

    HRESULT hr = myWICFactory->CreateDecoderFromFilename(
        uri,
        NULL,
        GENERIC_READ,
        WICDecodeMetadataCacheOnLoad,
        &pDecoder
    );

    읽어온 파일은 IWICBitmapFrameDecode객체에 저장됩니다.

     

    디코더는 이미지 파일의 특정 프레임을 가져오는 함수를 제공합니다.

    if (SUCCEEDED(hr)) {
        hr = pDecoder->GetFrame(0, &pSource);
    }

    정적 이미지와 동적 이미지 모두에 대응할 수 있도록 0번 프레임을 가져오도록 하겠습니다.

     

    이제 WIC객체를 D2D객체로 변환하는 작업을 수행해야 합니다.

    이 역할을 하는 객체는 IWICFormatConverter입니다.

    팩토리 객체로 해당 객체를 초기화하겠습니다.

    if (SUCCEEDED(hr)) {
        hr = myWICFactory->CreateFormatConverter(&pConverter);
    }
    if (SUCCEEDED(hr)) {
        hr = pConverter->Initialize(
            pSource,
            GUID_WICPixelFormat32bppPBGRA,
            WICBitmapDitherTypeNone,
            NULL,
            0.f,
            WICBitmapPaletteTypeMedianCut
        );
    }

     

    초기화한 객체로 비트맵 객체를 생성합니다.

    if (SUCCEEDED(hr)) {
        hr = myRenderTarget->CreateBitmapFromWicBitmap(
            pConverter,
            NULL,
            ppBitmap
        );
    }

     

    읽어온 이미지의 0번 프레임으로 비트맵 객체가 생성되었습니다.

    사용한 자원들을 초기화하고 결과를 반환합니다.

    SAFE_RELEASE(pDecoder);
    SAFE_RELEASE(pSource);
    SAFE_RELEASE(pStream);
    SAFE_RELEASE(pConverter);
    SAFE_RELEASE(pScaler);
    
    return hr;

     

    위 코드를 종합한 LoadBitmapFromFile함수의 전문은 다음과 같습니다.

    HRESULT MyApp::LoadBitmapFromFile(PCWSTR uri, ID2D1Bitmap** ppBitmap) {
        IWICBitmapDecoder* pDecoder = NULL;
        IWICBitmapFrameDecode* pSource = NULL;
        IWICStream* pStream = NULL;
        IWICFormatConverter* pConverter = NULL;
        IWICBitmapScaler* pScaler = NULL;
    
        if (!myWICFactory) CreateDeviceIndependentResources();
        if (!myRenderTarget) CreateDeviceResources();
    
        HRESULT hr = myWICFactory->CreateDecoderFromFilename(
            uri,
            NULL,
            GENERIC_READ,
            WICDecodeMetadataCacheOnLoad,
            &pDecoder
        );
    
        if (SUCCEEDED(hr)) {
            hr = pDecoder->GetFrame(0, &pSource);
        }
        if (SUCCEEDED(hr)) {
            hr = myWICFactory->CreateFormatConverter(&pConverter);
        }
    
        if (SUCCEEDED(hr)) {
            hr = pConverter->Initialize(
                pSource,
                GUID_WICPixelFormat32bppPBGRA,
                WICBitmapDitherTypeNone,
                NULL,
                0.f,
                WICBitmapPaletteTypeMedianCut
            );
        }
        if (SUCCEEDED(hr)) {
            hr = myRenderTarget->CreateBitmapFromWicBitmap(
                pConverter,
                NULL,
                ppBitmap
            );
        }
    
        SAFE_RELEASE(pDecoder);
        SAFE_RELEASE(pSource);
        SAFE_RELEASE(pStream);
        SAFE_RELEASE(pConverter);
        SAFE_RELEASE(pScaler);
    
        return hr;
    }

     

    4. LoadBitmapFromFile함수 호출

     

    더보기

    현재 구현한 함수는 정적 이미지를 생성합니다.

    이번 글의 목적은 이미지의 출력이므로, 프로그램이 초기화될 때 이미지를 생성하도록 하겠습니다.

     

    Initialize함수에 구현한 LoadBitmapFromFile함수를 호출합니다.

    HRESULT MyApp::initialize(HINSTANCE hInstance) {
        ...
        LoadBitmapFromFile(L"dx_logo.png", &myBitmap);
    
        return hr;
    }

    위와 같은 uri구성은 프로젝트 폴더가 기준이 됩니다.

    상위 폴더 혹은 하위 폴더의 파일을 지정할 경우, 그에 대응하는 uri를 작성해주세요.

     

    5. 렌더링 (OnRender)

     

    더보기

    비트맵을 윈도우에 가득 차도록 렌더링하겠습니다.

    OnRender함수에는 윈도우의 크기를 가져오는 코드가 이미 있습니다.

     

    OnRender함수에 비트맵을 그리는 코드를 추가합니다.

    HRESULT MyApp::OnRender() {
        ...
        D2D1_SIZE_F rtSize = myRenderTarget->GetSize();
        
        if (myBitmap) {
            myRenderTarget->DrawBitmap(myBitmap,
                D2D1::RectF(
                    0.0f, 0.0f,
                    rtSize.width, rtSize.height
                ));
        }
        ...
    }

    OnRender함수가 비트맵이 초기화되지 않았을 때에도 정상적으로 동작해야하므로 조건문을 추가했습니다. 

     


     

    본문의 코드의 결과는 다음과 같습니다.

    출력되는 이미지와 코드는 다음 Github에서 확인하실 수 있습니다.

     

    GitHub - ruru14/WinapiStudy

    Contribute to ruru14/WinapiStudy development by creating an account on GitHub.

    github.com

     

    이번 글이 도움이 되셨기를 바랍니다.

    감사합니다.

    댓글

Designed by Tistory.