python Png圖片壓縮工具
引言
最近在做 H5 小遊戲的開發,與 App 不同,由於 H5 所有的資源都是通過 CDN 獲取的,考慮到網路資源載入速度的問題,優化資源顯得格外重要。因此,圖片資源的壓縮也是必不可少的。
起源
起初,我們在 windows 下是通過一個叫做 PNGoo 的 GUI 工具來實現圖片資源批量壓縮的。但考慮壓縮資源還需要啟動一個應用,將圖片資源拖進去再開始壓縮,顯然不夠智慧,希望通過 python 指令碼自動完成。
後來,找到了這個工具的 Github 原始碼 pngoo ,才發現這個工具是基於 pngquant 這個開源庫實現的,類似的基於此壓縮演算法庫工具還有 Pngyu (Github 原始碼:
重點是這個開源的壓縮庫壓縮比達到 60%-80% ,也是相當可觀了。
原始碼資源
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)時,元資料總是被刪除。