Skip to content

Instantly share code, notes, and snippets.

@tuaplicacionpropia
Last active March 14, 2018 18:05
Show Gist options
  • Save tuaplicacionpropia/30066ce3a16b26bfbd85fa185d315a64 to your computer and use it in GitHub Desktop.
Save tuaplicacionpropia/30066ce3a16b26bfbd85fa185d315a64 to your computer and use it in GitHub Desktop.
Create CUSTOM SPRITESHEET from multiple selected sprites and from multiple spritesheets
#!/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