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