본문 바로가기

Project

Project03 API_Bitmap Viewer(작성중)

.Bitmap?
: 비트맵(영어: Bitmap, 문화어: 비트매프, 비트배렬표)은 컴퓨터 분야에서 디지털 이미지를 저장하는 데 쓰이는 이미지 파일 포맷 또는 메모리 저장 방식의 한 형태이다. 보다 일반적으로는 래스터 그래픽스(점방식)라고 한다. 화면 상의 각 점들을 직교좌표계를 사용하여 화소 단위로 나타낸다. 그림을 확대하면 각 점이 그대로 커져 경계선 부분이 오돌도돌하게 보이는 계단 현상이 나타나며, 이를 좀 더 부드럽게 처리 하기 위한 알고리즘들(쌍삼차 필터링, 이중선형 필터링 등)이 있다.
가로 곱하기 세로 만큼의 픽셀 정보를 다 저장해야 하기 때문에 벡터 방식의 이미지나 텍스트 자료에 비해 상대적으로 용량이 크고 처리 속도가 느리다. 이를 개선하기 위해 JPEG, GIF, PNG 등의 다양한 파일 형식이 개발되었다.

.Bitmap viewer 그리고 목표
: Bitmap을 열어서 1차적으로 파일내의 정보를 불러와서 화면에 나타내어주며 2차적으로는 data를 분석하여 그림이 출력되게 합니다 그리고 추가로 기타 회전 색반전등의 기능을 넣습니다

.진행계획
총 6단계로 나누어 비트맵뷰어를 작성해 나갈 계획입니다
각단계별 내용은 아래 표와 같습니다

 1_ 비트맵 정보 출력 및 구조파악
 2_ 비트맵 이미지 출력
 3_ 원하는 비트맵 선택하여 출력
 4_ 부가기능1 - 회전
 5_ 부가기능2 - 확대축소
 6_ 부가기능3 - 기타기능

.Bitmap의 기본적인 구조
 FILE HEADER : 파일에 대한 정보를 저장
 INFORMATION HEADER : 실제 비트맵의 세부 정보 저장
 PALETTL : 각 빅셀이 가지고 있는 RGB값을 저장
                 색상이 256이하일 경우에만 해당
 DATA : 각 픽셀의 색상값 저장

한가지 색으로만 되어 있는 비트맵을 헥사뷰로 출력하였습니다>>

위 그림의 정보를 토대로 분석하도록 하겠습니다

.Bitmap의 구조체 HEADER
1. FILEHEADER 구조체 - 헤더에 속하며 3가지의 정보를 가지고 있습니다 정보는 아래와 같습니다

typedef struct tagBITMAPFILEHEADER
{
          WORD               bfType;                  // "BM" 이라는 값을 저장함
          DWORD             bfSize;                   // 바이트 단위로 전체파일 크기
          WORD               bfReserved1;          // 예약된 변수
          WORD               bfReserved2;          // 예약된 변수
          DWORD             bfOffBits;                // 영상데이터 위치까지의 거리
} BITMAPFILEHEADER;

오픈한 파일이 비트맵 파일인지를 가리키는 것이 bfType입니다  "BM" 문자가 있다면 Bitmap입니다.
bfOffBits 는 파일 시작부분에서 실제 데이터가 존재하는 위치까지 바이트 단위의 거리를 나타내며. 오프셋 (offset) 이라고 합니다.

참고>> 
WORD 는 2바이트 (unsigned short), DWORD 는 4 바이트 (unsigned long) 
 

2. INFOHEADER - bmp의 상세한 정보를 가지고 있고 그내요은 아래와 같습니다

typedef struct tagBITMAPINFOHEADER
{
          DWORD              biSize;                       // 이 구조체의 크기 
          LONG                biWidth;                      // 픽셀단위로 영상의 폭
          LONG                biHeight;                     // 영상의 높이
          WORD                biplanes;                    // 비트 플레인 수 (항상 1)
          WORD                biBitCount;                 // 픽셀당 비트수 (컬러, 흑백 구별)
          DWORD              biCompression;          // 압축유무
          DWORD              biSizeImage;              // 영상의 크기 (바이트 단위)
          LONG                biXPelsPerMeter;        // 가로 해상도
          LONG                biYPelsPerMeter;        // 세로 해상도
          DWORD              biClrUsed;                 // 실제 사용 색상 수
          DWORD              biClrImportant;            // 중요한 색상 인덱스
} BITMAPINFOHEADER;

