F.R.I.D.A.Y.

Direct2D - WIC를 이용해 이미지 그리기 본문

DEV/Direct2D

Direct2D - WIC를 이용해 이미지 그리기

F.R.I.D.A.Y. 2021. 4. 24. 13:45
반응형

 WIC는 Windows Imaging Component라는 이름을 가진 코덱 프레임워크이다.

 

# 해당 내용은 Direct2D의 기본 구조를 알고 있어야 진행이 원활합니다.

# 해당 내용은 다음 포스트를 기반에 두고 있습니다.


WIC

 WIC는 COM[# Component Object Model] 객체로서, 이를 이용하려면 사용하려는 프로그램[# 정확히는 프로세스]에서 COM 객체의 메서드와 같은 요소에 접근하는 것을 허용하도록 해야합니다.

 

 애플리케이션의 시작에서 CoInitialize 함수를 호출합니다. 코드에서는 WinMain의 코드가 되겠네요.

	HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

	if (!win.Create(L"Win32 WIC", WS_OVERLAPPEDWINDOW)) return -1;

	ShowWindow(win.Window(), nCmdShow);
	UpdateWindow(win.Window());

	MSG msg;

	if(SUCCEEDED(hr)) {
		while (GetMessage(&msg, nullptr, 0, 0)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		CoUninitialize();
	}

 시작 부분의 CoInitialize 함수와 끝 부분의 CoUninitialize 함수는 연결되어 있으니 참고 바랍니다.

 

초기화 대상

 WIC를 이용해 이미지를 그리기 위한 방법은 상당히 많은 과정을 거칩니다. 먼저 WIC를 통해 이미지를 그리는데에는 아래의 인터페이스가 필요합니다.

  • ID2D1Factory[# Direct2D의 각종 구성요소를 생성할 수 있는 메서드 등이 포함된 인터페이스]
  • ID2D1HwndRenderTarget[# Direct2D에서 그리기할 영역 및 실제 그리기 작업의 주가 되는 인터페이스]
  • ID2D1Bitmap[# Direct2D에서 그리기를 할 때 사용할 비트맵]
  • IWICImagingFactory[# WIC의 이미지 관련 구성요소를 생성할 수 있는 인터페이스]
  • IWICBitmapDecoder[# WIC에서 불러온 이미지 파일의 데이터를 포함하는 인터페이스]
  • IWICBitmapFrameDecode[# WIC에서 IWICBitmapDecoder로부터 하나의 프레임을 가져와 보관하는 인터페이스, 움직이는 이미지 파일인 GIF에서 단일 프레임을 가져와 보관하는 방식]
  • IWICFormatConverter[# WIC에서 관리하는 이미지 데이터를 다른 포맷으로 변경하는 기능을 제공하는 인터페이스]

 이미지 하나 그리는데 이렇게 많은 인터페이스가 필요하다는게 너무 그렇죠? 하지만 이 일곱가지의 내용만 있으면 다양한 포맷을 하나의 코드로 전부 읽기부터 그릴 수 있으니 너무 많은 인터페이스를 사용한다고 그러지는 맙시다.

 

 초기화의 연결 구조를 도식화하면 위와 같습니다. 먼저 Direct2D는 그리기를 담당하고, 이미지의 로드는 WIC가 담당하는 방식입니다. 이 과정에서 Direct2D에서 그려야할 이미지는 pRT에서 IWICFormatConverter를 가져와 ID2D1Bitmap 데이터를 생성해 그리는 방식을 이용합니다.

 

 따라서 먼저 Draw를 담당할 Direct2D 영역의 초기화부터 진행합니다.

 

Direct2D 초기화

 Direct2D 부분은 이전의 많은 포스트에서 더 확인할 수 있습니다. 여기에서는 Direct2D가 중심이 아니라 이미지를 불러와 데이터를 구성하는 WIC 부분이 핵심이기 때문에 단순히 코드만 구성하겠습니다.

// OnCreate 메서드가 구성되어 있다고 가정한다.
// HandleMessage에 OnCreate가 바인딩 되어있다고 가정한다.

ID2D1Factory* pFactory;
ID2D1HwndRenderTarget *pRT;
// 위 두 항목은 MainWindow의 내부 멤버로 작동합니다. MainWindow 내부에서 선언해야합니다.


HRESULT OnCreate(){
	HRESULT hr;
    hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pFactory);
    
    if(SUCCEEDED(hr)){
    	RECT rc;
        GetClientRect(hWnd, &rc);
    
    	hr = pFactory->CreateHwndRenderTarget(
        	D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(hWnd,
            D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top), &pRT);
    }
    
    // 다음 코드가 들어갈 위치
    
    return hr;
}

 

WIC 구조 초기화

 WIC를 사용하기 위해서는 아래의 헤더와 라이브러리를 추가해야합니다.

#include <wincodec.h>
#pragma comment(lib, "windowscodecs")

 이 헤더는 WIC의 구성 요소를 정의하고 있습니다.[# 정의라기보단 함수 원형을 가지고 있고, 실제 정의는 그 코드를 알 수 없도록 lib 파일 형식의 windowscodecs에서 가지고 있습니다.]

 

IWICImagingFactory

 먼저 IWICImagingFactory 객체를 생성하겠습니다. 이 객체를 생성할 때는 CoCreateInstance 함수를 이용합니다.

// MainWindow 클래스 내부에 구현
IWICImagingFactory* pWicFactory;


// OnCreate 메서드에 구현
CoCreateInstance(CLSID_WICImagingFactory, nullptr, 
	CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pWicFactory));

 여기에서 CoCreateInstance로 생성되는 IWICImagingFactory 객체는 pWicFactory 포인터 멤버가 가지는 것으로 하겠습니다.

더보기

# CoInitialize 함수를 호출하지 않았나요?

 코드 작성을 시작함에 앞서 다시한번 강조합니다. 외부의 COM 요소에 접근하기 위해서는 접근 허용을 작업하는 CoInitialize 함수가 호출되어야 합니다. 만일 호출이 일어나지 않은채 CoCreateInstance 함수를 사용하게 되면 CoCreateInstance 함수의 반환값은 0x800401f0, CoInitialize 함수를 호출하라는 내용의 값이 들어가 있습니다.

 사진의 변수 추적 정보를 보면 CoCreateInstance 함수의 반환값을 가지고 있는 hr 변수는 CoInitialize 함수를 호출하지 않았음을 뜻하는 값이 들어있다는 것을 알 수 있습니다.

 

IWICBitmapDecoder

 IWICImagingFactory 객체는 데이터를 읽어오는 여러 메서드와 함께 각종 연산 및 WIC의 하위 구성요소를 생성할 수 있는 메서드를 제공합니다.

 우리는 이미지를 읽어와 프로그램에서 클라이언트 영역에 그려줄 것입니다. 따라서 읽어온 데이터를 보관할 장소가 필요하죠. IWICBitmapDecoder 인터페이스는 읽어온 데이터를 보관하는 컨테이너라고 생각하면 편하겠습니다.

 

 IWICBitmapDecoder를 생성하는 메서드는 두 가지가 존재합니다. 하나는 파일에서 불러오는 것이고, 다른 하나는 스트림[# 데이터가 이동하는 일련의 흐름]에서 가져오는 방식입니다. 우리는 파일 이름을 기준으로 데이터를 불러오는 CreateDecodeMetadataFromFilename 메서드를 이용하겠습니다.

 

 IWICImagingFactory가 생성된 뒤에 작업을 할 수 있으므로 SUCCEED 매크로 함수를 이용해 작업을 진행합니다.

// MainWindow 멤버 변수로 선언
IWICBitmapDecoder* pDecoder;

if(SUCCEEDED(hr)){
	hr = pWicFactory->CreateDecoderFromFilename(L"fileName.jpg", nullptr, GENERIC_READ, 
    	WICDecodeMetadataCacheOnDemand, &pDecoder);
}

 간단히 설명하면 세 번째 인자는 우리가 읽을 목적이기 때문에 GENERIC_READ 값을 넘겨준 것이고, 네 번째 옵션은 메타 데이터가 필요할 때 캐시하겠다는 내용입니다.

 

IWICBitmapFrameDecode

 이전 내용에서 얻어온 pDecode[# IWICBitmapDecoder]는 해당 이미지의 모든 정보[# 프레임(이미지 색상값을 나타내는 집합) 포함해 메타데이터]를 담고 있습니다. 이래서는 이미지의 단일 프레임만을 원하는 비트맵을 생성할 수 없습니다. 따라서 우리는 이미지 파일의 첫 번째 프레임 정보를 가져올 것입니다.

더보기

# 잠시만요, 첫 번째 프레임 정보라뇨?

 이미지 파일이 이미지 하나만 있지 않냐구요? 아닙니다. gif 파일 포맷을 보면 하나의 파일 안에 여러 이미지가 들어 있습니다. 여러 이미지를 통해 움직이는 이미지를 만들 수 있고, 또 우리는 페이스북 댓글로도 활용합니다.

블로그 최초의 gif가 되지 않을런지..?

 예시로 표시한 gif 이미지 파일도 단일 파일이지만 움직입니다. 즉, 파일 하나에 이미지 픽셀 정보가 여러 개 들어있다는 것을 의미합니다.

 

 이러한 프레임은 IWICBitmapDecoder에서 GetFrame 메서드를 통해 IWICBitmapFrameDecode에 보관할 것입니다. 프레임 정보는 제로베이스 인덱스를 가집니다. 즉, 첫 번째 프레임 정보를 가져오려면 인덱스는 0으로 설정해야한다는 것입니다.

// MainWindow 멤버 변수로 선언
IWICBitmapFrameDecode* pFrame;

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

 

IWICFormatConverter

 이렇게 파일에서 단일 이미지 프레임을 가져왔습니다. 그러나 아직 우리가 사용하기에는 적합하지 않습니다. 우리는 그리기 위해 비트맵 정보가 필요하지만, 가져온 프레임은 비트맵이 아닙니다. 즉, 다른 포맷을 사용하고 있다는 것입니다. 그래서 비트맵을 이용하기 위해 포맷 변환 과정이 필요합니다. 이 과정에서 IWICFormatConverter 인터페이스가 필요합니다.

 

 이 인터페이스는 제대로 사용하기 위해 아래의 두 과정을 꼭 거쳐야 합니다.

 

생성하기

  IWICFormatConverter 인터페이스는 IWICImagingFactory 객체로부터 초기화할 수 있습니다.

IWICFormatConverter* pConverter;

if(SUCCEEDED(hr)){
	hr = pWicFactory->CreateFormatConverter(&pConverter);
}

 객체는 생성했지만, 아직 우리는 어떤 포맷으로 변환할지는 선택하지 않았습니다. 이 변환할 포맷을 결정하는 작업이 아래 초기화 작업에서 이루어집니다.

 

초기화

 IWICFormatConverter 인터페이스에는 하위에 Initialize[# 좀 더 살펴봐야...] 라 불리는 메서드가 포함되어 있습니다.

if(SUCCEEDED(hr)){
	pConverter->Initialize(pFrame,
		GUID_WICPixelFormat32bppPBGRA,
    	WICBitmapDitherTypeNone,
    	nullptr,
    	0.0f,
    	WICBitmapPaletteTypeCustom
	);
}

  여기서 중요한 것은 GUID_WICPixelFormat32bppPBGRA입니다. Direct2D에서 이미지를 그릴 때 비트맵은 이 항목을 써야한다고 하네요.

더보기

# 참고 자료

 Before Direct2D can use an image, it must be converted to the 32bppPBGRA pixel format. To convert the image format, use the CreateFormatConverter method to create an IWICFormatConverter object. Once created, use the Initialize method to perform the conversion.

 

How to Draw a BitmapSource Using Direct2D - Win32 apps

This topic demonstrates the process for drawing an IWICBitmapSource by using Direct2D.

docs.microsoft.com

 

비트맵 생성

 여기까지 잘 따라왔다면 Direct2D의 비트맵을 생성하기까지 얼마 남지 않습니다. 이제 우리는 WicBitmap 정보를 D2D의 비트맵으로 변환하겠습니다.

 이 변환은 ID2D1HwndRenderTarget의 하위 메서드인 CreateBitmapFromWicBitmap으로 할 수 있습니다.

// MainWindow 멤버 변수로 선언
ID2D1Bitmap* pBitmap;

if(SUCCEEDED(hr)){
	hr = pRT->CreateBitmapFromWicBitmap(pConverter, nullptr, &pBitmap);
}

 여기까지 잘 따라왔다면 이제 Direct2D에서 직접 사용할 수 있는 비트맵 데이터가 생성되었습니다.

 

 

그리기

 잘 만들어진 데이터는 이제 그리기만 하면 됩니다. OnPaint 메서드를 만들고 HandleMessage에 바인딩합니다.

LRESULT OnPaint(){

}

LRESULT HandleMessage(...){
...
	switch(uMsg){
    	case WM_PAINT: return OnPaint();
    }
...
}

 

 OnPaint 메서드는 아래와 같이 구성합니다.

	pRT->BeginDraw();
	PAINTSTRUCT ps;
	BeginPaint(hWnd, &ps);

	RECT rc;
	GetClientRect(hWnd, &rc);

	D2D1_RECT_F rect = D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom);

	pRT->Clear(D2D1::ColorF(D2D1::ColorF::Black));

		pRT->DrawBitmap(pBitmap, rect);
	if (pBitmap) {
	}

	EndPaint(hWnd, &ps);
	return pRT->EndDraw();

 

 그리고 실행을 시켜보면 이미지 파일이 잘 있다는 가정 하에 아래와 같이 이미지가 출력됩니다.

 일전에 여행 다녀온 강릉인지 속초인지에서 찍은 사진을 불러왔습니다. 108M 화소[# 크기만 무려 20MB가 넘더군요]라 다 담기 역부족이었는지 일부 이미지 담기가 좋지 않은 것 같은데 이는 다른 기술을 통해 해결이 가능합니다.

 

# index

728x90
반응형

'DEV > Direct2D' 카테고리의 다른 글

Direct2D - WIC IWICBitmapScaler  (0) 2021.04.29
Direct2D - WIC IBitmapSource  (0) 2021.04.28
Direct2D - WIC 비트맵 그리기 사전 작업  (0) 2021.04.24
DirectWrite - IDWriteFactory  (0) 2021.04.21
DirectWrite - DirectWrite 되짚기  (0) 2021.04.14
Comments