| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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: |
| | |
| | |
| | |
| |
|
| | assert self.fp is not 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 |
| |
|
| | assert self.fp is not None |
| | 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") |
| |
|