File size: 4,785 Bytes
3f7c971
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#
# The Python Imaging Library.
# $Id$
#
# FLI/FLC file handling.
#
# History:
#       95-09-01 fl     Created
#       97-01-03 fl     Fixed parser, setup decoder tile
#       98-07-15 fl     Renamed offset attribute to avoid name clash
#
# Copyright (c) Secret Labs AB 1997-98.
# Copyright (c) Fredrik Lundh 1995-97.
#
# See the README file for information on usage and redistribution.
#
from __future__ import annotations

import os

from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16
from ._binary import i32le as i32
from ._binary import o8

#
# decoder


def _accept(prefix: bytes) -> bool:
    return (
        len(prefix) >= 6
        and i16(prefix, 4) in [0xAF11, 0xAF12]
        and i16(prefix, 14) in [0, 3]  # flags
    )


##
# Image plugin for the FLI/FLC animation format.  Use the <b>seek</b>
# method to load individual frames.


class FliImageFile(ImageFile.ImageFile):
    format = "FLI"
    format_description = "Autodesk FLI/FLC Animation"
    _close_exclusive_fp_after_loading = False

    def _open(self):
        # HEAD
        s = self.fp.read(128)
        if not (_accept(s) and s[20:22] == b"\x00\x00"):
            msg = "not an FLI/FLC file"
            raise SyntaxError(msg)

        # frames
        self.n_frames = i16(s, 6)
        self.is_animated = self.n_frames > 1

        # image characteristics
        self._mode = "P"
        self._size = i16(s, 8), i16(s, 10)

        # animation speed
        duration = i32(s, 16)
        magic = i16(s, 4)
        if magic == 0xAF11:
            duration = (duration * 1000) // 70
        self.info["duration"] = duration

        # look for palette
        palette = [(a, a, a) for a in range(256)]

        s = self.fp.read(16)

        self.__offset = 128

        if i16(s, 4) == 0xF100:
            # prefix chunk; ignore it
            self.__offset = self.__offset + i32(s)
            self.fp.seek(self.__offset)
            s = self.fp.read(16)

        if i16(s, 4) == 0xF1FA:
            # look for palette chunk
            number_of_subchunks = i16(s, 6)
            chunk_size = None
            for _ in range(number_of_subchunks):
                if chunk_size is not None:
                    self.fp.seek(chunk_size - 6, os.SEEK_CUR)
                s = self.fp.read(6)
                chunk_type = i16(s, 4)
                if chunk_type in (4, 11):
                    self._palette(palette, 2 if chunk_type == 11 else 0)
                    break
                chunk_size = i32(s)
                if not chunk_size:
                    break

        palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
        self.palette = ImagePalette.raw("RGB", b"".join(palette))

        # set things up to decode first frame
        self.__frame = -1
        self._fp = self.fp
        self.__rewind = self.fp.tell()
        self.seek(0)

    def _palette(self, palette, shift):
        # load palette

        i = 0
        for e in range(i16(self.fp.read(2))):
            s = self.fp.read(2)
            i = i + s[0]
            n = s[1]
            if n == 0:
                n = 256
            s = self.fp.read(n * 3)
            for n in range(0, len(s), 3):
                r = s[n] << shift
                g = s[n + 1] << shift
                b = s[n + 2] << shift
                palette[i] = (r, g, b)
                i += 1

    def seek(self, frame: int) -> None:
        if not self._seek_check(frame):
            return
        if frame < self.__frame:
            self._seek(0)

        for f in range(self.__frame + 1, frame + 1):
            self._seek(f)

    def _seek(self, frame: int) -> None:
        if frame == 0:
            self.__frame = -1
            self._fp.seek(self.__rewind)
            self.__offset = 128
        else:
            # ensure that the previous frame was loaded
            self.load()

        if frame != self.__frame + 1:
            msg = f"cannot seek to frame {frame}"
            raise ValueError(msg)
        self.__frame = frame

        # move to next frame
        self.fp = self._fp
        self.fp.seek(self.__offset)

        s = self.fp.read(4)
        if not s:
            msg = "missing frame size"
            raise EOFError(msg)

        framesize = i32(s)

        self.decodermaxblock = framesize
        self.tile = [("fli", (0, 0) + self.size, self.__offset, None)]

        self.__offset += framesize

    def tell(self) -> int:
        return self.__frame


#
# registry

Image.register_open(FliImageFile.format, FliImageFile, _accept)

Image.register_extensions(FliImageFile.format, [".fli", ".flc"])