Win32API : BMP 이미지 출력 및 더블버퍼링 Win32API


이번에는 이미지를 출력하는 처리를 다루어 보겠습니다.
Win32API 에서 기본적으로 다루는 형은 BMP 이미지 형입니다.

무압축 형태의 가장 기본적인 이미지 포멧으로, 용량이 크다는것은
감안해야 합니다. (물론 Direct,OpenGL 등을 기반으로 작업하면,
Jpg,Png 등을 다룰 수 있긴 합니다만)

그럼 글을 시작해 보겠습니다.

일단, 이미지 처리를 위해서는 이미지 파일을 관리하는 루틴이
추가적으로 필요합니다. 이미지라는건 일종의 리소스 데이터인 것이고,
필요한 자원을 메모리에 할당해야 사용할 수 있기 때문에, 윈도우가 
기본적으로 제공해주는 도형처럼 아무런 준비 없이 사용할 수가 없습니다.
따라서 리소스를 할당하고, 관리하는 추가적인 처리가 필요하게 됩니다.
(당연하지만 사용 후 해제도 해줘야 합니다)

여기서 이미지를 관리하는 방법은 크게 2가지로 나뉘어집니다.


1. API 리소스 파일에 직접 BMP 리소스를 로드하여 관리하는 방법.
    
    - 가장 기본적인 API 에서의 리소스 사용법입니다.
    - 이 사용법은 아이콘, 커서 등 모든 리소스 사용법과 동일하므로, 일관성이 있습니다.
    - 추가하고 사용하기가 쉽습니다. (경로관리가 필요하지 않게 됩니다.)
    - 구조적인 관리는 어렵습니다.
    
2. HBITMAP 형을 사용해여 LoadImage 함수로 외부의 파일을 읽어오고,
   로드된 이미지를 버퍼에 그리는 처리를 하여 관리하는 방법.

   - 경로를 통한 파일관리 처리를 필요로 합니다.
   - 프로젝트에 이미지 파일을 추가할 필요가 없습니다.
   - 제대로 준비하려면 몇가지 클래스와 처리함수 등을 준비해야 합니다.
      (하지만 한번 준비하면 관리도 편하고, 여러모로 이점이 많습니다.)


이정도가 됩니다. 간단하게 비교글도 작성해 두었습니다. 보통 입문서 등에서는
1번의 방법을 권장하고 있기도 합니다. 
별도의 함수를 사용하지 않고, 리소스를 직접 추가하는 작업은 리소스추가 작업에서합니다.


3


보기 -> 리소스 뷰 -> 리소스 추가 항목으로 접근해서 Bitmap 형식을 선택하고,
가져오기를 클릭하여 비트맵 파일을 리소스로 추가해 넣을 수 있습니다.
이런식으로 상당히 편하게 비트맵 파일을 불러와 사용할 수 있습니다.

하지만 이처럼 API 리소스 파일에 이미지를 등록하여 사용하는 방식으로는, 이미지를 많이
사용해야 되는 프로젝트에서 문제가 발생합니다. 쉽게 말해서 제대로된 게임 같은 것을
만들때 사용할 이미지를 모두 관리하기가 힘듭니다.




그래서 저는 이렇게 하지 않을 겁니다.

위의 스크린샷은 1번 방법에 대한 스크린샷입니다.

아래의 글과 연결되지 않습니다.



저는 LoadImage 함수로 외부의 파일을 읽어오는 방식을 사용할 것입니다.
이 방식은 추후에 STL 등과 함께 응용할 수도 있기도 하는 등, 처리 내용을 한번만
준비해 두면 이후에는 많은 이미지를 관리할때 훨씬 용이하게 사용할 수 있습니다.


일단 간단하게 사용법만 작성하겠습니다.
보다 구조적으로 정리하는건 추후에 생각해 보기로 합니다.




일단 실제로 출력에 사용하고 있는 HDC 변수 이외에 또 하나의 HDC 변수를
잡아주었습니다. HDC 객체는 단순히 하나의 사용 용도만 있는 것이 아니라.
쓰기에 따라서 다양한 용도로 사용이 가능합니다.

여기서는 BMP 이미지만 따로 그려두는 버퍼의 용도로 활용하기 위해서 HDC 객체를
사용할 것입니다. 이 버퍼에 이미지를 그려 두었다가, 출력함수를 통해서
실제로 화면에 출력할 내용을 담당하는 HDC 에 이 내용을 그려서 마무리짓게 됩니다.

참고로 이로써, 사용하는 HDC가 2개가 되었습니다.
이는 그림을 그려두는 버퍼공간이 2개가 되었음을 의미합니다.

HDC는 다양한 출력정보를 다루는 역할을 하기도 하지만

