Skip to content

Commit bb03136

Browse files
committed
Added LICEcap's LCF file format support
1 parent 06f6fec commit bb03136

File tree

2 files changed

+130
-11
lines changed

2 files changed

+130
-11
lines changed

anim_encoder.py

+17-11
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,23 @@ def find_matching_rect(bitmap, num_used_rows, packed, src, sx, sy, w, h):
128128
return None
129129

130130
def generate_animation(anim_name):
131-
frames = []
132-
rex = re.compile("screen_([0-9]+).png")
133-
for f in os.listdir(anim_name):
134-
m = re.search(rex, f)
135-
if m:
136-
frames.append((int(m.group(1)), anim_name + "/" + f))
137-
frames.sort()
138-
139-
images = [misc.imread(f) for t, f in frames]
131+
if anim_name.endswith('.lcf'):
132+
from lcf import process
133+
frames = [(f.delta, f) for f in process(anim_name)]
134+
images = [f.get_array() for t, f in frames]
135+
delays = [t for t, f in frames] + [END_FRAME_PAUSE]
136+
anim_name = anim_name[:-4]
137+
else:
138+
frames = []
139+
rex = re.compile("screen_([0-9]+).png")
140+
for f in os.listdir(anim_name):
141+
m = re.search(rex, f)
142+
if m:
143+
frames.append((int(m.group(1)), anim_name + "/" + f))
144+
frames.sort()
145+
images = [misc.imread(f) for t, f in frames]
146+
times = [t for t, f in frames]
147+
delays = (array(times[1:] + [times[-1] + END_FRAME_PAUSE]) - array(times)).tolist()
140148

141149
zero = images[0] - images[0]
142150
pairs = zip([zero] + images[:-1], images)
@@ -204,8 +212,6 @@ def generate_animation(anim_name):
204212
os.system("mv " + anim_name + "_packed_tmp.png " + anim_name + "_packed.png")
205213

206214
# Generate JSON to represent the data
207-
times = [t for t, f in frames]
208-
delays = (array(times[1:] + [times[-1] + END_FRAME_PAUSE]) - array(times)).tolist()
209215

210216
timeline = []
211217
for i in xrange(len(images)):

lcf.py

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import division
3+
4+
import zlib
5+
import struct
6+
from collections import namedtuple
7+
8+
import numpy as np
9+
10+
11+
LCF_VERSION = 0x11CEb001
12+
13+
LcfHeader = namedtuple('LcfHeader', 'version bpp w h bsize_w bsize_h nf cdata_left dsize')
14+
LcfBlock = namedtuple('LcfBlock', 'hdr frame_deltas frames')
15+
16+
17+
class LcfFrame(object):
18+
def __init__(self, frame, block, idx):
19+
self.frame = frame
20+
self.block = block
21+
self.idx = idx
22+
self.delta = block.frame_deltas[idx]
23+
24+
def get_array(self):
25+
hdr = self.block.hdr
26+
arr = np.ndarray(shape=(0, hdr.w, 3), dtype=np.uint8)
27+
for y, ypos in enumerate(xrange(0, hdr.h, hdr.bsize_h)):
28+
hei = min(hdr.h - ypos, hdr.bsize_h)
29+
line = np.ndarray(shape=(hei, 0, 3), dtype=np.uint8)
30+
for x, xpos in enumerate(xrange(0, hdr.w, hdr.bsize_w)):
31+
# wid = min(hdr.w - xpos, hdr.bsize_w)
32+
line = np.concatenate((line, self.frame[x][y]), axis=1)
33+
arr = np.concatenate((arr, line), axis=0)
34+
return arr
35+
36+
37+
class BadBlock(Exception):
38+
pass
39+
40+
41+
class CorruptedBlock(BadBlock):
42+
pass
43+
44+
45+
def read(fd, size):
46+
data = fd.read(size)
47+
if len(data) != size:
48+
raise BadBlock("Cannot read %s bytes from file" % size)
49+
return data
50+
51+
52+
def read_block(fd):
53+
hdr = LcfHeader(*struct.unpack('iiiiiiiii', read(fd, 9 * 4)))
54+
if hdr.version != LCF_VERSION:
55+
raise BadBlock("Wrong LCF version")
56+
if hdr.nf < 1 or hdr.nf > 1024:
57+
raise BadBlock("Wrong number of frames")
58+
if hdr.bpp != 16:
59+
raise CorruptedBlock("Wrong bits per pixel")
60+
frame_deltas = [struct.unpack('i', read(fd, 4))[0] for f in xrange(hdr.nf)]
61+
cdata = read(fd, hdr.cdata_left)
62+
decompress = zlib.decompressobj()
63+
data = decompress.decompress(cdata)
64+
if hdr.dsize != len(data):
65+
raise CorruptedBlock("Wrong uncompressed data size")
66+
# Decode slices
67+
bytespersample = (hdr.bpp + 7) // 8
68+
# Setup frames as blocks of ns_x * ns_y slices
69+
ns_x = (hdr.w + hdr.bsize_w - 1) // hdr.bsize_w
70+
ns_y = (hdr.h + hdr.bsize_h - 1) // hdr.bsize_h
71+
frames = [[[[] for y in xrange(ns_y)] for x in xrange(ns_x)] for f in xrange(hdr.nf)]
72+
sp = 0
73+
for y, ypos in enumerate(xrange(0, hdr.h, hdr.bsize_h)):
74+
hei = min(hdr.h - ypos, hdr.bsize_h)
75+
for x, xpos in enumerate(xrange(0, hdr.w, hdr.bsize_w)):
76+
wid = min(hdr.w - xpos, hdr.bsize_w)
77+
sz1 = hei * wid * bytespersample
78+
cnt = 0
79+
for i in xrange(hdr.nf):
80+
if cnt > 0:
81+
cnt -= 1
82+
else:
83+
buff = data[sp:sp + sz1]
84+
if len(buff) != sz1:
85+
raise CorruptedBlock("Wrong size read!")
86+
arr = np.fromstring(buff, dtype=np.uint16)
87+
lvalid = np.zeros(arr.size * 3, np.uint8)
88+
lvalid.view()[0::3] = (arr << 3) & 0xf8
89+
lvalid.view()[1::3] = (arr >> 3) & 0xfc
90+
lvalid.view()[2::3] = (arr >> 8) & 0xf8
91+
lvalid.shape = (hei, wid, 3)
92+
sp += sz1
93+
if i < hdr.nf - 1:
94+
cnt = ord(data[sp])
95+
sp += 1
96+
frames[i][x][y] = lvalid
97+
if sp != len(data):
98+
print "Not all data consumed (%s/%s)!" % (sp, len(data))
99+
return LcfBlock(hdr, frame_deltas, frames)
100+
101+
102+
def process(filename):
103+
blocks = []
104+
with open(filename) as fd:
105+
while True:
106+
try:
107+
blocks.append(read_block(fd))
108+
except CorruptedBlock as e:
109+
print(e)
110+
break
111+
except BadBlock:
112+
break
113+
return [LcfFrame(f, b, i) for i, b in enumerate(blocks) for f in b.frames]

0 commit comments

Comments
 (0)