TexturePacker是一個把好多小圖打成大圖的軟件,生成的是大圖以及小圖在大圖位置的.plist描述文件,但是不支持把大圖還原成小圖。網上偷的圖一般都是大圖和plist,想得到小圖比較麻煩,於是乎用python寫了個TexturePacker反向工具,把大圖導成小圖。
1.python要用到的庫
python的圖片處理要用到PIL(Python Image Library),mac10.10下安裝PIL會報
fatal error: 'X11/Xlib.h' file not found的錯,解決方法在此。而且在裝PIL前要先裝zlib/libpng/jpeg,安裝方法自行百度。
plist解析用了xml.dom為python自帶的庫,不用裝。
2.TexturePacker導出的plist結構分析
plist文件如下所示。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 <plist version="1.0"> 4 <dict> 5 <key>frames</key> 6 <dict> 7 <key>grossini_dance_03.png</key> 8 <dict> 9 <key>frame</key> 10 <string>{{46,324},{63,109}}</string> 11 <key>offset</key> 12 <string>{-6,-1}</string> 13 <key>rotated</key> 14 <false/> 15 <key>sourceColorRect</key> 16 <string>{{5,7},{63,109}}</string> 17 <key>sourceSize</key> 18 <string>{85,121}</string> 19 </dict> 20 </dict> 21 <key>metadata</key> 22 <dict> 23 <key>format</key> 24 <integer>2</integer> 25 <key>realTextureFileName</key> 26 <string>bbb.png</string> 27 <key>size</key> 28 <string>{512,512}</string> 29 <key>smartupdate</key> 30 <string>$TexturePacker:SmartUpdate:ea1bbb1419cd4c346debb54e1a7d5de2:1/1$</string> 31 <key>textureFileName</key> 32 <string>bbb.png</string> 33 </dict> 34 </dict> 35 </plist>
frames對應的value是所有小圖的信息。key為小圖的名字,dict為小圖的信息。
frame為圖片小圖位置及大小(這個大小是經過Trim的大小,TP會把png的無像素白邊剔除,來減小圖片的大小,也就是說大圖中小圖的大小不一定等與小圖真正的大小)。如上plist{{46,324},{63,109}},46和324為小圖在大圖中x,y坐標,{63,109}為經過裁剪的圖片大小,圖2其實是png格式黃色背景是空的,為了看着方便,加兩個黃色背景。{63,109}是紅框內的大小。
圖1
圖2
圖3
rotated是是否旋轉,這個光頭沒有旋轉,大圖中的play按鈕有旋轉。
sourceSize為小圖的大小。{85, 121}為小圖整個大小,沒有經過Trim。
sourceColorRect為經過Trim的圖在小圖中的起始坐標及大小。{{5,7},{63, 109}}中{5,7}為圖2中紅框左上角的坐標,{63,109}為大圖中小圖的大小。
offset為中心坐標偏移。圖3中小紅點處為原大小圖中心點p1,小紅點左邊的交叉點為經過Trim的圖片中心點p2,以p1為原點,p2的坐標就是這個offset{-6,-1},x向負軸偏移6像素,y向負軸偏移1像素,這里比較奇怪,y軸好像是向上為正了。
不知這個有offset有神馬用,下面的代碼沒有用offset,用sourceColorRect、sourceSize、frame、rotated就可以確定出小圖的樣子。
3.代碼分析
代碼中用到了PIL,PIL的參考手冊在這,程序中用到了open、new、crop、crop、rotate幾個api。
代碼分三個文件,主文件TexturePacker.py,運行此來生成小圖。PlistToDict.py來解析plist文件,生成map,key為小圖名,value為小圖信息。BraceParser.py為{{2,3},{4,5}}生成列表[[2,3][4,5]]。
知道了plist的含義,稍微會用PIL,代碼應該很好理解,代碼如下,玩具代碼,莫要嘲笑。
TexturePacker.py,類TextureParser第一個參數是plist及png文件位置,第二個為文件名字。
1 import PIL.Image as Image 2 import BraceParser 3 import PlistToDict 4 5 class TextureParser(object): 6 def __init__(self, path, name): 7 self.__resPath = path; 8 self._name = name; 9 self.__plistDict = PlistToDict.PlistToDict(path + "/" + name + ".plist").createDict(); 10 11 #return map[picName : {originPoint : {x:, y:}, size : {width:, height:}}] 12 def __getSmallPicInfos(self): 13 picInfo = {}; 14 for key, value in self.__plistDict["frames"].items(): 15 size = BraceParser.BraceParser(value["sourceSize"]).createList(); 16 origin = BraceParser.BraceParser(value["frame"]).createList(); 17 sourceSize = BraceParser.BraceParser(value["sourceColorRect"]).createList(); 18 picInfo[key] = {"size" : size, 19 "origin" : [origin[0][0], origin[0][1]], 20 "colorOrigin" : [sourceSize[0][0], sourceSize[0][1]], 21 "colorSize" : [sourceSize[1][0], sourceSize[1][1]], 22 "isRotated" : value["rotated"] 23 }; 24 return picInfo; 25 26 def smallPicsCreate(self, pathToStore = None): 27 image = Image.open(self.__resPath + "/" + self._name + ".png"); 28 picInfos = self.__getSmallPicInfos(); 29 for k, v in picInfos.items(): 30 if v["isRotated"] == True: 31 v["size"][0], v["size"][1] = v["size"][1], v["size"][0]; 32 v["colorSize"][0], v["colorSize"][1] = v["colorSize"][1], v["colorSize"][0]; 33 newImage = Image.new("RGBA", (int(v["size"][0]),int(v["size"][1]))); 34 box = (int(v["origin"][0]), int(v["origin"][1]), 35 int(v["origin"][0] + v["colorSize"][0]), int(v["origin"][1] + v["colorSize"][1])); 36 region = image.crop(box); 37 newImage.paste(region, (int(v["colorOrigin"][0]), int(v["colorOrigin"][1]))); 38 if v["isRotated"] == True: 39 newImage = newImage.rotate(90); 40 newImage.save(self.__resPath + k); 41 42 if __name__ == "__main__": 43 textureUnPacker = TextureParser("/Users/adv/Desktop/", "bbb"); 44 textureUnPacker.smallPicsCreate(); 45 print("success!")
PlistToDict.py,用的是dom解析plist。dom怎么用自行百度。
1 from xml.dom import minidom 2 3 class PlistToDict(object): 4 def __init__(self, plistPath): 5 dom = minidom.parse(plistPath); 6 self.__root = dom.documentElement; 7 8 # get root dict 9 def __getFirstDictDoc(self): 10 children = self.__root.childNodes; 11 for v in children: 12 if v.nodeType == v.ELEMENT_NODE and v.nodeName == "dict": 13 return v; 14 return None; 15 16 # get value by key in doc's children 17 def __getValueDocByKey(self, doc, key): 18 children = doc.childNodes; 19 for v in children: 20 if v.nodeType == v.ELEMENT_NODE and v.nodeName == "key" and v.firstChild.nodeValue == key: 21 node = v.nextSibling; 22 while node.nodeType != node.ELEMENT_NODE: 23 node = node.nextSibling; 24 if node == None: 25 return None; 26 return node; 27 return None; 28 29 def __firstElementNodeName(self, doc): 30 for v in doc.childNodes: 31 if v.nodeType == v.ELEMENT_NODE: 32 return v.nodeName; 33 34 def __docToDict(self, dom, dic): 35 keys = self.__getAllKeyValuesInDoc(dom); 36 for key in keys: 37 valueNode = self.__getValueDocByKey(dom, key); 38 if valueNode.nodeName == "dict": 39 dic[key] = {} 40 self.__docToDict(valueNode, dic[key]); 41 elif valueNode.nodeName == "false": 42 dic[key] = False; 43 elif valueNode.nodeName == "true": 44 dic[key] = True; 45 else: 46 dic[key] = valueNode.firstChild.nodeValue; 47 48 def __getAllKeyValuesInDoc(self, doc): 49 ret = []; 50 for v in doc.childNodes: 51 if v.nodeName == "key": 52 ret.append(v.firstChild.nodeValue); 53 return ret; 54 55 def createDict(self): 56 rootDict = self.__getFirstDictDoc(); 57 ret = {}; 58 self.__docToDict(rootDict, ret); 59 return ret;
BraceParser.py,用來解析括號。
1 class BraceParser(object): 2 def __init__(self, str): 3 self.__strToParse = str.replace(" ", ""); 4 5 def __firstStrIsLeftBrace(self, str): 6 return True if str[0] == "{" else False; 7 8 def __subOutBrace(self, str): 9 return str[1:-1]; 10 11 def __findAllSeqCommaPos(self, str): 12 bracketNum = 0; 13 ret = []; 14 for i, v in enumerate(str): 15 if v == "{": 16 bracketNum += 1; 17 elif v == "}": 18 bracketNum -= 1; 19 elif v == ",": 20 if bracketNum == 0: 21 ret.append(i); 22 return ret; 23 24 # {111,324},{100,100} return ["{111,324}", "{100,100}"] 25 def __getAllBraceStrs(self, str): 26 listStr = []; 27 posList = self.__findAllSeqCommaPos(str); 28 lastPos = -1; 29 for v in posList: 30 listStr.append(str[lastPos + 1: v]); 31 lastPos = v; 32 listStr.append(str[lastPos + 1: ]); 33 return listStr; 34 35 def __getValue(self, str): 36 listStr = str.split(","); 37 return listStr[0], listStr[1]; 38 39 40 def __listCreate(self, str, listIns): 41 if self.__firstStrIsLeftBrace(str) == True: 42 braceStrs = self.__getAllBraceStrs(str); 43 for v in braceStrs: 44 subList = []; 45 listIns.append(subList); 46 self.__listCreate(self.__subOutBrace(v), subList); 47 else: 48 x, y = self.__getValue(str); 49 listIns.append(float(x)); 50 listIns.append(float(y)); 51 52 def createList(self): 53 listIns = []; 54 str = self.__subOutBrace(self.__strToParse); 55 self.__listCreate(str, listIns); 56 return listIns;
最后,我想問博客園怎么上傳附件?