Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
UncertainProd
GitHub Repository: UncertainProd/FnF-Spritesheet-and-XML-Maker
Path: blob/master/src/engine/xmlpngengine.py
254 views
1
import xml.etree.ElementTree as ET
2
from PIL import Image, ImageChops
3
from os import path, linesep
4
5
from utils import imghashes, g_settings, clean_filename
6
7
from engine.packingalgorithms import GrowingPacker, OrderedPacker
8
from engine.spritesheetutils import get_true_frame, add_pose_numbers
9
from engine.imgutils import pad_img
10
11
12
def fast_image_cmp(im1, im2): # im1 == im2 ?
13
if im1.size != im2.size:
14
return False
15
if im1.tobytes() != im2.tobytes():
16
return False
17
18
return ImageChops.difference(im1, im2).getbbox() is None
19
20
def make_png_xml(frames, save_dir, character_name="Result", progressupdatefn=None, settings=None):
21
if settings is None:
22
settings = g_settings
23
prefix_type = settings.get('prefix_type', 'charname') # use character name or use a custom prefix instead
24
custom_prefix = settings.get('custom_prefix', '') # the custom prefix to use
25
must_use_prefix = settings.get('must_use_prefix', 0) != 0 # use the custom prefix even if frame is from existing spritesheet
26
padding_pixels = settings.get('frame_padding', 0)
27
packing_algorithm = settings.get('packing_algo', 0) # 0 = Growing Packer, 1 = Ordered Packer
28
# no_merge = settings.get('no_merge', 0) != 0 # no merging lookalike frames
29
30
# print(len(imghashes))
31
# print(len(frames))
32
33
try:
34
# init XML
35
root = ET.Element("TextureAtlas")
36
root.text = "\n"
37
root.tail = linesep
38
root.attrib['imagePath'] = f"{character_name}.png"
39
40
new_pose_names = add_pose_numbers(frames)
41
for f, pose in zip(frames, new_pose_names):
42
final_pose_name = pose
43
if f.data.from_single_png or (not f.data.from_single_png and f.modified):
44
if prefix_type == 'charname':
45
final_pose_name = f"{character_name} {final_pose_name}"
46
elif prefix_type == 'custom':
47
final_pose_name = f"{custom_prefix} {final_pose_name}"
48
else:
49
if must_use_prefix and prefix_type == 'custom':
50
final_pose_name = f"{custom_prefix} {final_pose_name}"
51
52
f.data.xml_pose_name = final_pose_name
53
54
frame_dict_arr = []
55
current_img_hashes = set([x.data.img_hash for x in frames])
56
57
# Doesn't quite work yet, still a WIP
58
# if no_merge:
59
# for f in frames:
60
# frame_dict_arr.append({
61
# "id": f.data.img_hash,
62
# "w": imghashes.get(f.data.img_hash).width + 2*padding_pixels,
63
# "h": imghashes.get(f.data.img_hash).height + 2*padding_pixels,
64
# "frame": f # this comes in handy later on
65
# })
66
# else:
67
# pass
68
# add the padding to width and height, then actually padding the images (kind of a hack but it works TODO: work out a better way to do this)
69
for imhash, img in imghashes.items():
70
if imhash in current_img_hashes:
71
frame_dict_arr.append({
72
"id": imhash,
73
"w": img.width + 2*padding_pixels,
74
"h": img.height + 2*padding_pixels
75
})
76
77
if packing_algorithm == 1:
78
packer = OrderedPacker()
79
else:
80
packer = GrowingPacker()
81
frame_dict_arr.sort(key= lambda rect: rect.get("h", -100), reverse=True)
82
83
packer.fit(frame_dict_arr)
84
85
final_img = Image.new("RGBA", (packer.root['w'], packer.root['h']), (0, 0, 0, 0))
86
# frame_dict_arr.sort(key=lambda x: x['id'].img_xml_data.xml_posename)
87
prgs = 0
88
for r in frame_dict_arr:
89
fit = r.get("fit")
90
91
# accounting for user-defined padding
92
imhash_img = imghashes.get(r['id'])
93
imhash_img = pad_img(imhash_img, False, padding_pixels, padding_pixels, padding_pixels, padding_pixels)
94
95
final_img.paste( imhash_img, (fit["x"], fit["y"]) )
96
prgs += 1
97
progressupdatefn(prgs, "Adding images to spritesheet...")
98
99
# Doesn't quite work yet, still a WIP
100
# if no_merge:
101
# for framedict in frame_dict_arr:
102
# frame = framedict['frame']
103
# subtexture_element = ET.Element("SubTexture")
104
# subtexture_element.tail = linesep
105
# w, h = imghashes.get(frame.data.img_hash).size
106
# subtexture_element.attrib = {
107
# "name" : frame.data.xml_pose_name,
108
# "x": str(framedict['fit']['x']),
109
# "y": str(framedict['fit']['y']),
110
# "width": str(w + 2*padding_pixels),
111
# "height": str(h + 2*padding_pixels),
112
# "frameX": str(frame.data.framex),
113
# "frameY": str(frame.data.framey),
114
# "frameWidth": str(frame.data.framew),
115
# "frameHeight": str(frame.data.frameh),
116
# }
117
# root.append(subtexture_element)
118
# prgs += 1
119
# progressupdatefn(prgs, f"Saving {frame.data.xml_pose_name} to XML...")
120
# else:
121
# pass
122
# convert frame_dict_arr into a dict[image_hash -> position in spritesheet]:
123
imghash_dict = { rect['id']: (rect['fit']['x'], rect['fit']['y']) for rect in frame_dict_arr }
124
for frame in frames:
125
subtexture_element = ET.Element("SubTexture")
126
subtexture_element.tail = linesep
127
w, h = imghashes.get(frame.data.img_hash).size
128
subtexture_element.attrib = {
129
"name" : frame.data.xml_pose_name,
130
"x": str(imghash_dict[frame.data.img_hash][0]),
131
"y": str(imghash_dict[frame.data.img_hash][1]),
132
"width": str(w + 2*padding_pixels),
133
"height": str(h + 2*padding_pixels),
134
"frameX": str(frame.data.framex),
135
"frameY": str(frame.data.framey),
136
"frameWidth": str(frame.data.framew),
137
"frameHeight": str(frame.data.frameh),
138
}
139
root.append(subtexture_element)
140
prgs += 1
141
progressupdatefn(prgs, f"Saving {frame.data.xml_pose_name} to XML...")
142
# im.close()
143
print("Saving XML...")
144
xmltree = ET.ElementTree(root)
145
cleanpath = path.join(save_dir, clean_filename(character_name))
146
with open(cleanpath + ".xml", 'wb') as f:
147
xmltree.write(f, xml_declaration=True, encoding='utf-8')
148
149
print("Saving Image...")
150
final_img = final_img.crop(final_img.getbbox())
151
final_img.save(cleanpath + ".png")
152
final_img.close()
153
154
print("Done!")
155
except Exception as e:
156
return -1, str(e)
157
158
return 0, None
159
160
def save_img_sequence(frames, savedir, updatefn):
161
# Saves each frame as a png
162
newposes = add_pose_numbers(frames)
163
for i, (frame, pose) in enumerate(zip(frames, newposes)):
164
try:
165
im = imghashes.get(frame.data.img_hash)
166
im = get_true_frame(im, frame.data.framex, frame.data.framey, frame.data.framew, frame.data.frameh)
167
168
cleanpath = path.join(savedir, clean_filename(f"{pose}.png"))
169
im.save(cleanpath)
170
im.close()
171
updatefn(i+1, f"Saving: {pose}.png")
172
except Exception as e:
173
return str(e)
174
return None
175
176
177
if __name__ == '__main__':
178
print("This program is just the engine! To run the actual application, Please type: \npython xmlpngUI.py\nor \npython3 xmlpngUI.py \ndepending on what works")
179