172 lines
5.4 KiB
Python
172 lines
5.4 KiB
Python
'''Read and write PBM format image file
|
|
PBM format: http://netpbm.sourceforge.net/doc/pbm.html
|
|
Only support single image in a file
|
|
'''
|
|
|
|
def _is_space(char):
|
|
r'''C isspace function'''
|
|
return char in [0x20, 0x09 ,0x0a ,0x0b, 0x0c, 0x0d] #[ \t\n\v\f\r]
|
|
|
|
def _is_new_line(char):
|
|
r''' char is \r or \n'''
|
|
return char in [0x0a ,0x0d] #[\n\r]
|
|
def _is_comment_start(char):
|
|
''' char is #'''
|
|
return char == 0x23 #[#]
|
|
|
|
def _read_header(instream):
|
|
r'''Read file header, get basic information
|
|
:param: instream: stream that support seek, tell, read
|
|
:returns: (
|
|
width,
|
|
hetght,
|
|
raster_data_format_P1_or_P4_or_UNKNOWN,
|
|
raster_start_position,
|
|
comment
|
|
)
|
|
'''
|
|
# magic number
|
|
instream.seek(0)
|
|
mg = instream.read(2)
|
|
if mg != b"P1" and mg != b"P4":
|
|
return (-1, -1, b"UNKNOWN", -1, b"")
|
|
# state machine
|
|
comment = bytearray()
|
|
buffer = bytearray() # list to store bytes
|
|
is_reading_info = False
|
|
is_reading_comment = False
|
|
width = -1
|
|
height = -1
|
|
byts = instream.read(1)
|
|
while len(byts) == 1:
|
|
char = byts[0]
|
|
# reading comment state
|
|
if is_reading_comment:
|
|
comment.append(char)
|
|
if _is_new_line(char):
|
|
is_reading_comment = False
|
|
# reading info state
|
|
elif is_reading_info:
|
|
if _is_comment_start(char):
|
|
is_reading_comment = True
|
|
elif _is_space(char):
|
|
if width < 0:
|
|
width = int(buffer.decode(),10)
|
|
elif height < 0:
|
|
height = int(buffer.decode(),10)
|
|
buffer = bytearray()
|
|
is_reading_info = False
|
|
if width >= 0 and height >= 0:
|
|
# read header finished
|
|
break
|
|
else:
|
|
buffer.append(char)
|
|
# normal state
|
|
elif not is_reading_info and not is_reading_comment:
|
|
if _is_space(char):
|
|
pass
|
|
elif _is_comment_start(char):
|
|
is_reading_comment = True
|
|
else:
|
|
buffer.append(char)
|
|
is_reading_info = True
|
|
byts = instream.read(1)
|
|
pos = instream.tell()
|
|
return (width, height, mg, pos, comment)
|
|
|
|
def _read_data(instream, width, height, format, offset=-1):
|
|
r'''Read image data
|
|
:param: instream: stream that support seek, tell, read
|
|
width: image width
|
|
height: image height
|
|
format: b'P1' or b'P4'
|
|
:return: data in MONO_HLSB(only on micropython, MSB on other platform) format bytes
|
|
'''
|
|
if format != b"P1" and format != b"P4":
|
|
return bytearray(0)
|
|
if offset >= 0:
|
|
instream.seek(offset)
|
|
width_count = width // 8
|
|
width_count += 0 if width % 8 == 0 else 1
|
|
size = width_count * height
|
|
# bytecode format
|
|
if format == b"P4":
|
|
return bytearray(instream.read(size))
|
|
# text format
|
|
data = bytearray(size)
|
|
byte_data = 0
|
|
bitp = 0
|
|
width_p = 0
|
|
index = 0
|
|
byts = instream.read(1)
|
|
while index < size and len(byts) == 1:
|
|
char = byts[0]
|
|
if _is_space(char):
|
|
pass
|
|
else:
|
|
byte_data = (byte_data << 1) | ((char-0x30) & 0x01)
|
|
bitp += 1
|
|
width_p += 1
|
|
if width_p == width:
|
|
byte_data <<= 8 - bitp
|
|
data[index] = byte_data
|
|
index += 1
|
|
byte_data = 0
|
|
bitp = 0
|
|
width_p = 0
|
|
elif (bitp >= 8):
|
|
data[index] = byte_data
|
|
index += 1
|
|
byte_data = 0
|
|
bitp = 0
|
|
byts = instream.read(1)
|
|
return data
|
|
|
|
def read_image(instream):
|
|
r'''Read image file, return basic information and data
|
|
:param: instream: stream that support seek, tell, read
|
|
:returns: (
|
|
width,
|
|
hetght,
|
|
raster_data_format_P1_or_P4_or_UNKNOWN,
|
|
image_data,
|
|
comment,
|
|
)
|
|
'''
|
|
width, height, mg, _, comment = _read_header(instream)
|
|
data = _read_data(instream, width, height, mg)
|
|
return (width, height, mg, data, comment)
|
|
|
|
def make_image(outstream, width, height, data, format='P4', comment="made with bpm.py"):
|
|
r'''Write an image file
|
|
:param: outstream: file to write
|
|
width: image width
|
|
height: image height
|
|
data: image data in MONO_HLSB(only on micropython, MSB on other platform) format
|
|
format: "P1" or "P4"
|
|
comment: string
|
|
:return: file size
|
|
'''
|
|
if isinstance(format, bytes) or isinstance(format, bytearray):
|
|
format = format.decode("utf8")
|
|
assert format == "P4" or format == "P1"
|
|
if isinstance(comment, bytes) or isinstance(comment, bytearray):
|
|
comment = comment.decode("utf8")
|
|
outstream.write("{:s}\n#{:s}\n{:d} {:d}\n".format(format, comment, width, height).encode())
|
|
if format == "P4":
|
|
outstream.write(data)
|
|
else:
|
|
wbyte = width // 8
|
|
wbyte += 0 if width % 8 == 0 else 1
|
|
for y in range(height):
|
|
for x in range(width):
|
|
offset = (y * wbyte) + (x // 8)
|
|
bit = (7 - (x % 8))
|
|
value = (data[offset] >> bit) & 0x01
|
|
if x != 0:
|
|
outstream.write(b' ')
|
|
outstream.write(b'0' if value == 0 else b'1')
|
|
outstream.write("\n".encode())
|
|
size = outstream.tell()
|
|
return size
|