| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | from __future__ import annotations
|
| |
|
| | import olefile
|
| |
|
| | from . import Image, ImageFile
|
| | from ._binary import i32le as i32
|
| |
|
| |
|
| | MODES = {
|
| |
|
| | (0x00007FFE,): ("A", "L"),
|
| |
|
| | (0x00010000,): ("L", "L"),
|
| | (0x00018000, 0x00017FFE): ("RGBA", "LA"),
|
| |
|
| | (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"),
|
| | (0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"),
|
| |
|
| | (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"),
|
| | (0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"),
|
| | }
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | def _accept(prefix: bytes) -> bool:
|
| | return prefix.startswith(olefile.MAGIC)
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | class FpxImageFile(ImageFile.ImageFile):
|
| | format = "FPX"
|
| | format_description = "FlashPix"
|
| |
|
| | def _open(self) -> None:
|
| |
|
| |
|
| |
|
| |
|
| | try:
|
| | self.ole = olefile.OleFileIO(self.fp)
|
| | except OSError as e:
|
| | msg = "not an FPX file; invalid OLE file"
|
| | raise SyntaxError(msg) from e
|
| |
|
| | root = self.ole.root
|
| | if not root or root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
|
| | msg = "not an FPX file; bad root CLSID"
|
| | raise SyntaxError(msg)
|
| |
|
| | self._open_index(1)
|
| |
|
| | def _open_index(self, index: int = 1) -> None:
|
| |
|
| |
|
| |
|
| | prop = self.ole.getproperties(
|
| | [f"Data Object Store {index:06d}", "\005Image Contents"]
|
| | )
|
| |
|
| |
|
| |
|
| | assert isinstance(prop[0x1000002], int)
|
| | assert isinstance(prop[0x1000003], int)
|
| | self._size = prop[0x1000002], prop[0x1000003]
|
| |
|
| | size = max(self.size)
|
| | i = 1
|
| | while size > 64:
|
| | size = size // 2
|
| | i += 1
|
| | self.maxid = i - 1
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | id = self.maxid << 16
|
| |
|
| | s = prop[0x2000002 | id]
|
| |
|
| | if not isinstance(s, bytes) or (bands := i32(s, 4)) > 4:
|
| | msg = "Invalid number of bands"
|
| | raise OSError(msg)
|
| |
|
| |
|
| | colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands))
|
| |
|
| | self._mode, self.rawmode = MODES[colors]
|
| |
|
| |
|
| | self.jpeg = {}
|
| | for i in range(256):
|
| | id = 0x3000001 | (i << 16)
|
| | if id in prop:
|
| | self.jpeg[i] = prop[id]
|
| |
|
| | self._open_subimage(1, self.maxid)
|
| |
|
| | def _open_subimage(self, index: int = 1, subimage: int = 0) -> None:
|
| |
|
| |
|
| |
|
| | stream = [
|
| | f"Data Object Store {index:06d}",
|
| | f"Resolution {subimage:04d}",
|
| | "Subimage 0000 Header",
|
| | ]
|
| |
|
| | fp = self.ole.openstream(stream)
|
| |
|
| |
|
| | fp.read(28)
|
| |
|
| |
|
| | s = fp.read(36)
|
| |
|
| | size = i32(s, 4), i32(s, 8)
|
| |
|
| | tilesize = i32(s, 16), i32(s, 20)
|
| |
|
| | offset = i32(s, 28)
|
| | length = i32(s, 32)
|
| |
|
| | if size != self.size:
|
| | msg = "subimage mismatch"
|
| | raise OSError(msg)
|
| |
|
| |
|
| | fp.seek(28 + offset)
|
| | s = fp.read(i32(s, 12) * length)
|
| |
|
| | x = y = 0
|
| | xsize, ysize = size
|
| | xtile, ytile = tilesize
|
| | self.tile = []
|
| |
|
| | for i in range(0, len(s), length):
|
| | x1 = min(xsize, x + xtile)
|
| | y1 = min(ysize, y + ytile)
|
| |
|
| | compression = i32(s, i + 8)
|
| |
|
| | if compression == 0:
|
| | self.tile.append(
|
| | ImageFile._Tile(
|
| | "raw",
|
| | (x, y, x1, y1),
|
| | i32(s, i) + 28,
|
| | self.rawmode,
|
| | )
|
| | )
|
| |
|
| | elif compression == 1:
|
| |
|
| | self.tile.append(
|
| | ImageFile._Tile(
|
| | "fill",
|
| | (x, y, x1, y1),
|
| | i32(s, i) + 28,
|
| | (self.rawmode, s[12:16]),
|
| | )
|
| | )
|
| |
|
| | elif compression == 2:
|
| | internal_color_conversion = s[14]
|
| | jpeg_tables = s[15]
|
| | rawmode = self.rawmode
|
| |
|
| | if internal_color_conversion:
|
| |
|
| | if rawmode == "RGBA":
|
| |
|
| |
|
| |
|
| | jpegmode, rawmode = "YCbCrK", "CMYK"
|
| | else:
|
| | jpegmode = None
|
| |
|
| | else:
|
| |
|
| | jpegmode = rawmode
|
| |
|
| | self.tile.append(
|
| | ImageFile._Tile(
|
| | "jpeg",
|
| | (x, y, x1, y1),
|
| | i32(s, i) + 28,
|
| | (rawmode, jpegmode),
|
| | )
|
| | )
|
| |
|
| |
|
| |
|
| |
|
| | if jpeg_tables:
|
| | self.tile_prefix = self.jpeg[jpeg_tables]
|
| |
|
| | else:
|
| | msg = "unknown/invalid compression"
|
| | raise OSError(msg)
|
| |
|
| | x = x + xtile
|
| | if x >= xsize:
|
| | x, y = 0, y + ytile
|
| | if y >= ysize:
|
| | break
|
| |
|
| | self.stream = stream
|
| | self._fp = self.fp
|
| | self.fp = None
|
| |
|
| | def load(self) -> Image.core.PixelAccess | None:
|
| | if not self.fp:
|
| | self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"])
|
| |
|
| | return ImageFile.ImageFile.load(self)
|
| |
|
| | def close(self) -> None:
|
| | self.ole.close()
|
| | super().close()
|
| |
|
| | def __exit__(self, *args: object) -> None:
|
| | self.ole.close()
|
| | super().__exit__()
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | Image.register_open(FpxImageFile.format, FpxImageFile, _accept)
|
| |
|
| | Image.register_extension(FpxImageFile.format, ".fpx")
|
| |
|