You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

347 lines
9.9 KiB

4 years ago
  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. import re
  28. from . import Image, ImageFile, ImagePalette
  29. from ._binary import i8
  30. __version__ = "0.7"
  31. # --------------------------------------------------------------------
  32. # Standard tags
  33. COMMENT = "Comment"
  34. DATE = "Date"
  35. EQUIPMENT = "Digitalization equipment"
  36. FRAMES = "File size (no of images)"
  37. LUT = "Lut"
  38. NAME = "Name"
  39. SCALE = "Scale (x,y)"
  40. SIZE = "Image size (x*y)"
  41. MODE = "Image type"
  42. TAGS = {COMMENT: 0, DATE: 0, EQUIPMENT: 0, FRAMES: 0, LUT: 0, NAME: 0,
  43. SCALE: 0, SIZE: 0, MODE: 0}
  44. OPEN = {
  45. # ifunc93/p3cfunc formats
  46. "0 1 image": ("1", "1"),
  47. "L 1 image": ("1", "1"),
  48. "Greyscale image": ("L", "L"),
  49. "Grayscale image": ("L", "L"),
  50. "RGB image": ("RGB", "RGB;L"),
  51. "RLB image": ("RGB", "RLB"),
  52. "RYB image": ("RGB", "RLB"),
  53. "B1 image": ("1", "1"),
  54. "B2 image": ("P", "P;2"),
  55. "B4 image": ("P", "P;4"),
  56. "X 24 image": ("RGB", "RGB"),
  57. "L 32 S image": ("I", "I;32"),
  58. "L 32 F image": ("F", "F;32"),
  59. # old p3cfunc formats
  60. "RGB3 image": ("RGB", "RGB;T"),
  61. "RYB3 image": ("RGB", "RYB;T"),
  62. # extensions
  63. "LA image": ("LA", "LA;L"),
  64. "RGBA image": ("RGBA", "RGBA;L"),
  65. "RGBX image": ("RGBX", "RGBX;L"),
  66. "CMYK image": ("CMYK", "CMYK;L"),
  67. "YCC image": ("YCbCr", "YCbCr;L"),
  68. }
  69. # ifunc95 extensions
  70. for i in ["8", "8S", "16", "16S", "32", "32F"]:
  71. OPEN["L %s image" % i] = ("F", "F;%s" % i)
  72. OPEN["L*%s image" % i] = ("F", "F;%s" % i)
  73. for i in ["16", "16L", "16B"]:
  74. OPEN["L %s image" % i] = ("I;%s" % i, "I;%s" % i)
  75. OPEN["L*%s image" % i] = ("I;%s" % i, "I;%s" % i)
  76. for i in ["32S"]:
  77. OPEN["L %s image" % i] = ("I", "I;%s" % i)
  78. OPEN["L*%s image" % i] = ("I", "I;%s" % i)
  79. for i in range(2, 33):
  80. OPEN["L*%s image" % i] = ("F", "F;%s" % i)
  81. # --------------------------------------------------------------------
  82. # Read IM directory
  83. split = re.compile(br"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$")
  84. def number(s):
  85. try:
  86. return int(s)
  87. except ValueError:
  88. return float(s)
  89. ##
  90. # Image plugin for the IFUNC IM file format.
  91. class ImImageFile(ImageFile.ImageFile):
  92. format = "IM"
  93. format_description = "IFUNC Image Memory"
  94. _close_exclusive_fp_after_loading = False
  95. def _open(self):
  96. # Quick rejection: if there's not an LF among the first
  97. # 100 bytes, this is (probably) not a text header.
  98. if b"\n" not in self.fp.read(100):
  99. raise SyntaxError("not an IM file")
  100. self.fp.seek(0)
  101. n = 0
  102. # Default values
  103. self.info[MODE] = "L"
  104. self.info[SIZE] = (512, 512)
  105. self.info[FRAMES] = 1
  106. self.rawmode = "L"
  107. while True:
  108. s = self.fp.read(1)
  109. # Some versions of IFUNC uses \n\r instead of \r\n...
  110. if s == b"\r":
  111. continue
  112. if not s or s == b'\0' or s == b'\x1A':
  113. break
  114. # FIXME: this may read whole file if not a text file
  115. s = s + self.fp.readline()
  116. if len(s) > 100:
  117. raise SyntaxError("not an IM file")
  118. if s[-2:] == b'\r\n':
  119. s = s[:-2]
  120. elif s[-1:] == b'\n':
  121. s = s[:-1]
  122. try:
  123. m = split.match(s)
  124. except re.error as v:
  125. raise SyntaxError("not an IM file")
  126. if m:
  127. k, v = m.group(1, 2)
  128. # Don't know if this is the correct encoding,
  129. # but a decent guess (I guess)
  130. k = k.decode('latin-1', 'replace')
  131. v = v.decode('latin-1', 'replace')
  132. # Convert value as appropriate
  133. if k in [FRAMES, SCALE, SIZE]:
  134. v = v.replace("*", ",")
  135. v = tuple(map(number, v.split(",")))
  136. if len(v) == 1:
  137. v = v[0]
  138. elif k == MODE and v in OPEN:
  139. v, self.rawmode = OPEN[v]
  140. # Add to dictionary. Note that COMMENT tags are
  141. # combined into a list of strings.
  142. if k == COMMENT:
  143. if k in self.info:
  144. self.info[k].append(v)
  145. else:
  146. self.info[k] = [v]
  147. else:
  148. self.info[k] = v
  149. if k in TAGS:
  150. n += 1
  151. else:
  152. raise SyntaxError("Syntax error in IM header: " +
  153. s.decode('ascii', 'replace'))
  154. if not n:
  155. raise SyntaxError("Not an IM file")
  156. # Basic attributes
  157. self._size = self.info[SIZE]
  158. self.mode = self.info[MODE]
  159. # Skip forward to start of image data
  160. while s and s[0:1] != b'\x1A':
  161. s = self.fp.read(1)
  162. if not s:
  163. raise SyntaxError("File truncated")
  164. if LUT in self.info:
  165. # convert lookup table to palette or lut attribute
  166. palette = self.fp.read(768)
  167. greyscale = 1 # greyscale palette
  168. linear = 1 # linear greyscale palette
  169. for i in range(256):
  170. if palette[i] == palette[i+256] == palette[i+512]:
  171. if i8(palette[i]) != i:
  172. linear = 0
  173. else:
  174. greyscale = 0
  175. if self.mode == "L" or self.mode == "LA":
  176. if greyscale:
  177. if not linear:
  178. self.lut = [i8(c) for c in palette[:256]]
  179. else:
  180. if self.mode == "L":
  181. self.mode = self.rawmode = "P"
  182. elif self.mode == "LA":
  183. self.mode = self.rawmode = "PA"
  184. self.palette = ImagePalette.raw("RGB;L", palette)
  185. elif self.mode == "RGB":
  186. if not greyscale or not linear:
  187. self.lut = [i8(c) for c in palette]
  188. self.frame = 0
  189. self.__offset = offs = self.fp.tell()
  190. self.__fp = self.fp # FIXME: hack
  191. if self.rawmode[:2] == "F;":
  192. # ifunc95 formats
  193. try:
  194. # use bit decoder (if necessary)
  195. bits = int(self.rawmode[2:])
  196. if bits not in [8, 16, 32]:
  197. self.tile = [("bit", (0, 0)+self.size, offs,
  198. (bits, 8, 3, 0, -1))]
  199. return
  200. except ValueError:
  201. pass
  202. if self.rawmode in ["RGB;T", "RYB;T"]:
  203. # Old LabEye/3PC files. Would be very surprised if anyone
  204. # ever stumbled upon such a file ;-)
  205. size = self.size[0] * self.size[1]
  206. self.tile = [("raw", (0, 0)+self.size, offs, ("G", 0, -1)),
  207. ("raw", (0, 0)+self.size, offs+size, ("R", 0, -1)),
  208. ("raw", (0, 0)+self.size, offs+2*size, ("B", 0, -1))]
  209. else:
  210. # LabEye/IFUNC files
  211. self.tile = [("raw", (0, 0)+self.size, offs,
  212. (self.rawmode, 0, -1))]
  213. @property
  214. def n_frames(self):
  215. return self.info[FRAMES]
  216. @property
  217. def is_animated(self):
  218. return self.info[FRAMES] > 1
  219. def seek(self, frame):
  220. if not self._seek_check(frame):
  221. return
  222. self.frame = frame
  223. if self.mode == "1":
  224. bits = 1
  225. else:
  226. bits = 8 * len(self.mode)
  227. size = ((self.size[0] * bits + 7) // 8) * self.size[1]
  228. offs = self.__offset + frame * size
  229. self.fp = self.__fp
  230. self.tile = [("raw", (0, 0)+self.size, offs, (self.rawmode, 0, -1))]
  231. def tell(self):
  232. return self.frame
  233. #
  234. # --------------------------------------------------------------------
  235. # Save IM files
  236. SAVE = {
  237. # mode: (im type, raw mode)
  238. "1": ("0 1", "1"),
  239. "L": ("Greyscale", "L"),
  240. "LA": ("LA", "LA;L"),
  241. "P": ("Greyscale", "P"),
  242. "PA": ("LA", "PA;L"),
  243. "I": ("L 32S", "I;32S"),
  244. "I;16": ("L 16", "I;16"),
  245. "I;16L": ("L 16L", "I;16L"),
  246. "I;16B": ("L 16B", "I;16B"),
  247. "F": ("L 32F", "F;32F"),
  248. "RGB": ("RGB", "RGB;L"),
  249. "RGBA": ("RGBA", "RGBA;L"),
  250. "RGBX": ("RGBX", "RGBX;L"),
  251. "CMYK": ("CMYK", "CMYK;L"),
  252. "YCbCr": ("YCC", "YCbCr;L")
  253. }
  254. def _save(im, fp, filename):
  255. try:
  256. image_type, rawmode = SAVE[im.mode]
  257. except KeyError:
  258. raise ValueError("Cannot save %s images as IM" % im.mode)
  259. frames = im.encoderinfo.get("frames", 1)
  260. fp.write(("Image type: %s image\r\n" % image_type).encode('ascii'))
  261. if filename:
  262. fp.write(("Name: %s\r\n" % filename).encode('ascii'))
  263. fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode('ascii'))
  264. fp.write(("File size (no of images): %d\r\n" % frames).encode('ascii'))
  265. if im.mode == "P":
  266. fp.write(b"Lut: 1\r\n")
  267. fp.write(b"\000" * (511-fp.tell()) + b"\032")
  268. if im.mode == "P":
  269. fp.write(im.im.getpalette("RGB", "RGB;L")) # 768 bytes
  270. ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, -1))])
  271. #
  272. # --------------------------------------------------------------------
  273. # Registry
  274. Image.register_open(ImImageFile.format, ImImageFile)
  275. Image.register_save(ImImageFile.format, _save)
  276. Image.register_extension(ImImageFile.format, ".im")