ImImagePlugin.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # IFUNC IM file handling for PIL
  6. #
  7. # history:
  8. # 1995-09-01 fl Created.
  9. # 1997-01-03 fl Save palette images
  10. # 1997-01-08 fl Added sequence support
  11. # 1997-01-23 fl Added P and RGB save support
  12. # 1997-05-31 fl Read floating point images
  13. # 1997-06-22 fl Save floating point images
  14. # 1997-08-27 fl Read and save 1-bit images
  15. # 1998-06-25 fl Added support for RGB+LUT images
  16. # 1998-07-02 fl Added support for YCC images
  17. # 1998-07-15 fl Renamed offset attribute to avoid name clash
  18. # 1998-12-29 fl Added I;16 support
  19. # 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
  20. # 2003-09-26 fl Added LA/PA support
  21. #
  22. # Copyright (c) 1997-2003 by Secret Labs AB.
  23. # Copyright (c) 1995-2001 by Fredrik Lundh.
  24. #
  25. # See the README file for information on usage and redistribution.
  26. #
  27. from __future__ import annotations
  28. import os
  29. import re
  30. from typing import IO, Any
  31. from . import Image, ImageFile, ImagePalette
  32. from ._util import DeferredError
  33. # --------------------------------------------------------------------
  34. # Standard tags
  35. COMMENT = "Comment"
  36. DATE = "Date"
  37. EQUIPMENT = "Digitalization equipment"
  38. FRAMES = "File size (no of images)"
  39. LUT = "Lut"
  40. NAME = "Name"
  41. SCALE = "Scale (x,y)"
  42. SIZE = "Image size (x*y)"
  43. MODE = "Image type"
  44. TAGS = {
  45. COMMENT: 0,
  46. DATE: 0,
  47. EQUIPMENT: 0,
  48. FRAMES: 0,
  49. LUT: 0,
  50. NAME: 0,
  51. SCALE: 0,
  52. SIZE: 0,
  53. MODE: 0,
  54. }
  55. OPEN = {
  56. # ifunc93/p3cfunc formats
  57. "0 1 image": ("1", "1"),
  58. "L 1 image": ("1", "1"),
  59. "Greyscale image": ("L", "L"),
  60. "Grayscale image": ("L", "L"),
  61. "RGB image": ("RGB", "RGB;L"),
  62. "RLB image": ("RGB", "RLB"),
  63. "RYB image": ("RGB", "RLB"),
  64. "B1 image": ("1", "1"),
  65. "B2 image": ("P", "P;2"),
  66. "B4 image": ("P", "P;4"),
  67. "X 24 image": ("RGB", "RGB"),
  68. "L 32 S image": ("I", "I;32"),
  69. "L 32 F image": ("F", "F;32"),
  70. # old p3cfunc formats
  71. "RGB3 image": ("RGB", "RGB;T"),
  72. "RYB3 image": ("RGB", "RYB;T"),
  73. # extensions
  74. "LA image": ("LA", "LA;L"),
  75. "PA image": ("LA", "PA;L"),
  76. "RGBA image": ("RGBA", "RGBA;L"),
  77. "RGBX image": ("RGB", "RGBX;L"),
  78. "CMYK image": ("CMYK", "CMYK;L"),
  79. "YCC image": ("YCbCr", "YCbCr;L"),
  80. }
  81. # ifunc95 extensions
  82. for i in ["8", "8S", "16", "16S", "32", "32F"]:
  83. OPEN[f"L {i} image"] = ("F", f"F;{i}")
  84. OPEN[f"L*{i} image"] = ("F", f"F;{i}")
  85. for i in ["16", "16L", "16B"]:
  86. OPEN[f"L {i} image"] = (f"I;{i}", f"I;{i}")
  87. OPEN[f"L*{i} image"] = (f"I;{i}", f"I;{i}")
  88. for i in ["32S"]:
  89. OPEN[f"L {i} image"] = ("I", f"I;{i}")
  90. OPEN[f"L*{i} image"] = ("I", f"I;{i}")
  91. for j in range(2, 33):
  92. OPEN[f"L*{j} image"] = ("F", f"F;{j}")
  93. # --------------------------------------------------------------------
  94. # Read IM directory
  95. split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
  96. def number(s: Any) -> float:
  97. try:
  98. return int(s)
  99. except ValueError:
  100. return float(s)
  101. ##
  102. # Image plugin for the IFUNC IM file format.
  103. class ImImageFile(ImageFile.ImageFile):
  104. format = "IM"
  105. format_description = "IFUNC Image Memory"
  106. _close_exclusive_fp_after_loading = False
  107. def _open(self) -> None:
  108. # Quick rejection: if there's not an LF among the first
  109. # 100 bytes, this is (probably) not a text header.
  110. assert self.fp is not None
  111. if b"\n" not in self.fp.read(100):
  112. msg = "not an IM file"
  113. raise SyntaxError(msg)
  114. self.fp.seek(0)
  115. n = 0
  116. # Default values
  117. self.info[MODE] = "L"
  118. self.info[SIZE] = (512, 512)
  119. self.info[FRAMES] = 1
  120. self.rawmode = "L"
  121. while True:
  122. s = self.fp.read(1)
  123. # Some versions of IFUNC uses \n\r instead of \r\n...
  124. if s == b"\r":
  125. continue
  126. if not s or s == b"\0" or s == b"\x1a":
  127. break
  128. # FIXME: this may read whole file if not a text file
  129. s = s + self.fp.readline()
  130. if len(s) > 100:
  131. msg = "not an IM file"
  132. raise SyntaxError(msg)
  133. if s.endswith(b"\r\n"):
  134. s = s[:-2]
  135. elif s.endswith(b"\n"):
  136. s = s[:-1]
  137. try:
  138. m = split.match(s)
  139. except re.error as e:
  140. msg = "not an IM file"
  141. raise SyntaxError(msg) from e
  142. if m:
  143. k, v = m.group(1, 2)
  144. # Don't know if this is the correct encoding,
  145. # but a decent guess (I guess)
  146. k = k.decode("latin-1", "replace")
  147. v = v.decode("latin-1", "replace")
  148. # Convert value as appropriate
  149. if k in [FRAMES, SCALE, SIZE]:
  150. v = v.replace("*", ",")
  151. v = tuple(map(number, v.split(",")))
  152. if len(v) == 1:
  153. v = v[0]
  154. elif k == MODE and v in OPEN:
  155. v, self.rawmode = OPEN[v]
  156. # Add to dictionary. Note that COMMENT tags are
  157. # combined into a list of strings.
  158. if k == COMMENT:
  159. if k in self.info:
  160. self.info[k].append(v)
  161. else:
  162. self.info[k] = [v]
  163. else:
  164. self.info[k] = v
  165. if k in TAGS:
  166. n += 1
  167. else:
  168. msg = f"Syntax error in IM header: {s.decode('ascii', 'replace')}"
  169. raise SyntaxError(msg)
  170. if not n:
  171. msg = "Not an IM file"
  172. raise SyntaxError(msg)
  173. # Basic attributes
  174. self._size = self.info[SIZE]
  175. self._mode = self.info[MODE]
  176. # Skip forward to start of image data
  177. while s and not s.startswith(b"\x1a"):
  178. s = self.fp.read(1)
  179. if not s:
  180. msg = "File truncated"
  181. raise SyntaxError(msg)
  182. if LUT in self.info:
  183. # convert lookup table to palette or lut attribute
  184. palette = self.fp.read(768)
  185. greyscale = 1 # greyscale palette
  186. linear = 1 # linear greyscale palette
  187. for i in range(256):
  188. if palette[i] == palette[i + 256] == palette[i + 512]:
  189. if palette[i] != i:
  190. linear = 0
  191. else:
  192. greyscale = 0
  193. if self.mode in ["L", "LA", "P", "PA"]:
  194. if greyscale:
  195. if not linear:
  196. self.lut = list(palette[:256])
  197. else:
  198. if self.mode in ["L", "P"]:
  199. self._mode = self.rawmode = "P"
  200. elif self.mode in ["LA", "PA"]:
  201. self._mode = "PA"
  202. self.rawmode = "PA;L"
  203. self.palette = ImagePalette.raw("RGB;L", palette)
  204. elif self.mode == "RGB":
  205. if not greyscale or not linear:
  206. self.lut = list(palette)
  207. self.frame = 0
  208. self.__offset = offs = self.fp.tell()
  209. self._fp = self.fp # FIXME: hack
  210. if self.rawmode.startswith("F;"):
  211. # ifunc95 formats
  212. try:
  213. # use bit decoder (if necessary)
  214. bits = int(self.rawmode[2:])
  215. if bits not in [8, 16, 32]:
  216. self.tile = [
  217. ImageFile._Tile(
  218. "bit", (0, 0) + self.size, offs, (bits, 8, 3, 0, -1)
  219. )
  220. ]
  221. return
  222. except ValueError:
  223. pass
  224. if self.rawmode in ["RGB;T", "RYB;T"]:
  225. # Old LabEye/3PC files. Would be very surprised if anyone
  226. # ever stumbled upon such a file ;-)
  227. size = self.size[0] * self.size[1]
  228. self.tile = [
  229. ImageFile._Tile("raw", (0, 0) + self.size, offs, ("G", 0, -1)),
  230. ImageFile._Tile("raw", (0, 0) + self.size, offs + size, ("R", 0, -1)),
  231. ImageFile._Tile(
  232. "raw", (0, 0) + self.size, offs + 2 * size, ("B", 0, -1)
  233. ),
  234. ]
  235. else:
  236. # LabEye/IFUNC files
  237. self.tile = [
  238. ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))
  239. ]
  240. @property
  241. def n_frames(self) -> int:
  242. return self.info[FRAMES]
  243. @property
  244. def is_animated(self) -> bool:
  245. return self.info[FRAMES] > 1
  246. def seek(self, frame: int) -> None:
  247. if not self._seek_check(frame):
  248. return
  249. if isinstance(self._fp, DeferredError):
  250. raise self._fp.ex
  251. self.frame = frame
  252. if self.mode == "1":
  253. bits = 1
  254. else:
  255. bits = 8 * len(self.mode)
  256. size = ((self.size[0] * bits + 7) // 8) * self.size[1]
  257. offs = self.__offset + frame * size
  258. self.fp = self._fp
  259. self.tile = [
  260. ImageFile._Tile("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))
  261. ]
  262. def tell(self) -> int:
  263. return self.frame
  264. #
  265. # --------------------------------------------------------------------
  266. # Save IM files
  267. SAVE = {
  268. # mode: (im type, raw mode)
  269. "1": ("0 1", "1"),
  270. "L": ("Greyscale", "L"),
  271. "LA": ("LA", "LA;L"),
  272. "P": ("Greyscale", "P"),
  273. "PA": ("LA", "PA;L"),
  274. "I": ("L 32S", "I;32S"),
  275. "I;16": ("L 16", "I;16"),
  276. "I;16L": ("L 16L", "I;16L"),
  277. "I;16B": ("L 16B", "I;16B"),
  278. "F": ("L 32F", "F;32F"),
  279. "RGB": ("RGB", "RGB;L"),
  280. "RGBA": ("RGBA", "RGBA;L"),
  281. "RGBX": ("RGBX", "RGBX;L"),
  282. "CMYK": ("CMYK", "CMYK;L"),
  283. "YCbCr": ("YCC", "YCbCr;L"),
  284. }
  285. def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
  286. try:
  287. image_type, rawmode = SAVE[im.mode]
  288. except KeyError as e:
  289. msg = f"Cannot save {im.mode} images as IM"
  290. raise ValueError(msg) from e
  291. frames = im.encoderinfo.get("frames", 1)
  292. fp.write(f"Image type: {image_type} image\r\n".encode("ascii"))
  293. if filename:
  294. # Each line must be 100 characters or less,
  295. # or: SyntaxError("not an IM file")
  296. # 8 characters are used for "Name: " and "\r\n"
  297. # Keep just the filename, ditch the potentially overlong path
  298. if isinstance(filename, bytes):
  299. filename = filename.decode("ascii")
  300. name, ext = os.path.splitext(os.path.basename(filename))
  301. name = "".join([name[: 92 - len(ext)], ext])
  302. fp.write(f"Name: {name}\r\n".encode("ascii"))
  303. fp.write(f"Image size (x*y): {im.size[0]}*{im.size[1]}\r\n".encode("ascii"))
  304. fp.write(f"File size (no of images): {frames}\r\n".encode("ascii"))
  305. if im.mode in ["P", "PA"]:
  306. fp.write(b"Lut: 1\r\n")
  307. fp.write(b"\000" * (511 - fp.tell()) + b"\032")
  308. if im.mode in ["P", "PA"]:
  309. im_palette = im.im.getpalette("RGB", "RGB;L")
  310. colors = len(im_palette) // 3
  311. palette = b""
  312. for i in range(3):
  313. palette += im_palette[colors * i : colors * (i + 1)]
  314. palette += b"\x00" * (256 - colors)
  315. fp.write(palette) # 768 bytes
  316. ImageFile._save(
  317. im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, -1))]
  318. )
  319. #
  320. # --------------------------------------------------------------------
  321. # Registry
  322. Image.register_open(ImImageFile.format, ImImageFile)
  323. Image.register_save(ImImageFile.format, _save)
  324. Image.register_extension(ImImageFile.format, ".im")