Skip to content

Commit 82509d5

Browse files
author
Matt Land
committed
refactor, improve testing validate, pass pylint
1 parent 188dc47 commit 82509d5

File tree

7 files changed

+109
-97
lines changed

7 files changed

+109
-97
lines changed

adafruit_imageload/bmp/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ def load(file, *, bitmap=None, palette=None):
5656

5757
if colors == 0 and color_depth >= 16:
5858
raise NotImplementedError("True color BMP unsupported")
59-
else:
60-
if colors == 0:
61-
colors = 2 ** color_depth
62-
from . import indexed
63-
return indexed.load(file, width, height, data_start, colors, color_depth, bitmap=bitmap,
64-
palette=palette)
59+
60+
if colors == 0:
61+
colors = 2 ** color_depth
62+
from . import indexed
63+
return indexed.load(file, width, height, data_start, colors, color_depth, bitmap=bitmap,
64+
palette=palette)

adafruit_imageload/pnm/__init__.py

Lines changed: 36 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,37 @@ def load(file, header, *, bitmap=None, palette=None):
3939
to do the actual data loading.
4040
Formats P1, P4 have two space padded pieces of information: width and height.
4141
All other formats have three: width, height, and max color value.
42+
This load function will move the file stream pointer to the start of data in all cases.
4243
"""
44+
# pylint: disable=too-many-branches
4345
magic_number = header[:2]
4446
file.seek(2)
4547
pnm_header = []
48+
next_value = bytearray()
4649
while True:
4750
# We have all we need at length 3 for formats P2, P3, P5, P6
4851
if len(pnm_header) == 3:
49-
break
52+
if magic_number in [b"P2", b"P5"]:
53+
from . import pgm
54+
55+
return pgm.load(
56+
file, magic_number, pnm_header, bitmap=bitmap, palette=palette
57+
)
58+
59+
if magic_number == b"P3":
60+
from . import ppm_ascii
61+
62+
return ppm_ascii.load(
63+
file, pnm_header[0], pnm_header[1], bitmap=bitmap, palette=palette
64+
)
65+
66+
if magic_number == b"P6":
67+
from . import ppm_binary
68+
69+
return ppm_binary.load(
70+
file, pnm_header[0], pnm_header[1], bitmap=bitmap, palette=palette
71+
)
72+
5073
if len(pnm_header) == 2 and magic_number in [b"P1", b"P4"]:
5174
bitmap = bitmap(pnm_header[0], pnm_header[1], 1)
5275
if palette:
@@ -66,46 +89,15 @@ def load(file, header, *, bitmap=None, palette=None):
6689
)
6790

6891
next_byte = file.read(1)
69-
if next_byte == b"#":
70-
while True:
71-
next_byte = file.read(1)
72-
if not next_byte:
73-
raise RuntimeError("Unsupported image format")
74-
if next_byte == b"\n":
75-
break
76-
if next_byte.isdigit():
77-
value = bytearray()
78-
while True:
79-
if not next_byte.isdigit():
80-
break
81-
value += next_byte
82-
next_byte = file.read(1)
83-
if not next_byte:
84-
raise RuntimeError("Unsupported image format")
85-
86-
pnm_header.append(int("".join(["%c" % char for char in value])))
87-
continue
88-
89-
if not next_byte:
90-
raise RuntimeError("Unsupported image format")
91-
92-
if magic_number in [b"P2", b"P5"]:
93-
from . import pgm
94-
95-
return pgm.load(file, magic_number, pnm_header, bitmap=bitmap, palette=palette)
96-
97-
if magic_number == b"P3":
98-
from . import ppm_ascii
99-
100-
return ppm_ascii.load(
101-
file, pnm_header[0], pnm_header[1], bitmap=bitmap, palette=palette
102-
)
103-
104-
if magic_number == b"P6":
105-
from . import ppm_binary
106-
107-
return ppm_binary.load(
108-
file, pnm_header[0], pnm_header[1], bitmap=bitmap, palette=palette
109-
)
110-
111-
raise RuntimeError("Unsupported image format {}".format(magic_number))
92+
if next_byte == b"":
93+
raise RuntimeError("Unsupported image format {}".format(magic_number))
94+
if next_byte == b"#": # comment found, seek until a newline or EOF is found
95+
while file.read(1) not in [b"", b"\n"]: # EOF or NL
96+
pass
97+
elif not next_byte.isdigit(): # boundary found in header data
98+
if next_value:
99+
# pull values until space is found
100+
pnm_header.append(int("".join(["%c" % char for char in next_value])))
101+
next_value = bytearray() # reset the byte array
102+
else:
103+
next_value += next_byte # push the digit into the byte array

adafruit_imageload/pnm/pgm/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ def load(file, magic_number, header, *, bitmap=None, palette=None):
4141

4242
if magic_number == b"P2": # To handle ascii PGM files.
4343
from . import ascii as pgm_ascii
44+
4445
return pgm_ascii.load(file, width, height, bitmap=bitmap, palette=palette)
4546

4647
if magic_number == b"P5": # To handle binary PGM files.
4748
from . import binary
49+
4850
return binary.load(file, width, height, bitmap=bitmap, palette=palette)
4951

5052
raise NotImplementedError("Was not able to send image")

adafruit_imageload/pnm/ppm_ascii.py

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,55 +36,54 @@
3636

3737
def load(file, width, height, bitmap=None, palette=None):
3838
"""
39-
4039
:param stream file: infile with the position set at start of data
4140
:param int width:
4241
:param int height:
4342
:param int max_colors: color space of file
4443
:param bitmap: displayio.Bitmap class
4544
:param palette: displayio.Palette class
46-
:return:
45+
:return tuple:
4746
"""
4847
palette_colors = set()
4948
data_start = file.tell()
50-
triplet = []
51-
color = bytearray()
52-
while True: # scan for all colors present in the file
53-
# read values from file, values can be string len 1-3 for values 0 - 255
54-
this_byte = file.read(1)
55-
if this_byte == b"":
56-
break
57-
if not this_byte.isdigit(): # completed one number
58-
triplet.append(int("".join(["%c" % char for char in color])))
59-
color = bytearray()
60-
if len(triplet) == 3:
61-
palette_colors.add(tuple(triplet))
62-
triplet = []
63-
continue
64-
color += this_byte
49+
for triplet in read_three_colors(file):
50+
palette_colors.add(triplet)
51+
6552
if palette:
6653
palette = palette(len(palette_colors))
6754
for counter, color in enumerate(palette_colors):
68-
palette[counter] = bytes(color)
69-
55+
palette[counter] = color
7056
if bitmap:
7157
file.seek(data_start)
7258
bitmap = bitmap(width, height, len(palette_colors))
7359
palette_colors = list(palette_colors)
7460
for y in range(height):
7561
for x in range(width):
76-
triplet = []
77-
color = bytearray()
78-
while True:
79-
this_byte = file.read(1)
62+
for color in read_three_colors(file):
63+
bitmap[x, y] = palette_colors.index(color)
64+
break # exit the inner generator
65+
return bitmap, palette
8066

81-
if not this_byte.isdigit(): # completed one number
82-
triplet.append(int("".join(["%c" % char for char in color])))
83-
color = bytearray()
84-
if len(triplet) == 3: # completed one pixel
85-
bitmap[x, y] = palette_colors.index(tuple(triplet))
86-
break
87-
continue
88-
color += this_byte
8967

90-
return bitmap, palette
68+
def read_three_colors(file):
69+
"""
70+
Generator to read integer values from file, in groups of three.
71+
Each value can be len 1-3, for values 0 - 255, space padded.
72+
:return tuple[int]:
73+
"""
74+
triplet = []
75+
color = bytearray()
76+
while True:
77+
this_byte = file.read(1)
78+
if this_byte.isdigit():
79+
color += this_byte
80+
# not a digit means we completed one number (found a space separator or EOF)
81+
elif color or (triplet and this_byte == b""):
82+
triplet.append(int("".join(["%c" % char for char in color])))
83+
color = bytearray()
84+
if len(triplet) == 3: # completed one pixel
85+
yield bytes(tuple(triplet))
86+
triplet = []
87+
# short circuit must be after all other cases, so we yield the last pixel before returning
88+
if this_byte == b"":
89+
return

adafruit_imageload/pnm/ppm_binary.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ def load(file, width, height, bitmap=None, palette=None):
4545
for y in range(height):
4646
data_line = iter(bytes(file.read(line_size)))
4747
for red in data_line:
48-
green = next(data_line)
49-
blue = next(data_line)
50-
palette_colors.add((red, green, blue))
48+
# red, green, blue
49+
palette_colors.add((red, next(data_line), next(data_line)))
5150

5251
if palette:
5352
palette = palette(len(palette_colors))
@@ -61,9 +60,10 @@ def load(file, width, height, bitmap=None, palette=None):
6160
x = 0
6261
data_line = iter(bytes(file.read(line_size)))
6362
for red in data_line:
64-
green = next(data_line)
65-
blue = next(data_line)
66-
bitmap[x, y] = palette_colors.index((red, green, blue))
63+
# red, green, blue
64+
bitmap[x, y] = palette_colors.index(
65+
(red, next(data_line), next(data_line))
66+
)
6767
x += 1
6868

6969
return bitmap, palette

adafruit_imageload/tests/displayio_shared_bindings.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ def __setitem__(self, key: Union[tuple, int], value: int) -> None:
7575
raise RuntimeError(f"set value as int, not {type(value)}")
7676
if value > 255:
7777
raise ValueError(f"pixel value {value} too large")
78+
if self.data.get(key):
79+
raise ValueError(f"pixel {self._decode(key)}/{key} already set, cannot set again")
7880
self.data[key] = value
7981

8082
def __getitem__(self, item: Union[tuple, int]) -> bytearray:
@@ -87,18 +89,22 @@ def __getitem__(self, item: Union[tuple, int]) -> bytearray:
8789
except KeyError:
8890
raise RuntimeError("no data at {} [{}]".format(self._decode(item), item))
8991

90-
def validate(self) -> None:
92+
def validate(self, detect_empty_image=True) -> None:
9193
"""
9294
method to to make sure all pixels allocated in the Bitmap
9395
were set with a value
9496
"""
97+
seen_colors = set()
9598
if not self.data:
9699
raise ValueError("no rows were set / no data in memory")
97-
for i in range(self.height * self.width, 0):
98-
try:
99-
self.data[i]
100-
except KeyError:
101-
raise ValueError("missing data at {i}")
100+
for y in range(self.height):
101+
for x in range(self.width):
102+
try:
103+
seen_colors.add(self[x,y])
104+
except KeyError:
105+
raise ValueError(f"missing data at {x},{y}")
106+
if detect_empty_image and len(seen_colors) < 2:
107+
raise ValueError('image detected as only one color. set detect_empty_image=False to ignore')
102108

103109
def __str__(self) -> str:
104110
"""

