1. 程式人生 > >python Png圖片壓縮工具

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)時,元資料總是被刪除。

參考