화면에 그려지는 버퍼공간 즉, 도화지와 같은 역할을 하기도 합니다.
도화지가 2개가 되었다는 느낌을 잘 기억해 두도록 합시다.
더블 버퍼링을 이해하는데 필요한 개념입니다.






버퍼공간을 할당하고, 비트맵 정보를 불러오는 부분입니다.
눈에 띄는 함수로는 CreateCompatibleDC 함수와, LoadImage 함수가 있습니다.


CreateCompatibleDC


이 함수는 비트맵과 관련된 DC를 다룰때 주로 사용됩니다.
일반적인 GetDC 등의 함수로 받아온 DC에 바로 비트맵 정보를 그려주는
처리를 하려고 하면, 항상 비트맵 정보를 사용해 매번 다시 이미지를 구성하여
그려주는 처리를 해 줘야 합니다.

이렇게 매번 이미지를 재구성해서 복사하면 처리가 매우 느려지게 될 겁니다.
그래서 비트맵 정보를 미리 버퍼공간을 할당하여 기록해둔 후, 이를 출력에 활용합니다.

인자로 DC를 받으면 받은 화면 DC와 동일한 특성을 갖는 메모리 DC를 만든 후
반환해 줍니다. 이 DC에 출력할 비트맵 이미지를 미리 그려놓고 출력시에
화면 DC에 복사해서 출력처리를 하게 됩니다.

비유하면 도장을 파 두고, 나중에 출력용 버퍼에 도장을 찍어 작업속도를
효율적으로 하는 것으로 생각하면 됩니다.

또한 이 공간을 마련하기 위해서 HDC 를 화면출력용도 외에 하나 더 만들어 
사용하게 된 것입니다.


LoadImage


이미지 파일의 경로와, 어떤 이미지를 불러들일까의 형을 인자로 받아서 지정해준
옵션값에 맞게 정보를 로드해 오는 역할을 합니다.

불러올 수 있는 형은 다양해서 특정 객체로 사용하려면 형변환을 거쳐야 합니다

(HBITMAP 형으로 변환해서 대입하고 있습니다.)







BitBlt


이렇게 그려준 후에 BitBlt 를 통해서 출력할 수 있습니다.
BitBlt 함수는 이미지를 그려준다 같은 개념이 아니라 DC 버퍼공간 간의
복사를 수행하는 역할을 합니다. 도화지에서 도화지로 옮겨주는 역할을 하는 것이죠.

그래서 출력용 DC 에 준비해둔 비트맵 DC를 복사하는 처리를 하고 있고,
이렇게 해서 출력처리가 가능합니다.


인자가 많은 편이므로, 첫번째 인자부터 번호를 매겨 소개합니다.

1. HDC - 출력용 DC 를 넘겨받습니다. 정보를 이 DC에 복사해 넣어줍니다.
2. X     - 이미지 출력을 시작할 좌표의 X 값를 받습니다.
3. Y     - 이미지 출력을 시작할 좌표의 Y 값를 받습니다.

이 인자들은 윈도우 창 내에서 어느 위치에 좌표를 뿌리기 시작할까에 대한 값입니다.






4. CX   - 출력할 이미지를 어느정도까지 출력할 것인가에 대한 가로값
5  CY   - 출력할 이미지를 어느정도까지 출력할 것인가에 대한 세로값

이 숫자는 이미지를 어느정도 크기까지만 출력할까에 대한 정보입니다. 조금
생소한 느낌이 들 수 있는데, 이미지의 크기가 100 x 100 인 이미지 이면서,
인자들을 90,90 으로 넣어주게 되면 90 픽셀 까지의 이미지 정보가 출력되고,
나머지 10픽셀에 관한 값을 무시하고 출력하지 않게 됩니다.

쉽게 말하면 그림을 끝에서 잘라내서 출력해준다 라고 생각하면 되겠습니다.
넣어준 픽셀값은 전체 픽셀에서 몇픽셀까지 살릴것인가에 대한 정보가 되지요.

6. HDC - 복사할 정보를 제공할 DC를 넘겨받습니다. 주로 이미지 정보를
             미리 그려둔 DC 를 인자로 넘겨 화면 DC로의 복사작업을 수행하게 됩니다.





7. X1    -  이미지의 시작을 어느정도 잘라낼 것인가에 대한 가로값
8. Y2    -  이미지의 시작을 어느정도 잘라낼 것인가에 대한 세로값

출력할 이미지를 처음에 일부 잘라내고, 특정 위치부터 뿌리는 옵션입니다.
넣어준 인자만큼의 픽셀을 자르고 이미지 출력을 시작하게 됩니다.

9.rop    - 복사하는 옵션입니다. 현재 사용한 인자인 SRCCOPY 를 사용하면,
             받은 이미지를 그대로 복사하게 됩니다.
             여러 옵션을 주어서 출력 옵션을 다르게 지정할 수도 있습니다. 





