|
#!/usr/bin/env python |
|
#-*- coding: utf-8 -*- |
|
#----------------------------------------------------------------------- |
|
# Author: delimitry |
|
#----------------------------------------------------------------------- |
|
|
|
import os |
|
import sys |
|
from PIL import Image |
|
from argparse import ArgumentParser |
|
|
|
|
|
def find_horizontal_borders(image, border_color): |
|
""" |
|
Find horizontal border lines with defined color |
|
""" |
|
w, h = image.size |
|
pixels = image.load() |
|
horizontals = [] |
|
for j in xrange(h): |
|
for i in xrange(w): |
|
if pixels[i, j] != border_color: |
|
break |
|
else: |
|
horizontals.append(j) |
|
return horizontals |
|
|
|
|
|
def find_vertial_borders(image, border_color): |
|
""" |
|
Find vertical border lines with defined color |
|
""" |
|
w, h = image.size |
|
pixels = image.load() |
|
verticals = [] |
|
for i in xrange(w): |
|
for j in xrange(h): |
|
if pixels[i, j] != border_color: |
|
break |
|
else: |
|
verticals.append(i) |
|
return verticals |
|
|
|
|
|
def find_areas(image, border_color): |
|
""" |
|
Find sprite areas and return a list of rectangles (x, y, w, h). |
|
""" |
|
w, h = image.size |
|
horizontals = find_horizontal_borders(image, border_color) |
|
areas = [] |
|
prev_horizontal = 0 |
|
for h in horizontals: |
|
if h - prev_horizontal > 1: |
|
verticals = find_vertial_borders( |
|
image.crop((0, prev_horizontal + 1, w, prev_horizontal + (h - prev_horizontal))), border_color) |
|
prev_vertical = 0 |
|
for v in verticals: |
|
if v - prev_vertical > 1: |
|
areas.append( |
|
(prev_vertical + 1, prev_horizontal + 1, v - prev_vertical - 1, h - prev_horizontal - 1)) |
|
prev_vertical = v |
|
prev_horizontal = h |
|
return areas |
|
|
|
|
|
def str_to_color(value): |
|
""" |
|
Convert str value to color (tuple of ints) |
|
""" |
|
try: |
|
if ',' not in value or value.count(',') not in (2, 3): |
|
raise |
|
color = tuple([int(x) for x in value.split(',')]) |
|
for c in color: |
|
if not 0 <= c <= 255: |
|
raise |
|
return color |
|
except: |
|
raise Exception('Invalid format of color. Must be (R,G,B) or (R,G,B,A), ' |
|
'where RGBA channels must be in [0..255]') |
|
|
|
|
|
def cut_and_save_sprites(image, areas, output_folder): |
|
""" |
|
Cut sprites from image using found areas and save to output folder, grouping them by size |
|
""" |
|
if not os.path.exists(output_folder): |
|
os.makedirs(output_folder) |
|
if not areas: |
|
return |
|
group = 0 |
|
index = 0 |
|
# get [w, h] from first area (x, y, w, h) |
|
prev_size = areas[0][2:4] |
|
for area in areas: |
|
# assume that a new group starts when a new size is found |
|
if area[2:4] != prev_size: |
|
prev_size = area[2:4] |
|
group += 1 |
|
index = 0 |
|
image.crop((area[0], area[1], area[0] + area[2] - 0, area[1] + area[3] - 0)).save( |
|
os.path.join(output_folder, 'group_%02d_index_%03d.png' % (group, index))) |
|
index += 1 |
|
|
|
|
|
def main(): |
|
# prepare arguments parser |
|
parser = ArgumentParser(usage='%(prog)s [options]', description='Spritesheet image cutter') |
|
parser.add_argument('-i', '--input', dest='input', help='spritesheet image') |
|
parser.add_argument('-c', '--color', dest='color', |
|
help='border RGB/RGBA color (e.g.: 255,0,128 or 0,0,0,255), by default color of top left pixel', default='') |
|
parser.add_argument('-o', '--output', dest='output', |
|
help='output sprites folder, by default current folder', default='./') |
|
parser.add_argument('-v', '--version', action='version', version='Spritesheet image cutter v0.1') |
|
|
|
args = parser.parse_args() |
|
if not args.input: |
|
parser.print_help() |
|
sys.exit() |
|
|
|
image = Image.open(args.input) |
|
left_top_color = image.getpixel((0, 0)) |
|
if args.color: |
|
try: |
|
border_color = str_to_color(args.color) |
|
except Exception as ex: |
|
sys.exit(ex) |
|
else: |
|
# get top left pixel color if not color passed |
|
border_color = left_top_color |
|
|
|
# check color channels |
|
if len(border_color) != len(left_top_color): |
|
print 'Warning: Image color channels number is %d, but you have passed %d.' % ( |
|
len(left_top_color), len(border_color)) |
|
sys.exit() |
|
|
|
areas = find_areas(image, border_color) |
|
if not areas: |
|
print 'No sprites were found.' |
|
sys.exit() |
|
print 'Saving %s sprites to "%s"' % (len(areas), os.path.abspath(args.output)) |
|
cut_and_save_sprites(image, areas, args.output) |
|
|
|
|
|
if __name__ == '__main__': |
|
main() |