问题
I'm trying to save several matplotlib figures into specific locations in a PDF being created with FPDF. I'd prefer not to have to dump these figures to files and then pull them back in. The better solution seems to be writing them to a buffer, then reading them back in, but I'm not sure that's possible.
Simplified code:
import pandas as pd
import matplotlib.pyplot as plt
from fpdf import FPDF
from io import BytesIO
import base64
# create figure
fig = plt.figure()
# add plot
ax = pd.Series( [1,3,2] ).plot()
# create bytesio object
bio = BytesIO()
# save figure to bytesio
fig.savefig(bio, format="png", bbox_inches='tight')
# this works to save the figure to a file
with open("myfile.png", "wb") as file:
file.write(bio.getvalue())
# create a PDF and set some attributes
pdf = FPDF(orientation="L", unit="in", format="letter")
pdf.set_font('Arial', 'B', 14)
pdf.add_page()
# try to read the bytesio object as though it's a file
# pdf.image() expects a file name or a URL: https://pyfpdf.readthedocs.io/en/latest/reference/image/index.html
# trying to create a data url with the base64-encoded data inside it
pdf.image( name='data://image/png;base64,' + base64.b64encode(bio.getvalue()).decode() , x=1, y=1, w=3.5, type='png')
# save pdf
pdf.output("mypdf.pdf")
Full stack trace:
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-17-0ec65d0f583e> in <module>()
30 # trying to create a data url with the base64-encoded data inside it
31
---> 32 pdf.image( name='data://image/png;base64,' + base64.b64encode(bio.getvalue()).decode() , x=1, y=1, w=3.5, type='png')
33
34 # save pdf
~\AppData\Local\Continuum\anaconda3\lib\site-packages\fpdf\fpdf.py in wrapper(self, *args, **kwargs)
148 self.error("No page open, you need to call add_page() first")
149 else:
--> 150 return fn(self, *args, **kwargs)
151 return wrapper
152
~\AppData\Local\Continuum\anaconda3\lib\site-packages\fpdf\fpdf.py in image(self, name, x, y, w, h, type, link)
969 info=self._parsejpg(name)
970 elif(type=='png'):
--> 971 info=self._parsepng(name)
972 else:
973 #Allow for additional formats
~\AppData\Local\Continuum\anaconda3\lib\site-packages\fpdf\fpdf.py in _parsepng(self, name)
1770 f = urlopen(name)
1771 else:
-> 1772 f=open(name,'rb')
1773 if(not f):
1774 self.error("Can't open image file: "+name)
FileNotFoundError: [Errno 2] No such file or directory: 'data://image/png;base64,iVBORw0KGgoAAAANSU #snipped for length#
Since pdf.image() can take a URL, I'd like to construct a data URL that doesn't actually try to open up a file. It would be like embedding an image inline in an HTML page. That sort of idea. I've tried several iterations of getvalue() and encode() and decode(), but I'm not getting the right combination.
I am successfully writing to a file in this example to make sure my BytesIO object has the right data in it, but dumping it into pdf.image() is not working.
来源:https://stackoverflow.com/questions/54376569/writing-matplotlib-figure-to-a-buffer-then-reading-as-a-file-for-embedding-an-i