Skip to content

Commit fc5720a

Browse files
authored
Separated utils from __init__.py
1 parent 9d1277c commit fc5720a

File tree

2 files changed

+290
-0
lines changed

2 files changed

+290
-0
lines changed

bitmap/__init__.py

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import numpy as np
2+
3+
__all__ = [
4+
"ofType",
5+
"getHeader",
6+
"getSignature",
7+
"getFileSize",
8+
"checkReserved",
9+
"getDataOffset",
10+
"getInfoHeader",
11+
"getInfoHeaderSize",
12+
"getBitmapWidth",
13+
"getBitmapHeight",
14+
"getPlanes",
15+
"getBitCount",
16+
"getCompression",
17+
"getImageSize",
18+
"getXpixelsPerM",
19+
"getYpixelsPerM",
20+
"getColorsUsed",
21+
"getColorsImportant",
22+
"getHeaderAndHeaderInfo",
23+
"getColorTable",
24+
"getRawPixelData",
25+
"getPixels"
26+
]
27+
28+
__doc__ = """Python-Bitmap
29+
A dependency free library to read bitmap images.
30+
31+
Example usage:
32+
33+
from bitmap import Image
34+
35+
with open('test.bmp') as file:
36+
a = Image(file.read())
37+
38+
print('Width: ', a.getBitmapWidth()) # Return width of image in pixels
39+
print('Height: ', a.getBitmapHeight()) # Return height of image in pixles
40+
41+
print('Data:\n\n', a.getPixels()) # Get total pixels
42+
43+
print('Pixel RGB value at value X, Y:', a.getPixels()[0][19]) # Return the RGB value as a list (note that the first pixel starts on 0 and not 1)"""
44+
45+
46+
class Image:
47+
48+
def __init__(self, content):
49+
50+
self.raw = content # Return the raw byte values
51+
52+
# -------------------------------------------------------------------------
53+
# END OF UTILS
54+
# -------------------------------------------------------------------------
55+
56+
def __getBytesAwayFromColorTable(self):
57+
58+
bitCount = Image(self.raw).getBitCount()
59+
if bitCount <= 8:
60+
return 54+(4*Image.bits_per_pixel[bitCount]['NumColors'])
61+
else:
62+
return 54
63+
64+
65+
def __colorTableAlgorithm(x): # Find the closest multiple of 4
66+
import math
67+
return 4*math.ceil(x/4)
68+
69+
# -------------------------------------------------------------------------
70+
# START OF HEADER
71+
# -------------------------------------------------------------------------
72+
# 14 bytes
73+
74+
def ofType(self):
75+
"""Return image type, returns False otherwise."""
76+
bytes_ = self.raw[:2]
77+
header_field = (b'BM', b'BA', b'CI', b'CP', b'IC', b'PT') # Different types
78+
if bytes_ in header_field: return bytes_ # Check if it appears in the list and return the value
79+
else: return False # Otherwise return False
80+
81+
def getHeader(self):
82+
"""Returns just the header data of the image."""
83+
return self.raw[:14]
84+
85+
def getSignature(self):
86+
"""Return the signature of the image (first two bytes)."""
87+
return self.raw[:2]
88+
89+
def getFileSize(self):
90+
"""Get the file size of the bitmap image (independant of file)."""
91+
return int.from_bytes(self.raw[2:6], byteorder='little')
92+
93+
def checkReserved(self):
94+
"""Return the reserved content of the image."""
95+
return self.raw[6:10]
96+
97+
def getDataOffset(self):
98+
"""Return the File offset to Raster data of the bitmap"""
99+
return self.raw[10:14]
100+
101+
# -------------------------------------------------------------------------
102+
# END OF HEADER
103+
# -------------------------------------------------------------------------
104+
105+
# -------------------------------------------------------------------------
106+
# START OF INFOHEADER
107+
# -------------------------------------------------------------------------
108+
# 40 bytes
109+
110+
def getInfoHeader(self):
111+
"""Return the Info Header of the image"""
112+
return self.raw[14:54]
113+
114+
def getInfoHeaderSize(self):
115+
"""Return the size of the Info Header of image"""
116+
return int.from_bytes(self.raw[14:18], byteorder='little')
117+
118+
def getBitmapWidth(self):
119+
"""Return the width in pixels of the bitmap image"""
120+
return int.from_bytes(self.raw[18:22], byteorder='little')
121+
122+
def getBitmapHeight(self):
123+
"""Return the height in pixels of the bitmap image"""
124+
return int.from_bytes(self.raw[22:26], byteorder='little')
125+
126+
def getPlanes(self):
127+
"""Return the plane of the image"""
128+
return int.from_bytes(self.raw[26:28], byteorder='little')
129+
130+
def getBitCount(self):
131+
"""Return the type of bits per pixel"""
132+
return int.from_bytes(self.raw[28:30], byteorder='little')
133+
134+
def getCompression(self):
135+
"""Return the compression of the image"""
136+
return int.from_bytes(self.raw[30:34], byteorder='little')
137+
138+
def getImageSize(self):
139+
"""Return the compressed size of the image"""
140+
return int.from_bytes(self.raw[34:38], byteorder='little')
141+
142+
# ---------------------
143+
# Compression must be 0
144+
# ---------------------
145+
146+
def getXpixelsPerM(self):
147+
"""The horizontal resolution in pixels per meter"""
148+
compression = Image(self.raw)
149+
if compression.getCompression() == 0: # Return the horizontal resolution
150+
return int.from_bytes(self.raw[38:42], byteorder='little')
151+
else: raise Exception('CompressionSizeError: The image compression size must be 0') # Raise an exception if compression is not 0
152+
153+
def getYpixelsPerM(self):
154+
"""The vertical resolution in pixels per meter"""
155+
compression = Image(self.raw)
156+
if compression.getCompression() == 0: # Return the vertical resolution
157+
return int.from_bytes(self.raw[42:46], byteorder='little')
158+
else: raise Exception('CompressionSizeError: The image compression size must be 0') # Raise an exception if compression is not 0
159+
160+
161+
def getColorsUsed(self):
162+
"""Number of colours used in the image"""
163+
compression = Image(self.raw)
164+
if compression.getCompression() == 0: # Return the number of colors used
165+
return self.raw[46:50]
166+
#return int.from_bytes(self.raw[46:50], byteorder='little')
167+
else: raise Exception('CompressionSizeError: The image compression size must be 0') # Raise an exception if compression is not 0
168+
169+
def getColorsImportant(self):
170+
"""Number of important colors, where 0 is all"""
171+
compression = Image(self.raw)
172+
if compression.getCompression() == 0: # Return the important colors
173+
return int.from_bytes(self.raw[50:54], byteorder='little')
174+
else: raise Exception('CompressionSizeError: The image compression size must be 0') # Raise an exception if compression is not 0
175+
176+
def getHeaderAndHeaderInfo(self):
177+
"""Return both the header and the info header of the image"""
178+
return self.raw[:54]
179+
180+
# -------------------------------------------------------------------------
181+
# END OF HEADER INFO
182+
# -------------------------------------------------------------------------
183+
184+
# -------------------------------------------------------------------------
185+
# START OF COLORTABLE
186+
# -------------------------------------------------------------------------
187+
# 4 * NumColors bytes
188+
189+
def getColorTable(self):
190+
"""Get the ColorTable of the image, returns 0 if the value of Image.getBitCount()is higher than 8. Run Image.bits_per_pixel for the full list of possible values."""
191+
bitCount = Image(self.raw).getBitCount()
192+
if bitCount <= 8:
193+
return self.raw[54:Image.__getBytesAwayFromColorTable(self)]
194+
else:
195+
return 0
196+
197+
# -------------------------------------------------------------------------
198+
# END OF COLORTABLE
199+
# -------------------------------------------------------------------------
200+
201+
# -------------------------------------------------------------------------
202+
# START OF PIXELDATA
203+
# -------------------------------------------------------------------------
204+
205+
def getRawPixelData(self):
206+
"""Return the raw pixel data of the image"""
207+
return self.raw[Image(self.raw).__getBytesAwayFromColorTable():]
208+
209+
def getPixels(self):
210+
211+
"""Convert the raw pixels to an X Y grid system"""
212+
213+
array = np.array(list(self.raw[54:]))
214+
215+
grid = array.reshape(-1, Image.getBitmapWidth(self), 3)
216+
return grid.tolist()
217+
218+
219+
220+
# -----------------------------------------------------------------------------
221+
# Credits:
222+
# -----------------------------------------------------------------------------
223+
224+
225+
# From https://stackoverflow.com/questions/49753697/getting-file-size-of-bmp-image/49753926#49753926
226+
# Thank you DarkArctic https://stackoverflow.com/users/1240522/darkarctic
227+
# Who suggested the "int.from_bytes(raw, byteorder='little')" method for reading the byte values
228+
229+
# Thank you SShah https://stackoverflow.com/users/7705353/sshah
230+
# Who helped solve the ColorTable byte size
231+
232+
# Thank you Andras Deak https://stackoverflow.com/users/5067311/andras-deak
233+
# Who contributed the 4*math.ceil(x/4) algorim (as well as help on the usage and several other functions)
234+
# Full transcript: https://chat.stackoverflow.com/transcript/message/42074275#42074275
235+
# For the impatient: https://chat.stackoverflow.com/transcript/message/42074683#42074683
236+
237+
# Thanks to Stack Overflow https://www.stackoverflow.com
238+
# And to the members of Room 6: Python https://chat.stackoverflow.com/rooms/6/python
239+
# Who have helped me countless times.
240+
241+
# Thank you DSM for Numpy reshape() solution: https://chat.stackoverflow.com/transcript/message/42500252#42500252
242+
243+
# To list solution: https://stackoverflow.com/questions/1966207/converting-numpy-array-into-python-list-structure
244+
245+
246+
# -----------------------------------------------------------------------------
247+
# References:
248+
# -----------------------------------------------------------------------------
249+
250+
# http://www.daubnet.com/en/file-format-bmp
251+
# https://en.wikipedia.org/wiki/Bit_plane
252+
# http://www.ece.ualberta.ca/~elliott/ee552/studentAppNotes/2003_w/misc/bmp_file_format/bmp_file_format.htm

