Skip to content

Commit 6b41ba6

Browse files
committed
Add support for GIF images
1 parent d244bc2 commit 6b41ba6

File tree

1 file changed

+151
-4
lines changed

1 file changed

+151
-4
lines changed

stage.py

Lines changed: 151 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import utime
22
import array
3+
import struct
34
import _stage
45

56

@@ -167,13 +168,13 @@ def read_palette(self):
167168
palette[color] = (c << 8) | (c >> 8)
168169
return palette
169170

170-
def read_data(self, offset=0, buffer=None):
171+
def read_data(self, buffer=None):
171172
line_size = self.width >> 1
172173
if buffer is None:
173174
buffer = bytearray(line_size * self.height)
174175

175176
with open(self.filename, 'rb') as f:
176-
f.seek(self.data + offset)
177+
f.seek(self.data)
177178
index = (self.height - 1) * line_size
178179
for line in range(self.height):
179180
chunk = f.read(line_size)
@@ -182,6 +183,142 @@ def read_data(self, offset=0, buffer=None):
182183
return buffer
183184

184185

186+
def read_blockstream(f):
187+
while True:
188+
size = f.read(1)[0]
189+
if size == 0:
190+
break
191+
for i in range(size):
192+
yield f.read(1)[0]
193+
194+
195+
class EndOfData(Exception):
196+
pass
197+
198+
199+
class LZWDict:
200+
def __init__(self, code_size):
201+
self.code_size = code_size
202+
self.clear_code = 1 << code_size
203+
self.end_code = self.clear_code + 1
204+
self.codes = []
205+
self.clear()
206+
207+
def clear(self):
208+
self.last = b''
209+
self.code_len = self.code_size + 1
210+
self.codes[:] = []
211+
212+
def decode(self, code):
213+
if code == self.clear_code:
214+
self.clear()
215+
return b''
216+
elif code == self.end_code:
217+
raise EndOfData()
218+
elif code < self.clear_code:
219+
value = bytes([code])
220+
elif code <= len(self.codes) + self.end_code:
221+
value = self.codes[code - self.end_code - 1]
222+
else:
223+
value = self.last + self.last[0:1]
224+
if self.last:
225+
self.codes.append(self.last + value[0:1])
226+
if (len(self.codes) + self.end_code + 1 >= 1 << self.code_len and
227+
self.code_len < 12):
228+
self.code_len += 1
229+
self.last = value
230+
return value
231+
232+
233+
def lzw_decode(data, code_size):
234+
dictionary = LZWDict(code_size)
235+
bit = 0
236+
try:
237+
byte = next(data)
238+
try:
239+
while True:
240+
code = 0
241+
for i in range(dictionary.code_len):
242+
code |= ((byte >> bit) & 0x01) << i
243+
bit += 1
244+
if bit >= 8:
245+
bit = 0
246+
byte = next(data)
247+
yield dictionary.decode(code)
248+
except EndOfData:
249+
while True:
250+
next(data)
251+
except StopIteration:
252+
return
253+
254+
255+
class GIF16:
256+
def __init__(self, filename):
257+
self.filename = filename
258+
259+
def read_header(self):
260+
with open(self.filename, 'rb') as f:
261+
header = f.read(6)
262+
if header not in {b'GIF87a', b'GIF89a'}:
263+
raise ValueError("Not GIF file")
264+
self.width, self.height, flags, self.background, self.aspect = (
265+
struct.unpack('<HHBBB', f.read(7)))
266+
self.palette_size = 1 << ((flags & 0x07) + 1)
267+
if not flags & 0x80 or self.palette_size > 16:
268+
raise ValueError("16-color GIF expected")
269+
270+
def read_palette(self):
271+
palette = array.array('H', (0 for i in range(16)))
272+
with open(self.filename, 'rb') as f:
273+
f.seek(13)
274+
for color in range(self.palette_size):
275+
buffer = f.read(3)
276+
c = color565(buffer[2], buffer[1], buffer[0])
277+
palette[color] = (c << 8) | (c >> 8)
278+
return palette
279+
280+
def read_data(self, buffer=None):
281+
line_size = self.width >> 1
282+
if buffer is None:
283+
buffer = bytearray(line_size * self.height)
284+
with open(self.filename, 'rb') as f:
285+
f.seek(13 + self.palette_size * 3)
286+
while True: # skip to first frame
287+
block_type = f.read(1)[0]
288+
if block_type == 0x2c:
289+
break
290+
elif block_type == 0x21: # skip extension
291+
extension_type = f.read(1)[0]
292+
while True:
293+
size = f.read(1)[0]
294+
if size == 0:
295+
break
296+
f.seek(1, size)
297+
elif block_type == 0x3b:
298+
raise ValueError("no frames")
299+
x, y, w, h, flags = struct.unpack('<HHHHB', f.read(9))
300+
if flags & 0x80:
301+
raise ValueError("local palette")
302+
if flags & 0x40:
303+
raise ValueError("interlaced")
304+
if w != self.width or h != self.height or x != 0 or y != 0:
305+
raise ValueError("partial frame")
306+
min_code_size = f.read(1)[0]
307+
x = 0
308+
y = 0
309+
for decoded in lzw_decode(read_blockstream(f), min_code_size):
310+
for pixel in decoded:
311+
if x & 0x01:
312+
buffer[(x >> 1) + y * line_size] |= pixel
313+
else:
314+
buffer[(x >> 1) + y * line_size] = pixel << 4
315+
x += 1
316+
if (x >= self.width):
317+
x = 0
318+
y += 1
319+
return buffer
320+
321+
185322
class Bank:
186323
def __init__(self, buffer=None, palette=None):
187324
self.buffer = buffer
@@ -192,9 +329,19 @@ def from_bmp16(cls, filename):
192329
bmp = BMP16(filename)
193330
bmp.read_header()
194331
if bmp.width != 16 or bmp.height != 256:
195-
raise ValueError("bitmap size not 16x256")
332+
raise ValueError("image size not 16x256")
196333
palette = bmp.read_palette()
197-
buffer = bmp.read_data(0)
334+
buffer = bmp.read_data()
335+
return cls(buffer, palette)
336+
337+
@classmethod
338+
def from_gif16(cls, filename):
339+
gif = GIF16(filename)
340+
gif.read_header()
341+
if gif.width != 16 or gif.height != 256:
342+
raise ValueError("image size not 16x256")
343+
palette = gif.read_palette()
344+
buffer = gif.read_data()
198345
return cls(buffer, palette)
199346

200347

0 commit comments

Comments
 (0)