PSDraw Module, EpsImagePlugin Module #################################### :doc: https://pillow.readthedocs.io/en/latest/reference/PSDraw.html, https://pillow.readthedocs.io/en/latest/reference/plugins.html#module-PIL.EpsImagePlugin, https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html#eps http://effbot.org/imagingbook/psdraw.htm, http://effbot.org/imagingbook/format-eps.htm .. note:: All source images in this document are derived from https://www.pexels.com (CC0 License). EpsImagePlugin basics ********************* Saving image as EPS is easy: .. code-block:: pycon >>> from PIL import Image >>> img = Image.open("data/srcimg01.jpg") >>> img.save("result/fromsrcimg01.eps") >>> open("result/fromsrcimg01.eps").readlines()[:3] ['%!PS-Adobe-3.0 EPSF-3.0\n', '%%Creator: PIL 0.1 EpsEncode\n', '%%BoundingBox: 0 0 670 445\n'] Actually, this capability is provided by PIL.EpsImagePlugin. Now, let us try to open EPS file and load: .. code-block:: pycon >>> img2 = Image.open("result/fromsrcimg01.eps") >>> img2 >>> img2.load() Traceback (most recent call last): File "", line 1, in File "c:\Python27\lib\site-packages\PIL\EpsImagePlugin.py", line 336, in load self.im = Ghostscript(self.tile, self.size, self.fp, scale) File "c:\Python27\lib\site-packages\PIL\EpsImagePlugin.py", line 137, in Ghostscript raise WindowsError('Unable to locate Ghostscript on paths') WindowsError: Unable to locate Ghostscript on paths If you encounter the same error, you have to install `GhostScript `_. PIL.EpsImagePlugin sometimes invokes ghostscript as needed. EpsImagePlugin is basically a backroom boy for most of end-users, but using this module directly is sometimes useful for us. For example, if we need to know BoundingBox of PostScript (PS), we can use a knowledge of GhostScript command which EpsImagePlugin has.: .. code-block:: pycon >>> # "xxxxx.ps" has no '%%%%BoundingBox' ... open("xxxxx.ps").readlines()[:5] ['%!PS-Adobe-3.0\n', 'save\n', '/showpage { } def\n', '%%EndComments\n', '%%BeginDocument\n'] >>> # ... import subprocess >>> from PIL import EpsImagePlugin >>> gs_binary = EpsImagePlugin.gs_windows_binary >>> gs_binary = ("gs" if not gs_binary else gs_binary) >>> gs_binary 'gswin64c' >>> command = [gs_binary, "-dBATCH", "-dNOPAUSE", "-sDEVICE=bbox", "-q"] >>> command.append("xxxxx.ps") >>> gs_output = subprocess.check_output(command, stderr=subprocess.STDOUT) >>> gs_output.strip().split("\n") ['%%BoundingBox: 0 222 596 620', '%%HiResBoundingBox: 0.000000 222.137993 595.439982 619.541981'] PSDraw basics ************* According to the official documents: The PSDraw module provides simple print support for Postscript printers. You can print text, graphics and images through this module. Actually, the PSDraw takes very minimal approach to provide this Postscript support, in other words, you may have to do much work to build the complex PostScript. For example, there is no support to calculate proper position to place texts, etc. If you are not familiar with PostScript, I do not recommend to use this module. If you just want to save image as EPS, see `EpsImagePlugin basics <#epsimageplugin-basics>`_. If you are familiar with PostScript, using the PSDraw should be very easy to you. PSDraw is a builder to PostScript documents: >>> from PIL import PSDraw >>> psd = PSDraw.PSDraw() # file=sys.stdout >>> psd.begin_document() %!PS-Adobe-3.0 save /showpage { } def %%EndComments %%BeginDocument /S { show } bind def /P { moveto show } bind def /M { moveto } bind def /X { 0 rmoveto } bind def /Y { 0 exch rmoveto } bind def /E { findfont dup maxlength dict begin { 1 index /FID ne { def } { pop pop } ifelse } forall /Encoding exch def dup /FontName exch def currentdict end definefont pop } bind def /F { findfont exch scalefont dup setfont [ exch /setfont cvx ] cvx bind def } bind def /Vm { moveto } bind def /Va { newpath arcn stroke } bind def /Vl { moveto lineto stroke } bind def /Vc { newpath 0 360 arc closepath } bind def /Vr { exch dup 0 rlineto exch dup neg 0 exch rlineto exch neg 0 rlineto 0 exch rlineto 100 div setgray fill 0 setgray } bind def /Tm matrix def /Ve { Tm currentmatrix pop translate scale newpath 0 0 .5 0 360 arc closepath Tm setmatrix } bind def /Vf { currentgray exch setgray fill setgray } bind def %%EndProlog >>> # >>> psd.setfont("Courier", 24) /PSDraw-Courier ISOLatin1Encoding /Courier E /F0 24 /PSDraw-Courier F >>> psd.text((0, 0), "Draws text at (0, 0).") 0 0 M (Draws text at \(0, 0\).) S >>> psd.end_document() %%EndDocument restore showpage %%End writing PS with PSDraw, and converting it to EPS ************************************************ At first, see below example: .. code-block:: python from PIL import Image, PSDraw # A4: 8.27 x 11.69 inches # => 595, 834 pixels if dpi=72 psd = PSDraw.PSDraw(open("result/fromsrcimg22.ps", "wb")) psd.begin_document() img = Image.open("data/srcimg22.jpg") img = img.rotate(90).resize((595, 834)) psd.image((0, 0, 595, 834), img, dpi=72) psd.end_document() This example seems somewhat nonsence. Because if you just want to save image as PostScript (family), you can simply save it with extention ".eps". Still you want to do so, maybe you want to build more complex PostScript document, and I assume you need so. I'll continue the story. The PostScript file :file:`result/fromsrcimg22.ps` generated by above code is not EPS but PS, so even if you change extention ".ps" to ".eps", you can't load as PIL.Image: .. code-block:: pycon >>> from PIL import Image >>> im = Image.open("result/fromsrcimg22.eps") # chenged extention, but not EPS Traceback (most recent call last): File "", line 1, in File "c:\Python27\lib\site-packages\PIL\Image.py", line 2321, in open im = factory(fp, filename) File "c:\Python27\lib\site-packages\PIL\ImageFile.py", line 97, in __init__ self._open() File "c:\Python27\lib\site-packages\PIL\EpsImagePlugin.py", line 308, in _open raise IOError("cannot determine EPS bounding box") IOError: cannot determine EPS bounding box `Andrew T. Young `_ concluded "the absolute minimum required to get some other program to buy a PS file as EPS is": Most of the time, all you need is the %%BoundingBox: I agree with him, I also think thumbnail is unnecessary. Unfortunately, the PSDraw itself have no support to decide %%BoundingBox, but with invoking GhostScript, we can detect %%BoundingBox of PostScript document: .. code-block:: python from io import BytesIO from PIL import Image, PSDraw def get_boundingbox(psdoc): import os, subprocess, tempfile from PIL import EpsImagePlugin t_fd, tfile = tempfile.mkstemp() os.close(t_fd) open(tfile, "w").write(psdoc) gs_binary = EpsImagePlugin.gs_windows_binary gs_binary = ("gs" if not gs_binary else gs_binary) command = [gs_binary, "-dBATCH", "-dNOPAUSE", "-sDEVICE=bbox", "-q", tfile] gs_output = subprocess.check_output( command, stderr=subprocess.STDOUT) return gs_output.decode("us-ascii").strip().split("\n") # A4: 8.27 x 11.69 inches # => 595, 834 pixels if dpi=72 out = BytesIO() psd = PSDraw.PSDraw(out) psd.begin_document() img = Image.open("data/srcimg22.jpg") img = img.rotate(90).resize((595, 834)) psd.image((0, 0, 595, 834), img, dpi=72) psd.end_document() result = out.getvalue().decode("us-ascii") # append BB to second line bb = get_boundingbox(result) # get BB from GS s1, _, s2 = result.partition("\n") newpsdoc = s1 + _ + bb[0] + _ + s2 open("result/fromsrcimg22.eps", "w").write(newpsdoc) Now :file:`result/fromsrcimg22.eps` can be loaded as PIL.Image: .. code-block:: pycon >>> from PIL import Image >>> im = Image.open("result/fromsrcimg22.eps") >>> im.info {'BoundingBox': '0 0 596 835', 'PS-Adobe': '3.0'} >>> im.show() More complex example (pscal wrapper) ************************************ This demo script is a tiny wrapper to `pscal `_. .. literalinclude:: _static/exams/epscalwi.py :caption: epscalwi.py :emphasize-lines: 54-56, 80 Usage: .. code-block:: console me@host: ~$ epscalwi.py -h usage: epscalwi.py [-h] [-d D] [-M] [-m] [-n] [-S] [-s] photo_image out_file [month_year [month_year ...]] positional arguments: photo_image photo image (if you don't want this, specify :devnull:) out_file output file name month_year 'month year' or 'month' optional arguments: -h, --help show this help message and exit -d D moon diameter (default = 13) -M show moon phases (southern hemisphere) -m show moon phases (northern hemisphere) -n show day numbering -S European style (Monday first) -s American style (Sunday first) me@host: ~$ epscalwi.py -m data/srcimg22.jpg result/2017_07.ps # => ps me@host: ~$ epscalwi.py -m data/srcimg22.jpg result/2017_07.eps # => eps me@host: ~$ epscalwi.py -m data/srcimg22.jpg result/2017_07.jpg # => jpg me@host: ~$ epscalwi.py -m data/srcimg22.jpg result/2019_03.jpg 6 2019 # of June, 2019 me@host: ~$ epscalwi.py -m :devnull: result/2019_03.jpg 6 2019 # no photo .. image:: _static/exams/result/2017_07.jpg