3. 팔레트 구조체  - 잘 사용되지 않습니다 내용은 아래와 같습니다 
typedef struct tagRGBQUAD
{
           BYTE          rgbBlue;                  // B 성분 (파란색)
           BYTE          rgbGreen;                // G 성분 (녹색)
           BYTE          rgbRed;                   // R 성분 (빨간색)
           BYTE          rgbReserved1;          // 예약된 변수
} RGBQUAD;

 팔레트는 인덱스에 의한 컬러값을 저장하기 위한 구조체이다. 이 구조체를 사용하여 팔레트의 수 만큼 배열을 할당하여 저장한다. 256 컬러모드의 영상은 팔레트배열 크기가 256 개, 16 비트 컬러 영상은 팔레트 크기가 2^16 개이다. biClrUsed 변수를 참조하면 된다. 흑백영상의 경우 팔레트는 256 개이며, 트루컬러의 경우는 인덱스 저장이 아니라 데이터값을 직접 저장하므로 팔레트가 없다.

.구조체 정보 출력
위의 구조체 정보는 bitmap의 파일에 형성하는데 있어서 중요한 정보들을 지니고 있습니다 이러한 정보들을 정해진 위치에서 불러들인 파일에 따라 그 값만 변화됩니다 불러들인 정보를 가지고 실제로 비트맵을 화면에 나타내는데에도 사용하고 어떻게 구성되어있는지에 대한 정보도 확인 할 수 있습니다

오프셋 : 위 구조체를 바탕으로 위치를 정리하였습니다


예제>magic number(signature)를 출력하기
1) 파일을 엽니다
 hFile=CreateFile(Sub,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);   

2)edit컨트롤을 사용하여 값이 들어갈곳을 만들어 줍니다
hEdit = CreateWindow(TEXT("edit"), NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_READONLY | ES_CENTER, 750, 105, 110, 25, hWnd,(HMENU)ID_EDIT,g_hInst,NULL);  

3)원하는 위치를 wsprintf를 이용하여 입력하고  SetWindowText를 이용하여 출력합니다
ReadFile(hFile,buf,BSIZE,&dwRead,NULL);
 wsprintf(tt, TEXT(" %c%c"),buf[0],buf[1]);
 SetWindowText(hEdit,tt);

결과보기>>추가로 file size 가로 세로 폭을 출력하였습니다

참고>>가로출력
 wsprintf(tt,TEXT("%d "),*(int *)(&buf[22])) ;
 SetWindowText(hEdit3,tt);


4. DATA - Bitmap 이미지의 정보가 들어있습니다


 1)RGB
Bitmap의 저장 방식 RGB세개의 색상으로 저장이 되는데 위 헥사뷰 36번부터 시작하며

 B
 FF 00 00 

위의 값이 반복되는것으로 보아 단순히 파란색인 그림임을 알 수 있습니다 

 2)4의배수(가로길이)와 패딩
 비트맵은 메모리 저장시, 가로줄의 크기는 항상 4 바이트의 배수가 되어야 한다. 실제 사용하는 영상의 가로길이는 4 바이트의 배수가 아닐 수 있으므로 이럴 경우는 4 의 배수바이트로 바꾸어 저장합니다

  예를 들어 지금 BMP 로 저장할  데이터의 실제 크기가 78 x 60 이라면 가로픽셀 78 은 78 byte 이고, 4 의 배수가 아니므로 80 바이트로 만들고 나머지 두 바이트는 아무 값이나 넣어줍니다. 실제 저장되는 메모리는 80 x 60 픽셀의 크기가 됩니다 여기서 남은 2바이트는 패딩값으로 비어 있는 쓰레기 값입니다


 3)반전
 우리가 보는 그림의 아래부터 순서대로 그림이 들어가기 때문에 실제로 저장된 상태는 뒤집혀 있습니다 이것을 바로 잡도록 해주어야 합니다

 


.비트맵 출력
비트맵을 정확하게 출력하기 위해서는 이상 3가지의 상황을 고려해야 합니다

고려하지 않고 출력하면 대체로 심각한 노이즈에 상하가 사선으로 짤려서 나오게 됩니다



제대로 출력을 하기 위해서는 
 1) setpixel명령을 사용하여  1pixel로 찍어주면됩니다 
 2) 2중 for를 사용하여 좌우 상하로 데이터를 찍어주는 중요한것은 3칸(rgb)씩 전진해야 합니다
 3) 데이터가 시작하는 부분 부터 iOff= *((int *)(&buf[10])); 시작하도록 합니다
 4) 패딩값을 계산하여 좌우 좌표가 패딩값이 시작하는 위치에 왔을때 건너뛰게 합니다
     공식 : (전체사이즈 - 픽셀사이즈)/높이 => iBMsize = (iBMsize - iW*iH)/iH;

