[Multimedia] show BMP image

DongHyeon·2023년 10월 19일
0

BMP file

  • BMP file format은 비트맵 디지털 이미지(Bitmap Digital image)를 저장하는 데 사용하는 그림파일 서식이다.
  • 일반적으로 1~24bit의 색을 표현할 수 있으며, 현재는 알파채널을 포함한 32bit 포맷 역시 발표되었다.

Alpha Channel

  • RGB채널에서 추가적인 데이터를 저장할 수 있는 채널
  • 일반적으로 불투명도를 지정하고 있다. (0은 완벽하게 투명, 1은 완전히 불투명)

    예를 들어 해당 사진과 같이
    왼쪽 상단에 RGB색상 #8921F2(rgb(137 33 242))(=#8921F2FF(rgba(137 33 242 255)))으로 보라색 색상을 띄고 있는 직사각형형태의 상자가 있다.
    이 때 알파 채널을 추가하여, 이를 불투명도를 낮추어, 알파채널의 값을 0.5=(0x80) 으로 지정하여, #8921F280(rgba(137 33 242 128)) 으로 설정하면 우측 하단과 같이 다소 투명하여 배경이 보이는 직사각형 모양의 상자로 바뀔 수 있다.

BMP file 형식

사진 정보


해당 데이터 역시 Hex editor로 열어보게 되면이와 같은 형식으로 나타난다.
일반적으로 BMP File은 아래와 같은 데이터 블록을 담고 있다.
모든 형식은 Little Endian으로 작성되어 있으며, 실제 정수형으로 전환시에 유의하도록 한다.

블록 이름정보
BMP 헤더BMP 파일에 대한 일반 정보를 담고 있다.
비트맵 정보(DIB 헤더)비트맵 그림에 대한 자세한 정보를 담고 있다.
색 팔레트인덱스 컬러 비트맵에 쓰이는 색의 정의를 담고 있다.
비트맵 데이터화소 대 화소 단위의 실제 그림을 담고 있다

해당 정보를 따라 가며, BMP파일의 정보를 분석해 보도록 하자.

BMP Header

일단 기본적으로 BMP파일에 대한 일반적인 정보를 가지고 있는 BMP 헤더 블록에서 어떤 정보를 담고 있는지 확인해보자

오프셋크기목적
02BMP 파일을 식별하는 데 쓰이는 매직 넘버: 0x42 0x4D (B와 M에 대한 ASCII 코드 포인트)
24BMP 파일 크기 (바이트 단위)
62예약필드. 실제값은 그림을 만든 데 쓰인 응용 프로그램에 따라 달라진다.
82예약필드. 실제값은 그림을 만든 데 쓰인 응용 프로그램에 따라 달라진다.
104비트맵 데이터를 찾을 수 있는 시작 오프셋 (바이트 단위)

으로 설명되어있다.
이는 14Byte까지, BMP Header의 정보를 담고 있음을 뜻하며 해당 위치까지 정보가 끝나면, DIB헤더 (비트맵정보)가 담겨져있다.
주요 포인트로는
이와 같이 BMP 파일을 식별하는 데 쓰이는 매직넘버, 0x42,0x4D를 통해 BM이라는 문자를 나타내고 있다. 또한, 매직 넘버 이후 4바이트를 16진수를 10진수로 변환 결과 (Little Endian형식) 1428766으로, 해당 이미지의 용량과 일치하는 모습을 볼 수 있다.
가장 눈여겨 볼 정보는 비트맵 데이터를 찾을 수 있는 시작 오프셋 영역인데, 이를 통해 색상정보가 있는 영역의 시작이 어디인지 확인할 수 있다.

해당 영역은 36 00 00 00 으로 54Byte부터 정보가 시작됨을 알 수 있다.

DIB Header (비트맵 정보)

오프셋크기목적
144이 헤더의 크기 (40 바이트)
184비트맵 가로 (단위는 화소, signed integer).
224비트맵 세로 (단위는 화소, signed integer).
262사용하는 색 판(color plane)의 수. 1로 설정해야 한다.
282한 화소에 들어가는 비트 수이며 그림의 색 깊이를 뜻한다. 보통 값은 1, 4, 8, 16, 24, 32이다.
304압축 방식
344그림 크기.
384그림의 가로 해상도. (미터 당 화소, signed integer)
424그림의 세로 해상도. (미터 당 화소, signed integer)
464색 팔레트의 색 수, 또는 0에서 기본값 2n.
504중요한 색의 수. 모든 색이 중요할 경우 0. 일반적으로 무시.

여기서 우리가 눈 여겨 보아야할 것은 헤더의 크기, 비트맵의 가로의 길이와 세로길이이다.
먼저 해당 부분을 가져오면,
으로 DIB헤더의 길이는 40Byte이다. 보통 호환성을 이유로 오래된 DIB헤더 포맷을 유지하고 있기 때문에, 대부분 40Byte이나, 다른 경우도 있으니 유의하도록 하자.
다음은 비트맵의 가로 세로 길이이다.
먼저 가로의 길이는 878px이고,
세로의 길이는 542px이다.
이로써 출력을 진행할 수 있게 되었다.

