原创

python Png图片压缩工具

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://linsh-tech.blog.csdn.net/article/details/83040077

引言

最近在做 H5 小游戏的开发,与 App 不同,由于 H5 所有的资源都是通过 CDN 获取的,考虑到网络资源加载速度的问题,优化资源显得格外重要。因此,图片资源的压缩也是必不可少的。

 

起源

起初,我们在 windows 下是通过一个叫做 PNGoo 的 GUI 工具来实现图片资源批量压缩的。但考虑压缩资源还需要启动一个应用,将图片资源拖进去再开始压缩,显然不够智能,希望通过 python 脚本自动完成。

后来,找到了这个工具的 Github 源码 pngoo ,才发现这个工具是基于 pngquant 这个开源库实现的,类似的基于此压缩算法库工具还有 Pngyu (Github 源码:Pngyu)和网页版压缩工具的 TinyPng

重点是这个开源的压缩库压缩比达到 60%-80% ,也是相当可观了。

 

源码资源

  • Pngyu Win 和 Mac 下皆可使用的 GUI 工具

  • pngoo 使用 Visual Studio 2015 即可打开且编译运行

  • pngquant 基于 C 语言编写的开源 png 图片压缩库

 

python 实现

直接下载 win 和 mac 平台的命令行工具包:

具体实现步骤如下:

  • 遍历指定目录下所有的 .png 后缀的文件;

  • 根据是否覆盖源文件进行压缩处理。

核心的方法有:

  • getImages

    这个方法用来获取需要进行压缩的图片

    # 获取文件列表
    def getImages():
        print u"========= 开始遍历图片"
        global file_list
        files = os.listdir(PngSrcRoot)
        for file in files:
            # 过滤出 png 图片
            if os.path.isdir(file):
                print u"过滤掉文件目录:"+file
            else:
                endStr = os.path.splitext(file)[1]
                if endStr == file_end:
                    if isBack(file):
                        print u"过滤掉黑名单中的文件:"+file
                    else:
                        file_list.append(file)
                        # print u"文件 " + file + u" 添加到压缩列表"

    假如需要获取子目录下的图片资源,可以改写成递归调用的方式,改成如下即可很简单:

    def getImages(path, recursion):
        global file_list
        files = os.listdir(path)
        for file in files:
            # 过滤出 png 图片
            if os.path.isdir(file):
                getImages(path+'/'+file, recursion)
            else:
                endStr = os.path.splitext(file)[1]
                if endStr == file_end:
                    if isBack(file):
                        print u"过滤掉黑名单中的文件:"+file
                    else:
                        file_list.append(path+'/'+file)

    使用递归遍历的方式需要保存完整的文件路径(绝对路径或相对路径),非递归可直接保存文件名即可。

  • compress

    这是压缩文件的方法,当然要根据是否覆盖源文件做区分处理:

    # 压缩一个图片
    def compress(fileName):
        srcPath = PngSrcRoot + '/' + fileName
        outPath = SaveRoot + '/' + fileName
        if SaveToOriginalDir:   # 使用 .png 后缀,且通过 -f 覆盖源文件
            cmd = PngquantExe + " -f --ext "+ file_end + " " + srcPath + " --quality " + compress_quality
            os.system(cmd)
            return
        else:                   # 默认压缩到当前目录下,并加上 '-fs8.png' 后缀
            cmd = PngquantExe + " --ext "+ file_temp_end + " " + srcPath + " --quality " + compress_quality
            os.system(cmd)
        # 复制到文件夹
        fileOriginalName = os.path.splitext(fileName)[0]
        compressed_srcpath = PngSrcRoot + '/'+fileOriginalName + file_temp_end
        if os.path.exists(compressed_srcpath):
            if os.path.exists(outPath):
                os.remove(outPath)
            shutil.move(compressed_srcpath, outPath)          #移动文件

    覆盖源文件的直接使用命令参数 -f--force 即可,保存到另外目录下的使用 -fs8.png 做后缀,移动到目标地址时再改名即可。当然也可以直接使用 -o 参数,达到一样的效果,省去了移动文件的操作。

完整的脚本如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
​
import os
import os.path
import shutil
import sys
​
# 压缩结果是否覆盖源文件
SaveToOriginalDir=True
​
SelfPath = sys.path[0]
# 压缩工具
PngquantExe=SelfPath+".\pngquant\pngquant"  # 参考 https://pngquant.org/ 工具来实现的
​
​
# 工程根目录
PathWorkspaceRoot = os.path.abspath(
    os.path.join(os.path.dirname(__file__), ".."))
print u"当前工作目录: "+PathWorkspaceRoot
# 压缩资源目录
PngSrcRoot=PathWorkspaceRoot+"../../resource/@ui"
# 压缩后存放的目录
SaveRoot=PathWorkspaceRoot+"../../resource/@ui_pressed"
# 压缩过的图片列表
CompressFilesRecord=PngSrcRoot+'/compress_record.txt'
​
# 黑名单(不需要压缩的图片)
Backlits=[
    'NetworkTips_atlas0.png',
    'Common_atlas0.png',
    'BldgUpgrade_atlas0.png'
    ]