소스예제는 아래와 같습니다

소스코드
/* 비트맵 가로 세로 계산 */
  iW = *((int *)(&buf[18])); 
  iH = *((int *)(&buf[22]));
      
  iW = iW*3;
  iH = iH;
    
  iOff= *((int *)(&buf[10]));
        
  iBMsize=*((int *)(&buf[34]));
    
  iBMsize = (iBMsize - iW*iH)/iH;
  
  hdc =GetDC(hWnd);
  hMemDC = CreateCompatibleDC(hdc);

  MyBitmap = CreateCompatibleBitmap(hdc,iW/3,iH);    
  OldBitmap = (HBITMAP)SelectObject(hMemDC,MyBitmap);
  for(iY=1;iY<=iH;iY++)
  {        
    for(iX=0;iX<iW;iX+=3)
    {  
      SetPixel(hMemDC,iX/3,iH-iY,RGB(*(char *)(&buf[iOff+2]),*(char *)(&buf[iOff+1]),*(char *)(&buf[iOff])));
      iOff +=3;
    }
    iOff=iOff+iBMsize;
  }

  SelectObject(hMemDC,OldBitmap);
  DeleteDC(hMemDC);
  ReleaseDC(hWnd,hdc);
  
  InvalidateRect(hWnd,NULL,TRUE); 


참고>>hMemDC?
위코드에서는 그려지는 대상이 hMemDC입니다 이유는 고속복사 BitBlt를 사용하여 메모리DC에 저장한다음 출력해주기 위해서 입니다 BitBlt를 사용하지 않아도 출력이 되지만 화면을 움직일때마다 다시 그리게 되면 아래서 부터 위로 그려지는 속도가 느껴질 정도로 느려지게 됩니다 
BitBlt(hdc,30,50,(*(int *)(&buf[18])),(*(int *)(&buf[22])),hMemDC,0,0,SRCCOPY);

고>>MyBitmap = CreateCompatibleBitmap(hdc,iW/3,iH);  
비트맵을 그리기위해서는 비트맵을 그릴 공간이 필요한데 그공간을 마련해주는 역활을 하는것이 CreateCompatibleBitmap(hdc,iW/3,iH); 입니다

.API 전체적 흐름
: 프로그램을 제작하기전에 어떻게 구현해 나갈것인가 미리 구상을 해보았습니다 단순히 기본문에서 소스들을 추가해나가니 소스가 깔끔하지 못하고 복잡해지며 정리가 잘되지 않았습니다 그리고 에러 발생시 처리를 해야될 상황에서 곤란하여 기본형에서 시작하되 원하는 기능과 관련 소스들을 정리하여 만들어 나가는 것이 가장 좋은 방법이 아닐까 생각이되어 그려보게 되었습니다

전체적인 흐름>>

1) Create : 창을 생성하고 초기 값을 세팅하여 미리 지정해둔 비트맵을 화면에 뿌려줍니다

2) 버튼 컨트롤
   WM_KEYDOWN : 버튼을 눌력 상하로 스크롤이 가능 합니다 간단합니다 원하는 키값을 받으면 원하는 동작을 하도록 하면됩니다 아주 기본입니다

   WM_MOUSEWHEEL : 스크롤시 마우스 휠을 가능하도록 하였습니다
   case WM_MOUSEWHEEL:
    SystemParametersInfo(SPI_GETWHEELSCROLLLINES,0,&Lines,0);
    for(i=0;i<Lines;i++)
    {
      if((short)HIWORD(wParam)>0)
      {
        ylnc = -Lines*20;
      }
      else
      {
        ylnc = Lines*20;
      }
    }
    ylnc = max(-yPos,min(ylnc,(*(int *)(&buf[22]))-yPos));
    yPos = yPos + ylnc;
    GetClientRect(hWnd,&crt);
    SetRect(&rt,10,50,(*(int *)(&buf[18]))+50,(*(int *)(&buf[22]))+50);
    ScrollWindow(hWnd,0,-ylnc,&rt,&rt);
    SetScrollPos(hWnd,SB_VERT,yPos,TRUE);
    return 0;  

   WM_VSCROLL : 그림이 화면 밖으로 나가게 되면 스크롤을 하여 화면 밖의 이미지를 볼수 있도록 합니다
  case WM_VSCROLL:
    ylnc = 0;
    switch(LOWORD(wParam))
    {
      case SB_LINEUP:
        ylnc = -20;
        break;
      case SB_LINEDOWN:
        ylnc = 20;
        break;
      case SB_PAGEUP:
        ylnc = -200;
        break;
      case SB_PAGEDOWN:
        ylnc = 200;
        break;
    }
    if(0 > yPos+ylnc)
      ylnc =-yPos;
    if((*(int *)(&buf[22]))<yPos+ylnc)
      ylnc=(*(int *)(&buf[22]))-yPos;
      yPos = yPos + ylnc;
    
    GetClientRect(hWnd,&crt);
    SetRect(&rt,10,50,(*(int *)(&buf[18]))+50,(*(int *)(&buf[22]))+50);
    ScrollWindow(hWnd,0,-ylnc,&rt,&rt);
    SetScrollPos(hWnd,SB_VERT,yPos,TRUE);
    return 0;

