1- "Module for interacting with the LBX file format."
1+ """Module for interacting with the LBX file format.
2+
3+ LBX File Format - lossless compression for segmented images
4+
5+ HEADERS
6+ bytes 0-3 -> LBX version
7+ bytes 4-7 -> pixel width
8+ bytes 8-11 -> pixel height
9+ bytes 12-15 -> decompressed bytes
10+ bytes 16-19 -> # colors
11+ bytes 20-23 -> # blocks
12+
13+ BODY
14+ - COLORS -
15+ - BLOCKS -
16+
17+ COLOR
18+ byte 0 -> red
19+ byte 1 -> blue
20+ byte 2 -> green
21+ byte 3 -> alpha (always 255)
22+
23+ BLOCK
24+ bytes 0-1 -> R | G value of pixel (superpixel layer #)
25+ bytes 2-3 -> # consecutive occurences
26+ """
27+ import itertools
28+ import struct
29+
30+ import numpy as np
31+ from PIL import Image
32+
233
334def encode (image ):
435 """Converts a `PIL.Image` to a `io.BytesIO` with LBX encoded data.
@@ -11,6 +42,7 @@ def encode(image):
1142 """
1243 return image
1344
45+
1446def decode (lbx ):
1547 """Decodes a `io.BytesIO` with LBX encoded data into a `PIL.Image`.
1648
@@ -20,4 +52,33 @@ def decode(lbx):
2052 Returns:
2153 A `PIL.Image` of the decoded data.
2254 """
23- return lbx
55+ version , width , height , byteLength , numColors , numBlocks = \
56+ map (lambda x : x [0 ], struct .iter_unpack ('<i' , lbx .read (24 )))
57+ colormap = np .array (
58+ list (_grouper (
59+ map (lambda x : x [0 ],
60+ struct .iter_unpack ('<B' , lbx .read (4 * numColors ))),
61+ 4 )) +
62+ [[0 , 0 , 0 , 255 ]]
63+ )
64+
65+ image_data = np .zeros ((width * height , 4 ), dtype = 'uint8' )
66+ offset = 0
67+ for _ in range (numBlocks ):
68+ layer = struct .unpack ('<H' , lbx .read (2 ))[0 ]
69+ run_length = struct .unpack ('<H' , lbx .read (2 ))[0 ]
70+ image_data [offset :offset + run_length , :] = colormap [layer ]
71+ offset += run_length
72+ assert offset == width * height , 'number of bytes read does not equal numBytes in header'
73+
74+ reshaped_image = np .reshape (image_data , (height , width , 4 ))
75+ im = Image .fromarray (reshaped_image , mode = 'RGBA' )
76+ return im
77+
78+
79+ def _grouper (iterable , n , fillvalue = None ):
80+ "Collect data into fixed-length chunks or blocks"
81+ # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
82+ args = [iter (iterable )] * n
83+ return itertools .zip_longest (* args , fillvalue = fillvalue )
84+
0 commit comments