Skip to content

Commit 90d4c94

Browse files
committed
feat: add support for old mks format
1 parent 1bda17a commit 90d4c94

File tree

2 files changed

+404
-37
lines changed

2 files changed

+404
-37
lines changed

gcode_thumbnail_tool.py

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,27 @@
5555
Should be single lines only.
5656
"""
5757

58-
REGEX_MKS = re.compile(
59-
r"(?P<prefix>;simage|;;gimage):(?P<data>.*?)M10086 ;$",
60-
re.DOTALL | re.MULTILINE,
58+
REGEX_MKS_OLD = re.compile(
59+
r"(?P<prefix>;simage|;?;gimage):(?P<lines>.*?(?:\n|\r\n?)(M10086\s*;.*?(?:\n|\r\n))*)M10086\s*;(?:\n|\r\n|$)"
6160
)
6261
"""
63-
MKS format:
62+
Old MKS format:
6463
65-
;simage:<hex encoded data>M10086;<nl>
64+
;simage:<hex encoded data><nl>
65+
M10086 ;<hex encoded data><nl>
66+
[...]
67+
68+
;gimage:<hex encoded data><nl>
69+
M10086 ;<hex encoded data><nl>
70+
[...]
71+
M10086 ;<nl>
6672
67-
TODO: Single lines or multiple lines? Need sample!
73+
Spread across multiple lines
6874
"""
6975

70-
REGEX_WEEDO = re.compile(
71-
r"W221(?:\n|\r\n?)(?P<lines>(W220\s+.*?(?:\n|\r\n?))+)W222",
72-
re.DOTALL | re.MULTILINE,
73-
)
76+
REGEX_MKS_NEW = re.compile(r"", re.DOTALL | re.MULTILINE)
77+
78+
REGEX_WEEDO = re.compile(r"W221(?:\n|\r\n?)(?P<lines>(W220\s+.*?(?:\n|\r\n?))+)W222")
7479
"""
7580
Weedo format:
7681
@@ -184,7 +189,7 @@ def extract_thumbnails_from_gcode(gcode: str) -> Optional[ExtractedImages]:
184189
extractors = [
185190
("generic", (REGEX_GENERIC, _extract_generic_base64_thumbnails)),
186191
("snapmaker", (REGEX_SNAPMAKER, _extract_generic_base64_thumbnails)),
187-
# ("mks", (REGEX_MKS, _extract_mks_thumbnails)),
192+
("mks_old", (REGEX_MKS_OLD, _extract_old_mks_thumbnails)),
188193
("weedo", (REGEX_WEEDO, _extract_weedo_thumbnails)),
189194
("qidi", (REGEX_QIDI, _extract_qidi_thumbnails)),
190195
("flashprint", _extract_flashprint_thumbnails),
@@ -260,32 +265,66 @@ def _extract_generic_base64_thumbnails(matches: list[re.Match]) -> list[PILImage
260265

261266

262267
def _extract_old_mks_thumbnails(matches: list[re.Match]) -> list[PILImage]:
263-
"""Extracts a thumbnail from hex binary data used by MKS printers"""
268+
"""
269+
MKS extractor, old format
270+
271+
Thumbnail blocks start with either ``;simage:`` or ``;;gimage:``. What follows
272+
is a line of encoded pixel data, 2 bytes per RGB pixel in little endian, for the
273+
first row of the image. Following rows are encoded with a leading
274+
``M10086 ;``, the data following in the same format, row by row. Finally, a
275+
lone ``M10086 ;`` signifies the end.
264276
265-
OPTIONS = {";;gimage": (200, 200), ";simage": (100, 100)}
277+
Width and height can be determined directly from the data size (width is
278+
length of first line in bytes / 2, height the amount of lines in total).
279+
280+
The individual pixels are encoded as RGB565. Bitshift accordingly to convert
281+
back to RGB888.
282+
"""
283+
284+
logger = logging.getLogger(__name__)
285+
286+
def pop_short(byts: bytearray):
287+
v1, v2 = byts.pop(0), byts.pop(0)
288+
return v2 << 8 | v1 # convert back to big endian
289+
290+
def rgb565_to_rgb888(val: int) -> tuple[int, int, int]:
291+
r = ((val >> 11) & 31) << 3
292+
g = ((val >> 5) & 31) << 2
293+
b = ((val) & 31) << 3
294+
return r, g, b
266295

267296
result = []
268297

269-
extracted = set()
298+
logger.debug(f"Found {len(matches)} matches...")
270299
for match in matches:
271-
for prefix, dimensions in OPTIONS.items():
272-
if prefix in extracted:
273-
continue
300+
lines = _remove_line_prefix(match.group("lines"), r"M10086\s*;").splitlines()
274301

275-
if match.group("prefix") != prefix:
276-
continue
302+
height = len(lines)
303+
width = 0
277304

278-
encoded_image = bytes(
279-
bytearray.fromhex(_remove_whitespace(match.group("data")))
280-
)
305+
hex_bytes = bytearray()
306+
for line in lines:
307+
decoded = bytearray.fromhex(line)
308+
if width == 0:
309+
width = int(len(decoded) / 2) # 2 hex encoded bytes per pixel
310+
hex_bytes.extend(decoded)
281311

282-
image = Image.frombytes(
283-
"RGB", dimensions, encoded_image, "raw", "BGR;16", 0, 1
284-
)
285-
result.append(image)
286-
extracted.add(prefix)
312+
logger.debug(
313+
f"Found {len(hex_bytes)} bytes of image data, width is {width}, height is {height}"
314+
)
287315

288-
break
316+
pixel_data = bytearray()
317+
pixel_count = 0
318+
while len(hex_bytes):
319+
rgb = rgb565_to_rgb888(pop_short(hex_bytes))
320+
pixel_data.extend(rgb)
321+
pixel_count += 1
322+
323+
logger.debug(f"Width: {width}, height: {height}, pixels: {pixel_count}")
324+
assert pixel_count == width * height
325+
326+
image = Image.frombytes("RGB", (width, height), pixel_data, "raw", "RGB", 0, 1)
327+
result.append(image)
289328

290329
return result
291330

@@ -326,21 +365,20 @@ def _extract_qidi_thumbnails(matches: list[re.Match]) -> list[PILImage]:
326365
327366
The pixel count should match the width x height.
328367
329-
To convert to 24 bit color, bitshift to the right as needed to fetch
330-
the 5 bits of the channel and then shift each value by 3 bits to the left
331-
again.
368+
The individual pixels are encoded as RGB555, with bit 5 having to be
369+
ignored. Bitshift accordingly to convert back to RGB888.
332370
"""
333371

334372
logger = logging.getLogger(__name__)
335373

336374
RUNLENGTH_MASK_PIXEL = 32
337375
RUNLENGTH_MASK_NUM = 12288
338376

339-
def pop4b(byts: bytearray):
377+
def pop_short(byts: bytearray):
340378
v1, v2 = byts.pop(0), byts.pop(0)
341379
return v1 << 8 | v2
342380

343-
def val2rgb(val: int) -> tuple[int, int, int]:
381+
def rgb555_to_rgb888(val: int) -> tuple[int, int, int]:
344382
r = ((val >> 11) & 31) << 3
345383
g = ((val >> 6) & 31) << 3
346384
b = ((val) & 31) << 3
@@ -363,12 +401,12 @@ def val2rgb(val: int) -> tuple[int, int, int]:
363401
pixel_data = bytearray()
364402
pixel_count = 0
365403
while len(hex_bytes):
366-
val = pop4b(hex_bytes)
367-
pixel = val2rgb(val)
404+
val = pop_short(hex_bytes)
405+
pixel = rgb555_to_rgb888(val)
368406

369407
if val & RUNLENGTH_MASK_PIXEL == RUNLENGTH_MASK_PIXEL:
370408
# this is a repeated pixel, fetch the count!
371-
val = pop4b(hex_bytes)
409+
val = pop_short(hex_bytes)
372410
assert val & RUNLENGTH_MASK_NUM == RUNLENGTH_MASK_NUM
373411

374412
for _ in range(val & 4095):

0 commit comments

Comments
 (0)