참고>> 휠과 스크롤
휠과 스크롤은 약간 관계가 있습니다 키보드를 눌려 한칸씩 스트롤이 되는것과 휠을 돌려서 한칸씩스크롤 되는것은 비슷하기 때문입니다 위의 2코드는 버튼과 마우스휠 동시에도 잘작동하도록 각각의 케이스 문에다가 scroll함수를 넣어두었습니다 휠이 스크롤이 아닌 다른용도로 사용한다면 스크롤이 아닌 다른 기능을 추가하여 스크롤을 사용합니다 확대 축소나 혹은 휠로 파일을 이동하도록 하는 것도 가능합니다

3)PAINT : 비트맵을 계산하여 화면에 뿌려줍니다 여기서 2가지 파트로 나누었습니다
Paint : hMemDC를 가지고 BitBlt를 이용하여 고속복사를 수행합니다 고속복사를 사용하지 않게 되면 화면 에 그림이 떠지는 속도가 상당히 느려집니다 아마 아래에서 부터 위로 그려지는 에니메이션을 보게 될것입니다

void BitmapDraw(hWnd) : 비트맵의 정보를 뿌려주는 역활과 데이터를 계산하여 hMemDC에 넣어주는 역활을 합니다 위에 있는 이중for문이 여기에 해당합니다 이렇게 따로 함수 처리를 해준 이유는 open을 눌러 비트맵을 선택했을때나 초기 시작화면 기타 처리 상황이 발생 했을 경우에 그림을 표시하기 위해서 만들었습니다

4)COMMAND : radio와 Menu.rc를 처리하고 BitmapDraw() switch에 들어온 명령에 따라 호출됩니다

추가>> 경로 출력
:비트맵을 불러드릴때 sample.bmp를 처음 불러들이고 이후에 선택된 값을 불러드릴때 불러드린 경로를 출력하도록 하였습니다

먼처 배열을 선언하고 초기화를 하여 줍니다

static TCHAR *Sub=TEXT("park.bmp");

그리고 열어주는 처리를 합니다
  case ID_FILE_OPEN1:
    memset(&OFN,0,sizeof(OPENFILENAME));
    OFN.lStructSize=sizeof(OPENFILENAME);
    OFN.hwndOwner=hWnd;
    OFN.lpstrFilter=TEXT("모든 파일(*.*)\0*.*\0");
    OFN.lpstrFile=lpstrFile;
    OFN.nMaxFile=MAX_PATH;
    Sub=OFN.lpstrFile;

    hFile=CreateFile(Sub,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);


결과보기>> 현재까지 구현된 기본적인 비트맵 뷰어입니다
    1)기본 출력화면입니다

    2)90도 회전한 화면입니다

   3)180도 회전한 화면입니다

4)270도 회전한 화면입니다

   5)흑백처리한 화면입니다


업데이트1.확대와 축소
: 버튼을 눌러 확대와 축소의 기능을 넣으려고 하였습니다 제일처음 생각난것이 StretchBlt입니다 코드를 작성하고 테스트를 해보니 문제점이 발견되었습니다 확대는 잘되지만 축소를 하게되면 몇몇 부분에서 색깔값이 겹쳐 그림이 이상하게 출력이 됩니다

그래서 생각한것이 현재 그림의 상태보다 확대 될때는 StretchBlt를 사용하고 축소가 될때는 지정한 값보다 반만큼 그려주도록 하였습니다

먼저 버튼으로 확대 축소 버튼을 만들었습니다

원본이미지>>

축소이미지>> 정상적으로 잘 출력됩니다

 


후기>>
지금까지 비트맵의 정보를 불러오고 비트맵의 이미지를 출력하여 보고 추가로 부가적인 기능들을 구현해 보았습니다 시작은 그림판과 동일한 구현이 목표였지만 쉽지 않았습니다 하지만 기본적인 뷰어로써의 기능은 구현이 되어 만족스럽습니다!!