-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathwritebmp.py
216 lines (192 loc) · 9.89 KB
/
writebmp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# Copyrighted by Sean Lydon and licensed under GPLv3
# Work inspired by John Ortiz, Defcon 21
import math
import optparse
import struct
import sys
try:
from ordereddict import OrderedDict
except ImportError:
from collections import OrderedDict
STANDARD_PALETTE = ''.join([
'\x00\x00\x00\x00\x00\x00\x80\x00\x00\x80\x00\x00\x00\x80\x80\x00\x80\x00',
'\x00\x00\x80\x00\x80\x00\x80\x80\x00\x00\xC0\xC0\xC0\x00\xC0\xDC\xC0\x00',
'\xF0\xCA\xA6\x00\x00\x20\x40\x00\x00\x20\x60\x00\x00\x20\x80\x00\x00\x20',
'\xA0\x00\x00\x20\xC0\x00\x00\x20\xE0\x00\x00\x40\x00\x00\x00\x40\x20\x00',
'\x00\x40\x40\x00\x00\x40\x60\x00\x00\x40\x80\x00\x00\x40\xA0\x00\x00\x40',
'\xC0\x00\x00\x40\xE0\x00\x00\x60\x00\x00\x00\x60\x20\x00\x00\x60\x40\x00',
'\x00\x60\x60\x00\x00\x60\x80\x00\x00\x60\xA0\x00\x00\x60\xC0\x00\x00\x60',
'\xE0\x00\x00\x80\x00\x00\x00\x80\x20\x00\x00\x80\x40\x00\x00\x80\x60\x00',
'\x00\x80\x80\x00\x00\x80\xA0\x00\x00\x80\xC0\x00\x00\x80\xE0\x00\x00\xA0',
'\x00\x00\x00\xA0\x20\x00\x00\xA0\x40\x00\x00\xA0\x60\x00\x00\xA0\x80\x00',
'\x00\xA0\xA0\x00\x00\xA0\xC0\x00\x00\xA0\xE0\x00\x00\xC0\x00\x00\x00\xC0',
'\x20\x00\x00\xC0\x40\x00\x00\xC0\x60\x00\x00\xC0\x80\x00\x00\xC0\xA0\x00',
'\x00\xC0\xC0\x00\x00\xC0\xE0\x00\x00\xE0\x00\x00\x00\xE0\x20\x00\x00\xE0',
'\x40\x00\x00\xE0\x60\x00\x00\xE0\x80\x00\x00\xE0\xA0\x00\x00\xE0\xC0\x00',
'\x00\xE0\xE0\x00\x40\x00\x00\x00\x40\x00\x20\x00\x40\x00\x40\x00\x40\x00',
'\x60\x00\x40\x00\x80\x00\x40\x00\xA0\x00\x40\x00\xC0\x00\x40\x00\xE0\x00',
'\x40\x20\x00\x00\x40\x20\x20\x00\x40\x20\x40\x00\x40\x20\x60\x00\x40\x20',
'\x80\x00\x40\x20\xA0\x00\x40\x20\xC0\x00\x40\x20\xE0\x00\x40\x40\x00\x00',
'\x40\x40\x20\x00\x40\x40\x40\x00\x40\x40\x60\x00\x40\x40\x80\x00\x40\x40',
'\xA0\x00\x40\x40\xC0\x00\x40\x40\xE0\x00\x40\x60\x00\x00\x40\x60\x20\x00',
'\x40\x60\x40\x00\x40\x60\x60\x00\x40\x60\x80\x00\x40\x60\xA0\x00\x40\x60',
'\xC0\x00\x40\x60\xE0\x00\x40\x80\x00\x00\x40\x80\x20\x00\x40\x80\x40\x00',
'\x40\x80\x60\x00\x40\x80\x80\x00\x40\x80\xA0\x00\x40\x80\xC0\x00\x40\x80',
'\xE0\x00\x40\xA0\x00\x00\x40\xA0\x20\x00\x40\xA0\x40\x00\x40\xA0\x60\x00',
'\x40\xA0\x80\x00\x40\xA0\xA0\x00\x40\xA0\xC0\x00\x40\xA0\xE0\x00\x40\xC0',
'\x00\x00\x40\xC0\x20\x00\x40\xC0\x40\x00\x40\xC0\x60\x00\x40\xC0\x80\x00',
'\x40\xC0\xA0\x00\x40\xC0\xC0\x00\x40\xC0\xE0\x00\x40\xE0\x00\x00\x40\xE0',
'\x20\x00\x40\xE0\x40\x00\x40\xE0\x60\x00\x40\xE0\x80\x00\x40\xE0\xA0\x00',
'\x40\xE0\xC0\x00\x40\xE0\xE0\x00\x80\x00\x00\x00\x80\x00\x20\x00\x80\x00',
'\x40\x00\x80\x00\x60\x00\x80\x00\x80\x00\x80\x00\xA0\x00\x80\x00\xC0\x00',
'\x80\x00\xE0\x00\x80\x20\x00\x00\x80\x20\x20\x00\x80\x20\x40\x00\x80\x20',
'\x60\x00\x80\x20\x80\x00\x80\x20\xA0\x00\x80\x20\xC0\x00\x80\x20\xE0\x00',
'\x80\x40\x00\x00\x80\x40\x20\x00\x80\x40\x40\x00\x80\x40\x60\x00\x80\x40',
'\x80\x00\x80\x40\xA0\x00\x80\x40\xC0\x00\x80\x40\xE0\x00\x80\x60\x00\x00',
'\x80\x60\x20\x00\x80\x60\x40\x00\x80\x60\x60\x00\x80\x60\x80\x00\x80\x60',
'\xA0\x00\x80\x60\xC0\x00\x80\x60\xE0\x00\x80\x80\x00\x00\x80\x80\x20\x00',
'\x80\x80\x40\x00\x80\x80\x60\x00\x80\x80\x80\x00\x80\x80\xA0\x00\x80\x80',
'\xC0\x00\x80\x80\xE0\x00\x80\xA0\x00\x00\x80\xA0\x20\x00\x80\xA0\x40\x00',
'\x80\xA0\x60\x00\x80\xA0\x80\x00\x80\xA0\xA0\x00\x80\xA0\xC0\x00\x80\xA0',
'\xE0\x00\x80\xC0\x00\x00\x80\xC0\x20\x00\x80\xC0\x40\x00\x80\xC0\x60\x00',
'\x80\xC0\x80\x00\x80\xC0\xA0\x00\x80\xC0\xC0\x00\x80\xC0\xE0\x00\x80\xE0',
'\x00\x00\x80\xE0\x20\x00\x80\xE0\x40\x00\x80\xE0\x60\x00\x80\xE0\x80\x00',
'\x80\xE0\xA0\x00\x80\xE0\xC0\x00\x80\xE0\xE0\x00\xC0\x00\x00\x00\xC0\x00',
'\x20\x00\xC0\x00\x40\x00\xC0\x00\x60\x00\xC0\x00\x80\x00\xC0\x00\xA0\x00',
'\xC0\x00\xC0\x00\xC0\x00\xE0\x00\xC0\x20\x00\x00\xC0\x20\x20\x00\xC0\x20',
'\x40\x00\xC0\x20\x60\x00\xC0\x20\x80\x00\xC0\x20\xA0\x00\xC0\x20\xC0\x00',
'\xC0\x20\xE0\x00\xC0\x40\x00\x00\xC0\x40\x20\x00\xC0\x40\x40\x00\xC0\x40',
'\x60\x00\xC0\x40\x80\x00\xC0\x40\xA0\x00\xC0\x40\xC0\x00\xC0\x40\xE0\x00',
'\xC0\x60\x00\x00\xC0\x60\x20\x00\xC0\x60\x40\x00\xC0\x60\x60\x00\xC0\x60',
'\x80\x00\xC0\x60\xA0\x00\xC0\x60\xC0\x00\xC0\x60\xE0\x00\xC0\x80\x00\x00',
'\xC0\x80\x20\x00\xC0\x80\x40\x00\xC0\x80\x60\x00\xC0\x80\x80\x00\xC0\x80',
'\xA0\x00\xC0\x80\xC0\x00\xC0\x80\xE0\x00\xC0\xA0\x00\x00\xC0\xA0\x20\x00',
'\xC0\xA0\x40\x00\xC0\xA0\x60\x00\xC0\xA0\x80\x00\xC0\xA0\xA0\x00\xC0\xA0',
'\xC0\x00\xC0\xA0\xE0\x00\xC0\xC0\x00\x00\xC0\xC0\x20\x00\xC0\xC0\x40\x00',
'\xC0\xC0\x60\x00\xC0\xC0\x80\x00\xC0\xC0\xA0\x00\xF0\xFB\xFF\x00\xA4\xA0',
'\xA0\x00\x80\x80\x80\x00\x00\x00\xFF\x00\x00\xFF\x00\x00\x00\xFF\xFF\x00',
'\xFF\x00\x00\x00\xFF\x00\xFF\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\x00'])
BMP_HEADER_FMT = '<2s6I2H6I' # See named tuple for description
BMP_TYPE_8_BIT = '8'
BMP_TYPE_8_BIT_GS = 'g'
BMP_TYPE_24_BIT = '2'
VALID_TYPES = (BMP_TYPE_8_BIT, BMP_TYPE_8_BIT_GS, BMP_TYPE_24_BIT)
def _default_headers(bmp_type=BMP_TYPE_8_BIT):
hsize = struct.calcsize(BMP_HEADER_FMT)
headers = OrderedDict([
('bfType', 'BM'), # 'BM'
('bfSize', 0), # Total length of the bitmap file
('bfReserved', 0), # Reserved <unused>
('bfOffBits', hsize), # Offset of the start of the pixel data
('biSize', 40), # Size of BITMAPINFOHEADER
('biWidth', 0), # Width in pixels
('biHeight', 0), # Height in pixels
('biPlanes', 1), # Always 1
('biBitCount', 24), # Color bits per pixel
('biCompression', 0), # BI_RGB, BI_RLE8, BI_RLE4
('biSizeImage', 0), # Total bytes in image
('biXPelsPerMeter', 0), # 0, or optional horizontal resolution
('biYPelsPerMeter', 0), # 0, or optional vertical resolution
('biClrUsed', 0), # Colors actually used (normally zero,
# can be lower than biBitCount)
('biClrImportant', 0), # important colors actualy used (normally zero)
])
if bmp_type in (BMP_TYPE_8_BIT, BMP_TYPE_8_BIT_GS):
headers['bfOffBits'] += len(STANDARD_PALETTE)
headers['biBitCount'] = 8
headers['biClrUsed'] = 256
return headers
def write_BMP(data, outfile, width=0, bmp_type=BMP_TYPE_8_BIT):
"""Wrap data into a new BMP file.
Constructs a new BMP file displaying the data provided.
Args:
data: A buffer of the data.
outfile: The name of the output file, use '-' for stdout.
width: Optional enforced width of the BMP.
bmp_type: the type BMP_TYPE_* of the BMP constructed.
Returns:
None
"""
parts = []
headers = _default_headers(bmp_type)
# data length
dl = len(data)
if not width:
width = int(math.sqrt(dl/3) + 1) if bmp_type == BMP_TYPE_24_BIT else \
int(math.sqrt(dl) + 1)
# bytes per pixel
bpp = 3 if bmp_type == BMP_TYPE_24_BIT else 1
pad = 0 if (width * bpp) % 4 == 0 else (4 - ((width * bpp) % 4))
# bytes per line
bpl = width * bpp + pad
# data bytes per line
dbpl = width * bpp - pad
height = dl/dbpl if dl % dbpl == 0 else dl/dbpl + 1
tot_bytes = bpl * height
print 'dl={7}, bpp={0}, pad={1}, bpl={2}, dbpl={3}, width={4}, height={5}, tot_bytes={6}'.format(bpp, pad, bpl, dbpl, width, height, tot_bytes, dl)
headers['bfSize'] = tot_bytes + headers['bfOffBits']
headers['biWidth'] = width
headers['biHeight'] = height
headers['biSizeImage'] = tot_bytes
parts.append(struct.pack(BMP_HEADER_FMT, *headers.values()))
if bmp_type == BMP_TYPE_8_BIT_GS:
parts.append(''.join(map(lambda x: struct.pack('<4B', *x),
zip(range(256), range(256), range(256), [0] * 256))))
elif bmp_type == BMP_TYPE_8_BIT:
subparts = []
for i in xrange(256):
subparts.append(struct.pack('<4B', ord(STANDARD_PALETTE[4*i]),
ord(STANDARD_PALETTE[4*i+1]), ord(STANDARD_PALETTE[4*i+2]), 0))
parts.append(''.join(subparts))
# Wow it doesn't look like he uses pad_bytes...I'll try once without...
#parts.append(data)
#parts.append(''.join(['\x00'] * (headers['bfSize'] - (dl + headers['bfOffBits']))))
#print 'buffering: %d' % (headers['bfSize'] - (dl + headers['bfOffBits']))
# Now with
lines = []
padbuf = ''.join(['\x00'] * pad)
for i in xrange(0, dl, dbpl):
lines.append(data[i:i+dbpl] + padbuf)
parts.append(''.join(lines[::-1]))
parts.append(''.join(['\x00'] * (tot_bytes - len(parts[-1]))))
#headers['bfSize'] -
#(dl + headers['bfOffBits']))))
print headers['bfSize']
print sum(map(len, parts))
print headers['bfOffBits']
print '\n'.join(map(lambda x: str(len(x)), parts))
assert(sum(map(len, parts)) == headers['bfSize'])
if outfile == '-':
sys.stdout.write(''.join(parts))
else:
with open(outfile, 'w') as fp:
fp.write(''.join(parts))
def main():
option_parser = optparse.OptionParser(
usage="""{0} [OPTIONS] <inputfile> <outputfile>
This script will turn a file into a BMP image of its bytes. The
types of BMP's are:
'8': 8-bit BMP (default)
'g': 8-bit grayscale BMP
'2': 24-bit BMP
If you perfer the file to be output to stdout, then provide '-' as
the <outputfile>.
Help: -h, --help
Usage:
{0} someexecutable.exe prettybytes_8bit.bmp
{0} -w 100 -t g someexecutable.exe prettybytes_8bit_grayscale.bmp
{0} -t 2 someexecutable.exe prettybytes_24bit.bmp
{0} someexecutable.exe -""".format(sys.argv[0]))
option_parser.add_option('-t', '--type', default='8', type='string',
dest='type', metavar='VAL', help="Type of BMP ('8', 'g', '2').")
option_parser.add_option('-w', '--width', default=0, type='int',
dest='width', metavar='NUM', help='Width in pixels of the BMP.')
options, args = option_parser.parse_args()
if len(args) != 2 or options.type not in VALID_TYPES:
option_parser.print_usage()
raise SystemExit
with open(args[0], 'r') as fp:
data = fp.read()
write_BMP(data, args[1], options.width, options.type)
if __name__ == '__main__':
main()