5555Should 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"""
7580Weedo 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
262267def _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