2010년 11월 26일 금요일

API에서 Bitmap 투명하게 출력하기


예전에는 투명하게 칠하기 위해서 매우 복잡한 과정을 거쳤었다. 가장 대표적인 것이 마스크 연산이었다. 마스크 연산이란 두 이미지를 AND 연산하여 배경을 잘라내는 것을 말한다.

Window98/2000 이상의 운영체제에서는 투명한 비트맵을 그리기 위해서 복잡한 과정을 거칠 필요가 없이 단 하나의 API를 사용하여 해결 할수 있다. 다음과 같이 코드를 변경한다 .

 //dc.BitBlt(0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, &MemDC, 0, 0, SRCCOPY);
 ::TransparentBlt(dc.m_hDC, 
      0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, 
      MemDC.m_hDC,
      0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, 
      RGB(192, 192, 192));
 //dc.StretchBlt(bmpInfo.bmWidth, 0, bmpInfo.bmWidth*2, bmpInfo.bmHeight*2, &MemDC, 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, SRCCOPY);
 ::TransparentBlt(dc.m_hDC, 
      bmpInfo.bmWidth, 0, 
      bmpInfo.bmWidth * 2, bmpInfo.bmHeight * 2, 
      MemDC.m_hDC, 
      0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, 
      RGB(192, 192, 192));

코딩 후 프로그램을 빌드해 보면 링크드 Error 가 난다. ::TransparentBlt() 함수의 선언은 있으나 라이브러리 파일을 연결하지 못해서 발생한 문제인데, 이와 같이 링크 문제가 발생했을 때는 선언만 있고 실체가 없기 때문이다. 간혹 전역 함수나 변수에 대한 선언만 있고 실제 구현이 없어도 이와 비슷한 에러가 링크 시에 발생한다. 그러므로 주의해야 한다.

암튼 현재 예제에서 이 문제를 해결하려면 :: TransparentBlt() 함수가 들어 있는 라이브러리 파일을 설정해 주어야 한다. [Alt + F7] 키를 눌러서 설정 부분을 본 다음에 링크 탭에서 다음과 같이 라이브러리 모듈 부분에 Msimg32.lib 라고 추가해 준다.

BOOL TransparentBlt(
  HDC
 hdcDest,        // handle to destination DC
  int nXOriginDest,   // x-coord of destination upper-left corner
  int nYOriginDest,   // y-coord of destination upper-left corner
  int nWidthDest,     // width of destination rectangle
  int hHeightDest,    // height of destination rectangle
  HDC hdcSrc,         // handle to source DC
  int nXOriginSrc,    // x-coord of source upper-left corner
  int nYOriginSrc,    // y-coord of source upper-left corner
  int nWidthSrc,      // width of source rectangle                        <=    1
  int
 nHeightSrc,     // height of source rectangle                       <=    2
  UINT crTransparent  // color to make transparent
);

  1 (소스의 가로)   과   2 (소스의 세로) 는 소스의 원래 값보다 작아야 한다 .
즉, 1 과 2는 소스 DC가 소스비트맵을  선택하고 있을경우 소스 비트맵의 가로와
세로길이보다 커서는 안된다 . (크면 출력 안됨) 
  그런데  StretchBlt 함수의  1과 2는 소스 비트맵의 가로와 세로길이보다
 커도 출력잘된다. 

필요한 lib파일.
Msimg32.lib

이 ::TransparentBlt() 함수의 인자는 StretchBlt() 함수와 거의 비슷핟. 다만 CDC 객체에 대한 포인터가 들어가는 자리에 DC의 핸들값이 들어가야 하므로 CDC의 데이터 멤버인 m_hDC를 인자로 두었다. 윈도우 객체가 윈도우 핸들을 데이터 멤버로 가지고 있는 것처럼 모든 GDI 클래스들로 핸들을 데이터 멤버로 가지고 있다. API 함수들은 C++이 아니라 기본 C 스타일이므로 각각을 식별하는 핸들 값을 인자로 사용한다.
 
그리고 맨 마지막 인자에는 어떤 모드 값이 들어 가는 것이 아니라 투명하게 처리될 이미지의 색상을 RGB 값으로 명시한다. 우리가 출력한 이미지의 배경색의 RGB값은 192, 192, 192인 회색이었다. 그러므로 배경 부분이 투명하게 처리되도록 배경의 RGB값을 인자로 주었다. 만일 RGB(255, 255, 255)준다면 흰색이 투명하게 되어 화면에 출력될 것이다.
이처럼 배경을 투명하게 하는 것이 아니라 이미지자체를 투명하게 (반투명) 출력하는 방법도 있다. ::AlphaBlend() API가 이러한 기능을 제공하는데 이미지의 투명한 정도를 직접 조절할 수도 있으므로 잘 사용하면 매우 보기좋은 사용자 인티페이스를 만들 수 있다. 기왕 MSN의 이너테피스와 비슷하게 만들어 보기로 했으니 좀더 그 모습에 근접하도록 예제를 변경해 보자. 우선 클라이언트 뷰 클래스에 WM_ERASEBKGND 메시지 핸들러를 등록하여 추가적으로 코딩해 보자.