아무튼 이렇게 해서 비트맵 이미지를 한번 출력해보는데까지의 처리를 살펴보았습니다.
다만 이렇게만 작성하면 

이미지 한장을 단순히 출력만 할때는 상관없지만, 다른 이미지와 겹쳐서 출력한다거나,
도형과 함께 출력하는 경우 깜빡이는 현상이 발생하게 됩니다.

이는, 출력용 DC 에 뭔가 그릴 정보를 전달하는 경우 DC 공간을 다 덮어쓰면서,
해당 그릴 정보만 받아 화면에 출력하는 처리를 하기 때문입니다.

도형을 하나 출력하는 호출을 하면, 흰 바탕에 도형 하나만 그린 정보를 DC에 전달하게
되고, 이게 화면에 뿌려집니다.

그 후에 바로 이미지 정보를 출력하는 호출을 하면 이번에는 이미지 정보가 화면에
뿌려질 것입니다. 방금 뿌린 도형과 흰 바탕을 덮어쓰면서요.

이들 정보는 하나의 DC에 모두 합쳐져서 그려진 것이 아니라,
따로따로 놀고 있으며, 각자 출력 DC에 복사를 요청하고 출력을 요청하고 있습니다.
그래서 하나씩만 출력되는 현상이 반복되어 일어나면서
깜빡이는 현상이 발생하는 것입니다.

(이거 출력되었다가 저거 출력되었다가 하는게 반복됩니다.
 출력정보는 따로따로의 정보들이므로, 전혀 다른 이미지 2장을 반복해서
 보여주는 꼴과 같은 꼴이 되는 겁니다.)





그래서 더블 버퍼링 개념이 적용됩니다.
더블 버퍼링 개념은 Back DC 라는 개념의 DC를 하나 마련하고,

이 DC에 모든 출력정보를 다 그려둔 후,
출력 DC로 다 그려진 정보를 복사해 와서 출력을 한다는 의미가 있습니다.

이렇게 하면 도중에 출력되는 일이 없고 완성본만 출력하게 되므로,
깜빡이는 현상이 발생하지 않게 됩니다.



참고로, 지금 상태는 이미지를 출력하기 위해 DC 를 하나 더 준비해 둔 상태입니다.
따라서, 이 DC 를 Back DC 로 활용하면 됩니다.
여기에 모든 출력정보를 다 모아주고, 출력정보가 모인 DC를
BitBlt 으로 복사하여 화면 DC 를 구성하면 중간 출력 없이 최종 출력 픽셀 정보를
준비할 수 있어, 깜빡임 등의 현상이 일어나지 않게 할 수 있습니다.







그래서 코드를 이렇게 수정했습니다. 원래는 출력 DC 를 인자로 넘겨서
출력용 함수를 호출했던 것을, 비트맵 정보의 DC 를 넘기도록 했습니다.
이렇게 하면 비트맵 DC에 출력에 대한 정보가 쌓이게 됩니다.

그런 후에 이 DC 를 화면 DC로 복사하여 최종적으로 출력을 하면 됩니다.





이런식의 작업으로,  더블 버퍼링을 적용하였고,
도형과 비트맵을 동시에 출력한 결과를 볼 수 있습니다.




DC나 비트맵 데이터들도 사용후 해제를 해 줘야 합니다.


마지막으로 이러한 DC 및 비트맵 정보 역시 메모리에 할당된 것이므로,
다 사용한 후에는 반드시 해제를 해줘야 합니다.


GetDC 로 받은 화면 DC는 ReleaseDC 로 해제하였고,
CreateCompatibleDC 는 SelectObject 로 선택하여,
DelestDC 로 해제하면 됩니다.

HBITMAP 자료형도 DeleteObject 로 해제하면 됩니다.


이들 DC 는 메모리 릭을 잡은 디버그 코드로도 릭이 발생한다는 것을
잡을 수 없기 때문에 해제하는데 더욱 신경을 써야 합니다.



이상으로 BitBlt 을 사용해 간단하게 이미지를 출력해보고,
더블 버퍼링을 적용해 깜빡임 현상이 없게 만드는 것 까지
포스팅으로 작성해 보았습니다.

다음에는 특정 RGB 값을 무효처리 하여,
투명한 출력 처리를 수행할 수 있는 TransParent 함수를 살펴보도록 하겠습니다.



덧글

  • ㅇㅇ 2019/05/19 16:57 # 삭제 답글

    안녕하세요!! 몇년 전 글이지만 서치 통해서 들어왔습니다 ㅠ_ㅠ
    포스트 도움되었습니다. 잘 보고 갑니다!
댓글 입력 영역


라운드 시계