python Png圖片壓縮工具

引言

最近在做 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)時,元數據總是被刪除。

 

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章