Skip to content

Instantly share code, notes, and snippets.

@weisi
Created October 11, 2012 05:17
Show Gist options
  • Save weisi/3870322 to your computer and use it in GitHub Desktop.
Save weisi/3870322 to your computer and use it in GitHub Desktop.
Black-and-white Windows Bitmap -> CompuServe RLE converter
#!/usr/bin/env python3
# Weisi Dai <multiple1902@gmail.com>
# 09055029, Xi'an Jiaotong University
# Oct 11, 2012
# Released under MIT License
# Reads a black-and-white Windows Bitmaap, parses it and converts it to CompuServe RLE format.
import struct
from types import * # assert
from collections import namedtuple
data_filename = "test-32-32.bmp"
output_filename = "test-32-32.rle"
data_bytearr = open(data_filename, "rb").read()
s_bmfh = ''.join(['<', # Windows Bitmap is declared little endian
'2s', # 0x0000 ... 0x0001: BM
'L', # 0x0002 ... 0x0005: File Size: dword
'2H', # 0x0006 ... 0x0009: Reserved: must be zero
'L', # 0x000A ... 0x000D: Bitmap data offset
])
size_bmfh_0 = 14
size_bmfh = struct.calcsize(s_bmfh)
s_bmih = ''.join(['<', # Little endian
'L', # 0x000E ... 0x0011: Bitmap info header size. Should be 0x28.
'L', # 0x0012 ... 0x0015: Bitmap width.
'L', # 0x0016 ... 0x0019: Bitmap height.
'H', # 0x001A ... 0x001B: Bitmap planes. Should be 1.
'H', # 0x001C ... 0x001D: Bits per pixel. Should be 1 for black-white.
'L', # 0x001E ... 0x0021: Compression method. 0 for BI_RGB.
'L', # 0x0022 ... 0x0025: Raw bitmap data size. Can be 0 for BI_RGB files.
'l', # 0x0026 ... 0x0029: Horizontal resolution. Signed.
'l', # 0x002A ... 0x002D: Vertical resolution. Signed.
'L', # 0x002E ... 0x0031: Colors in the palette. 0 for 2^n.
'L', # 0x0032 ... 0x0035: Important colors. Ignored.
])
size_bmih_0 = 40
size_bmih = struct.calcsize(s_bmih)
s_palette = ''.join(['<', # Little endian
'8B', # R1, G1, B1, NO1, R2, G2, B2, NO2
])
size_palette_0 = 8
size_palette = struct.calcsize(s_palette)
assert size_bmfh is size_bmfh_0, "BitMapFileHeader: Size is {0}, should be {1}.".format(size_bmfh, size_bmfh_0)
assert size_bmih is size_bmih_0, "BitMapInfoHeader: Size is {0}, should be {1}.".format(size_bmih, size_bmih_0)
assert size_palette is size_palette_0, "BitMapPalette: Size is {0}, should be {1}.".format(size_palette, size_palette_0)
iReadPointer = 0
data_bmfh = data_bytearr[iReadPointer : iReadPointer + size_bmfh]
iReadPointer += size_bmfh
data_bmih = data_bytearr[iReadPointer : iReadPointer + size_bmih]
iReadPointer += size_bmih
data_palette = data_bytearr[iReadPointer : iReadPointer + size_palette]
iReadPointer += size_palette
BitMapFileHeader_Tuple = namedtuple('BitMapFileHeader', ' '.join([
'MagicNumber',
'FileSize',
'Reserved1',
'Reserved2',
'BitmapDataOffset',
])
)
BitMapInfoHeader_Tuple = namedtuple('BitMapInfoHeader', ' '.join([
'InfoHeaderSize',
'Width',
'Height',
'ColorPlanes',
'BitsPerPixel',
'CompressionMethod',
'ImageSize',
'HorizontalResolution',
'VerticalResolution',
'ColorsInPalette',
'ImportantColors',
])
)
BitMapPalette_Tuple = namedtuple('BitMapPalette', ' '.join([
'R1',
'G1',
'B1',
'NO1',
'R2',
'G2',
'B2',
'NO2',
])
)
BitMapFileHeader = BitMapFileHeader_Tuple._make(struct.unpack(s_bmfh, data_bmfh))
assert BitMapFileHeader.Reserved1 == 0, "BitMapFileHeader.Reserved1 is not zero."
assert BitMapFileHeader.Reserved2 == 0, "BitMapFileHeader.Reserved2 is not zero."
BitMapInfoHeader = BitMapInfoHeader_Tuple._make(struct.unpack(s_bmih, data_bmih))
assert BitMapInfoHeader.ColorPlanes == 1, "BitMapInfoHeader.ColorPlanes is not 1"
assert BitMapInfoHeader.BitsPerPixel == 1, "BitMapInfoHeader.BitsPerPixel is not 1"
BitMapPalette = BitMapPalette_Tuple._make(struct.unpack(s_palette, data_palette))
print(BitMapFileHeader)
print(BitMapInfoHeader)
print(BitMapPalette)
BitMapDataBit = []
BitMapDataByte = data_bytearr[iReadPointer:]
for a_byte in BitMapDataByte:
for i in range(8):
BitMapDataBit.append(1 if (a_byte & (1 << (7 - i))) else 0)
assert len(BitMapDataBit) == BitMapInfoHeader.Width * BitMapInfoHeader.Height, "Wrong BitMap Data Size"
iBitPointer = len(BitMapDataBit)
BitMapArray = [[0 for i in range(256)] for j in range(192)]
for i in range(BitMapInfoHeader.Height):
for j in range(BitMapInfoHeader.Width):
iBitPointer -= 1
if BitMapDataBit[iBitPointer] == 1:
BitMapArray[i][BitMapInfoHeader.Width - j - 1] = 1
s_rle_header = ''.join(['<', # Little endian
'B', # Esc, 0x1B
'B', # G, 0x47
'B', # Size, 0x48
])
s_rle_footer = ''.join (['<', # Little endian
'B', # Esc 1, 0x1B
'B', # Esc 2, 0x1B;
'B', # G, 0x47
'B', # N, 0x4E
])
RleHeader = struct.pack(s_rle_header, 0x1B, 0x47, 0x48)
RleFooter = struct.pack(s_rle_footer, 0x1B, 0x1B, 0x47, 0x4E)
BlackCount = 0x20
WhiteCount = 0x20
iBlackFlag = 1
RleBody = bytearray()
for i in range(192):
for j in range(256):
if iBlackFlag == 1:
if BitMapArray[i][j] == 0:
BlackCount += 1
else:
WhiteCount += 1
iBlackFlag = 0
if BlackCount == 0x7E:
iBlackFlag = 0
else: # iBlackFlag == 0
if BitMapArray[i][j] == 1:
WhiteCount += 1
else:
RleBody.append(BlackCount)
RleBody.append(WhiteCount)
BlackCount = 0x21
WhiteCount = 0x20
iBlackFlag = 1
if WhiteCount == 0x7E:
RleBody.append(BlackCount)
RleBody.append(WhiteCount)
BlackCount = 0x20
WhiteCount = 0x20
iBlackFlag = 1
RleBody.append(BlackCount)
RleBody.append(WhiteCount)
RleContent = RleHeader + RleBody + RleFooter
open(output_filename, 'wb').write(RleContent)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment