PdfImagePlugin.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # PDF (Acrobat) file handling
  6. #
  7. # History:
  8. # 1996-07-16 fl Created
  9. # 1997-01-18 fl Fixed header
  10. # 2004-02-21 fl Fixes for 1/L/CMYK images, etc.
  11. # 2004-02-24 fl Fixes for 1 and P images.
  12. #
  13. # Copyright (c) 1997-2004 by Secret Labs AB. All rights reserved.
  14. # Copyright (c) 1996-1997 by Fredrik Lundh.
  15. #
  16. # See the README file for information on usage and redistribution.
  17. #
  18. ##
  19. # Image plugin for PDF images (output only).
  20. ##
  21. import io
  22. import os
  23. import time
  24. from . import Image, ImageFile, ImageSequence, PdfParser, __version__
  25. #
  26. # --------------------------------------------------------------------
  27. # object ids:
  28. # 1. catalogue
  29. # 2. pages
  30. # 3. image
  31. # 4. page
  32. # 5. page contents
  33. def _save_all(im, fp, filename):
  34. _save(im, fp, filename, save_all=True)
  35. ##
  36. # (Internal) Image save plugin for the PDF format.
  37. def _save(im, fp, filename, save_all=False):
  38. is_appending = im.encoderinfo.get("append", False)
  39. if is_appending:
  40. existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="r+b")
  41. else:
  42. existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b")
  43. resolution = im.encoderinfo.get("resolution", 72.0)
  44. info = {
  45. "title": None
  46. if is_appending
  47. else os.path.splitext(os.path.basename(filename))[0],
  48. "author": None,
  49. "subject": None,
  50. "keywords": None,
  51. "creator": None,
  52. "producer": None,
  53. "creationDate": None if is_appending else time.gmtime(),
  54. "modDate": None if is_appending else time.gmtime(),
  55. }
  56. for k, default in info.items():
  57. v = im.encoderinfo.get(k) if k in im.encoderinfo else default
  58. if v:
  59. existing_pdf.info[k[0].upper() + k[1:]] = v
  60. #
  61. # make sure image data is available
  62. im.load()
  63. existing_pdf.start_writing()
  64. existing_pdf.write_header()
  65. existing_pdf.write_comment(f"created by Pillow {__version__} PDF driver")
  66. #
  67. # pages
  68. ims = [im]
  69. if save_all:
  70. append_images = im.encoderinfo.get("append_images", [])
  71. for append_im in append_images:
  72. append_im.encoderinfo = im.encoderinfo.copy()
  73. ims.append(append_im)
  74. number_of_pages = 0
  75. image_refs = []
  76. page_refs = []
  77. contents_refs = []
  78. for im in ims:
  79. im_number_of_pages = 1
  80. if save_all:
  81. try:
  82. im_number_of_pages = im.n_frames
  83. except AttributeError:
  84. # Image format does not have n_frames.
  85. # It is a single frame image
  86. pass
  87. number_of_pages += im_number_of_pages
  88. for i in range(im_number_of_pages):
  89. image_refs.append(existing_pdf.next_object_id(0))
  90. page_refs.append(existing_pdf.next_object_id(0))
  91. contents_refs.append(existing_pdf.next_object_id(0))
  92. existing_pdf.pages.append(page_refs[-1])
  93. #
  94. # catalog and list of pages
  95. existing_pdf.write_catalog()
  96. page_number = 0
  97. for im_sequence in ims:
  98. im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
  99. for im in im_pages:
  100. # FIXME: Should replace ASCIIHexDecode with RunLengthDecode
  101. # (packbits) or LZWDecode (tiff/lzw compression). Note that
  102. # PDF 1.2 also supports Flatedecode (zip compression).
  103. bits = 8
  104. params = None
  105. decode = None
  106. if im.mode == "1":
  107. filter = "DCTDecode"
  108. colorspace = PdfParser.PdfName("DeviceGray")
  109. procset = "ImageB" # grayscale
  110. elif im.mode == "L":
  111. filter = "DCTDecode"
  112. # params = f"<< /Predictor 15 /Columns {width-2} >>"
  113. colorspace = PdfParser.PdfName("DeviceGray")
  114. procset = "ImageB" # grayscale
  115. elif im.mode == "P":
  116. filter = "ASCIIHexDecode"
  117. palette = im.getpalette()
  118. colorspace = [
  119. PdfParser.PdfName("Indexed"),
  120. PdfParser.PdfName("DeviceRGB"),
  121. 255,
  122. PdfParser.PdfBinary(palette),
  123. ]
  124. procset = "ImageI" # indexed color
  125. elif im.mode == "RGB":
  126. filter = "DCTDecode"
  127. colorspace = PdfParser.PdfName("DeviceRGB")
  128. procset = "ImageC" # color images
  129. elif im.mode == "CMYK":
  130. filter = "DCTDecode"
  131. colorspace = PdfParser.PdfName("DeviceCMYK")
  132. procset = "ImageC" # color images
  133. decode = [1, 0, 1, 0, 1, 0, 1, 0]
  134. else:
  135. raise ValueError(f"cannot save mode {im.mode}")
  136. #
  137. # image
  138. op = io.BytesIO()
  139. if filter == "ASCIIHexDecode":
  140. ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)])
  141. elif filter == "DCTDecode":
  142. Image.SAVE["JPEG"](im, op, filename)
  143. elif filter == "FlateDecode":
  144. ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)])
  145. elif filter == "RunLengthDecode":
  146. ImageFile._save(im, op, [("packbits", (0, 0) + im.size, 0, im.mode)])
  147. else:
  148. raise ValueError(f"unsupported PDF filter ({filter})")
  149. #
  150. # Get image characteristics
  151. width, height = im.size
  152. existing_pdf.write_obj(
  153. image_refs[page_number],
  154. stream=op.getvalue(),
  155. Type=PdfParser.PdfName("XObject"),
  156. Subtype=PdfParser.PdfName("Image"),
  157. Width=width, # * 72.0 / resolution,
  158. Height=height, # * 72.0 / resolution,
  159. Filter=PdfParser.PdfName(filter),
  160. BitsPerComponent=bits,
  161. Decode=decode,
  162. DecodeParams=params,
  163. ColorSpace=colorspace,
  164. )
  165. #
  166. # page
  167. existing_pdf.write_page(
  168. page_refs[page_number],
  169. Resources=PdfParser.PdfDict(
  170. ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)],
  171. XObject=PdfParser.PdfDict(image=image_refs[page_number]),
  172. ),
  173. MediaBox=[
  174. 0,
  175. 0,
  176. width * 72.0 / resolution,
  177. height * 72.0 / resolution,
  178. ],
  179. Contents=contents_refs[page_number],
  180. )
  181. #
  182. # page contents
  183. page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % (
  184. width * 72.0 / resolution,
  185. height * 72.0 / resolution,
  186. )
  187. existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)
  188. page_number += 1
  189. #
  190. # trailer
  191. existing_pdf.write_xref_and_trailer()
  192. if hasattr(fp, "flush"):
  193. fp.flush()
  194. existing_pdf.close()
  195. #
  196. # --------------------------------------------------------------------
  197. Image.register_save("PDF", _save)
  198. Image.register_save_all("PDF", _save_all)
  199. Image.register_extension("PDF", ".pdf")
  200. Image.register_mime("PDF", "application/pdf")