EpsImagePlugin.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # EPS file handling
  6. #
  7. # History:
  8. # 1995-09-01 fl Created (0.1)
  9. # 1996-05-18 fl Don't choke on "atend" fields, Ghostscript interface (0.2)
  10. # 1996-08-22 fl Don't choke on floating point BoundingBox values
  11. # 1996-08-23 fl Handle files from Macintosh (0.3)
  12. # 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.4)
  13. # 2003-09-07 fl Check gs.close status (from Federico Di Gregorio) (0.5)
  14. # 2014-05-07 e Handling of EPS with binary preview and fixed resolution
  15. # resizing
  16. #
  17. # Copyright (c) 1997-2003 by Secret Labs AB.
  18. # Copyright (c) 1995-2003 by Fredrik Lundh
  19. #
  20. # See the README file for information on usage and redistribution.
  21. #
  22. import io
  23. import os
  24. import re
  25. import sys
  26. from . import Image, ImageFile
  27. from ._binary import i32le as i32
  28. # __version__ is deprecated and will be removed in a future version. Use
  29. # PIL.__version__ instead.
  30. __version__ = "0.5"
  31. #
  32. # --------------------------------------------------------------------
  33. split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
  34. field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
  35. gs_windows_binary = None
  36. if sys.platform.startswith("win"):
  37. import shutil
  38. if hasattr(shutil, "which"):
  39. which = shutil.which
  40. else:
  41. # Python 2
  42. import distutils.spawn
  43. which = distutils.spawn.find_executable
  44. for binary in ("gswin32c", "gswin64c", "gs"):
  45. if which(binary) is not None:
  46. gs_windows_binary = binary
  47. break
  48. else:
  49. gs_windows_binary = False
  50. def has_ghostscript():
  51. if gs_windows_binary:
  52. return True
  53. if not sys.platform.startswith("win"):
  54. import subprocess
  55. try:
  56. with open(os.devnull, "wb") as devnull:
  57. subprocess.check_call(["gs", "--version"], stdout=devnull)
  58. return True
  59. except OSError:
  60. # No Ghostscript
  61. pass
  62. return False
  63. def Ghostscript(tile, size, fp, scale=1):
  64. """Render an image using Ghostscript"""
  65. # Unpack decoder tile
  66. decoder, tile, offset, data = tile[0]
  67. length, bbox = data
  68. # Hack to support hi-res rendering
  69. scale = int(scale) or 1
  70. # orig_size = size
  71. # orig_bbox = bbox
  72. size = (size[0] * scale, size[1] * scale)
  73. # resolution is dependent on bbox and size
  74. res = (
  75. float((72.0 * size[0]) / (bbox[2] - bbox[0])),
  76. float((72.0 * size[1]) / (bbox[3] - bbox[1])),
  77. )
  78. import subprocess
  79. import tempfile
  80. out_fd, outfile = tempfile.mkstemp()
  81. os.close(out_fd)
  82. infile_temp = None
  83. if hasattr(fp, "name") and os.path.exists(fp.name):
  84. infile = fp.name
  85. else:
  86. in_fd, infile_temp = tempfile.mkstemp()
  87. os.close(in_fd)
  88. infile = infile_temp
  89. # Ignore length and offset!
  90. # Ghostscript can read it
  91. # Copy whole file to read in Ghostscript
  92. with open(infile_temp, "wb") as f:
  93. # fetch length of fp
  94. fp.seek(0, io.SEEK_END)
  95. fsize = fp.tell()
  96. # ensure start position
  97. # go back
  98. fp.seek(0)
  99. lengthfile = fsize
  100. while lengthfile > 0:
  101. s = fp.read(min(lengthfile, 100 * 1024))
  102. if not s:
  103. break
  104. lengthfile -= len(s)
  105. f.write(s)
  106. # Build Ghostscript command
  107. command = [
  108. "gs",
  109. "-q", # quiet mode
  110. "-g%dx%d" % size, # set output geometry (pixels)
  111. "-r%fx%f" % res, # set input DPI (dots per inch)
  112. "-dBATCH", # exit after processing
  113. "-dNOPAUSE", # don't pause between pages
  114. "-dSAFER", # safe mode
  115. "-sDEVICE=ppmraw", # ppm driver
  116. "-sOutputFile=%s" % outfile, # output file
  117. # adjust for image origin
  118. "-c",
  119. "%d %d translate" % (-bbox[0], -bbox[1]),
  120. "-f",
  121. infile, # input file
  122. # showpage (see https://bugs.ghostscript.com/show_bug.cgi?id=698272)
  123. "-c",
  124. "showpage",
  125. ]
  126. if gs_windows_binary is not None:
  127. if not gs_windows_binary:
  128. raise WindowsError("Unable to locate Ghostscript on paths")
  129. command[0] = gs_windows_binary
  130. # push data through Ghostscript
  131. try:
  132. startupinfo = None
  133. if sys.platform.startswith("win"):
  134. startupinfo = subprocess.STARTUPINFO()
  135. startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  136. subprocess.check_call(command, startupinfo=startupinfo)
  137. im = Image.open(outfile)
  138. im.load()
  139. finally:
  140. try:
  141. os.unlink(outfile)
  142. if infile_temp:
  143. os.unlink(infile_temp)
  144. except OSError:
  145. pass
  146. return im.im.copy()
  147. class PSFile(object):
  148. """
  149. Wrapper for bytesio object that treats either CR or LF as end of line.
  150. """
  151. def __init__(self, fp):
  152. self.fp = fp
  153. self.char = None
  154. def seek(self, offset, whence=io.SEEK_SET):
  155. self.char = None
  156. self.fp.seek(offset, whence)
  157. def readline(self):
  158. s = self.char or b""
  159. self.char = None
  160. c = self.fp.read(1)
  161. while c not in b"\r\n":
  162. s = s + c
  163. c = self.fp.read(1)
  164. self.char = self.fp.read(1)
  165. # line endings can be 1 or 2 of \r \n, in either order
  166. if self.char in b"\r\n":
  167. self.char = None
  168. return s.decode("latin-1")
  169. def _accept(prefix):
  170. return prefix[:4] == b"%!PS" or (len(prefix) >= 4 and i32(prefix) == 0xC6D3D0C5)
  171. ##
  172. # Image plugin for Encapsulated Postscript. This plugin supports only
  173. # a few variants of this format.
  174. class EpsImageFile(ImageFile.ImageFile):
  175. """EPS File Parser for the Python Imaging Library"""
  176. format = "EPS"
  177. format_description = "Encapsulated Postscript"
  178. mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"}
  179. def _open(self):
  180. (length, offset) = self._find_offset(self.fp)
  181. # Rewrap the open file pointer in something that will
  182. # convert line endings and decode to latin-1.
  183. fp = PSFile(self.fp)
  184. # go to offset - start of "%!PS"
  185. fp.seek(offset)
  186. box = None
  187. self.mode = "RGB"
  188. self._size = 1, 1 # FIXME: huh?
  189. #
  190. # Load EPS header
  191. s_raw = fp.readline()
  192. s = s_raw.strip("\r\n")
  193. while s_raw:
  194. if s:
  195. if len(s) > 255:
  196. raise SyntaxError("not an EPS file")
  197. try:
  198. m = split.match(s)
  199. except re.error:
  200. raise SyntaxError("not an EPS file")
  201. if m:
  202. k, v = m.group(1, 2)
  203. self.info[k] = v
  204. if k == "BoundingBox":
  205. try:
  206. # Note: The DSC spec says that BoundingBox
  207. # fields should be integers, but some drivers
  208. # put floating point values there anyway.
  209. box = [int(float(i)) for i in v.split()]
  210. self._size = box[2] - box[0], box[3] - box[1]
  211. self.tile = [
  212. ("eps", (0, 0) + self.size, offset, (length, box))
  213. ]
  214. except Exception:
  215. pass
  216. else:
  217. m = field.match(s)
  218. if m:
  219. k = m.group(1)
  220. if k == "EndComments":
  221. break
  222. if k[:8] == "PS-Adobe":
  223. self.info[k[:8]] = k[9:]
  224. else:
  225. self.info[k] = ""
  226. elif s[0] == "%":
  227. # handle non-DSC Postscript comments that some
  228. # tools mistakenly put in the Comments section
  229. pass
  230. else:
  231. raise IOError("bad EPS header")
  232. s_raw = fp.readline()
  233. s = s_raw.strip("\r\n")
  234. if s and s[:1] != "%":
  235. break
  236. #
  237. # Scan for an "ImageData" descriptor
  238. while s[:1] == "%":
  239. if len(s) > 255:
  240. raise SyntaxError("not an EPS file")
  241. if s[:11] == "%ImageData:":
  242. # Encoded bitmapped image.
  243. x, y, bi, mo = s[11:].split(None, 7)[:4]
  244. if int(bi) != 8:
  245. break
  246. try:
  247. self.mode = self.mode_map[int(mo)]
  248. except ValueError:
  249. break
  250. self._size = int(x), int(y)
  251. return
  252. s = fp.readline().strip("\r\n")
  253. if not s:
  254. break
  255. if not box:
  256. raise IOError("cannot determine EPS bounding box")
  257. def _find_offset(self, fp):
  258. s = fp.read(160)
  259. if s[:4] == b"%!PS":
  260. # for HEAD without binary preview
  261. fp.seek(0, io.SEEK_END)
  262. length = fp.tell()
  263. offset = 0
  264. elif i32(s[0:4]) == 0xC6D3D0C5:
  265. # FIX for: Some EPS file not handled correctly / issue #302
  266. # EPS can contain binary data
  267. # or start directly with latin coding
  268. # more info see:
  269. # https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
  270. offset = i32(s[4:8])
  271. length = i32(s[8:12])
  272. else:
  273. raise SyntaxError("not an EPS file")
  274. return (length, offset)
  275. def load(self, scale=1):
  276. # Load EPS via Ghostscript
  277. if not self.tile:
  278. return
  279. self.im = Ghostscript(self.tile, self.size, self.fp, scale)
  280. self.mode = self.im.mode
  281. self._size = self.im.size
  282. self.tile = []
  283. def load_seek(self, *args, **kwargs):
  284. # we can't incrementally load, so force ImageFile.parser to
  285. # use our custom load method by defining this method.
  286. pass
  287. #
  288. # --------------------------------------------------------------------
  289. def _save(im, fp, filename, eps=1):
  290. """EPS Writer for the Python Imaging Library."""
  291. #
  292. # make sure image data is available
  293. im.load()
  294. #
  295. # determine postscript image mode
  296. if im.mode == "L":
  297. operator = (8, 1, "image")
  298. elif im.mode == "RGB":
  299. operator = (8, 3, "false 3 colorimage")
  300. elif im.mode == "CMYK":
  301. operator = (8, 4, "false 4 colorimage")
  302. else:
  303. raise ValueError("image mode is not supported")
  304. base_fp = fp
  305. wrapped_fp = False
  306. if fp != sys.stdout:
  307. if sys.version_info.major > 2:
  308. fp = io.TextIOWrapper(fp, encoding="latin-1")
  309. wrapped_fp = True
  310. try:
  311. if eps:
  312. #
  313. # write EPS header
  314. fp.write("%!PS-Adobe-3.0 EPSF-3.0\n")
  315. fp.write("%%Creator: PIL 0.1 EpsEncode\n")
  316. # fp.write("%%CreationDate: %s"...)
  317. fp.write("%%%%BoundingBox: 0 0 %d %d\n" % im.size)
  318. fp.write("%%Pages: 1\n")
  319. fp.write("%%EndComments\n")
  320. fp.write("%%Page: 1 1\n")
  321. fp.write("%%ImageData: %d %d " % im.size)
  322. fp.write('%d %d 0 1 1 "%s"\n' % operator)
  323. #
  324. # image header
  325. fp.write("gsave\n")
  326. fp.write("10 dict begin\n")
  327. fp.write("/buf %d string def\n" % (im.size[0] * operator[1]))
  328. fp.write("%d %d scale\n" % im.size)
  329. fp.write("%d %d 8\n" % im.size) # <= bits
  330. fp.write("[%d 0 0 -%d 0 %d]\n" % (im.size[0], im.size[1], im.size[1]))
  331. fp.write("{ currentfile buf readhexstring pop } bind\n")
  332. fp.write(operator[2] + "\n")
  333. if hasattr(fp, "flush"):
  334. fp.flush()
  335. ImageFile._save(im, base_fp, [("eps", (0, 0) + im.size, 0, None)])
  336. fp.write("\n%%%%EndBinary\n")
  337. fp.write("grestore end\n")
  338. if hasattr(fp, "flush"):
  339. fp.flush()
  340. finally:
  341. if wrapped_fp:
  342. fp.detach()
  343. #
  344. # --------------------------------------------------------------------
  345. Image.register_open(EpsImageFile.format, EpsImageFile, _accept)
  346. Image.register_save(EpsImageFile.format, _save)
  347. Image.register_extensions(EpsImageFile.format, [".ps", ".eps"])
  348. Image.register_mime(EpsImageFile.format, "application/postscript")