Numpy: Vaguely aware, not familiar.
Why strings? The bad answer is simply because that’s how it is done in the code I found when I searched on how to write a bmp file.
Sounds like I should check Numpy? Does it include functions to convert to string for an xxx.write(...). Or dies it have equivalent output function?
Ok here is all the sordid detail. I will be pasting below
1. Python27 Code
2. Python27 Output
3. Python37 Code
4. Python37 Output
The two programs are more or less identical. There are some instructions comments (just a few!). The only difference in the versions is the initialisation of the pixel string (and a padding string) as either char or byte.
You can edit the file names if you want to verify for yourself that the output file is correct, or comment that out the file open and write as you like.
The output logs speak for themselves!
On my machine the 3.7 version uses about 40 MB of RAM (out of 16 GB) and about 12% of CPU.
Here are the graphs of the elapsed time as the string builds:
https://gyazo.com/33d110b55689358d9e2331f9cd99977e
1. Python 2.7 Code
# Testing struct.pack and string catenation in Python2 and 3
# This is a demo cut down from real app (which draws charts from survey data)
# creates a 'square rainbow' bmp file
## edit for Py 2 (char strings) or 3 (byte strings) versions
# edit these for your set up and test
Size = 1024 # test image size, pixels
path = 'D:/Python27/MyScripts/Test/' # for the bmp file
import csv
import os
import struct
from math import trunc, ceil, floor
import time
def BuildImage(name, XY):
# name : filename
# XY : (width, height) pixels
# for stats and timing
n0 = 0
t00 = time.clock()
t01 = t00
chtName = path+'cht_'+name+'.bmp'
print("drawing "+chtName)
hdr = bmpHdr(XY)
#print(hdr)
pixels ='' ## Py2
##pixels = bytes('', 'utf-8') ## Py3
for Y in range(0, XY[1]): # (BMPs are L to R from the bottom L row)
for X in range(0, XY[0]):
# square rainbow for time tests - as oposed to real data
x = floor((255 * X)/XY[0])
y = floor((255 * Y)/XY[1])
(r,g,b) = [x, y, 128] #Colour(data[x ,y])
pixels += struct.pack('<BBB',b,g,r)
row_mod = (hdr['width']*hdr['colordepth']/8) % 4
if row_mod == 0:
padding = 0
else:
padding = (4 - row_mod)
padbytes = '' # P2
#padbytes = bytes('', 'utf-8') # P3
for i in range(padding):
padbytes += struct.pack('<B',0)
pixels = pixels + padbytes
# stats log
if(0 == Y % 100 or Y == 0):
n = len(pixels)
t02 = time.clock()
log = "{0:5d} L={1:8,d}, delta={2:7,d}, pad={3:4d}".format(XY[0]-Y, n, n-n0, padding)
log += ", time = {0:6.3f}, cum = {1:7.3f}".format(t02-t01, t02-t00)
print(log)
t01 = t02
n0 = n
print("pixels generated, len = "+str(len(pixels)))
bmp_write(chtName, hdr, pixels)
def bmpHdr(XY):
print("bmphdr xy "+str(XY))
hdr = {
'mn1':66,
'mn2':77,
'filesize':0,
'undef1':0,
'undef2':0,
'offset':54,
'headerlength':40,
'width':XY[0], #256
'height':XY[1], #256
'colorplanes':0,
'colordepth':24,
'compression':0,
'imagesize':0,
'res_hor':0,
'res_vert':0,
'palette':0,
'importantcolors':0
}
return hdr
#Function to write a bmp file. It takes a dictionary (hdr) of
#header values and the pixel data (pixels) and writes them
#to a file. This function is called at the bottom of the code.
def bmp_write(name, hdr, pixels):
print('making bmp with '+str(len(pixels))+" pixels")
mn1 = struct.pack('<B',hdr['mn1'])
mn2 = struct.pack('<B',hdr['mn2'])
filesize = struct.pack('<L',hdr['filesize'])
undef1 = struct.pack('<H',hdr['undef1'])
undef2 = struct.pack('<H',hdr['undef2'])
offset = struct.pack('<L',hdr['offset'])
headerlength = struct.pack('<L',hdr['headerlength'])
width = struct.pack('<L',hdr['width'])
height = struct.pack('<L',hdr['height'])
colorplanes = struct.pack('<H',hdr['colorplanes'])
colordepth = struct.pack('<H',hdr['colordepth'])
compression = struct.pack('<L',hdr['compression'])
imagesize = struct.pack('<L',hdr['imagesize'])
res_hor = struct.pack('<L',hdr['res_hor'])
res_vert = struct.pack('<L',hdr['res_vert'])
palette = struct.pack('<L',hdr['palette'])
importantcolors = struct.pack('<L',hdr['importantcolors'])
#create the outfile
outfile = open(name,'wb') # 'bitmap_image.bmp'
#write the header + the_bytes
hdr = mn1+mn2
hdr += filesize+undef1+undef2
hdr += offset+headerlength+width+height
hdr += colorplanes+colordepth+compression+imagesize+res_hor+res_vert
hdr += palette+importantcolors
print("headers = "+str(hdr))
bmp = hdr + pixels
print('writing bmp, len = '+str(len(bmp)))
outfile.write(bmp)
###################################
def main():
time0 = time.clock()
print("start {0}x{0} bmp file @ {1:.3f}".format(Size, time0))
# set the size of the bmp image here
BuildImage("test", (Size,Size))
time1 = time.clock()
print("Chart complete, run time {0:.3f} secs".format(time1-time0))
if __name__ == '__main__':
main()
2. Python 2.7 Output
Output:
Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:53:40) [MSC v.1500 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>>
=============== RESTART: D:\Python27\MyScripts\Test\test27.py ===============
start 1024x1024 bmp file @ 0.000
drawing D:/Python27/MyScripts/Test/cht_test.bmp
bmphdr xy (1024, 1024)
1024 L= 3,072, delta= 3,072, pad= 0, time = 0.013, cum = 0.013
924 L= 310,272, delta=307,200, pad= 0, time = 0.163, cum = 0.175
824 L= 617,472, delta=307,200, pad= 0, time = 0.162, cum = 0.337
724 L= 924,672, delta=307,200, pad= 0, time = 0.161, cum = 0.498
624 L=1,231,872, delta=307,200, pad= 0, time = 0.179, cum = 0.677
524 L=1,539,072, delta=307,200, pad= 0, time = 0.241, cum = 0.918
424 L=1,846,272, delta=307,200, pad= 0, time = 0.267, cum = 1.185
324 L=2,153,472, delta=307,200, pad= 0, time = 0.212, cum = 1.397
224 L=2,460,672, delta=307,200, pad= 0, time = 0.259, cum = 1.656
124 L=2,767,872, delta=307,200, pad= 0, time = 0.225, cum = 1.882
24 L=3,075,072, delta=307,200, pad= 0, time = 0.234, cum = 2.115
pixels generated, len = 3145728
making bmp with 3145728 pixels
headers = BM
3. Python 3.7 Code
# Testing struct.pack and string catenation in Python2 and 3
# This is a demo cut down from real app (which draws charts from survey data)
# creates a 'square rainbow' bmp file
## edit for Py 2 (char strings) or 3 (byte strings) versions
# edit these for your set up and test
Size = 1024 # test image size, pixels
path = 'D:/Python37/MyScripts/Test/' # for the bmp file
import csv
import os
import struct
from math import trunc, ceil, floor
import time
def BuildImage(name, XY):
# name : filename
# XY : (width, height) pixels
# for stats and timing
n0 = 0
t00 = time.clock()
t01 = t00
chtName = path+'cht_'+name+'.bmp'
print("drawing "+chtName)
hdr = bmpHdr(XY)
#print(hdr)
##pixels ='' ## Py2
pixels = bytes('', 'utf-8') ## Py3
for Y in range(0, XY[1]): # (BMPs are L to R from the bottom L row)
for X in range(0, XY[0]):
# square rainbow for time tests - as oposed to real data
x = floor((255 * X)/XY[0])
y = floor((255 * Y)/XY[1])
(r,g,b) = [x, y, 128] #Colour(data[x ,y])
pixels += struct.pack('<BBB',b,g,r)
row_mod = (hdr['width']*hdr['colordepth']/8) % 4
if row_mod == 0:
padding = 0
else:
padding = (4 - row_mod)
##padbytes = '' # P2
padbytes = bytes('', 'utf-8') # P3
for i in range(padding):
padbytes += struct.pack('<B',0)
pixels = pixels + padbytes
# stats log
if(0 == Y % 100 or Y == 0):
n = len(pixels)
t02 = time.clock()
log = "{0:5d} L={1:8,d}, delta={2:7,d}, pad={3:4d}".format(XY[0]-Y, n, n-n0, padding)
log += ", time = {0:6.3f}, cum = {1:7.3f}".format(t02-t01, t02-t00)
print(log)
t01 = t02
n0 = n
print("pixels generated, len = "+str(len(pixels)))
bmp_write(chtName, hdr, pixels)
def bmpHdr(XY):
print("bmphdr xy "+str(XY))
hdr = {
'mn1':66,
'mn2':77,
'filesize':0,
'undef1':0,
'undef2':0,
'offset':54,
'headerlength':40,
'width':XY[0], #256
'height':XY[1], #256
'colorplanes':0,
'colordepth':24,
'compression':0,
'imagesize':0,
'res_hor':0,
'res_vert':0,
'palette':0,
'importantcolors':0
}
return hdr
#Function to write a bmp file. It takes a dictionary (hdr) of
#header values and the pixel data (pixels) and writes them
#to a file. This function is called at the bottom of the code.
def bmp_write(name, hdr, pixels):
print('making bmp with '+str(len(pixels))+" pixels")
mn1 = struct.pack('<B',hdr['mn1'])
mn2 = struct.pack('<B',hdr['mn2'])
filesize = struct.pack('<L',hdr['filesize'])
undef1 = struct.pack('<H',hdr['undef1'])
undef2 = struct.pack('<H',hdr['undef2'])
offset = struct.pack('<L',hdr['offset'])
headerlength = struct.pack('<L',hdr['headerlength'])
width = struct.pack('<L',hdr['width'])
height = struct.pack('<L',hdr['height'])
colorplanes = struct.pack('<H',hdr['colorplanes'])
colordepth = struct.pack('<H',hdr['colordepth'])
compression = struct.pack('<L',hdr['compression'])
imagesize = struct.pack('<L',hdr['imagesize'])
res_hor = struct.pack('<L',hdr['res_hor'])
res_vert = struct.pack('<L',hdr['res_vert'])
palette = struct.pack('<L',hdr['palette'])
importantcolors = struct.pack('<L',hdr['importantcolors'])
#create the outfile
outfile = open(name,'wb') # 'bitmap_image.bmp'
#write the header + the_bytes
hdr = mn1+mn2
hdr += filesize+undef1+undef2
hdr += offset+headerlength+width+height
hdr += colorplanes+colordepth+compression+imagesize+res_hor+res_vert
hdr += palette+importantcolors
print("headers = "+str(hdr))
bmp = hdr + pixels
print('writing bmp, len = '+str(len(bmp)))
outfile.write(bmp)
###################################
def main():
time0 = time.clock()
print("start {0}x{0} bmp file @ {1:.3f}".format(Size, time0))
# set the size of the bmp image here
BuildImage("test", (Size,Size))
time1 = time.clock()
print("Chart complete, run time {0:.3f} secs".format(time1-time0))
if __name__ == '__main__':
main()
4. Python 3.7 Output
Output:
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license()" for more information.
>>>
=============== RESTART: D:\Python37\MyScripts\Test\test37.py ===============
Warning (from warnings module):
File "D:\Python37\MyScripts\Test\test37.py", line 130
time0 = time.clock()
DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead
start 1024x1024 bmp file @ 0.415
Warning (from warnings module):
File "D:\Python37\MyScripts\Test\test37.py", line 23
t00 = time.clock()
DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead
drawing D:/Python37/MyScripts/Test/cht_test.bmp
bmphdr xy (1024, 1024)
Warning (from warnings module):
File "D:\Python37\MyScripts\Test\test37.py", line 57
t02 = time.clock()
DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead
1024 L= 3,072, delta= 3,072, pad= 0, time = 0.006, cum = 0.006
924 L= 310,272, delta=307,200, pad= 0, time = 0.541, cum = 0.546
824 L= 617,472, delta=307,200, pad= 0, time = 1.545, cum = 2.091
724 L= 924,672, delta=307,200, pad= 0, time = 2.540, cum = 4.632
624 L=1,231,872, delta=307,200, pad= 0, time = 28.350, cum = 32.981
524 L=1,539,072, delta=307,200, pad= 0, time = 55.790, cum = 88.771
424 L=1,846,272, delta=307,200, pad= 0, time = 64.711, cum = 153.482
324 L=2,153,472, delta=307,200, pad= 0, time = 76.717, cum = 230.199
224 L=2,460,672, delta=307,200, pad= 0, time = 86.619, cum = 316.818
124 L=2,767,872, delta=307,200, pad= 0, time = 98.077, cum = 414.895
24 L=3,075,072, delta=307,200, pad= 0, time = 105.663, cum = 520.558
pixels generated, len = 3145728
making bmp with 3145728 pixels
headers = b'BM\x00\x00\x00\x00\x00\x00\x00\x006\x00\x00\x00(\x00\x00\x00\x00\x04\x00\x00\x00\x04\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
writing bmp, len = 3145782
Warning (from warnings module):
File "D:\Python37\MyScripts\Test\test37.py", line 135
time1 = time.clock()
DeprecationWarning: time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead
Chart complete, run time 547.024 secs
>>>