​
# 文件后缀名
file_end='.png'
file_temp_end='-fs8.png'
​
# 压缩品质范围
compress_quality='75-80'
​
# 文件列表
file_list=[]
​
# 清理旧文件
def initDir():
    global SaveRoot
    if(SaveToOriginalDir):
        if os.path.exists(CompressFilesRecord):
            print u"图片已经压缩过了!"
            return
        SaveRoot = PngSrcRoot
    else:
        if os.path.exists(SaveRoot):
            print u"压缩文件存放目录清空"
            shutil.rmtree(SaveRoot)
        print u"创建压缩文件存放目录:"+SaveRoot
        os.makedirs(SaveRoot)
​
# 获取文件列表
def getImages():
    print u"========= 开始遍历图片"
    global file_list
    files = os.listdir(PngSrcRoot)
    for file in files:
        # 过滤出 png 图片
        if os.path.isdir(file):
            print u"过滤掉文件目录:"+file
        else:
            endStr = os.path.splitext(file)[1]
            if endStr == file_end:
                if isBack(file):
                    print u"过滤掉黑名单中的文件:"+file
                else:
                    file_list.append(file)
                    # print u"文件 " + file + u" 添加到压缩列表"
​
# 开始图片压缩任务
def startCompress():
    print u"========= 开始压缩图片"
    record_file = open(CompressFilesRecord,'w')
    if not os.path.exists(CompressFilesRecord):
        print u"创建压缩文件日志文件"
    for file in file_list:
        print u"压缩图片:"+file
        compress(file)
        record_file.write(file+'\n')
    record_file.close()
​
def main():
    initDir()
    getImages()
    startCompress()
    print u"========= 图片压缩完成"
    
# 判断是否在黑名单中
def isBack(filePath):
    for i in Backlits:
        if(filePath.find(i) != -1):
            return True
    return False
​
# 压缩一个图片
def compress(fileName):
    srcPath = PngSrcRoot + '/' + fileName
    outPath = SaveRoot + '/' + fileName
    if SaveToOriginalDir:   # 使用 .png 后缀,且通过 -f 覆盖源文件
        cmd = PngquantExe + " -f --ext "+ file_end + " " + srcPath + " --quality " + compress_quality
        os.system(cmd)
        return
    else:                   # 默认压缩到当前目录下,并加上 '-fs8.png' 后缀
        cmd = PngquantExe + " --ext "+ file_temp_end + " " + srcPath + " --quality " + compress_quality
        os.system(cmd)
    # 复制到文件夹
    fileOriginalName = os.path.splitext(fileName)[0]
    compressed_srcpath = PngSrcRoot + '/'+fileOriginalName + file_temp_end
    if os.path.exists(compressed_srcpath):
        if os.path.exists(outPath):
            os.remove(outPath)
        shutil.move(compressed_srcpath, outPath)          #移动文件
​
if __name__ == '__main__':
    main()
    sys.exit(0)

执行方式可以在脚本目录下执行:

$ python 脚本名称.py

假如使用 Visual Studio Code 的话,可以直接添加一个任务:

  • task.json 中的 "tasks" 添加一个任务:

    {
        "label": "压缩 UI 图片", // 压缩 ui 图片
        "type": "shell",
        "presentation": {
            "echo": true,
            "reveal": "always",
            "focus": true,
            "panel": "shared"
        },
        "command": "python",
        "args": [
            "${workspaceRoot}/subproj/png图片压缩工具/ImgCompress.py" // 上面压缩脚本的相对路径
        ],
        "group": "build",
        "problemMatcher": []
    }
  • 执行时在 VS Code 使用快捷键 Ctrl+Shift+P 换出任务列表,选择 压缩 UI 图片 即可开始执行上面的压缩脚本。

 

pngquant 相关参数:

  • --quality min-max

    min 和 max 是从 0-100 的数值,用于设置压缩后图片的品质,品质越高压缩率越低;如果转换后的图片比最低品质还低,就不保存,并返回错误码99

  • --ext new.png

    设置输出图片的后缀名,默认使用 -fs8.png 做后缀(防止与源文件重名),假如设置 -ext=.png 则需要带上 --force 参数,否则会提示输出文件与输入文件重名无法覆盖;

  • -o out.png--output out.png

    压缩后图片的输出路径设置参数,不设置则默认输出当源文件相同路径下;

  • --skip-if-larger

    假如压缩后的图片文件比源文件还大,则放弃压缩结果;

  • --speed N

    转换速度与品质的比例。1(最佳品质),10(速度最快),默认是3;

  • --nofs

    禁用 Floyd–Steinberg dithering (即基于错误扩散的抖动算法)效果。

    而另外一个参数 --floyd=0.5 则用于控制抖动的等级,取值范围 0-1 ,0表示无抖动(等价于--nofs),1表示满级,这里 = 符号是必须的;

  • --posterize bits

    按位数减少调色板的精度。当图像在低深度屏幕上显示时使用(例如,16位显示或压缩的纹理在ARBB44格式);

  • --strip

    不要复制可选的 PNG 块。在MAC(使用Cocoa reader)时,元数据总是被删除。

 

参考

文章最后发布于: 2018-10-13 16:53:45
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览