본문 바로가기

Library/Windows Programming

BMP 디코더를 작성할 때 주의점 : 스캔 라인 바이트 정렬

비트맵 이미지를 다루기 위한 API가 준비되어 있는 플랫폼에서 비트맵을 처리하는 것은 매우 간단하다. 또, 플랫폼의 지원보다 더 좋은 이미지 프로세싱이 필요하다면, 외부 이미지 라이브러리를 사용하면 된다. FreeImage, ImageMagick, CxImage, DevIL 등은 잘 구현된 공개 이미지 라이브러리들이며, 이외에도 쓸만한 라이브러리들을 쉽게 찾을 수 있다. 이들을 사용하여 이미지를 읽어들이거나 필터링을 적용하는 것만 목적이라면, 사실 비트맵 이미지들의 내부 구조를 파악할 필요는 없다. 그러나, 이런 지원이 없거나 주로 사용하던 이미지 라이브러리가 해당 플랫폼에서 컴파일되지 않는다면, 이미지 파일 구조를 직접 읽어들이는 고통스러운 작업이 필요하며, 파일 내부 구조에 대한 이해가 필수적이다. 한 가지 다행스러운 점은, BMP 파일의 경우 구조가 매우 단순하기 때문에 BMP 디코더, 인코더를 직접 작성하는 것은 그렇게 어렵지 않다는 점이다.

BMP 파일 포맷에 대해 이미 많은 사이트에서 설명하고 있기 때문에 헤더와 픽셀 포맷에 대해서는 더 할 말은 없다. 그러나, 처음 이 포맷을 직접 다루어 보는 사람이라면, 스캔 라인의 바이트 정렬을 주의해야 한다.

BMP 포맷이 bpp(pit per pixel)에 따라 저장되는 픽셀 구조가 달라진다는 것은 이미 잘 알려진 사실이다. 그러나, bpp를 참고하여 픽셀만 차곡차곡 읽어들이면 출력되는 이미지가 밀리는 현상이 발생할텐데, 이것은 패딩(padding)을 고려하지 않았기 때문이다. 예를 들어, bpp가 24인 경우, 한 픽셀의 크기는 3 바이트다. 그러나, 메모리에 존재하는 BMP 이미지의 한 줄의 길이는 너비값 * 3이 아니다. 한 줄의 길이를 4로 나누어 떨어지도록, 추가 바이트가 존재하기 때문이다. 따라서, 메모리에서 실제 스캔 라인의 길이는 너비와 픽셀 크기(bpp에 의존하는)를 곱한 값에 패딩 바이트를 더한 값이다. 물론, 너비와 픽셀 당 바이트 수를 곱한 값이 4의 배수라면 추가 바이트는 없다. 이것은 성능상의 이유 때문인데, 32 비트 기계는 4 바이트로 정렬된 데이터를 좀 더 효율적으로 처리할 수 있기 때문이다. 추가되는 바이트는 단순히 다음과 같이 구할 수 있다.


long row_length = width * bpp / 8; // 주의 : bpp는 8 비트보다 작을 수도 있다
long pad = 0; // 추가되는 바이트
while(row_length % 4 != 0) {
    ++row_length;
    ++pad;
}


이 코드는 단순하지만 이해하기 쉽다. 비트 연산자를 사용하면 좀 더 짧고 좋은 코드를 작성할 수도 있다. 당연히, 비트 연산을 이용하여 처리하는게 훨씬 좋은 성능을 보여준다. BMP 이미지의 한 줄의 끝에 도달했을 때, 패딩 바이트 수를 더해줘야 다음 라인의 시작 위치를 정확하게 얻어낼 수 있다.

BMP 디코더를 작성할 때 문제가 될만한 부분은, 이 패딩을  고려하지 않아서 잘못된 메모리 참조를 하는 경우가 대부분이다. 패딩 문제만 신경 쓴다면, 나머지는 헤더 정보를 직접적으로 사용하기만 하면 되므로 큰 어려움 없이 코드를 작성할 수 있을 것이다.