我做了一个处理Markdown图片的小工具,在之前给这个工具添加了主流图床支持后功能基本完备了。昨天写了一篇影评的博客,图片都是从电影中截取的,每张都是1Mb以上,颇占云存储空间,所以决定加上图片压缩功能。
bing了一番以后,决定使用Pillow库实现压缩功能,虽然这篇文章说了为什么Pillow库用于压缩的缺陷,但该作者给出的另一个压缩方案是windows软件,只能通过命令行调取,并不能直接通过pip库集成,综合考虑下来还是使用Pillow库,至少作为一个可选项是不错的,后期可以加入其它的压缩服务支持,比如在线无损压缩服务。
先通过pip安装包:
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
from PIL import Image
from ..config import Config
import os
from ..tools.debug import Debug
class Compress:
def __init__(self, imageFile: str, compressLimit: int = 500) -> None:
"""初始化压缩上下文管理器
imageFile: 用于压缩的原图片路径
compressLimit: 压缩门槛(高于该值的图片才会被压缩,单位kb)
"""
self.__imageFile = imageFile
self.__compressLimit: int = compressLimit
def __enter__(self):
# 对png图片使用quantize压缩
basename = os.path.basename(self.__imageFile)
_, _, ext = basename.rpartition(".")
imageSize = self.__class__.getSize(self.__imageFile)
# 没有达到压缩门槛,不压缩
if imageSize < self.__compressLimit:
return self.__imageFile
Debug.print("开始对{}进行压缩,压缩前大小{}kb".format(
self.__imageFile, int(imageSize)))
if ext == "png":
self.__compressPng(self.__imageFile)
else:
self.__compressImage(self.__imageFile)
outPutFile = self.__getOutPutFile(self.__imageFile)
if os.path.exists(outPutFile):
return outPutFile
else:
# 没有产生压缩图片,返回原图
return self.__imageFile
def __exit__(self, expType, expVal, expTrace):
# 如果存在压缩后的临时文件,删除
outPutFile = self.__getOutPutFile(self.__imageFile)
if os.path.exists(outPutFile):
os.remove(outPutFile)
def getSize(cls, file):
# 获取文件大小:KB
size = os.path.getsize(file)
return size / 1024
def __getOutPutFile(self, infile: str) -> str:
"""获取输出文件路径
infile: 待处理文件路径
return: 输出文件路径
"""
fileName: str = os.path.basename(infile)
sysConfig = Config.getInstance()
return sysConfig.getTmpDir()+sysConfig.getPathSplit()+fileName
def __compressPng(self, infile: str) -> None:
"""压缩png图片到输出目录
infile: 待压缩图片
"""
im: Image.Image = Image.open(infile)
new_im = im.quantize(colors=256)
new_im.save(self.__getOutPutFile(infile))
pressedSize = self.__class__.getSize(self.__getOutPutFile(infile))
Debug.print("{}压缩后的大小{}kb".format(infile, int(pressedSize)))
def __compressImage(self, infile, step=10, quality=80) -> None:
"""对图片进行多轮压缩以达到压缩门槛(仅限JPG图片)
infile: 压缩源文件
step: 每一轮压缩增加的压缩率差值
quality: 起始压缩率
"""
maxSize = self.__compressLimit
o_size = self.__class__.getSize(infile)
if o_size <= maxSize:
return
outfile = self.__getOutPutFile(infile)
im = Image.open(infile)
while o_size > maxSize:
im.save(outfile, quality=quality)
if quality - step < 0:
break
quality -= step
o_size = self.__class__.getSize(outfile)
Debug.print("{}压缩后的大小{}kb".format(infile, int(o_size)))
return
Config
和Debug
等项目中地其它组件在这里只起到配置和调试相关功能,并不影响压缩功能。
这里的主要关键在于Pillow
库压缩图片需要对png
格式和jpg
格式单独对待,处理方式不同,im.save(outfile, quality=quality)
这种方式并不能压缩png
格式的图片。
此外之所以我将这个模块写成上下文管理器,是因为在我的程序中是需要压缩后上传到网络图库,本地不需要压缩后的图片,所以最好自动删除,故此上下文管理器的方式应该是最佳的。
使用:
with Compress(localImg, compressLimit) as compressedImg:
imgService = ImgServiceManager.getImgService()
return imgService.upload(compressedImg)
测试:
开始对7526c60df75a574cd2b42da1239308df.png进行压缩,压缩前大小929kb
开始对c105b51e21824730b09f68bbc8352c63.jpeg进行压缩,压缩前大小2002kb
开始对i6q1uk.png进行压缩,压缩前大小1084kb
c105b51e21824730b09f68bbc8352c63.jpeg压缩后的大小340kb
7526c60df75a574cd2b42da1239308df.png压缩后的大小292kb
参考资料:
文章评论
用opencv压怎么样?
没试过,应该差不多,openCV也是有损压缩,我正在整合tinyPNG,是无损压缩,就是有调用次数限制。
@魔芋红茶 我用opencv试了一下,结果越压越大
@nfmd 这么骚的吗233
@魔芋红茶 一张2.47M的图片压成4.14M,我都不知道怎么压出来的
@nfmd 我刚才试了tinyPNG,很好,无损压缩,而且速度很快,每个月500次调用也够用了
@nfmd 我也试了一下,确实不错。整合也很方便。
使用WebP也不错