在Django中获取matplotlib生成的PNG失败
我想用 Django 来展示 matplotlib 生成的图片。
如果这个图片是一个静态的 PNG 文件,下面的代码就能很好地工作:
from django.http import HttpResponse
def static_image_view(request):
response = HttpResponse(mimetype='image/png')
with open('test.png', 'rb') as f:
response.write(f.read())
return response
但是,如果这个图片是动态生成的:
import numpy as np
import matplotlib
matplotlib.use('Agg')
from matplotlib import pyplot as plt
def dynamic_image_view(request):
response = HttpResponse(mimetype='image/png')
fig = plt.figure()
plt.plot(np.random.rand(100))
plt.savefig(response, format='png')
plt.close(fig)
return response
在 Chrome 浏览器(版本 36.0)中访问这个网址时,图片会显示几秒钟,然后消失,变成替代文本。看起来浏览器不知道图片已经加载完成,反而等到超时才反应。通过查看 Chrome 的开发者工具中的网络部分可以支持这个猜想:虽然图片大约在 1 秒后就出现了,但对应的 HTTP 请求状态在大约 5 秒后变成了“失败”。
再强调一下,这种奇怪的现象只发生在动态生成的图片上,所以这应该不是 Chrome 的问题(虽然在 IE 或 Firefox 中没有发生这种情况,可能是因为它们处理超时请求的规则不同)。
更麻烦的是(也就是更难重现),这个问题似乎和网络速度有关。如果我从中国的 IP 访问这个网址,就会出现这个问题,但如果通过美国的代理访问(那里的速度似乎更快),就不会出现这个问题……
根据 @HSquirrel 的建议,我测试了把 PNG 写入临时磁盘文件。奇怪的是,用 matplotlib 保存文件没有成功,
plt.savefig('MPL.png', format='png')
with open('MPL.png', 'rb') as f:
response.write(f.read())
但用 PIL 保存文件就成功了:
import io
from PIL import Image
f = io.BytesIO()
plt.savefig(f, format='png')
f.seek(0)
im = Image.open(f)
im.save('PIL.png', 'PNG')
尝试去掉临时文件的做法失败了:
im.save(response, 'PNG')
不过,如果我用 PIL 而不是 matplotlib 来生成图片数据流,就不需要临时磁盘文件。下面的代码可以正常工作:
from PIL import Image, ImageDraw
im = Image.new('RGBA', (256,256), (0,255,0,255))
draw = ImageDraw.Draw(im)
draw.line((100,100, 150,200), fill=128, width=3)
im.save(response, 'PNG')
最后,savefig(response, 'jpeg')
完全没有问题。
1 个回答
你有没有试过把图片保存到硬盘上,然后再返回那个图片?(你可以定期清理硬盘上这些生成的图片,按照它们创建的时间来清理)
如果这样做还是有同样的问题,那可能是生成png的方式出了问题。你可以使用一些图像库(比如PIL)来确保你所有的png都是以一种适合所有浏览器的方式重新生成的。
编辑: 我查看了你链接的png文件,并且用不同的程序和PIL对它进行了些操作。每次我得到的二进制数据都不一样。看起来每个程序都会决定保留哪些部分,去掉哪些部分。它们对png图像数据的编码方式也不一样(就我所见,我并不是这方面的专家,只是根据规格看了一下二进制数据)。
你可以选择几种不同的解决方案:
1. 快速且简单的方法:
import io
from PIL import Image
f = io.BytesIO()
plt.savefig(f, format='png')
f.seek(0)
im = Image.open(f)
tempfilename = generatetempfilename()
im.save(tempfilename, 'PNG')
with open(tempfilename, 'rb') as f:
response.write(f.read())
2. 调整matplotlib生成PNG文件的方式(可能只需使用PIL)。可以参考 http://matplotlib.org/users/customizing.html#customizing-matplotlib
3. 如果可以的话,使用jpeg格式。
4. 找出matplotlib生成的PNG文件有什么问题,并尝试修复它的二进制数据(我不推荐这样做)。你可以使用xxd(linux命令:xxd test.png
)来查看文件的二进制内容,然后根据png的规格来了解情况:
概述
块规格