How to create bmp file from hex data in python?

2.3k Views Asked by At

The hex data array only contains the gray color value, do not have any bmp file information.

But I know the resolution(width & height in pixels), the image is gray color, which means each pixel is 8 bit.

Any tools(online or offline) or code to generate bmp file from the data?

For example , the following code not work, the resulution is 8*33

import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np

s = "FFFFFFFF B0A7FFFF FFFFFFB3 807D8EFF FFFFFF94 707783CC FFFFFF89 7A8988A4 FFFFFFAD 929298BE FFFFFFAD 979AA8E2 FFFFFFAF 8991A9FF FFFFFF9F 808BA6FF FFFFFFAB 8694AFFF FFFFFFB2 8A96A0FF FFFFFFA8 859496FF FFFFFFB3 88809ADA FFFFFFA6 7B728DD7 FFFFFF85 6F7084D4 FFFFFF86 66647BDA FFFFFF8D 606482DD FFFFFF8B 666788DC FFFFFF7B 616282CE FFFFFF86 63657AC2 FFFFFFA1 72697FCB FFFFFF9B 75636FC6 FFFFFF88 6B596EC1 FFFFFF8B 675A80D5 FFFFFFA0 6D5E79DE FFFFFF8F 6B5C73FF FFFFD67F 605E8FFF FFFFDA7F 665B96FF FFFFD384 645A86FF FFFFFF84 6F6E86FF FFFFFFBE 979DC0FF FFFFFFFF C2CCFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFCBB9 9EB7FFFF FFBEA17C 677A99FF FFC59F66 565972C5 BACC9D70 595A76CA 9E997665 5E699EE0 977B6A6B 7F94B0FF FFBDA5A4 C4D9FFFF"

data = s.replace(" ", "").decode('hex')
plt.imsave('filename.bmp', np.array(data).reshape(8,33), cmap=cm.gray)
1

There are 1 best solutions below

0
On

I was looking for an answer to a similar question and was unable to locate a 'quick' understandable solution in 'pure' python. So I wrote some code to create a bitmap (from character array data which is converted to RGB values in the example). Please read comments after MIT license of code example below. They describe the information one needs to create a bitmap from any data.

I incorporated information from http://blog.paphus.com/blog/2012/08/14/write-your-own-bitmaps/ and enter link description here

My output was: enter image description here

#!/usr/bin/python
# ----------------------- START MIT LICENSE -----------------------
# Copyright (c) 2018 Benjamin Spiegl

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ----------------------- END MIT LICENSE -----------------------

# The pixel data has to be written with some minor modifications.
# First, the bottom row of the image appears first in the data.
# If you forget this step your image will be vertically flipped.
# Second, the pixes are written in BGR (blue - green - red) format, which is the opposite of the normal RGB.
# Finally, at the end of each row you must pad it with bytes till it is a multiple of 4.
# All numbers in the headers must be LITTLE-ENDIAN.
# For instance, when representing 54 as four bytes, those four bytes must be 54, 0, 0, 0.
# To represent 24 as two bytes, the two bytes must be 24, 0.

from numpy import array
from math import floor

def make_hex_list(int_val=None, force_byte_len=None, little_endian=True):
    if int_val is None:
        return []
    raw_pix_hex = hex(int_val)[2:]
    first_hex_str = ''  # is falsy
    if len(raw_pix_hex) % 2:  # odd number of hex vals
        first_hex_str = '0x0' + raw_pix_hex[0]
        raw_pix_hex = raw_pix_hex[1:]
    if first_hex_str:
        raw_pix_hex_list = [first_hex_str] + ['0x' + raw_pix_hex[lit_pair_idx * 2:lit_pair_idx * 2 + 2]
                                              for lit_pair_idx in range(int(floor(len(raw_pix_hex) / 2)))]
    else:
        raw_pix_hex_list = ['0x' + raw_pix_hex[lit_pair_idx * 2:lit_pair_idx * 2 + 2]
                            for lit_pair_idx in
                            range(int(floor(len(raw_pix_hex) / 2)))]  # max 4 bytes (32 bit) -> should be ok
    if force_byte_len and len(raw_pix_hex_list) != force_byte_len:  # force 4 bytes
        raw_pix_hex_list = ['0x00'] * (force_byte_len - len(raw_pix_hex_list)) + raw_pix_hex_list
    elif len(raw_pix_hex_list) > 4:
        raise OverflowError('base matrix too large for 32 bit bitmap.')
    if little_endian:
        return list(reversed([int(hx, 16) for hx in raw_pix_hex_list]))
    else:
        return [int(hx, 16) for hx in raw_pix_hex_list]

# A=yellow, T=red, C=green, G=blue, D=grey, N=black
bmp_BGR_colors = {'A': (0, 255, 255), 'T': (0, 0, 255), 'C': (0, 255, 0), 'G': (255, 0, 0),
                  'N': (0, 0, 0), 'D': (150, 150, 150)}

bmp_name = 'bitmap_test.bmp'
magnify = 16
mybase_arr = array([['G', 'A', 'G', 'A', 'D', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['G', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N'],
                    ['N', 'A', 'G', 'A', 'A', 'D', 'T', 'T', 'A', 'N']])
arr_height, arr_width = mybase_arr.shape
# prepare header information
pixel_width = arr_width * magnify  # force 4 bytes
pixel_height = arr_height * magnify  # force 4 bytes
linepad = pixel_width * 3 % 4  # ensure valid row bytes (3 bytes per pixel; 1 per color channel)
total_pad = pixel_height * linepad
pix_offset = [0x36, 0x00, 0x00, 0x00]  # force 4 byte
raw_pix_data_size = pixel_width * pixel_height * 3 + total_pad
raw_pix_data_size_hex_list = make_hex_list(int_val=raw_pix_data_size, force_byte_len=4)
file_size = 0x36 + raw_pix_data_size  # force 4 byte
# create header values for byte conversion
header_vals = [0x42, 0x4d] + make_hex_list(int_val=file_size, force_byte_len=4) + [0x00]*4 + pix_offset + \
              [0x28, 0x00, 0x00, 0x00] + make_hex_list(int_val=pixel_width, force_byte_len=4) + \
              make_hex_list(int_val=pixel_height, force_byte_len=4) + \
              make_hex_list(int_val=1, force_byte_len=2) + \
              make_hex_list(int_val=24, force_byte_len=2) + make_hex_list(int_val=0, force_byte_len=4) + \
              raw_pix_data_size_hex_list + [0x13, 0x0b, 0x00, 0x00]*2 + [0x00, 0x00, 0x00, 0x00]*2
assert(len(header_vals) == pix_offset[0])
# create pixel values for byte conversion
if linepad:  # need to 0-pad lines to a multiple of 4 bytes per line
    pixel_vals = list()
    for reverse_row in list(reversed(mybase_arr)):
        for cpy in range(magnify):  # repeat line 16 times (magnify)
            pixel_vals.extend([val for pix in reverse_row for val in bmp_BGR_colors[pix] * magnify] + [0x00] * linepad)
else:  # no padding needed
    pixel_vals = [val for reverse_row in list(reversed(mybase_arr))
                  for cpy in range(magnify) for pix in reverse_row
                  for val in bmp_BGR_colors[pix]*magnify]  # last for to unpack tuples
assert(len(pixel_vals) == raw_pix_data_size)
with open(bmp_name, 'wb') as bmp_file:
    bmp_file.write(bytearray(header_vals + pixel_vals))  # convert values to bytes