Spaces:
Paused
Paused
| # | |
| # 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"]) | |