< DrawBk Click!! >
void CMSN_CopyView::DrawBk(CDC *pDC)
{
 CRect Rect;
 int nR, nG, nB;
 int nH = 0, nW = 0;

 GetClientRect(&Rect);
 nW = Rect.Width();
 nH = Rect.Height();
 // 화면의 절반에 그라데이션 효과를 주기위한 펜 객체들을 생성한다. 
 CPen* pPen[64];
 for(int i=0; i<64; ++i)
 {
  nB = 245 + (i);
  if(nB > 255) nB = 255;

  nG = 210 + (i + 4);
  if(nG > 255) nG = 255;

  nR = 200 + (i + 7);
  if(nB > 255) nB = 255;
  pPen[i] = new CPen(PS_SOLID, 1, RGB(nR, nG, nB));
 }
 // 화면의 절반에 선을 그려 넣어, 그라데이션 효과를 준다. 
 for(i=0; i<nH/2; ++i)
 {
  pDC->SelectObject(pPen[(i * 63) / nH]);
  pDC->MoveTo(0, i);
  pDC->LineTo(nW, i);
 }
 // 펜 객체들을 제거한다. 
 for(i=0; i<64; i++) delete pPen[i];
 // 나머지 반은 단순히 흰색으로 채운다. 
 pDC->SelectStockObject(WHITE_PEN);
 for(int j = nH / 2; j < nH; j++)
 {
  pDC->MoveTo(0, j);
  pDC->LineTo(nW, j);
 }
 pDC->SelectStockObject(BLACK_PEN);
}

< 설 명 >
방금 코딩한 DrawBk() 함수는 배경에 그라데이션 효과를 주는 함수이다. 그라데이션의 범위는 클라이언트 뷰 화면의  절반 부분인데, 색상이 아래로 갈수록 점점 밝아지도록 하였다. 이를 위해서 총 64개의 펜 객체를 각각 색상이 점점 증가하도록 하였다. 그리고 화면의 나머지 절반에 대해서는 단순히 흰색을 칠했다. 이 흰색으로 칠한 부분은 영역과 브러시를 만들어서 칠하여도 상관없고 단순한 흰색의 사각형으로 해도 좋을 것이다.
중요한 것은 DrawBk() 함수를 호출하는 곳이 바로 WM_ERASEBKGND 메지시 핸들러 인데, 이 메시지 핸들러는 WM_PAINT가 발생하기 전에 호출된다. 따라서 OnPaint() 함수에서 출력된 결과는 OnEraseBkgnd() 함수에서 출력한 겨로가보다 위로 나타나기 마련이다.
지금 당장 좋은 결과를 보았으나 나중에 서브 클래싱을 통한 컨트롤 프로그래밍에서는 보다 더 나은 사용자 인터페이스를 보게 해줄 것이다. 그리고 OnEraseBkgnd() 메시지 핸들러에서 맨 마지막에 "return CView::OnEraseBkgnd(pDC); " 코드를 주석처리 하여 CView 클래스의 OnEraseBkgnd() 함수를 명시적으로 호출하지 ㅇ낳도록 하여야 한다. 그렇지 않으면 CView 클래스의 코드가 동작하게 되면서 배경을 다시 그려 버린다. 그렇게 되면 지금 작성한 코드가 동작은 하지만 결국 덮어쓰여서 겨로가를볼 수 없게 된다. 때문에 그냥 TRUE 를 리턴하도록 위저드가 생성한 코드를 수정한 것이다. 화면에 그리기를 처리하는데 있어서 OnPaint() 함수만 있는 것이 아니라는 사실을 본 예제를 통해서 꼭 알아두길 바란다.
OnEraseBkgnd() 함수에서는 IDB_Bitmap_Alpha 비트맵 리소스를 화면에 출력하도록 하는데, 이 이미지를 BitBlt() 함수를 이용하여 출력하고 그 오른쪽에 :: AlphaBlend() API 함수를 이용하여 두 번 출력 하였다.
결국 두 이미지는 같은 것이며 화면에 보이는 효과만 다를 뿐이다. ::AlphaBlend() 함수의 인자는 ::TransparenetBlt() 함수나 StretchBlt() 함수와 거의 동일하다. 다만 마지막 인자로 BLENDFUNCTIOIN 구조체가 주어지는데 구조체의 선언은 다음과 같다.

typedef struct _BLENDFUNCTION {
   BYTE   BlendOp;
   BYTE   BlendFlgs;
   BYTE   SourceConstantAlpha;
   BYTE   AlphaFormat;
}BLENDFUNCTION, *PBLENDFUNCTION, *LPBLENDCTION;

첫 번째 멤버 : 출력 옵션인데, 현재 이 값은 AC_SRC_OVER 값만 사용되며 플래그는 사용되지 않는 멤버이다.
세 번째 멤버 : 비트맵의 투명도를 정해 주는 것인데, 0에서 255 사이의 값이 올 수 있으며 0이면 완전히 투명해 지고 255이면 불투명해 진다. 그러므로 중간의 값을 필요에 따라 적절히 사용하여야 할것이다.
마지막 멤버 : 비트맵을 해석하는 방법을 설정하는 것인데, 현재 이 값은 AC_SRC_ALPHA라는 값이나 0이 되어야 한다. 앞서 RGB값에 대해서 이야기 할 때 각각이 9비트이므로 총 24비트로 색상을 표현한다고 말했었다. 그렇다면 왜 32비트 컬러라고 하며 나머지 8비트는 어떤 역할을 하는 것일까? 이는 색상이 아니라 색상의 투명도를 명시하는 값이 되는 것이다.

댓글 없음:

댓글 쓰기