1# -*- coding: utf-8 -*- 2 3# romiq.kh@gmail.com, 2014 4 5import array, struct, io 6 7from . import EngineError 8 9try: 10 from PIL import Image 11except ImportError: 12 Image = None 13 14try: 15 from PIL import FliImagePlugin 16except ImportError: 17 pass 18 19FLC_HEADER = [ 20 ["fsize", 1, "I", False], 21 ["ftype", 1, "H", False], 22 ["frames_num", 1, "H", True], 23 ["width", 1, "H", True], 24 ["height", 1, "H", True], 25 ["depth", 1, "H", False], 26 ["flags", 1, "H", True], 27 ["speed", 1, "I", True], 28 ["reserved1", 1, "H", False], 29 ["created", 1, "I", True], 30 ["creator", 1, "I", True], 31 ["updated", 1, "I", True], 32 ["updater", 1, "I", True], 33 ["aspect_dx", 1, "H", True], 34 ["aspect_dy", 1, "H", True], 35 ["ext_flags", 1, "H", False], 36 ["keyframes", 1, "H", False], 37 ["totalframes", 1, "H", False], 38 ["req_memory", 1, "I", False], 39 ["max_regions", 1, "H", False], 40 ["transp_num", 1, "H", False], 41 ["reserved2", 24, "s", False], 42 ["oframe1", 1, "I", False], 43 ["oframe2", 1, "i", False], 44 ["reserved3", 40, "s", False], 45] 46 47class FLCLoader: 48 def __init__(self): 49 self.rgb = None 50 self.image = None 51 self.width = 0 52 self.height = 0 53 self.frame_num = 0 54 self.delay = 0 55 56 57 def load_info(self, f): 58 self.image = Image.open(f) 59 self.frame_num = 1 60 try: 61 while 1: 62 self.image.seek(self.image.tell() + 1) 63 self.frame_num += 1 64 except EOFError: 65 pass # end of sequence 66 67 68 def parseflcchunks(self, f, offset, limit, level = 0, maxchunks = None,): 69 def check_hdr(size, delta, name, offset): 70 if delta < size: 71 raise EngineError("Incorrect FLC %s chunk at 0x{:08x}".format( 72 (name, offset))) 73 74 chunks = [] 75 while True: 76 if limit is not None: 77 if offset >= limit: 78 break 79 if maxchunks is not None: 80 if len(chunks) >= maxchunks: 81 break 82 chunk = {"offset": offset} 83 temp = f.read(6) 84 sz, tp = struct.unpack_from("<IH", temp) 85 offset += 6 86 87 chunk["size"] = sz 88 chunk["type"] = tp 89 delta = sz - 6 90 if delta < 0: 91 raise EngineError("Incorrect FLC chunk at 0x{:08x}".format( 92 chunk["offset"])) 93 94 raw_chunks = [ 95 0x4, # COLOR_256 96 0x7, # DELTA_FLC 97 0xf, # BYTE_RUN 98 0xF100, # PREFIX_TYPE - mismaked, out destination ignore this 99 ] 100 #print("{}CHUNK 0x{:x}, size = {}".format(" "*level, tp, sz)) 101 # do not parse 3rd level 0x12 chunk 102 if tp == 0x12 and level == 2: 103 tp = 0x4 104 105 # parse chunks 106 if tp in raw_chunks: 107 temp = f.read(delta) 108 offset += delta 109 chunk["data"] = temp 110 elif tp == 0x12: 111 # PSTAMP 112 check_hdr(6, delta, "PSTAMP", offset) 113 temp = f.read(6) 114 delta -= 6 115 height, width, xlate = struct.unpack_from("<3H", temp) 116 offset += 6 117 offset, subchunks = self.parseflcchunks(f, offset, 118 offset + delta, level + 1, 1) 119 chunk["chunks"] = subchunks 120 #print(subchunks) 121 elif tp == 0xF1FA: 122 # FRAME_TYPE 123 check_hdr(10, delta, "FRAME_TYPE", offset) 124 temp = f.read(10) 125 delta -= 10 126 sub_num, delay, reserved, width, height = \ 127 struct.unpack_from("<5H", temp) 128 offset += 10 129 chunk["delay"] = delay 130 chunk["width"] = width 131 chunk["height"] = height 132 offset, subchunks = self.parseflcchunks(f, offset, 133 offset + delta, level + 1, sub_num) 134 chunk["chunks"] = subchunks 135 else: 136 raise Exception("Unknown FLC chunk type 0x{:04x} at 0x{:x08x}".\ 137 format(tp, offset)) 138 139 chunks.append(chunk) 140 141 return offset, chunks 142 143 def load_data(self, f): 144 # parse header 145 offset = 0 146 hdr_keys = [] 147 hdr_struct = "<" 148 for hnam, hsz, htp, hed in FLC_HEADER: 149 hdr_keys.append(hnam) 150 if hsz == 1: 151 hdr_struct += htp 152 else: 153 hdr_struct += "%d" % hsz + htp 154 155 header = {} 156 temp = f.read(128) 157 hdr = struct.unpack_from(hdr_struct, temp) 158 159 offset += 128 160 161 if len(hdr) != len(hdr_keys): 162 raise EngineError("Incorrect FLC header {} != {}".format( 163 len(hdr), len(hdr_keys))) 164 for hid in range(len(hdr)): 165 header[hdr_keys[hid]] = hdr[hid] 166 167 if header["ftype"] != 0xAF12: 168 raise EngineError("Unsupported FLC type (0x{:04x})".format( 169 header["ftype"])) 170 171 # check if not EGI ext 172 if header["creator"] == 0x45474900: 173 if header["ext_flags"] != 0: 174 raise EngineError("Unsupported FLC EGI extension") 175 176 # NOTE: we recreate FLC to avoid Pilllow bug 177 # 1. remove 0xf100 chunk (PREFIX, implementation specific) 178 # 2. remobe 0x12 subchunk (PSTAMP) from 1st frame 179 180 # read chunks 181 _, chunks = self.parseflcchunks(f, offset, header["fsize"]) 182 183 f.seek(0) 184 buf = io.BytesIO() 185 buf.write(f.read(128)) # clone header 186 for chunk in chunks: 187 if chunk["type"] == 0xF100: 188 continue 189 elif chunk["type"] == 0xF1FA: 190 rebuild = False 191 nchunks = [] 192 nsz = 16 # I6H - type, size, sub_num, delay, 193 # reserved, width, height 194 for schunk in chunk["chunks"]: 195 if schunk["type"] == 0x12: # detect mailformed PSTAMP 196 rebuild = True 197 elif rebuild: 198 nchunks.append(schunk) 199 nsz += schunk["size"] 200 if rebuild: 201 buf.write(struct.pack("<I6H", nsz, 0xF1FA, len(nchunks), 202 chunk["delay"], 0, chunk["width"], chunk["height"])) 203 for schunk in nchunks: 204 f.seek(schunk["offset"]) 205 buf.write(f.read(schunk["size"])) 206 continue 207 # copy chunk 208 buf.write(f.read(chunk["size"])) 209 210 buf.seek(0) 211 self.image = Image.open(buf) 212