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

所以需要:
后台中想办法,对于gridfs中,获取到了图片的二进制数据后,想办法去压缩后,再返回给前端
不过先去搞清楚:
pip 和pillow的区别
python pil vs pillow
PIL=Python Imaging Library,很好,不过2009年就停止开发了
后续fork出分支,继续开发到现在的是:Pillow
先去安装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | ➜ 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)…
➜ 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找到合适的性价比高的图片压缩尺寸
【总结】
此处用如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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
1 2 3 4 5 6 7 8 9 10 11 12 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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: 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调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 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中实现二进制数据的图片的压缩