折腾:
【记录】用Flask实现测评系统的后端
期间,现在情况是:
虽然前端小程序中可以通过url去访问flask后台中封装的mongodb的gridfs的文件了
但是有些图片比较大,导致前端加载很慢:

所以需要:
后台中想办法,对于gridfs中,获取到了图片的二进制数据后,想办法去压缩后,再返回给前端
不过先去搞清楚:
pip 和pillow的区别
python pil vs pillow
PIL=Python Imaging Library,很好,不过2009年就停止开发了
后续fork出分支,继续开发到现在的是:Pillow
先去安装:
➜ EvaluationSystemServer git:(master) ✗ pipenv install pillow Installing pillow… Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple Collecting pillow Downloading https://pypi.tuna.tsinghua.edu.cn/packages/d1/21/bef2816809fac16754e07ed935469fc65f42ced1a94766de7c804179311d/Pillow-5.3.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl (3.6MB) Installing collected packages: pillow Successfully installed pillow-5.3.0 Adding pillow to Pipfile's [packages]… Pipfile.lock (cacb65) out of date, updating to (28e8c6)… Locking [dev-packages] dependencies… Locking [packages] dependencies… Updated Pipfile.lock (cacb65)! Installing dependencies from Pipfile.lock (cacb65)… 🐍 ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 19/19 — 00:00:29 ➜ EvaluationSystemServer git:(master) ✗ pipenv graph Flask-Cors==3.0.7 - Flask [required: >=0.9, installed: 1.0.2] - click [required: >=5.1, installed: 7.0] - itsdangerous [required: >=0.24, installed: 1.1.0] - Jinja2 [required: >=2.10, installed: 2.10] - MarkupSafe [required: >=0.23, installed: 1.1.0] - Werkzeug [required: >=0.14, installed: 0.14.1] - Six [required: Any, installed: 1.12.0] Flask-PyMongo==2.2.0 - Flask [required: >=0.11, installed: 1.0.2] - click [required: >=5.1, installed: 7.0] - itsdangerous [required: >=0.24, installed: 1.1.0] - Jinja2 [required: >=2.10, installed: 2.10] - MarkupSafe [required: >=0.23, installed: 1.1.0] - Werkzeug [required: >=0.14, installed: 0.14.1] - PyMongo [required: >=3.0, installed: 3.7.2] Flask-RESTful==0.3.7 - aniso8601 [required: >=0.82, installed: 4.0.1] - Flask [required: >=0.8, installed: 1.0.2] - click [required: >=5.1, installed: 7.0] - itsdangerous [required: >=0.24, installed: 1.1.0] - Jinja2 [required: >=2.10, installed: 2.10] - MarkupSafe [required: >=0.23, installed: 1.1.0] - Werkzeug [required: >=0.14, installed: 0.14.1] - pytz [required: Any, installed: 2018.7] - six [required: >=1.3.0, installed: 1.12.0] gevent==1.3.7 - greenlet [required: >=0.4.14, installed: 0.4.15] gunicorn==19.9.0 numpy==1.15.4 Pillow==5.3.0 python-dotenv==0.10.1
再去试试
先去解决是否支持从binary data中读取image:
【已解决】Python的Pillow如何从二进制数据中读取图像数据
接下来问题就是:
【已解决】Python的Pillow中如何压缩缩放图片且保持原图宽高比例
然后就是resize后thumbnail后,如何返回二进制数据:
【已解决】Python的Pillow如何返回图像的二进制数据
接下来就是:
【已解决】调试Pillow找到合适的性价比高的图片压缩尺寸
【总结】
此处用如下代码:
import io
from PIL import Image
fileBytes = fileObj.read()
log.debug("len(fileBytes)=%s", len(fileBytes))
if fileType == MongoFileType.IMAGE.value:
imageStream = io.BytesIO(fileBytes)
imageFile = Image.open(imageStream)
log.debug("imageFile=%s", imageFile)
log.debug("imageFile.size=%s", imageFile.size)
# imageFile.thumbnail(IMAGE_COMPRESS_SIZE, Image.ANTIALIAS)
imageFile.thumbnail(IMAGE_COMPRESS_SIZE, Image.LANCZOS)
log.debug("imageFile=%s", imageFile)
imageOutput = io.BytesIO()
log.debug("imageOutput=%s", imageOutput)
imageSuffix = fileObj.filename.split(".")[-1] # png
log.debug("imageSuffix=%s", imageSuffix)
imageFormat = imageSuffix.upper() # PNG
if imageFormat == "JPG":
imageFormat = "JPEG"
log.debug("imageFormat=%s", imageFormat)
# imageFile.save(imageOutput)
imageFile.save(imageOutput, imageFormat)
log.debug("imageFile=%s", imageFile)
log.debug("imageFile.size=%s", imageFile.size)
compressedImageBytes = imageOutput.getvalue()
# log.debug("compressedImageBytes=%s", compressedImageBytes)
log.debug("len(compressedImageBytes)=%s", len(compressedImageBytes))
fileBytes = compressedImageBytes实现了性价比高的,从原始的png,jpg的图片的二进制数据中,压缩图片,保存出压缩后图片的二进制数据
效果:
原图:

压缩后的:

jpg:

压缩后:

