Last active
March 14, 2018 18:05
-
-
Save tuaplicacionpropia/30066ce3a16b26bfbd85fa185d315a64 to your computer and use it in GitHub Desktop.
Create CUSTOM SPRITESHEET from multiple selected sprites and from multiple spritesheets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
#coding:utf-8 | |
from __future__ import print_function | |
from PIL import Image | |
import math | |
import os | |
import tempfile | |
import shutil | |
from threading import Thread | |
import sys | |
import libarchive.public | |
import shutil | |
import psd_tools | |
import itertools | |
UNPACK_SPLIT_SIZE = 2 | |
#https://stackoverflow.com/questions/13584586/sprite-sheet-detect-individual-sprite-bounds-automatically?rq=1 | |
class Sprite: | |
def __init__(self): | |
self.start_x = -1 | |
self.start_y = -1 | |
self.end_x = -1 | |
self.end_y = -1 | |
def expand (self, point): | |
if (self.start_x < 0 and self.start_y < 0 and self.end_x < 0 and self.end_y < 0): | |
self.start_x = point[0] | |
self.start_y = point[1] | |
self.end_x = point[0] | |
self.end_y = point[1] | |
else: | |
if (point[0] < self.start_x): | |
self.start_x = point[0] | |
if (point[0] > self.end_x): | |
self.end_x = point[0] | |
if (point[1] < self.start_y): | |
self.start_y = point[1] | |
if (point[1] > self.end_y): | |
self.end_y = point[1] | |
def belongs (self, point): | |
result = False | |
result = True | |
result = result and point[0] >= self.start_x and point[0] <= self.end_x | |
result = result and point[1] >= self.start_y and point[1] <= self.end_y | |
return result | |
def __str__(self): | |
result = "" | |
result = result + "(" | |
result = result + str(self.start_x) | |
result = result + ", " | |
result = result + str(self.start_y) | |
result = result + ", " | |
result = result + str(self.end_x) | |
result = result + ", " | |
result = result + str(self.end_y) | |
result = result + ")" | |
return result | |
def mergeSprites (self, sprite2): | |
result = None | |
sprite1 = self | |
if (sprite1 != None and sprite2 != None): | |
result = Sprite() | |
result.start_x = min(sprite1.start_x, sprite2.start_x) | |
result.start_y = min(sprite1.start_y, sprite2.start_y) | |
result.end_x = max(sprite1.end_x, sprite2.end_x) | |
result.end_y = max(sprite1.end_y, sprite2.end_y) | |
return result | |
@staticmethod | |
def loadSprite (pos, sprites): | |
result = None | |
for sprite in sprites: | |
if sprite.belongs(pos): | |
result = sprite | |
break | |
return result | |
@staticmethod | |
def firstNonSprites (sprites, minimumSprite): | |
result = None | |
for sprite in sprites: | |
if (sprite.end_x - sprite.start_x + 1) < minimumSprite or (sprite.end_y - sprite.start_y + 1) < minimumSprite: | |
result = sprite | |
break | |
return result | |
def findNextSprite (self, sprites): | |
result = None | |
pivot = self | |
distance = 99999999 | |
for sprite in sprites: | |
if sprite != pivot: | |
itemDistance = pivot.distanceSprites(sprite) | |
if (itemDistance < distance): | |
distance = itemDistance | |
result = sprite | |
return result | |
def distancePointSprite (self, point): | |
result = 99999999 | |
sprite = self | |
if (point != None): | |
result = min(SpriteTools.distancePoints(point, (sprite.start_x, sprite.start_y)), result) | |
result = min(SpriteTools.distancePoints(point, (sprite.end_x, sprite.start_y)), result) | |
result = min(SpriteTools.distancePoints(point, (sprite.start_x, sprite.end_y)), result) | |
result = min(SpriteTools.distancePoints(point, (sprite.end_x, sprite.end_y)), result) | |
return result | |
def distanceSprites (self, sprite2): | |
result = 99999999 | |
sprite1 = self | |
if (sprite2 != None): | |
result = min(sprite1.distancePointSprite((sprite2.start_x, sprite2.start_y)), result) | |
result = min(sprite1.distancePointSprite((sprite2.end_x, sprite2.start_y)), result) | |
result = min(sprite1.distancePointSprite((sprite2.start_x, sprite2.end_y)), result) | |
result = min(sprite1.distancePointSprite((sprite2.end_x, sprite2.end_y)), result) | |
return result | |
class SpriteTools: | |
@staticmethod | |
def exploreBoundedBox (pStart, img, size): | |
result = None | |
q = [] | |
q.append(pStart) | |
result = Sprite() | |
result.expand(pStart) | |
marks = [] | |
while (len(q) > 0): | |
p = q.pop(0) | |
result.expand((p[0], p[1])) | |
neighbouring = SpriteTools.loadEightNeighbouringPixels(p, img, size) | |
for n in neighbouring: | |
if SpriteTools.hayColor(img, n[0], n[1], size) and not n in marks: | |
#print("EXPLORE HAY COLOR (" + str(n[0]) + ", " + str(n[1]) + ")") | |
marks.append(n) | |
q.append(n) | |
if (size > 1): | |
result.end_x += (size - 1) | |
result.end_y += (size - 1) | |
return result | |
@staticmethod | |
def appendPointIfValid (newPoint, result, img): | |
if (newPoint[0] >= 0 and newPoint[1] >= 0 and newPoint[0] < img.width and newPoint[1] < img.height): | |
result.append(newPoint) | |
@staticmethod | |
def loadFourNeighbouringPixels (point, img, size=1): | |
result = None | |
result = [] | |
SpriteTools.appendPointIfValid((point[0] + 0, point[1] - size), result, img) | |
SpriteTools.appendPointIfValid((point[0] - size, point[1] + 0), result, img) | |
SpriteTools.appendPointIfValid((point[0] + size, point[1] + 0), result, img) | |
SpriteTools.appendPointIfValid((point[0] + 0, point[1] + size), result, img) | |
return result | |
@staticmethod | |
def loadEightNeighbouringPixels (point, img, size=1): | |
result = None | |
result = [] | |
SpriteTools.appendPointIfValid((point[0] + 0, point[1] - size), result, img) | |
SpriteTools.appendPointIfValid((point[0] - size, point[1] + 0), result, img) | |
SpriteTools.appendPointIfValid((point[0] + size, point[1] + 0), result, img) | |
SpriteTools.appendPointIfValid((point[0] + 0, point[1] + size), result, img) | |
SpriteTools.appendPointIfValid((point[0] - size, point[1] - size), result, img) | |
SpriteTools.appendPointIfValid((point[0] + size, point[1] - size), result, img) | |
SpriteTools.appendPointIfValid((point[0] - size, point[1] + size), result, img) | |
SpriteTools.appendPointIfValid((point[0] + size, point[1] + size), result, img) | |
return result | |
#Pitagoras | |
@staticmethod | |
def distancePoints (point1, point2): | |
result = 99999999 | |
if (point1 != None and point2 != None): | |
a = abs(point2[0] - point1[0]) | |
b = abs(point2[1] - point1[1]) | |
result = math.sqrt(math.pow(a, 2) + math.pow(b, 2)) | |
return result | |
@staticmethod | |
def fixMergeSprites (sprites, minimumSize): | |
result = [] | |
pivotNonSprite = Sprite.firstNonSprites(sprites, minimumSize) | |
while (pivotNonSprite != None): | |
nextSprite = pivotNonSprite.findNextSprite(sprites) | |
if nextSprite == None: | |
break | |
mergeSprite = pivotNonSprite.mergeSprites(nextSprite) | |
sprites.remove(nextSprite) | |
sprites.remove(pivotNonSprite) | |
sprites.append(mergeSprite) | |
pivotNonSprite = Sprite.firstNonSprites(sprites, minimumSize) | |
result = sprites | |
return result | |
@staticmethod | |
def hayColor (im, startx, starty, size=16): | |
result = False | |
if (im != None and startx >= 0 and starty >= 0 and size > 0): | |
maxy = min(starty + size, im.height) - 1 | |
maxx = min(startx + size, im.width) - 1 | |
for y in range(starty, maxy, 1): | |
for x in range(startx, maxx, 1): | |
pixel = im.getpixel((x, y)) | |
if pixel[3] > 0: | |
result = True | |
break | |
return result | |
@staticmethod | |
def proportionalSize (size, spriteSize=16): | |
result = None | |
result = size | |
if (spriteSize > 1): | |
result = int(size / spriteSize) * spriteSize | |
result += spriteSize if size % spriteSize > 0 else 0 | |
return result | |
class FileTools: | |
@staticmethod | |
def createTmpFile (filename): | |
result = None | |
tmpdir = tempfile.mkdtemp(prefix='.tmp_') | |
result = os.path.join(tmpdir, filename) | |
return result | |
@staticmethod | |
def createTmpFolder (folderName): | |
result = None | |
tmpdir = tempfile.mkdtemp(prefix='.tmp_') | |
directory = os.path.join(tmpdir, folderName) | |
if not os.path.exists(directory): | |
os.makedirs(directory) | |
result = directory | |
return result | |
@staticmethod | |
def getFileBasename (fullPath): | |
result = None | |
fullName = os.path.basename(fullPath) | |
result = os.path.splitext(fullName)[0] | |
return result | |
@staticmethod | |
def getFileName (fullPath): | |
result = None | |
result = os.path.basename(fullPath) | |
return result | |
@staticmethod | |
def existsFile (path): | |
result = False | |
if (os.path.isfile(path)): | |
result = True | |
return result | |
@staticmethod | |
def remove (path): | |
result = False | |
if (os.path.isfile(path)): | |
os.remove(path) | |
elif (os.path.isdir(path)): | |
shutil.rmtree(path) | |
if not os.path.exists(path): | |
result = True | |
return result | |
@staticmethod | |
def getFolder (path): | |
result = None | |
result = os.path.dirname(path) | |
return result | |
@staticmethod | |
def mkdir (parent, folderName): | |
result = None | |
folder = os.path.join(parent, folderName) | |
if not os.path.exists(folder): | |
os.makedirs(folder) | |
result = folder | |
return result | |
@staticmethod | |
def copyFile (path): | |
result = None | |
result = FileTools.createTmpFile(FileTools.getFileName(path)) | |
shutil.copy2(path, result) | |
return result | |
@staticmethod | |
def decompress(path): | |
result = None | |
folder = FileTools.createTmpFolder("tmpzip") | |
#print("folder " + folder) | |
for entry in libarchive.public.file_pour(path): | |
print(entry) | |
itemFile = os.path.join(folder, str(entry)) | |
#print("middle= " + itemFile) | |
if itemFile.find("/") >= 0: | |
FileTools.mkdir(folder, os.path.dirname(itemFile)) | |
if not itemFile.endswith("/"): | |
#print("writing... " + itemFile) | |
out = open(itemFile, 'wb') | |
for block in entry.get_blocks(): | |
out.write(block) | |
result = folder | |
if path.endswith("tar.7z"): | |
newFile = os.path.join(folder, FileTools.getFileBasename(path)) | |
result = FileTools.decompress(newFile) | |
elif path.endswith("t7z"): | |
newFile = os.path.join(folder, FileTools.getFileBasename(path) + ".tar") | |
result = FileTools.decompress(newFile) | |
return result | |
@staticmethod | |
def _checkProcessing (path, array): | |
result = False | |
if (path != None and array != None): | |
for item in array: | |
if (path.find(item) == 0): | |
result = True | |
break | |
return result | |
@staticmethod | |
def getFileSize (path): | |
result = 0L | |
if (path != None): | |
result = os.stat(path).st_size | |
return result | |
class Spritesheet: | |
def __init__ (self): | |
pass | |
# def unzip (self, iFile): | |
# return FileTools.decompress(iFile) | |
def unpack (self, imgFile, minimumSprite = 16): | |
result = None | |
result = [] | |
minimumSprite = int(minimumSprite) | |
#imgFile="imagegrid.png" | |
im = Image.open(imgFile) | |
print(im.format, im.size, im.mode) | |
if (im.mode != "RGBA"): | |
im = im.convert("RGBA") | |
#PNG (640, 252) RGBA | |
#im.show() | |
print("width = " + str(im.width)) | |
print("height = " + str(im.height)) | |
sprites = [] | |
for y in range(0, im.height, minimumSprite): | |
print("row = " + str(y+1) + " of " + str(im.height)) | |
for x in range(0, im.width, minimumSprite): | |
haycolor = SpriteTools.hayColor(im, x, y, minimumSprite) | |
if (haycolor): | |
#print("HAY COLOR (" + str(x) + ", " + str(y) + ")") | |
pos = (x, y) | |
sprite = Sprite.loadSprite(pos, sprites) | |
if (sprite != None): | |
x = sprite.end_x + (minimumSprite - 1) | |
else: | |
sprite = SpriteTools.exploreBoundedBox(pos, im, minimumSprite) | |
#print(">>>>>> EXPLORE sprite " + str(sprite)) | |
sprites.append(sprite) | |
if (minimumSprite > 1): | |
sprites = SpriteTools.fixMergeSprites(sprites, minimumSprite) | |
#print("sprites") | |
#print(str(sprites)) | |
idx = 1 | |
tmpFolder = FileTools.createTmpFolder(FileTools.getFileBasename(imgFile)) | |
for sprite in sprites: | |
print("sprite " + str(idx) + ". -> " + str(sprite)) | |
imSprite = im.crop((sprite.start_x, sprite.start_y, sprite.end_x + 1, sprite.end_y + 1)) | |
#imSprite.show() | |
imSpriteFilename = "sprite" + str(idx) + ".png" | |
imSpriteFile = os.path.join(tmpFolder, imSpriteFilename) | |
imSprite.save(imSpriteFile) | |
idx += 1 | |
result.append(imSpriteFile) | |
return result | |
def gridify (self, imgFile, gridSize=16): | |
result = None | |
result = [] | |
gridSize = int(gridSize) | |
#imgFile = "free3.png" | |
im = Image.open(imgFile) | |
print(im.format, im.size, im.mode) | |
#PNG (640, 252) RGBA | |
#im.show() | |
print("width = " + str(im.width)) | |
print("height = " + str(im.height)) | |
numColumns = int(im.width / gridSize) | |
numColumns += 1 if im.width % gridSize > 0 else 0 | |
numRows = int(im.height / gridSize) | |
numRows += 1 if im.height % gridSize > 0 else 0 | |
folder = FileTools.createTmpFolder(FileTools.getFileBasename(imgFile)) | |
for row in range(numRows): | |
arrayRow = [] | |
for col in range(numColumns): | |
sx = col*gridSize | |
sy = row*gridSize | |
ex = sx + gridSize | |
ey = sy + gridSize | |
imSprite = im.crop((sx, sy, ex, ey)) | |
spriteName = "grid_" + str(col) + "x" + str(row) + ".png" | |
spriteFile = os.path.join(folder, spriteName) | |
imSprite.save(spriteFile) | |
arrayRow.append(spriteFile) | |
result.append(arrayRow) | |
return result | |
def buildGridInkscape (self, gridSprites, gridSize=16): | |
result = None | |
numRows = len(gridSprites) | |
numColumns = len(gridSprites[0]) | |
gridSize = int(gridSize) | |
result = FileTools.createTmpFile("grid.svg") | |
f = open(result, 'w') | |
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n') | |
f.write('<!-- Created with Inkscape (http://www.inkscape.org/) -->\n') | |
f.write('<svg\n') | |
f.write(' xmlns:dc="http://purl.org/dc/elements/1.1/"\n') | |
f.write(' xmlns:cc="http://creativecommons.org/ns#"\n') | |
f.write(' xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n') | |
f.write(' xmlns:svg="http://www.w3.org/2000/svg"\n') | |
f.write(' xmlns="http://www.w3.org/2000/svg"\n') | |
f.write(' xmlns:xlink="http://www.w3.org/1999/xlink"\n') | |
f.write(' xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"\n') | |
f.write(' xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"\n') | |
f.write(' width="64"\n') | |
f.write(' height="64"\n') | |
f.write(' viewBox="0 0 64.000001 64.000001"\n') | |
f.write(' id="svg2"\n') | |
f.write(' version="1.1"\n') | |
f.write(' inkscape:version="0.91 r13725"\n') | |
f.write(' sodipodi:docname="drawing.svg">\n') | |
f.write(' <defs\n') | |
f.write(' id="defs4" />\n') | |
f.write(' <sodipodi:namedview\n') | |
f.write(' id="base"\n') | |
f.write(' pagecolor="#ffffff"\n') | |
f.write(' bordercolor="#666666"\n') | |
f.write(' borderopacity="1.0"\n') | |
f.write(' inkscape:pageopacity="0.0"\n') | |
f.write(' inkscape:pageshadow="2"\n') | |
f.write(' inkscape:zoom="11.2"\n') | |
f.write(' inkscape:cx="32.271954"\n') | |
f.write(' inkscape:cy="50.163736"\n') | |
f.write(' inkscape:document-units="px"\n') | |
f.write(' inkscape:current-layer="layer1"\n') | |
f.write(' showgrid="true"\n') | |
f.write(' units="px"\n') | |
f.write(' inkscape:window-width="1855"\n') | |
f.write(' inkscape:window-height="1056"\n') | |
f.write(' inkscape:window-x="65"\n') | |
f.write(' inkscape:window-y="24"\n') | |
f.write(' inkscape:window-maximized="1">\n') | |
f.write(' <inkscape:grid\n') | |
f.write(' type="xygrid"\n') | |
f.write(' id="grid8068"\n') | |
f.write(' spacingx="' + str(gridSize) + '"\n') | |
f.write(' spacingy="' + str(gridSize) + '"\n') | |
f.write(' dotted="false"\n') | |
f.write(' originx="0" />\n') | |
f.write(' </sodipodi:namedview>\n') | |
f.write(' <metadata\n') | |
f.write(' id="metadata7">\n') | |
f.write(' <rdf:RDF>\n') | |
f.write(' <cc:Work\n') | |
f.write( 'rdf:about="">\n') | |
f.write(' <dc:format>image/svg+xml</dc:format>\n') | |
f.write(' <dc:type\n') | |
f.write(' rdf:resource="http://purl.org/dc/dcmitype/StillImage" />\n') | |
f.write(' <dc:title></dc:title>\n') | |
f.write(' </cc:Work>\n') | |
f.write(' </rdf:RDF>\n') | |
f.write(' </metadata>\n') | |
f.write(' <g\n') | |
f.write(' inkscape:label="Capa 1"\n') | |
f.write(' inkscape:groupmode="layer"\n') | |
f.write(' id="layer1"\n') | |
f.write(' >\n') | |
for row in range(numRows): | |
arrayRow = gridSprites[row] | |
for col in range(numColumns): | |
spriteName = arrayRow[col] | |
if (SpriteTools.hayColor(Image.open(spriteName), 0, 0, gridSize)): | |
sx = col*gridSize | |
sy = row*gridSize | |
f.write(' <image\n') | |
f.write(' sodipodi:absref="' + spriteName + '"\n') | |
f.write(' xlink:href="' + spriteName + '"\n') | |
f.write(' width="' + str(gridSize) + '"\n') | |
f.write(' height="' + str(gridSize) + '"\n') | |
f.write(' preserveAspectRatio="none"\n') | |
f.write(' id="image' + str(spriteName) + '"\n') | |
f.write(' x="' + str(sx) + '"\n') | |
f.write(' y="' + str(sy) + '" />\n') | |
f.write(' </g>\n') | |
f.write('</svg>\n') | |
f.close() | |
return result | |
def editInkscape (self, svgFile): | |
result = None | |
os.system("inkscape -g " + svgFile) | |
result = FileTools.createTmpFile("svg.png") | |
os.system("inkscape -z -D -d " + str(90) + " -e " + result + " " + svgFile) | |
return result | |
def repack (self, imgFile, minimumSprite=16): | |
result = None | |
minimumSprite = int(minimumSprite) | |
gridSprites = self.gridify(imgFile, minimumSprite) | |
gridSvg = self.buildGridInkscape(gridSprites, minimumSprite) | |
exportInkscape = self.editInkscape(gridSvg) | |
result = [] | |
if (FileTools.existsFile(exportInkscape)): | |
sprites = self.unpack(exportInkscape, int(minimumSprite / UNPACK_SPLIT_SIZE)) | |
self.fillSprites(sprites, minimumSprite) | |
result = self.pack(sprites, minimumSprite, 2 * minimumSprite) | |
FileTools.remove(FileTools.getFolder(gridSprites[0][0])) | |
FileTools.remove(gridSvg) | |
FileTools.remove(exportInkscape) | |
return result | |
def _imSize (self, sprite): | |
result = 0 | |
im = Image.open(sprite) | |
result = im.width * im.height | |
print("size = " + str(result)) | |
im.close() | |
return result | |
def _imHeight (self, sprite): | |
result = 0 | |
im = Image.open(sprite) | |
result = im.height | |
im.close() | |
return result | |
def sort (self, imgFile, minimumSprite=16): | |
result = None | |
minimumSprite = int(minimumSprite) | |
sprites = self.unpack(imgFile, int(minimumSprite / UNPACK_SPLIT_SIZE)) | |
sprites.sort(key=self._imHeight) | |
result = self.pack(sprites, minimumSprite, (2 * minimumSprite)) | |
return result | |
def _genSplitByHeight (self, sprites, imgFile, name, curHeight, folder, minimumSprite=16): | |
result = None | |
#print("sprites>>>>>>>>>>>>>>>>>>>> = " + str(sprites)) | |
outPack = self.pack(sprites, minimumSprite, 0) | |
#print("outPack = " + str(outPack)) | |
newName = "height" + str(curHeight) + "_" + name + ".png" | |
result = os.path.join(folder, newName) | |
#print("result = " + str(result)) | |
os.rename(outPack, result) | |
return result | |
def selectByHeight (self, src, height, minimumSprite=16): | |
result = None | |
height = int(height) | |
minimumSprite = int(minimumSprite) | |
folder = FileTools.createTmpFolder(FileTools.getFileBasename(src)) | |
sprites = [] | |
for filename in os.listdir(src): | |
path = os.path.join(src, filename) | |
sprites.append(path) | |
for sprite in sprites: | |
self.fillSprite(sprite, minimumSprite) | |
idx = 1 | |
for sprite in sprites: | |
im = Image.open(sprite) | |
#print("im.height = " + str(im.height) + " cmp = " + str(height)) | |
if im.height == height: | |
#print("copy sprite = " + sprite) | |
sprFile = FileTools.copyFile(sprite) | |
#print("copy target sprite = " + sprFile) | |
newFile = os.path.join(folder, str(idx) + ".png") | |
os.rename(sprFile, newFile) | |
#print("copy newFile = " + newFile) | |
idx += 1 | |
im.close() | |
result = folder | |
return result | |
def rescaleFixedHeight (self, imgFile, fixedHeight): | |
result = None | |
result = FileTools.createTmpFile(FileTools.getFileName(imgFile)) | |
im = Image.open(imgFile) | |
newHeight = fixedHeight | |
factor = float(fixedHeight) / float(im.height) | |
fnewWidth = float(im.width) * factor | |
newWidth = int(fnewWidth) | |
if (float(int(fnewWidth)) + 0.49) < fnewWidth: | |
newWidth += 1 | |
newHeight += 1 | |
im.thumbnail((newWidth,newHeight), Image.ANTIALIAS) | |
im.save(result) | |
im.close() | |
return result | |
def preview (self, folder): | |
result = None | |
result = FileTools.createTmpFolder(FileTools.getFileBasename(folder)) | |
for filename in os.listdir(folder): | |
newName = "preview_" + filename | |
path = os.path.join(folder, filename) | |
newPath = os.path.join(result, newName) | |
im = Image.open(path) | |
if im.height > 848 and (im.width * im.height) > (848*848): | |
outfile = self.rescaleFixedHeight(path, 848) | |
os.rename(outfile, newPath) | |
else: | |
outfile = FileTools.copyFile(path) | |
os.rename(outfile, newPath) | |
im.close() | |
return result | |
def splitByHeight (self, imgFile, name, minimumSprite=16): | |
result = None | |
folder = FileTools.createTmpFolder(FileTools.getFileBasename(imgFile)) | |
minimumSprite = int(minimumSprite) | |
sprites = None | |
if os.path.isfile(imgFile): | |
sprites = self.unpack(imgFile, int(minimumSprite / UNPACK_SPLIT_SIZE)) | |
else: | |
sprites = [] | |
for filename in os.listdir(imgFile): | |
path = os.path.join(imgFile, filename) | |
sprites.append(path) | |
for sprite in sprites: | |
self.fillSprite(sprite, minimumSprite) | |
newSprites = [] | |
sprites.sort(key=self._imHeight) | |
#print("len sprites = " + str(len(sprites))) | |
splitHeight = None | |
curHeight = 0 | |
for sprite in sprites: | |
im = Image.open(sprite) | |
#print("sprite = " + str(sprite) + "; width = " + str(im.width) + "; height = " + str(im.height)) | |
if (curHeight <= 0 or im.height != curHeight): | |
if curHeight > 0: | |
self._genSplitByHeight(splitHeight, imgFile, name, curHeight, folder, minimumSprite) | |
splitHeight = [] | |
curHeight = im.height | |
splitHeight.append(sprite) | |
im.close() | |
if curHeight > 0: | |
self._genSplitByHeight(splitHeight, imgFile, name, curHeight, folder, minimumSprite) | |
result = folder | |
return result | |
''' | |
TREES SIZE: | |
16 | |
32 | |
48 | |
64 | |
96 | |
128 | |
160 | |
192 | |
256 | |
ORIGINAL | |
''' | |
def applyTreeSpriteMod (self, imgFile, minimumSprite=16, availableHeights=[16, 12, 10, 8, 6, 4, 3, 2, 1]): | |
result = None | |
result = [] | |
minimumSprite = int(minimumSprite) | |
self.fillSprite(imgFile, minimumSprite) | |
availableHeights = [16, 12, 10, 8, 6, 4, 3, 2, 1] | |
availableHeights.sort(reverse=True) | |
im = Image.open(imgFile) | |
originalWidth = im.width | |
originalHeight = im.height | |
im.close() | |
if (originalHeight <= availableHeights[0]): | |
result.append(imgFile) | |
for pnewHeight in availableHeights: | |
newHeight = pnewHeight * minimumSprite | |
if (newHeight < originalHeight): | |
factor = float(newHeight) / float(originalHeight) | |
newWidth = int(float(originalWidth) * factor) | |
print("newHeight = " + str(newHeight)) | |
print("originalHeight = " + str(originalHeight)) | |
print("factor = " + str(factor)) | |
print("originalWidth = " + str(originalWidth)) | |
print("newWidth = " + str(newWidth)) | |
print("") | |
im = Image.open(imgFile) | |
if (im.mode != "RGBA"): | |
im = im.convert("RGBA") | |
im.thumbnail((newWidth,newHeight), Image.ANTIALIAS) | |
outfile = FileTools.createTmpFile(FileTools.getFileBasename(imgFile) + ".png") | |
im.save(outfile) | |
im.close() | |
result.append(outfile) | |
if False and len(result) <= 0: | |
result.append(imgFile) | |
return result | |
def applyTreesMod (self, imgFile, minimumSprite=16): | |
result = None | |
minimumSprite = int(minimumSprite) | |
sprites = self.unpack(imgFile, int(minimumSprite / UNPACK_SPLIT_SIZE)) | |
newSprites = [] | |
for sprite in sprites: | |
newTrees = self.applyTreeSpriteMod(sprite, minimumSprite) | |
newSprites.extend(newTrees) | |
newSprites.sort(key=self._imHeight) | |
result = self.pack(newSprites, minimumSprite, (2 * minimumSprite)) | |
return result | |
def manualEdit (self, imgFile, minimumSprite=16): | |
result = None | |
minimumSprite = int(minimumSprite) | |
gridSprites = self.gridify(imgFile, minimumSprite) | |
gridSvg = self.buildGridInkscape(gridSprites, minimumSprite) | |
result = self.editInkscape(gridSvg) | |
FileTools.remove(FileTools.getFolder(gridSprites[0][0])) | |
FileTools.remove(gridSvg) | |
return result | |
def buildTheme (self, imgFile, minimumSprite=16): | |
result = None | |
minimumSprite = int(minimumSprite) | |
gridSprites = self.gridify(imgFile, minimumSprite) | |
gridSvg = self.buildGridInkscape(gridSprites, minimumSprite) | |
exportInkscape = self.editInkscape(gridSvg) | |
result = [] | |
if (FileTools.existsFile(exportInkscape)): | |
sprites = self.unpack(exportInkscape, int(minimumSprite / UNPACK_SPLIT_SIZE)) | |
self.fillSprites(sprites, minimumSprite) | |
result = sprites | |
FileTools.remove(FileTools.getFolder(gridSprites[0][0])) | |
FileTools.remove(gridSvg) | |
FileTools.remove(exportInkscape) | |
return result | |
#pack_sprites | |
def fillSprite (self, sprite, minimumSprite=16): | |
if sprite != None: | |
minimumSprite = int(minimumSprite) | |
im = Image.open(sprite) | |
modWidth = im.width % minimumSprite | |
modHeight = im.height % minimumSprite | |
newWidth = im.width + ((minimumSprite - modWidth) if modWidth > 0 else 0) | |
newHeight = im.height + ((minimumSprite - modHeight) if modHeight > 0 else 0) | |
if (newWidth != im.width or newHeight != im.height): | |
print("FILL PREV " + str(im.width) + "x" + str(im.height)) | |
print("FILL POST " + str(newWidth) + "x" + str(newHeight)) | |
newImage = Image.new("RGBA", (newWidth,newHeight)) | |
newImage.paste(im, (0, 0)) | |
newImage.save(sprite) | |
newImage.close() | |
im.close() | |
def fillSprites (self, sprites, minimumSprite=16): | |
minimumSprite = int(minimumSprite) | |
for sprite in sprites: | |
self.fillSprite(sprite, minimumSprite) | |
def packFolder (self, folder, minimumSprite=16, separator=32): | |
result = None | |
minimumSprite = int(minimumSprite) | |
separator = int(separator) | |
array = [] | |
for root, dirs, files in os.walk(folder): | |
for filename in files: | |
path = os.path.join(folder, filename) | |
if filename.endswith(".png"): | |
array.append(path) | |
break | |
print(str(array)) | |
#result = self.pack([array]) | |
result = self.pack(array, minimumSprite, separator) | |
return result | |
def showImage (self, imgFile): | |
im = Image.open(imgFile) | |
im.show() | |
def pack (self, array, minimumSprite=16, separator=32): | |
result = None | |
minimumSprite = int(minimumSprite) | |
separator = int(separator) | |
if (len(array) > 0 and not (isinstance(array[0], list) or isinstance(array[0], tuple))): | |
array = [array] | |
images = [] | |
for item in array: | |
for imgFile in item: | |
#im = Image.open(imgFile) | |
images.append(imgFile) | |
resIm = None | |
oneColumn = False | |
if oneColumn: | |
maxWidth = 0 | |
totalHeight = 0 | |
for imgFile in images: | |
im = Image.open(imgFile) | |
maxWidth = max(im.width, maxWidth) | |
totalHeight += im.height | |
im.close() | |
resIm = Image.new("RGBA", (maxWidth, totalHeight)) | |
prevHeight = 0 | |
for imgFile in images: | |
im = Image.open(imgFile) | |
resIm.paste(im, (0, prevHeight, im.width, prevHeight + im.height)) | |
prevHeight += im.height | |
im.close() | |
else: | |
maxWidth = 2048 | |
realWidth = 0 | |
rows = [] | |
rowWidth = 0 | |
curRow = [] | |
rows.append(curRow) | |
for imgFile in images: | |
im = Image.open(imgFile) | |
imWidth = SpriteTools.proportionalSize(im.width, minimumSprite) + separator | |
if ((rowWidth + imWidth) > maxWidth): | |
curRow = [] | |
rowWidth = 0 | |
rows.append(curRow) | |
rowWidth += imWidth | |
realWidth = max(realWidth, rowWidth) | |
curRow.append(imgFile) | |
im.close() | |
maxHeight = 0 | |
for row in rows: | |
maxRowHeight = 0 | |
for imgFile in row: | |
im = Image.open(imgFile) | |
imHeight = SpriteTools.proportionalSize(im.height, minimumSprite) + separator | |
maxRowHeight = max(imHeight, maxRowHeight) | |
im.close() | |
maxHeight += maxRowHeight | |
realHeight = maxHeight | |
newRealSquareWidth = int(math.sqrt(float(realWidth * realHeight)) + 1.0) | |
oldRealHeight = realHeight | |
print("realWidth = " + str(realWidth) + " realHeight = " + str(realHeight) + " square = " + str(newRealSquareWidth)) | |
if (True or newRealSquareWidth < maxWidth): | |
realWidth = newRealSquareWidth | |
realHeight = newRealSquareWidth | |
newRows = [] | |
rowWidth = 0 | |
curRow = [] | |
newRows.append(curRow) | |
curRealHeight = 0 | |
curRowHeight = 0 | |
for row in rows: | |
for imgFile in row: | |
im = Image.open(imgFile) | |
imWidth = SpriteTools.proportionalSize(im.width, minimumSprite) + separator | |
if ((rowWidth + imWidth) > realWidth): | |
curRow = [] | |
rowWidth = 0 | |
newRows.append(curRow) | |
curRealHeight += curRowHeight | |
curRowHeight = 0 | |
curRowHeight = max(curRowHeight, im.height) | |
rowWidth += imWidth | |
curRow.append(imgFile) | |
im.close() | |
curRealHeight += curRowHeight | |
# if (curRealHeight > realHeight): | |
# print("curRealHeight = " + str(curRealHeight) + " realHeight = " + str(realHeight)) | |
realHeight = curRealHeight | |
rows = newRows | |
#realHeight += 512 | |
resIm = Image.new("RGBA", (realWidth, realHeight)) | |
prevHeight = 0 | |
for row in rows: | |
prevWidth = 0 | |
maxRowHeight = 0 | |
for imgFile in row: | |
im = Image.open(imgFile) | |
imWidth = SpriteTools.proportionalSize(im.width, minimumSprite) + separator | |
imHeight = SpriteTools.proportionalSize(im.height, minimumSprite) + separator | |
resIm.paste(im, (prevWidth, prevHeight, prevWidth + im.width, prevHeight + im.height)) | |
prevWidth += imWidth | |
maxRowHeight = max(imHeight, maxRowHeight) | |
im.close() | |
prevHeight += maxRowHeight | |
result = FileTools.createTmpFile("spritesheet.png") | |
resIm.save(result) | |
return result | |
def printPixels (self, imgFile): | |
im = Image.open(imgFile) | |
print(im.format, im.size, im.mode) | |
print("width = " + str(im.width)) | |
print("height = " + str(im.height)) | |
for y in range(im.height): | |
print("row = " + str(y+1) + " of " + str(im.height)) | |
for x in range(im.width): | |
pixel = im.getpixel((x, y)) | |
print("pixel (" + str(x) + ", " + str(y) + ") -> " + str(pixel)) | |
def detectBackgroundColor (self, imgFile): | |
result = None | |
im = Image.open(imgFile) | |
mapColors = {} | |
for y in range(im.height): | |
for x in range(im.width): | |
pixel = im.getpixel((x, y)) | |
keyPixel = str(pixel) | |
countColor = 0 | |
if (keyPixel in mapColors): | |
countColor = mapColors[keyPixel] | |
countColor += 1 | |
mapColors[keyPixel] = countColor | |
maxColor = 0 | |
for key in mapColors.keys(): | |
countColor = mapColors[key] | |
if (result == None or countColor > maxColor): | |
result = key | |
maxColor = countColor | |
return result | |
def convertRGBA (self, imgFile): | |
result = None | |
im = Image.open(imgFile) | |
result = imgFile | |
if (im.mode != "RGBA"): | |
im = im.convert("RGBA") | |
result = FileTools.createTmpFile(FileTools.getFileBasename(imgFile) + ".png") | |
im.save(result) | |
return result | |
def setTransparency (self, imgFile): | |
result = None | |
im = Image.open(imgFile) | |
pixel1 = im.getpixel((0, 0)) | |
pixel2 = im.getpixel((im.width - 1, 0)) | |
pixel3 = im.getpixel((0, im.height - 1)) | |
pixel4 = im.getpixel((im.width - 1, im.height - 1)) | |
if (pixel1[3] == 0 or pixel2[3] == 0 or pixel3[3] == 0 or pixel4[3] == 0): | |
result = imgFile | |
else: | |
background = self.detectBackgroundColor(imgFile) | |
pixdata = im.load() | |
for y in range(im.height): | |
for x in range(im.width): | |
if str(pixdata[x, y]) == background: | |
pixdata[x, y] = (255, 255, 255, 0) | |
result = FileTools.createTmpFile(FileTools.getFileBasename(imgFile) + ".png") | |
im.save(result) | |
return result | |
def generate (self, files, minimumSprite=16): | |
result = None | |
minimumSprite = int(minimumSprite) | |
arrayFilter = [] | |
for imgFile in files: | |
gridSprites = self.gridify(imgFile) | |
gridSvg = self.buildGridInkscape(gridSprites, minimumSprite) | |
exportInkscape = self.editInkscape(gridSvg) | |
arrayFilter.append(exportInkscape) | |
FileTools.remove(FileTools.getFolder(gridSprites[0][0])) | |
FileTools.remove(gridSvg) | |
arraySprites = [] | |
for exportInkscape in arrayFilter: | |
sprites = self.unpack(exportInkscape, int(minimumSprite / UNPACK_SPLIT_SIZE)) | |
self.fillSprites(sprites, minimumSprite) | |
arraySprites.append(sprites) | |
FileTools.remove(exportInkscape) | |
result = self.pack(arraySprites, minimumSprite, 2*minimumSprite) | |
for itemSprites in arraySprites: | |
folder = FileTools.getFolder(itemSprites[0]) | |
# print("folder = " + folder) | |
#exists = FileTools.remove(folder) | |
# if (not exists): | |
# print("ERROR") | |
return result | |
def folderSvg2Png (self, folderSvg, dpi=90): | |
result = None | |
result = [] | |
dpi = int(dpi) | |
for root, dirs, files in os.walk(folderSvg): | |
for filename in files: | |
path = os.path.join(folderSvg, filename) | |
output = self.svg2Png(path, dpi) | |
result.append(output) | |
print(path) | |
break | |
return result | |
def svg2Png (self, svgFile, dpi=90): | |
result = None | |
dpi = int(dpi) | |
result = FileTools.createTmpFile(FileTools.getFileBasename(svgFile) + ".png") | |
os.system("inkscape -z -D -d " + str(dpi) + " -e " + result + " " + svgFile) | |
return result | |
def _processFile(self, imgFile, minimumSprite, result, index): | |
sprites = self.buildTheme(imgFile, minimumSprite) | |
result[index] = sprites | |
def fastGenerateFolder (self, folder, minimumSprite=16): | |
result = None | |
arrayFiles = [] | |
minimumSprite = int(minimumSprite) | |
for filename in os.listdir(folder): | |
if (filename.endswith(".png")): | |
path = os.path.join(folder, filename) | |
arrayFiles.append(path) | |
if (len(arrayFiles) > 0): | |
result = self.fastGenerate(arrayFiles, minimumSprite) | |
return result | |
def gif2png (self, imgFile): | |
result = None | |
im = Image.open(imgFile) | |
newImage = Image.new("RGBA", (im.width, im.height)) | |
newImage.paste(im, (0, 0)) | |
result = FileTools.createTmpFile(FileTools.getFileBasename(imgFile) + ".png") | |
newImage.save(result) | |
return result | |
def fastGenerate (self, files, minimumSprite=16): | |
result = None | |
minimumSprite = int(minimumSprite) | |
size = len(files) | |
arraySprites = [None] * size | |
maxThreads = 5 | |
numThreads = min(maxThreads, size) | |
numLoops = size / numThreads | |
numLoops += 1 if size % numThreads > 0 else 0 | |
for loop in range(numLoops): | |
numThreadsLoop = numThreads | |
if loop == (numLoops - 1) and (size % numThreads) > 0: | |
numThreadsLoop = (size % numThreads) | |
threads = [None] * numThreadsLoop | |
for i in range(numThreadsLoop): | |
dataIdx = i + (numThreads * loop) | |
threads[i] = Thread(target=self._processFile, args=(files[dataIdx], minimumSprite, arraySprites, dataIdx)) | |
threads[i].start() | |
for i in range(numThreadsLoop): | |
threads[i].join() | |
result = self.pack(arraySprites, minimumSprite, 2 * minimumSprite) | |
for itemSprites in arraySprites: | |
if (len(itemSprites) > 0): | |
folder = FileTools.getFolder(itemSprites[0]) | |
#exists = FileTools.remove(folder) | |
result = self.sort(result) | |
return result | |
def organizeParts (self, parts, maximumMegas): | |
result = None | |
result = [] | |
maxSizeBytes = maximumMegas * 1024 * 1024 | |
part = None | |
maxPart = 0L | |
for ifile in parts: | |
fileSize = FileTools.getFileSize(ifile) | |
if part == None or (maxPart + fileSize) > maxSizeBytes: | |
part = [] | |
maxPart = 0L | |
result.append(part) | |
maxPart += fileSize | |
part.append(ifile) | |
return result | |
def _psdlayers2png (self, psd, layers): | |
result = None | |
images = [] | |
for layer in layers: | |
imgLayer = layer.as_PIL() | |
fileLayerPng = FileTools.createTmpFile("psdlayer.png") | |
imgLayer.save(fileLayerPng) | |
images.append(fileLayerPng) | |
numImages = len(images) | |
if (numImages > 0): | |
newImage = Image.new("RGBA", (psd.width, psd.height)) | |
i = numImages - 1 | |
while i >= 0: | |
layer = layers[i] | |
img = images[i] | |
im = Image.open(img) | |
if (im.mode != "RGBA"): | |
im = im.convert("RGBA") | |
newImage.paste(im, (layer.bbox.x1, layer.bbox.y1)) | |
i -= 1 | |
result = FileTools.createTmpFile("psdlayers.png") | |
newImage.save(result) | |
return result | |
def psd2png (self, imgFile): | |
result = None | |
psd = psd_tools.PSDImage.load(imgFile) | |
visibleLayers = [] | |
for layer in psd.layers: | |
if layer.visible: | |
visibleLayers.append(layer) | |
parts = [] | |
numVisible = len(visibleLayers) | |
for i in range(numVisible): | |
selectedNumLayers = i + 1 | |
combinations = list(itertools.combinations(visibleLayers, selectedNumLayers)) | |
for combination in combinations: | |
part = self._psdlayers2png(psd, combination) | |
parts.append(part) | |
result = self.pack(parts) | |
return result | |
def prepare (self, folder, minimumSprite=16): | |
result = None | |
minimumSprite = int(minimumSprite) | |
arrayFiles = [] | |
#files = [os.path.join(sys.argv[2], f) for f in os.listdir(sys.argv[2]) if os.path.isfile(os.path.join(sys.argv[2], f))] | |
for filename in os.listdir(folder): | |
#print("root = " + root) | |
svgFilesFolder = [] | |
path = os.path.join(folder, filename) | |
print("\nfolder = " + folder + " PROCESSING " + filename + " -> " + path + "\n") | |
if (os.path.isfile(path)): | |
if filename.endswith(".png"): | |
outFile = FileTools.copyFile(path) | |
outFile = self.convertRGBA(outFile) | |
outFile = self.setTransparency(outFile) | |
arrayFiles.extend(self.unpack(outFile, int(minimumSprite / UNPACK_SPLIT_SIZE))) | |
elif filename.endswith(".png"): | |
outFile = FileTools.copyFile(path) | |
outFile = self.psd2png(outFile) | |
arrayFiles.extend(self.unpack(outFile, int(minimumSprite / UNPACK_SPLIT_SIZE))) | |
elif filename.endswith(".gif"): | |
outFile = self.gif2png(path) | |
outFile = self.convertRGBA(outFile) | |
outFile = self.setTransparency(outFile) | |
arrayFiles.extend(self.unpack(outFile, int(minimumSprite / UNPACK_SPLIT_SIZE))) | |
elif filename.endswith(".svg"): | |
svgFilesFolder.append(path) | |
elif filename.endswith(".zip") or filename.endswith(".tar.gz") or filename.endswith(".tgz") or filename.endswith(".tar.7z") or filename.endswith(".t7z") or filename.endswith(".7z"): | |
newFolder = FileTools.decompress(path) | |
subfolder = self.prepare(newFolder, minimumSprite) | |
for childroot, childdirs, childfiles in os.walk(subfolder): | |
for childfilename in childfiles: | |
childpath = os.path.join(childroot, childfilename) | |
arrayFiles.extend(self.unpack(childpath, int(minimumSprite / UNPACK_SPLIT_SIZE))) | |
else: | |
print("\nfolder = " + folder + " PROCESSING " + filename + " -> " + path + "\n") | |
subfolder = self.prepare(path, minimumSprite) | |
subfiles = [] | |
for childfilename in os.listdir(subfolder): | |
childpath = os.path.join(subfolder, childfilename) | |
if (os.path.isfile(childpath)): | |
arrayFiles.extend(self.unpack(childpath, int(minimumSprite / UNPACK_SPLIT_SIZE))) | |
if (len(svgFilesFolder) > 0): | |
for path in svgFilesFolder: | |
outFile = self.svg2Png(path) | |
outFile = self.convertRGBA(outFile) | |
outFile = self.setTransparency(outFile) | |
arrayFiles.extend(self.unpack(outFile, int(minimumSprite / UNPACK_SPLIT_SIZE))) | |
multipleParts = self.organizeParts(arrayFiles, 3) | |
arrayFiles = [] | |
for multiPart in multipleParts: | |
packFile = self.pack(multiPart, minimumSprite, 2 * minimumSprite) | |
print("PACK FILES = " + packFile) | |
arrayFiles.append(packFile) | |
result = FileTools.createTmpFolder("prepare") | |
idx = 1 | |
maxLength = len(str(len(arrayFiles))) | |
print("\nfolder = " + str(folder) + " FILES = " + str(arrayFiles) + " result = " + str(result)) | |
for path in arrayFiles: | |
suffix = ("0" * (maxLength - len(str(idx)))) + str(idx) | |
newPath = os.path.join(result, "sprites_" + suffix + ".png") | |
#print("" + path + " -> " + newPath) | |
os.rename(path, newPath) | |
idx += 1 | |
return result | |
def build (self, folder, minimumSprite=16): | |
result = None | |
minimumSprite = int(minimumSprite) | |
arrayFiles = self.prepare(folder, minimumSprite) | |
result = self.fastGenerate(arrayFiles, minimumSprite) | |
return result | |
def execute (self, method, args): | |
result = None | |
func = getattr(self, method, None) | |
if (func == None): | |
print("method '" + method + "' not found") | |
else: | |
result = func(*args) | |
print(result) | |
return result | |
def proc (self, arg1, arg2): | |
print('arg1 = ' + str(arg1) + "'; arg2 = '" + str(arg2) + "'") | |
print(sys.argv) | |
#./spritesheet.py fastGenerateFolder /tmp/.tmp_oxag1I/prepare | |
Spritesheet().execute(sys.argv[1], sys.argv[2:]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment