红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 编程语言
  3. Python
  4. 正文

Python 图片压缩

2021年7月14日 2539点热度 0人点赞 8条评论

我做了一个处理Markdown图片的小工具markdown-img,在之前给这个工具添加了主流图床支持后功能基本完备了。昨天写了一篇影评的博客扎导版正义联盟观影吐槽,图片都是从电影中截取的,每张都是1Mb以上,颇占云存储空间,所以决定加上图片压缩功能。

bing了一番以后,决定使用Pillow库实现压缩功能,虽然使用 python 压缩 png 图片,高达 80% 压缩率,肉眼无差异(一):为什么不用 pillow库这篇文章说了为什么Pillow库用于压缩的缺陷,但该作者给出的另一个压缩方案是windows软件,只能通过命令行调取,并不能直接通过pip库集成,综合考虑下来还是使用Pillow库,至少作为一个可选项是不错的,后期可以加入其它的压缩服务支持,比如在线无损压缩服务tinypng。

先通过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)
​
    @classmethod
    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

参考资料:

  • 使用 python 压缩 png 图片,高达 80% 压缩率,肉眼无差异(一):为什么不用 pillow库

  • Python实现图片压缩

  • Pillow官方手册

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 图片压缩
最后更新:2021年7月14日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

  • nfmd

    用opencv压怎么样?

    2021年7月14日
    回复
  • 魔芋红茶

    没试过,应该差不多,openCV也是有损压缩,我正在整合tinyPNG,是无损压缩,就是有调用次数限制。

    2021年7月14日
    回复
    • nfmd

      @魔芋红茶 我用opencv试了一下,结果越压越大

      2021年7月14日
      回复
      • 魔芋红茶

        @nfmd 这么骚的吗233

        2021年7月14日
        回复
        • nfmd

          @魔芋红茶 一张2.47M的图片压成4.14M,我都不知道怎么压出来的 :lol:

          2021年7月14日
          回复
          • 魔芋红茶

            @nfmd 我刚才试了tinyPNG,很好,无损压缩,而且速度很快,每个月500次调用也够用了

            2021年7月14日
          • nfmd

            @nfmd 我也试了一下,确实不错。整合也很方便。

            2021年7月14日
  • nfmd

    使用WebP也不错

    2021年7月14日
    回复
  • 取消回复

    *

    code

    COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
    本网站由提供CDN加速/云存储服务

    Theme Kratos Made By Seaton Jiang

    宁ICP备2021001508号

    宁公网安备64040202000141号