效果还是很不错的。
【后记】
后来单独封装成独立的函数,并且支持更多种功能和调用方式,代码如下:
python/crifanLib/crifanFile.py
def isFileObject(fileObj): """"check is file like object or not""" if sys.version_info[0] == 2: return isinstance(fileObj, file) else: # for python 3: # has read() method for: # io.IOBase # io.BytesIO # io.StringIO # io.RawIOBase return hasattr(fileObj, 'read')
以及:
python/crifanLib/crifanMultimedia.py
def resizeImage(inputImage,
newSize,
resample=Image.BICUBIC, # Image.LANCZOS,
outputFormat=None,
outputImageFile=None
):
"""
resize input image
resize normally means become smaller, reduce size
:param inputImage: image file object(fp) / filename / binary bytes
:param newSize: (width, height)
:param resample: PIL.Image.NEAREST, PIL.Image.BILINEAR, PIL.Image.BICUBIC, or PIL.Image.LANCZOS
https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.thumbnail
:param outputFormat: PNG/JPEG/BMP/GIF/TIFF/WebP/..., more refer:
https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
if input image is filename with suffix, can omit this -> will infer from filename suffix
:param outputImageFile: output image file filename
:return:
input image file filename: output resized image to outputImageFile
input image binary bytes: resized image binary bytes
"""
openableImage = None
if isinstance(inputImage, str):
openableImage = inputImage
elif isFileObject(inputImage):
openableImage = inputImage
elif isinstance(inputImage, bytes):
inputImageLen = len(inputImage)
openableImage = io.BytesIO(inputImage)
imageFile = Image.open(openableImage) # <PIL.PngImagePlugin.PngImageFile image mode=RGBA size=3543x3543 at 0x1065F7A20>
imageFile.thumbnail(newSize, resample)
if outputImageFile:
# save to file
imageFile.save(outputImageFile)
imageFile.close()
else:
# save and return binary byte
imageOutput = io.BytesIO()
# imageFile.save(imageOutput)
outputImageFormat = None
if outputFormat:
outputImageFormat = outputFormat
elif imageFile.format:
outputImageFormat = imageFile.format
imageFile.save(imageOutput, outputImageFormat)
imageFile.close()
compressedImageBytes = imageOutput.getvalue()
compressedImageLen = len(compressedImageBytes)
compressRatio = float(compressedImageLen)/float(inputImageLen)
print("%s -> %s, resize ratio: %d%%" % (inputImageLen, compressedImageLen, int(compressRatio * 100)))
return compressedImageBytesdemo调用:
import sys
import os
curFolder = os.path.abspath(__file__)
parentFolder = os.path.dirname(curFolder)
parentParentFolder = os.path.dirname(parentFolder)
parentParentParentFolder = os.path.dirname(parentParentFolder)
sys.path.append(curFolder)
sys.path.append(parentFolder)
sys.path.append(parentParentFolder)
sys.path.append(parentParentParentFolder)
import datetime
from crifanMultimedia import resizeImage
def testFilename():
imageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day.png"
outputImageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day_300x300.png"
print("imageFilename=%s" % imageFilename)
beforeTime = datetime.datetime.now()
resizeImage(imageFilename, (300, 300), outputImageFile=outputImageFilename)
afterTime = datetime.datetime.now()
print("procesTime: %s" % (afterTime - beforeTime))
outputImageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day_800x800.png"
beforeTime = datetime.datetime.now()
resizeImage(imageFilename, (800, 800), outputImageFile=outputImageFilename)
afterTime = datetime.datetime.now()
print("procesTime: %s" % (afterTime - beforeTime))
def testFileObject():
imageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day.png"
imageFileObj = open(imageFilename, "rb")
outputImageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day_600x600.png"
beforeTime = datetime.datetime.now()
resizeImage(imageFileObj, (600, 600), outputImageFile=outputImageFilename)
afterTime = datetime.datetime.now()
print("procesTime: %s" % (afterTime - beforeTime))
def testBinaryBytes():
imageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/take tomato.png"
imageFileObj = open(imageFilename, "rb")
imageBytes = imageFileObj.read()
# return binary bytes
beforeTime = datetime.datetime.now()
resizedImageBytes = resizeImage(imageBytes, (800, 800))
afterTime = datetime.datetime.now()
print("procesTime: %s" % (afterTime - beforeTime))
print("len(resizedImageBytes)=%s" % len(resizedImageBytes))
# save to file
outputImageFilename = "/Users/crifan/dev/tmp/python/resize_image_demo/hot day_750x750.png"
beforeTime = datetime.datetime.now()
resizeImage(imageBytes, (750, 750), outputImageFile=outputImageFilename)
afterTime = datetime.datetime.now()
print("procesTime: %s" % (afterTime - beforeTime))
imageFileObj.close()
def demoResizeImage():
testFilename()
testFileObject()
testBinaryBytes()
if __name__ == "__main__":
demoResizeImage()
# imageFilename=/Users/crifan/dev/tmp/python/resize_image_demo/hot day.png
# procesTime: 0:00:00.619377
# procesTime: 0:00:00.745228
# procesTime: 0:00:00.606060
# 1146667 -> 753258, resize ratio: 65%
# procesTime: 0:00:00.773289
# len(resizedImageBytes)=753258
# procesTime: 0:00:00.738237更多代码详见:
转载请注明:在路上 » 【已解决】Python中实现二进制数据的图片的压缩