출력 진행

출력을 진행하기에 앞서 비트맵 데이터를 찾을 수 있는 시작 오프셋은 BMP헤더에서 54Byte(BMP Header:14Byte, DIB Header:40Byte)였다.
이 때 출력 할 때 중요한 사실 세 가지가 있는데,

  • 첫번째는 BMP파일은 일반적인 이미지 파일과는 다르게 이미지의 Bitmap이 왼쪽하단에서 오른쪽하단을 시작으로 왼쪽상단에서 오른쪽상단으로 위아래가 반전되어 저장되어있다
    즉 pixel정보를 가져올 때 상하 반전을 할 필요가 있다는 것이다.
  • 두번째는 해당 정보에서 R,G,B의 값이 순차적으로 적혀있는 것이 아닌 B,G,R순으로 저장되어있다.
  • 세번째로 정보를 가져올 때, 한 줄에 대한 데이터는 항상 4Byte의 배수의 형태로 padding되어 있다.

다음은 코드를 통해 자세한 내용을 알아 보도록 하자.

작성 코드

from PIL import Image
#BMP파일 오픈
with open("forestfire.bmp","rb") as file:
    data=file.read()
# 파일 헤더에서 오프셋 정보와 이미지의 크기를 읽어옴.
bfoffbits=int.from_bytes(data[0x0a:0x0e],"little")
biheight=int.from_bytes(data[0x16:0x1a],"little")
biwidth=int.from_bytes(data[0x12:0x16],"little")
#print(bfoffbits) # 54
#print(biheight) # 542
#print(biwidth) # 878
# 새로운 이미지 생성
img=Image.new(mode="RGB",size=(biwidth,biheight))
pix=img.load()
# 픽셀 데이터를 읽어 이미지에 설정
for y in range(biheight):
    for x in range(biwidth):
        # 순서대로 이미지에 채워넣는데, BGR순으로 저장되어 3칸씩 띄우고, bfoffbits를 더함
        pixel=(y*biwidth+x)*3+bfoffbits+2*y # 2*y는 4의 배수가 되게끔 padding을 한 것
        # 받은 숫자는 왼쪽 "하단"부터 차례대로 채워지고, BGR순으로 저장이 됨
        pix[x,biheight-1-y]=(data[pixel+2],data[pixel+1],data[pixel]) #pixel array
# 이미지 출력
img.show()

으로 bmp파일을 이미지로 표현할 수 있다.
먼저 bfoffhits=ing.from_bytes(data[0x0a:0x0e],"little") 를 통해 시작 오프셋 영역, 즉 색상정보가 있는 영역의 시작이 어디인지 확인할 수 있다. 해당 출력 값은 54로 54번째 byte부터 색상영역이 시작된다

  • biheight=int.from_bytes(data[0x16:0x1a],"little")
  • biwidth=int.from_bytes(data[0x12:0x16],"little")
    를 통해 이미지의 크기는 pix[x,y]=(878,542)로 구성된 사진임을 확인할 수 있다.

다음은 픽셀 데이터를 읽어 이미지에 저장하는 방식인데,

for y in range(biheight):
    for x in range(biwidth):
        pixel=(y*biwidth+x)*3+bfoffbits+2*y 
        pix[x,biheight-1-y]=(data[pixel+2],data[pixel+1],data[pixel])

해당 영역을 보면, 먼저 pixel의 값은 bfoffbits의 값을 더하고 나서 색상 영역이 시작된다. 이때, 각 색은 B,G,R순서로 3개의 byte를 통해 한 픽셀의 색상정보를 담고 있으므로, 한 픽셀이 지나갈 때 마다 3byte의 정보를 담아오는데, 가로의 길이는 878으로 4의 배수가 아니다. 따라서 가장 가까운 4의 배수인 880으로 맞춰주어야 하기 때문에, 각 y축마다 2Byte의 Padding이 생기는 것이다.
아래 방향에서 위 방향으로 픽셀의 값이 지정되어 있기 때문에, 상하 반전을 할 필요가 있다. 따라서 해당 받은 픽셀의 값을 채워넣는 곳을 pix[x,biheight-1-y]으로 선언할 필요가 있으며,
BGR순서로 받기 때문에, pixel값에 (R,G,B)순서로 받기 위해 (data[pixel+2],data[pixel+1],data[pixel])의 순서로 픽셀의 값을 채우고 있으며,

출력


으로 정상적으로 (878,542)크기의 이미지가 출력되는 것을 확인할 수 있다.
이러한 방식으로, mat파일(MATLAB배열 파일), jpg, png(사진 압축 파일:추가적인 작업 필요) 등 여러가지 형식을 가지고 있더라도, 구조를 알고 있다면, 해당 이미지를 출력하는 코드를 직접 작성하는 것도 가능할 것이다.

profile
I'm Free!

0개의 댓글