adafruit_imageload/tests/test_ppm_load.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
2828
"""
2929
import os
30+
from io import BytesIO
3031
from unittest import TestCase
3132
from .. import pnm
33+
from ..pnm.ppm_ascii import read_three_colors
3234
from .displayio_shared_bindings import Bitmap_C_Interface, Palette_C_Interface
3335

3436

@@ -45,12 +47,12 @@ def test_load_works_p3_ascii(self):
4547
with open(test_file, "rb") as file:
4648
bitmap, palette = pnm.load(
4749
file, b"P3", bitmap=Bitmap_C_Interface, palette=Palette_C_Interface
48-
)
50+
) # type: Bitmap_C_Interface, Palette_C_Interface
4951

5052
self.assertTrue(isinstance(palette, Palette_C_Interface))
5153
self.assertEqual(6, palette.num_colors)
5254
palette.validate()
53-
# self.fail(str(palette))
55+
#self.fail(str(palette))
5456
self.assertTrue(isinstance(bitmap, Bitmap_C_Interface), bitmap)
5557
self.assertEqual(6, bitmap.colors)
5658
self.assertEqual(16, bitmap.width)
@@ -69,11 +71,22 @@ def test_load_works_p6_binary(self):
6971
with open(test_file, "rb") as file:
7072
bitmap, palette = pnm.load(
7173
file, b"P6", bitmap=Bitmap_C_Interface, palette=Palette_C_Interface
72-
)
74+
) # type: Bitmap_C_Interface, Palette_C_Interface
7375
self.assertEqual(6, palette.num_colors)
7476
palette.validate()
7577
self.assertTrue(isinstance(bitmap, Bitmap_C_Interface), bitmap)
7678
self.assertEqual(6, bitmap.colors)
7779
self.assertEqual(16, bitmap.width)
7880
self.assertEqual(16, bitmap.height)
7981
bitmap.validate()
82+
83+
def test_load_three_colors_tail(self):
84+
buffer = BytesIO(b'211 222 233')
85+
for i in read_three_colors(buffer):
86+
self.assertEqual(b'\xd3\xde\xe9', i)
87+
88+
def test_load_three_colors_middle(self):
89+
buffer = BytesIO(b'0 128 255 45 55 25')
90+
for i in iter(read_three_colors(buffer)):
91+
self.assertEqual(b'\x00\x80\xff', i)
92+
break

0 commit comments

Comments
 (0)