BMP 파일을 헥스 에디터로 열어 본다. 첫 14바이트는 이렇게 생겼다.
42 4D 46 00 00 00 00 00 00 00 36 00 00 00
이것이 전체 BITMAPFILEHEADER다. 42 4D는 ASCII 문자열 "BM"에 해당하는 시그니처다. 다음 4바이트는 파일 크기를 바이트 단위로 나타낸다 — 46 00 00 00는 리틀 엔디언으로 70을 의미한다. 그다음 4바이트는 예약 필드로 항상 0이다. 마지막 4바이트는 픽셀 데이터까지의 오프셋 — 36 00 00 00은 54이며, 픽셀 배열이 파일의 54번째 바이트부터 시작한다는 뜻이다.
압축 마커도, 색상 프로필도, 알파 채널도 없다. 시그니처, 크기, 오프셋뿐이다. BMP는 이름이 의미하는 그대로 비트의 지도(map)다.
BMP는 실제로 무엇인가
BMP는 Bitmap의 약자이며, 보다 공식적으로는 **Device Independent Bitmap(DIB)**라고 한다. 마이크로소프트는 1990년 Windows 3.0과 함께 이 포맷을 도입했는데, 특정 디스플레이 장치에 종속되지 않고 래스터 이미지를 저장하기 위함이었다. DIB 이전의 Windows는 **Device Dependent Bitmap(DDB)**를 사용했는데, 이는 설치된 그래픽 카드의 픽셀 형식에 맞춰 저장되었다. 한 머신에서 저장한 DDB가 다른 머신에서는 읽히지 않을 수 있었다.
BMP는 디코더가 이미지를 재구성할 수 있을 만큼 충분한 메타데이터를 파일에 저장해 이 문제를 해결했다. 폭, 높이, 비트 심도, 색상 팔레트, 압축 유형, 그리고 원시 픽셀 배열. 운영체제는 헤더를 읽고 버퍼를 할당한 뒤 픽셀을 메모리로 복사한다. 24비트 무압축 이미지의 경우 픽셀 데이터는 보통 아래에서 위로, 행 단위로 4바이트 경계에 맞춰 패딩된 RGB 트리플렛이다.
이 단순함 덕분에 BMP는 10년 넘게 Windows의 기본 이미지 포맷이었다. Paintbrush, Windows 배경화면 엔진, 초기 스캐너, 그리고 수많은 내장 툴이 모두 BMP를 네이티브로 사용했다.
파일 구조
BMP 파일은 순차적으로 배엮된 네 부분으로 구성된다.
BITMAPFILEHEADER (14 bytes)
BITMAPINFOHEADER (40 bytes for the classic version)
Color Table (optional, for indexed color modes)
Pixel Data (the actual image)
BITMAPFILEHEADER (14 bytes)
Bytes 0-1: Signature "BM" (0x42 0x4D)
Bytes 2-5: FileSize (uint32, little-endian)
Bytes 6-9: Reserved (always 0)
Bytes 10-13: Offset (uint32, offset to pixel data from file start)
시그니처가 항상 "BM"인 것은 아니다. Windows는 커서와 아이콘 변형을 위해 "BA", "CI", "CP", "IC", "PT"도 지원하지만, 실제로 열게 될 BMP 파일은 모두 "BM"으로 시작한다.
BITMAPINFOHEADER (40 bytes, BITMAPINFOHEADER type)
Bytes 0-3: HeaderSize (40)
Bytes 4-7: Width (int32, pixels)
Bytes 8-11: Height (int32, pixels; negative means top-down)
Bytes 12-13: Planes (1)
Bytes 14-15: BitCount (1, 4, 8, 16, 24, or 32)
Bytes 16-19: Compression (0 = none, 1 = RLE8, 2 = RLE4, 3 = bitfields)
Bytes 20-23: ImageSize (0 if uncompressed)
Bytes 24-27: XpixelsPerMeter (physical resolution)
Bytes 28-31: YpixelsPerMeter (physical resolution)
Bytes 32-35: ColorsUsed (0 means 2^BitCount)
Bytes 36-39: ColorsImportant (0 means all)
이후 Windows 버전에서는 알파 마스크, 색 공간, ICC 프로필을 지원하기 위해 52, 56, 108, 124바이트의 더 큰 헤더가 추가되었다. 하지만 여전히 가장 흔히 마주치는 것은 40바이트 버전이다.
Height는 방향을 의미한다
높이 필드가 양수면 이미지는 아래에서 위로(bottom-up) 저장된다. 파일의 0번 행은 이미지의 맨 아래 행이다. 높이 필드가 음수면 이미지는 위에서 아래로(top-down) 저장된다. 이는 BMP의 조용한 특이점 중 하나로, 대부분의 다른 포맷이 기본적으로 위에서 아래로 저장하는 것과 다르다.
Color Table
비트 심도가 1, 4, 8인 경우 BMP는 인덱스된 팔레트를 사용한다. 각 팔레트 항목은 4바이트로, 차례로 파랑, 초록, 빨강, 그리고 거의 항상 무시되는 예약 알파 바이트다. 256색 BMP는 헤더 직후에 1,024바이트의 색상 테이블을 가진다. 뒤따르는 픽셀 데이터는 색상이 아니라 해당 테이블에 대한 인덱스다.
Pixel Data Padding
각 스캔라인은 4바이트의 배수여야 한다. 폭이 5픽셀인 24비트 이미지는 행당 15바이트가 필요하지만, 16바이트로 올림된다. 이 추가 패딩 바이트는 낭비되는 공간이며, BMP가 비효율적인 또 하나의 작은 이유다.
다음은 2 x 2 24비트 무압축 BMP의 전체 예시다.
Offset 0x00: 42 4D 46 00 00 00 00 00 00 00 36 00 00 00 -- BITMAPFILEHEADER
Offset 0x0E: 28 00 00 00 02 00 00 00 02 00 00 00 01 00 18 00 -- BITMAPINFOHEADER
Offset 0x22: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00
Offset 0x32: 00 00 00 00 00 00 00 00
Offset 0x36: FF 00 00 00 FF 00 00 00 FF -- bottom row: red, green, blue (with padding)
Offset 0x3F: 00 FF FF 00 FF FF 00 00 00 -- top row: cyan, magenta, yellow (with padding)
1990년의 포맷 환경
BMP는 독자적으로 등장하지 않았다. 1990년이 되면 이미 여러 이미지 포맷이 주목을 받고 있었다.
| Format | Compression | Color depth | Transparency | Patent risk | Typical use |
|---|---|---|---|---|---|
| GIF | LZW | 256 colors max | 1-bit | Yes (LZW) | Web graphics |
| JPEG | DCT + Huffman | 24-bit | None | Mostly no | Photographs |
| TIFF | Multiple optional | Up to 48-bit | Yes | Optional | Print, scanning |
| PCX | RLE | Up to 24-bit | None | No | DOS games, clip art |
| PICT | RLE | Up to 32-bit | Yes | No | Classic Mac OS |
| BMP | None or RLE | Up to 32-bit | None | No | Windows wallpaper, icons |
GIF는 이미 웹의 기본 포맷이었지만, LZW 특허 때문에 상업적 사용이 위험했다. JPEG은 사진용으로 설계되었고 손실 압축으로 데이터를 버렸다. TIFF는 강력했지만 과도하게 설계되어 있어 보편적인 TIFF 리더를 작성하는 것 자체가 하나의 프로젝트였다. PCX는 DOS와 함께 쇠퇴하고 있었다.
BMP의 장점은 단순함이었다. 다른 포맷보다 작거나 빠르거나 기능이 많지 않았다. 그저 Windows가 별도 라이브러리 없이 이해할 수 있는 포맷이었다.
BMP가 먹혔던 이유
BMP는 당시 맥락에서 분명한 장점이 있었다.
디코딩 복잡도가 0이다. 24비트 무압축 BMP는 고정 크기 헤더를 읽고 픽셀을 프레임버퍼로 직접 복사하기만 하면 렌더링된다. 허프만 테이블도, DEFLATE 상태 머신도, 프로그레시브 패스도 없다. 이 덕분에 CPU 성능이 제한적인 임베디드 시스템에 이상적이었다.
특허 부담이 없다. GIF의 LZW와 달리 BMP 압축은 없거나 RLE 중 하나로, 둘 다 특허에 묶이지 않았다. 이는 오픈소스 툴과 GIF 라이선스를 피하고 싶어 하는 상용 소프트웨어에 중요했다.
Windows와 직접 통합된다. Win32 API는 DIB 섹션을 중심으로 LoadBitmap, BitBlt, StretchBlt를 제공했다. 개발자는 BMP를 메모리에 로드한 뒤 몇 줄의 C 코드로 화면에 뿌릴 수 있었다.
예측 가능한 메모리 레이아웃. 무압축 BMP 픽셀은 화면 메모리에 직접 매핑되므로, 1990년대 게임 엔진과 그래픽 툴은 BMP를 텍스처 교환 포맷으로 사용했다. 파일을 메모리 매핑하고 픽셀 오프셋을 원시 버퍼로 취급할 수 있었다.
BMP의 한계
BMP의 강점이 곧 약점이 되었다. 이 포맷은 업계의 변화를 따라가지 못했다.
효과적인 압축이 없다. 무압축 BMP는 거대하다. 1920 x 1080 24비트 이미지는 5.93MB다. 같은 이미지를 JPEG 품질 90으로 저장하면 약 300–500KB다. 인터넷 속도와 저장 비용이 제약이 되면서 BMP는 민망한 존재가 되었다.
RLE 압축이 약하다. BMP는 RLE8과 RLE4 러닝 길이 인코딩을 지원하지만, RLE는 큰 균일 영역이 있는 이미지에서만 효과적이다. 사진과 그라데이션은 잘 압축되지 않는다. 실제로 RLE로 인코딩된 BMP는 드물다.
진정한 투명도가 없다. 표준 BMP에는 알파 채널이 없다. 32비트 BMP 변형에는 알파 바이트가 포함되지만, 많은 툴이 이를 무시하거나 예약 필드로 취급한다. 웹 그래픽과 게임 스프라이트에서는 BMP가 PNG에 비해 무용지물이었다.
애니메이션, 메타데이터, 색상 관리가 없다. BMP는 픽셀 외에는 거의 저장하지 않는다. EXIF도, 원래 헤더의 ICC 프로필 지원도, 애니메이션 프레임도 없다. 워크플로가 정교해질수록 BMP는 원시적이기만 했다.
아래에서 위로 저장된다. 기본적인 아래에서 위로의 행 순서는 디코더를 혼란스럽게 하고 변환에 추가 연산을 요구한다. 이익 없이 마찰만 더하는 작은 디테일이다.
BMP를 대체한 포맷들
BMP의 한계는 후속 포맷을 직접적으로 형성했다.
**PNG(1996)**는 투명도와 압축 문제를 해결했다. 무손실 압축, 8비트 알파, 그리고 TIFF보다 단순한 스펙을 제공했다. PNG는 웹과 크로스플랫폼 애플리케이션에서 거의 모든 곳의 BMP를 대체했다.
**JPEG(1992)**는 사진 문제를 해결했다. DCT 기반 손실 압축으로 사진을 무압축 BMP보다 10–20배 작게 만들었다. 연속톤 이미지라면 어떤 것이든 JPEG이 기본이 되었다.
**WebP(2010)**는 손실/무손실 모드, 애니메이션, 알파를 하나의 컨테이너에 담았다. 무손실 WebP는 일반적으로 PNG보다 20–30% 작으며, PNG 자첵 BMP보다 훨씬 작다.
**HEIC/HEIF(2013)**는 HEVC 기반 압축을 정지 이미지에 가져왔다. JPEG보다 작은 파일 크기에 더 나은 품질을 제공하고, 알파와 깊이 맵을 지원하며, 이제 Apple 기기의 기본 포맷이다. iPhone이 사진을 저장하면 대개 HEIC다.
이 모든 포맷은 BMP가 특정 사용 사례에서 더 이상 충분하지 않았기 때문에 존재한다.
BMP가 여전히 쓰이는 곳
BMP는 사라지지 않았다. 단지 단순함이 비효율성보다 더 값진 틈새로 밀려난 것이다.
Windows 내장 코드. Windows 부팅 화면, 레거시 대화 상자, 일부 시스템 리소스는 여전히 BMP를 사용한다. Win32 GDI API는 DIB 데이터를 기대하며, 마이크로소프트가 이 코드 경로를 다시 작성할 가치가 없다.
임베디드 시스템과 마이크로컨트롤러. Arduino나 STM32의 작은 LCD는 PNG를 디코딩할 RAM과 CPU를 가지지 못한다. 16비트 무압축 BMP는 거의 코드 없이 프레임버퍼로 직접 복사할 수 있다.
의료 및 과학 영상. 일부 레거시 DICOM 뷰어와 실험 장비는 모든 시스템에서 열리기 때문에 BMP로 낸다. 이 포맷의 단순함은 규제 환경에서 상호운용성 리스크를 줄여준다.
리버스 엔지니어링과 교육. BMP는 여전히 이미지 파일 구조를 가르치기 가장 좋은 포맷 중 하나다. 헤더가 작고, 픽셀 레이아웃이 명확하며, 오후에 동작하는 디코더를 만들 수 있다.
게임 개발 레거시. 오래된 게임 엔진과 텍스처 툴은 BMP를 중간 포맷으로 사용했다. 일부 모딩 커뮤니티는 여전히 BMP 에셋을 다룬다. 왜냐하면 툴이 그 주위에 구축되었기 때문이다.
시그니처로 BMP 감지하기
.bmp 확장자를 믿지 마라. 첫 2바이트를 읽어라.
TypeScript
async function isBmp(file: File): Promise<boolean> {
const buffer = await file.slice(0, 2).arrayBuffer()
const bytes = new Uint8Array(buffer)
return bytes.length === 2 && bytes[0] === 0x42 && bytes[1] === 0x4d
}
Python
def is_bmp(path: str) -> bool:
with open(path, "rb") as f:
return f.read(2) == b"BM"
Go
func isBmp(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
buf := make([]byte, 2)
if _, err := f.Read(buf); err != nil {
return false
}
return bytes.Equal(buf, []byte("BM"))
}
PHP
function isBmp(string $path): bool {
$header = file_get_contents($path, false, null, 0, 2);
return $header === "BM";
}
ImageMagick CLI
magick identify -verbose image.bmp | grep "Format:"
# Format: BMP (Microsoft Windows bitmap image)
또는 더 단순하게:
file image.bmp
# image.bmp: PC bitmap, Windows 3.x format, 1920 x 1080 x 24
BMP의 미래
BMP는 완성된 포맷이다. 핵심 스펙은 Windows 95 시대 이후 의미 있게 변경되지 않았다. 이는 한계이자 동시에 보장이다: 1995년에 작성한 BMP 파일이 오늘날에도 여전히 올바르게 열린다.
이 포맷은 새로운 기능을 얻지 않을 것이다. 더 나은 압축, 제대로 된 알파 처리, 애니메이션 지원 같은 것은 없다. 아무도 더 나은 BMP에 투자하지 않는다. 더 나은 BMP가 필요한 모든 맥락에서 PNG, WebP, AVIF, HEIC가 이미 승리했기 때문이다.
그러나 BMP는 이미 자리 잡은 곳에서 계속 쓰일 것이다. Windows 내장 코드, 임베디드 디스플레이, 레거시 의료 장비, 교실. BMP는 화려하지 않고 비효율적이지만, 너무 많은 시스템이 읽는 법을 알고 있어 제거하기 어렵다.
레거시 BMP 파일을 현대 기기에서 계속 사용해야 한다면 실용적인 선택은 변환이다. 사진의 경우 JPEG이나 HEIC로 변환하면 공간을 절약한다. 투명도가 있는 그래픽의 경우 PNG나 WebP가 올바른 대상이다. 그리고 Apple 생태계와 연결해야 할 때는 HEIC를 보편적으로 읽을 수 있는 포맷으로 변환하는 것이 첫 단계다.
HEIC to JPG converter는 브라우저 안에서 모두 실행되므로 파일이 기기를 떠나지 않는다. 무손실 출력과 투명도가 필요하면 HEIC to PNG를 사용해 본다. 웹에 최적화된 크기를 원한다면 HEIC to WebP가 더 작은 파일과 폭넓은 브라우저 지원을 제공한다.
BMP는 업계에 귀중한 교훈을 남겼다. 어디에나 있기 위해 최고일 필요는 없다. 모두가 구현할 만큼 단순하고, 아무도 제거할 엄두를 내지 못할 만큼 굳건하면 충분하다.



