BMP 파일 DarkKaiser, 2007년 7월 17일2023년 9월 6일 1. 서문BMP 파일은 이미지 파일중에 가장 간단한 파일중 하나입니다. 기본적으로 BMP 파일은 1, 4, 8, 16, 24 및 32비트를 지원합니다. 하지만 16과 32비트는 거의 사용하는 예가 없습니다. BMP 파일은 사용이 쉬운만큼 용량이 큰 파일입니다. 그렇기 때문에 이 파일 역시 기본적인 압축형식을 지원합니다. 그 방식은 간단한 압축방식인 Run-Length Compresstion 방식이며, 4비트와 8비트를 지원합니다.(?RLE4, ?RLE8). 어쨌든 이 압축방식의 특성은 간단한 색상의 그림 블록의 경우에는 비교적 효율이 좋으나, 복잡한 색상 혹은 완벽히 랜덤한 색상의 경우는 전혀 압축이 되지 않을수 있습니다. 그래서, 대부분의 BMP 파일은 압축을 사용하는 예가 드물게 됩니다. BMP 파일은 파일과 영상의 정보를 저장하는 헤드 부분이 존재합니다. 먼저 헤드 부분의 정보를 읽어 영상의 크기, 흑백인지 컬러인지의 유무, 팔레트의 크기 정보 등을 얻어낸 후, 이 정보를 이용하여 실제 영상데이터를 읽어들이면 영상 입력이 완료됩니다. BMP 파일은 처음 두 바이트는 ‘BM’ 이라는 문자가 기록되어 있습니다. 이 문자는 현재 다루고 있는 파일이 BMP 파일 형식이라는 의미입니다.2. 비트맵 데이터 형식기본적인 비트맵 파일의 구조는 다음과 같습니다. 파일 헤더이미지 헤더팔레트영상 데이터(거꾸로 들어있음) 보는것과 같이 BMP 파일은 상당히 단순한 구조를 갖고 있습니다. 또한, 만약 Windows 환경에서 프로그래밍을 하는 경우에는 windows.h 헤더화일에 정의가 되어 있습니다. 다른 환경에서 프로그래밍을 하는 경우에는 아래와 같이 설명하는 예를 직접 정의한 헤더 파일을 가지고 계셔야 합니다. 3. File Header파일 헤더는 BITMAPFILEHEAER라는 구조체에 정의되어 있습니다. 이 헤더의 주된 임무(?)는 현재의 파일 포맷이 정말 BMP인지에 대한 정보를 주는것입니다. 대부분의 파일은 자신의 성격에 맞게 확장자 이름이 정해져 있습니다. BMP 파일 역시 확장자는 *.BMP로 정의 되어 있겠죠. 그러나 불행히도, 우연히 다른 파일이 XX.BMP라고 썼을수 있으므로, BMP 디코더가 이를 분석해 내야 합니다. 그 분석 방법에는 아래와 같이 세가지 방법이 있습니다. 그 방법을 알기에 앞서 BITMAPFILEHEADER의 레이어별 구조를 보면, Field nameSize in BytesDescriptionbfType2BM이라는 캐릭터형이 저장되어있습니다.bfSize4파일의 전체 크기를 표시합니다.bfReserved12사용하지 않습니다.bfReserved22사용하지 않습니다.bfOffBits4실질 데이터(pixel)의 시작좌표를 Offset 주소를 나타냅니다. 위 구조를 이해하셨다면, 현재의 파일포맷이 정말 BMP 파일인지를 구분하는 방법을 아래와 같이 추려낼수 있습니다. 파일의 첫 2바이트가 BM으로 시작하는가. 바이트 단위의 정확한 파일크기와 헤더내의 bfSize가 있어야할 값과 일치하는가. bfReserved1과 bfReserved2가 있어야할 위치에 0값이 있는가. 세가지를 모두 체크하면, BMP 파일 여부를 판명할수 있습니다. 하지만, 디코더의 크기를 줄이기 위해, 첫 번째것만 체크하는 경우도 있습니다. 파일의 헤더의 역할은 포맷의 명확성을 표시하는 한편, 파일내에 있어서 실질 데이터(pixel data)의 오프셋 주소값을 나타내줍니다. 다시말해, 실질 데이터의 파일내 위치를 정의해 주는 역할을 합니다. 이는 bfOffBits에서 설정해 있습니다. 대부분의 어플리케이션에서는 실질데이터는 BITMAPINFOHEADER 구조체나 팔레트 다음에 위치하도록 되어있습니다. 어쨌든, 가끔 그 사이에 다른 데이터가 존재할수 있으므로(이를 filler byte라함), 명확성을 위해, bfOffbits를 이용하여, BITMAPFILEHEADER 구조체와 실질데이터를 구분합니다. 4. Image Header이미지 헤더는 BITMAPFILEHEADER 구조체 다음에 바로 위치하도록 되어있습니다. 이미지 헤더는 2가지 종류가 있습니다. 앞에서 언급했듯이 BMP 파일은 두가지 종류가 있습니다. windows version 3와 OS/2에서 사용하는 BMP가 있다고 말씀드렸습니다. 이 경우, 이를 구분하여 저장해야 합니다. 먼저 전자의 경우에 저장해야할 이미지 헤더 구조체는 BITMAPINFOHEADER에 정의 되어있고, 후자의 경우는 BITAMPCOREHEADER에 정의 되어 있습니다. 이 두가지를 구분하는 방법은 어떠한 식별 필드가 존재하는 것이 아니라, 그 크기로 식별을 해야합니다. 이를 설명하기 위해서는 일단 위 두가지의 구조체를 알아보도록 하겠습니다. 4.1. BITAMPINFOHEADER 구조체 Field nameSizeDescriptionbiSize4헤더 크기(최소 40bytesbiWidth4이미지 폭biHeight4이미지 높이biPlanes2현재 지원값은 1입니다.biBitCount2비트수 1,4,8,16,24,32biCompression4압축타입 : BI_RGB(0),BI_?RLE8(1),BI_?RLE4(2),BI_BITFIELDS(3)biSizeImage4이미지 크기biX?PelsPerMeter4미터당 픽셀수 x축biY?PelsPerMeter4미터당 픽셀수 y축biClrUsed4실질적으로 사용될 컬러맵의 엔트리수biClrImportant4주로 사용되는 컬러수 4.2. BITMAPCOREHEADER 구조체 Field nameSizeDescriptionbcSize4헤더 크기(12bytes)bcWidth2이미지 폭bcHeight2이미지 높이bcPlanes2현재 지원값은 1입니다.bcBitCount2비트수 1,4,8,24 위의 구조체를 보면, 두가지를 구분할 방법을 찾으셨을껍니다. 바로 헤더의 크기로 구분하면 되겠죠? 윈도우에서 지원하는 BMP의 경우 헤더의 크기가 최소 40바이트가 필요합니다. 그렇기 때문에 수치적으로 쉽게 구분을 할수 있습니다. 이제 구조체를 하나하나 보면, 이미지 헤더의 역할은 이미지의 비트수와 크기, 차원, 그리고 압축여부를 알려주는 역할을 합니다. 즉, 이미지 자체의 정보를 갖고 있다고 보시면됩니다. 위 구조체는 한번 읽어보시면, 이해가 가실것이고, 좀더 설명이 필요한 것만, 체크해 보도록 하겠습니다.위의 항목중 biHeight를 보면, 이는 기본적으로 unsigned 값입니다. 즉, 양수값입니다. 만약 이 값이 음수였다면, 실질데이터(pixel data)가 bottom up방식이 아닌 top down 방식으로 저장되어있다는 의미입니다. 이 경우는 압축을 지원할수 없습니다. 그렇기 때문에 대부분의 biHeight값은 양수의 값을 갖게 됩니다. 그밖에 biPlanes은 차원(gif의 움직이는 파일처럼)을 나타내는데, 현재는 1차원만을 지원합니다.(움직이는 bmp는 없습니다.) 또, biBitCount는 이미지의 비트수를 나타냅니다. biX?PelsPerMeter와 biY?PelsPerMeter는 모니터상의 화면과 프린트시의 화면크기가 다를수 있으므로, 이를 설정하기 위한 필드입니다. BITAMPCOREHEADER 구조체를 보면, 대부분은 BITMAPINFOHEADER와 비슷한 기능을 합니다.차이점은 이는 압축을 지원하지 않으며, 지원하는 비트수가 적다는 차이가 있습니다.(하지만, 사실상 비트수에서는 16과 32비트수는 거의 사용하지 않습니다.) 5. Color Palette컬러팔레트는 세가지 포맷형식이 있습니다. 이는 BMP 파일의 비트수에 의해 구분이 되는데, 1, 4, 8비트를 갖는 이미지인 경우 RGB값을 나타내기 위해 컬러맵을 사용합니다. 비트수는 이미지 헤더에서 biBitCount나 bcBitCount에서 판단할수 있습니다. 여기서 윈도우 BMP 파일인 경우는 RGBQUAD 구조체를 사용하고, OS/2에서는 RGBTRIPLE 구조체를 이용합니다. RGBQUAD 구조체 FieldSizeDescriptionrgbBlue1rgbGreen1rfbRed1rgbReserved1항상 0값 RGBTRIPLE 구조체 FieldSizeDescriptionrgbtBue1rgbtGreen1rgbtRed1 세 번째 포맷형식은 컬러맵을 사용하지 않습니다. 일단 BI_BITFIELDS를 세팅하기 위한 biCompression이 없는 216, 24, 32비트 이미지인 경우는 컬러 팔레트를 사용하지 않습니다. 이는 팔레트 자체가 용량이 커버리기 때문입니다. 만약, 16, 32비트이고, BI_BITFIELDS인 biCompression값을 갖는다면, RGBQUAD 구조체에는 마스크를 사용합니다.(RGBTRIPLE은 압축을 지원하지 않으므로, 생각하지 않으셔도 됩니다.) 마스크에 대해 대강 설명하면, 미술시간에 종이에 무늬를 그리고, 칼로 짤라내고, 그 위에 스프레이 같은걸로 막 뿌리면, 종이 아래에 자른 무늬가 생기죠? 이때 짜른 종이가 바로 마스크입니다. 컴퓨터에서 마스크란 일련의 비트열입니다. 예를 들면 열 개의 비트열이 있다고 생각해보죠. 그중에 특정위치의 비트값을 알아내고 싶다면, 마스크와 AND연산 그리고 SHIFT연산을 이용하여 구해낼수 있습니다. 6. DIB 사용시 주의점 이미지는 거꾸로 저장됨실제로 비트맵 영상이 저장될 때는 이미지가 거꾸로 저장되어 있다. 따라서, 비트맵에서 영상데이터를 나중에 영상처리를 위해 사용할 배열로 다시 저장할 때는 거꾸로 반전시켜 저장해주면 된다. 영상 가로 길이는 4바이트의 배수비트맵은 메모리 저장시, 가로줄의 크기는 항상 4바이트의 배수가 되어야 한다. 실제 사용하는 영상의 가로 길이는 4바이트의 배수가 아닐 수 있으므로 이럴 경우는 4의 배수바이트로 바꾸어 저장한다.#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4) // 4바이트 배수로 변환 7. BMP 형식 영상파일 입력과 출력 #include <stdio.h> #include <windows.h> #define WIDTHBYTES(bits) (((bits)+31)/32*4) /* 영상 가로길이는 4바이트의 배수여야 함 */ #define BYTE unsigned char void main() { FILE *infile; infile=fopen("talent.bmp", "rb"); if(infile==NULL) { printf("There is no file!!!\n"); exit(1); } BITMAPFILEHEADER hf; fread(&hf,sizeof(BITMAPFILEHEADER),1,infile); /* 파일헤드를 읽음 */ if(hf.bfType!=0x4D42) exit(1); BITMAPINFOHEADER hInfo; fread(&hInfo,sizeof(BITMAPINFOHEADER),1,infile); /* 영상헤드를 읽음 */ printf("Image Size: (%3dx%3d)\n",hInfo.biWidth,hInfo.biHeight); printf("Pallete Type: %dbit Colors\n",hInfo.biBitCount); /* 256칼라 이하의 경우는 취급하지 않음 */ if(hInfo.biBitCount!=8 ) { printf("Bad File format!!"); exit(1); } RGBQUAD hRGB[256]; fread(hRGB,sizeof(RGBQUAD),256,infile); /* 팔레트를 파일에서 읽음 */ /* 영상데이타를 저장할 메모리 할당 */ BYTE *lpImg = new BYTE [hInfo.biSizeImage]; fread(lpImg,sizeof(char),hInfo.biSizeImage,infile); fclose(infile); /* 역상의 이미지 구하기 */ int rwsize = WIDTHBYTES(hInfo.biBitCount*hInfo.biWidth); for(int i=0; i<hInfo.biHeight; i++) { for(int j=0; j<hInfo.biWidth; j++) { lpImg[i*rwsize+j] = 255-lpImg[i*rwsize+j]; } } /* 영상 출력 */ FILE *outfile = fopen("OutImg.bmp","wb"); fwrite(&hf,sizeof(char),sizeof(BITMAPFILEHEADER),outfile); fwrite(&hInfo,sizeof(char),sizeof(BITMAPINFOHEADER),outfile); fwrite(hRGB,sizeof(RGBQUAD),256,outfile); fwrite(lpImg,sizeof(char),hInfo.biSizeImage,outfile); fclose(outfile); /* 메모리 해제 */ delete []lpImg; } 8. 팔레트를 사용하는 BMP 영상을 읽고 역상을 계산하여 트루컬러로 저장 #include <stdio.h> #include <windows.h> #define WIDTHBYTES(bits) (((bits)+31)/32*4) /* 영상 가로길이는 4바이트의 배수여야 함 */ #define BYTE unsigned char void main() { FILE *infile; infile=fopen("pshop256.bmp", "rb"); if(infile==NULL) { printf("There is no file!!!\n"); exit(1); } BITMAPFILEHEADER hf; fread(&hf,sizeof(BITMAPFILEHEADER),1,infile); /* 파일헤드를 읽음 */ if(hf.bfType!=0x4D42) exit(1); BITMAPINFOHEADER hInfo; fread(&hInfo,sizeof(BITMAPINFOHEADER),1,infile); /* 영상헤드를 읽음 */ printf("Image Size: (%3dx%3d)\n",hInfo.biWidth,hInfo.biHeight); printf("Pallete Type: %dbit Colors\n",hInfo.biBitCount); /* 256칼라 이하의 경우는 취급하지 않음 */ if(hInfo.biBitCount<8 ) { printf("Bad File format!!"); exit(1); } RGBQUAD *pRGB; if(hInfo.biClrUsed!=0) /* 팔레트가 있는 경우 */ { pRGB= new RGBQUAD [hInfo.biClrUsed]; /* 팔레트의 크기만큼 메모리를 할당함 */ fread(pRGB,sizeof(RGBQUAD),hInfo.biClrUsed,infile); /* 팔레트를 파일에서 읽음 */ } /* 영상데이타를 저장할 메모리 할당 */ BYTE *lpImg = new BYTE [hInfo.biSizeImage]; fread(lpImg,sizeof(char),hInfo.biSizeImage,infile); fclose(infile); /* 역상의 이미지 구하기 */ int rwsize = WIDTHBYTES(hInfo.biBitCount*hInfo.biWidth); int rwsize2= WIDTHBYTES(24*hInfo.biWidth); BYTE *lpOutImg = new BYTE [3*rwsize*hInfo.biHeight]; int index, R, G, B, i,j; if(hInfo.biBitCount==24) /* 만일 입력영상이 트루(24비트) 칼라인 경우 */ for(i=0; i<hInfo.biHeight; i++) { for(j=0; j<hInfo.biWidth; j++) { /* 팔레트가 없으므로 영상데이타가 바로 칼라값 */ lpOutImg[i*rwsize2+3*j+2] = 255-lpImg[i*rwsize+3*j+2]; lpOutImg[i*rwsize2+3*j+1] = 255-lpImg[i*rwsize+3*j+1]; lpOutImg[i*rwsize2+3*j ] = 255-lpImg[i*rwsize+3*j ]; } } else /* 트루칼라가 아닌 경우 */ for(i=0; i<hInfo.biHeight; i++) { for(j=0; j<hInfo.biWidth; j++) { index = lpImg[i*rwsize+j]; /* 영상데이타는 팔레트의 인덱스임 */ R = pRGB[index].rgbRed; /* 팔레트에서 실제 영상데이타를 가져옴(R) */ G = pRGB[index].rgbGreen; /* G */ B = pRGB[index].rgbBlue; /* B */ R = 255-R; G = 255-G; B = 255-B; /* 역상을 계산함 */ lpOutImg[i*rwsize2+3*j+2] = (BYTE)R; lpOutImg[i*rwsize2+3*j+1] = (BYTE)G; lpOutImg[i*rwsize2+3*j ] = (BYTE)B; } } /* 영상 출력 (24비트인 트루칼라로 출력) */ hInfo.biBitCount =24; hInfo.biSizeImage = 3*rwsize*hInfo.biHeight; hInfo.biClrUsed = hInfo.biClrImportant =0; hf.bfOffBits = 54; /* 팔레트가 없으므로 값이 변함 */ hf.bfSize = hf.bfOffBits+hInfo.biSizeImage; FILE *outfile = fopen("OutImg24.bmp","wb"); fwrite(&hf,sizeof(char),sizeof(BITMAPFILEHEADER),outfile); fwrite(&hInfo,sizeof(char),sizeof(BITMAPINFOHEADER),outfile); fwrite(lpOutImg,sizeof(char),3*rwsize*hInfo.biHeight,outfile); fclose(outfile); /* 메모리 해제 */ if(hInfo.biClrUsed!=0) delete []pRGB; delete []lpOutImg; delete []lpImg; } 디지털 이미지 프로세싱 파일포맷BMP