bitmap/utils.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
__all__ = [
2+
"rasterData",
3+
"compression",
4+
"bits_per_pixel"
5+
]
6+
7+
__doc__ = """ rasterData(bits)
8+
Returns the color (color), compression type (compression) and byte pixels (byte_pixels)
9+
10+
11+
compression
12+
13+
Returns the compression type ranging from: "no compression", "8bit RLE encoding" or "4bit RLE encoding"
14+
15+
bits_per_pixel
16+
17+
Returns type of bits and the number of colours supported
18+
"""
19+
20+
21+
22+
def rasterData(bits):
23+
if bits == 1: return {'color':'black/white', 'compression':0, 'byte_pixels':8}
24+
elif bits == 4: return {'color':'16_color', 'compression':0, 'byte_pixels':2}
25+
elif bits == 8: return {'color':'256_color', 'compression':0, 'byte_pixels':1}
26+
else: raise Exception('UnrecordedError: This bit number is unknown or has not been recorded')
27+
28+
compression = { # Compression type and format
29+
0:('BI_RGB', 'no compression'),
30+
1:('BI_RLE8', '8bit RLE encoding'),
31+
2:('BI_RLE4', '4bit RLE encoding')}
32+
33+
bits_per_pixel = { # Type of bits and the number of colours supported
34+
1:{'type':'monochrome palette', 'NumColors':1},
35+
4:{'type':'4bit palletised', 'NumColors':16},
36+
8:{'type':'8bit palletised', 'NumColors':256},
37+
16:{'type':'16bit RGB', 'NumColors':65536},
38+
24:{'type':'24bit RGB', 'NumColors':16000000}}

0 commit comments

Comments
 (0)