折腾:
【记录】用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 compressedImageBytes
demo调用:
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中实现二进制数据的图片的压缩