process_atlas.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. from PIL import Image
  2. from xml.dom import minidom
  3. import os
  4. import atlas
  5. import process
  6. def as_int(attr, df = 0):
  7. if not attr:
  8. return df
  9. return int(attr)
  10. def as_float(attr, df = 0):
  11. if not attr:
  12. return df
  13. return float(attr)
  14. def as_bool(attr):
  15. if not attr:
  16. return False
  17. lw = attr.lower()
  18. return lw == "true" or lw == "1"
  19. def fixImage(image):
  20. if image.mode != "RGBA":
  21. return image
  22. data = image.load()
  23. for y in xrange(image.size[1]):
  24. for x in xrange(image.size[0]):
  25. a = data[x,y][3]
  26. #if a == 0 or a == 1:
  27. if a == 0:
  28. data[x, y] = (0,0,0,0)
  29. return image
  30. def premultipliedAlpha(image):
  31. if image.mode != "RGBA":
  32. return image
  33. data = image.load()
  34. for y in xrange(image.size[1]):
  35. for x in xrange(image.size[0]):
  36. dt = data[x,y]
  37. a = dt[3]
  38. data[x, y] = ((dt[0] * a) / 255, (dt[1] * a) / 255, (dt[2] * a) / 255, a)
  39. return image
  40. class frame:
  41. def __init__(self, image, bbox, image_element, rs):
  42. self.image = image
  43. self.image_element = image_element
  44. self.node = None
  45. self.resanim = rs
  46. self.border_top = self.border_left = 0
  47. self.border_right = self.border_bottom = 1
  48. if not bbox:
  49. bbox = (0,0,1,1)
  50. self.bbox = bbox
  51. class ResAnim:
  52. def __init__(self):
  53. self.frames = []
  54. self.name = ""
  55. self.frame_size = (1, 1)
  56. self.frame_scale = 1.0
  57. self.columns = 0
  58. self.rows = 0
  59. self.walker = None
  60. def frame_cmp_sort(f1, f2):
  61. return f2.image.size[0] - f1.image.size[0]
  62. def applyScale(intVal, scale):
  63. return int(intVal * scale + 0.5)
  64. def applyScale2(x, scale):
  65. initialX = x
  66. best = None
  67. while 1:
  68. X = x * scale
  69. d = abs(X - int(X))
  70. if not best:
  71. best = (d, X)
  72. if best[0] > d:
  73. best = (d, X)
  74. eps = 0.00000001
  75. if d < eps:
  76. return int(X)
  77. if x > initialX * 2:
  78. return int(best[1])
  79. x += 1
  80. def nextPOT(v):
  81. v = v - 1;
  82. v = v | (v >> 1);
  83. v = v | (v >> 2);
  84. v = v | (v >> 4);
  85. v = v | (v >> 8);
  86. v = v | (v >>16);
  87. return v + 1
  88. class settings:
  89. def __init__(self):
  90. self.get_size = None
  91. self.set_node = None
  92. self.padding = 1
  93. self.max_w = 2048
  94. self.max_h = 2048
  95. self.atlasses = []
  96. self.square = False
  97. def pack(st, frames, sw, sh):
  98. atl = atlas.Atlas(st.padding, sw, sh)
  99. not_packed = []
  100. for fr in frames:
  101. ns = st.get_size(fr)
  102. node = atl.add(ns[0], ns[1], fr)
  103. if not node:
  104. not_packed.append(fr)
  105. else:
  106. st.set_node(fr, node)
  107. #atl.add(250, 250)
  108. #atl.save()
  109. return not_packed, atl
  110. def get_pow2list(mn, mx):
  111. ls = []
  112. mn = nextPOT(mn)
  113. mx = nextPOT(mx)
  114. while 1:
  115. ls.append(mn)
  116. mn *= 2
  117. if mn > mx:
  118. break
  119. return ls
  120. def pck(st, frames):
  121. if 0:
  122. st = settings()
  123. while frames:
  124. sq = 0
  125. min_w = 64
  126. min_h = 64
  127. for f in frames:
  128. size = st.get_size(f)
  129. sq += size[0] * size[1]
  130. min_w = max(min_w, size[0])
  131. min_h = max(min_h, size[1])
  132. sizes_w = get_pow2list(min_w, st.max_w)
  133. sizes_h = get_pow2list(min_h, st.max_h)
  134. for sw in sizes_w:
  135. for sh in sizes_h:
  136. end = sh == sizes_h[-1] and sw == sizes_w[-1]
  137. if st.square and sw != sh:
  138. continue
  139. if sw * sh < sq and not end:
  140. continue
  141. not_packed, bn = pack(st, frames, sw, sh)
  142. st.atlasses.append(bn)
  143. if not not_packed:
  144. return
  145. if end:
  146. frames = not_packed
  147. else:
  148. st.atlasses.pop()
  149. class atlas_Processor(process.Process):
  150. node_id = "atlas"
  151. def __init__(self):
  152. self.atlas_group_id = 0
  153. def process(self, context, walker):
  154. self.atlas_group_id += 1
  155. #meta = context.add_meta()
  156. anims = []
  157. frames = []
  158. while True:
  159. next = walker.next()
  160. if not next:
  161. break
  162. image_el = next.root
  163. image_name = image_el.getAttribute("file")
  164. if not image_name:
  165. continue
  166. file_path = next.getPath("file")
  167. #print image_path
  168. image = None
  169. #fn = self._getExistsFile(image_path)
  170. #virtual_width = 1
  171. #virtual_height = 1
  172. path = context.src_data + file_path
  173. try:
  174. image = Image.open(path)
  175. except IOError:
  176. pass
  177. if image:
  178. # virtual_width = int(image.size[0] * scale + 0.001)
  179. # virtual_height= int(image.size[1] * scale + 0.001)
  180. pass
  181. else:
  182. context.error("can't find image:\n%s\n" % (path, ))
  183. image = Image.new("RGBA", (0, 0))
  184. resAnim = ResAnim()
  185. resAnim.walker = next
  186. resAnim.image = image
  187. resAnim.name = image_name
  188. anims.append(resAnim)
  189. columns = as_int(image_el.getAttribute("cols"))
  190. frame_width = as_int(image_el.getAttribute("frame_width"))
  191. rows = as_int(image_el.getAttribute("rows"))
  192. frame_height = as_int(image_el.getAttribute("frame_height"))
  193. border = as_int(image_el.getAttribute("border"))
  194. #sq = as_float(image_el.getAttribute("scale_quality"), 1)
  195. #next.scale_quality *= sq
  196. if not columns:
  197. columns = 1
  198. if not rows:
  199. rows = 1
  200. if frame_width:
  201. columns = image.size[0] / frame_width
  202. else:
  203. frame_width = image.size[0] / columns
  204. if frame_height:
  205. rows = image.size[1] / frame_height
  206. else:
  207. frame_height = image.size[1] / rows
  208. size_warning = False
  209. if frame_width * columns != image.size[0]:
  210. size_warning = True
  211. context.warning("image has width %d and %d columns:" % (image.size[0], columns))
  212. if frame_height * rows != image.size[1]:
  213. size_warning = True
  214. context.warning("<image has height %d and %d rows:" % (image.size[1], rows))
  215. if size_warning:
  216. context.warnings += 1
  217. finalScale = context.get_apply_scale(True, next)
  218. upscale = False
  219. if finalScale > 1:
  220. if not context.args.upscale:
  221. finalScale = 1
  222. else:
  223. upscale = True
  224. resAnim.frame_scale = context.get_apply_scale(False, next)
  225. #todo, fix bug when frame_scale > 1 and finalScale = 1
  226. resAnim.frame_size = (applyScale(frame_width, finalScale),
  227. applyScale(frame_height, finalScale))
  228. resAnim.columns = columns
  229. resAnim.rows = rows
  230. for row in xrange(rows):
  231. for col in xrange(columns):
  232. rect = (col * frame_width, row * frame_height, (col + 1) * frame_width, (row + 1)* frame_height, )
  233. frame_image = image.crop(rect)
  234. def resize():
  235. ax = applyScale2(frame_width, finalScale);
  236. ay = applyScale2(frame_height, finalScale);
  237. bx = int(ax / finalScale)
  238. by = int(ay / finalScale)
  239. im = Image.new("RGBA", (bx, by))
  240. im.paste(frame_image, (0, 0, frame_image.size[0], frame_image.size[1]))
  241. frame_image = im.resize((ax, ay), Image.ANTIALIAS)
  242. frame_image = frame_image.crop((0, 0, resAnim.frame_size[0], resAnim.frame_size[1]))
  243. if context.args.resize:
  244. if as_bool(image_el.getAttribute("trueds")):
  245. frame_image = frame_image.resize((resAnim.frame_size[0], resAnim.frame_size[1]), Image.ANTIALIAS)
  246. else:
  247. ax = applyScale2(frame_width, finalScale);
  248. ay = applyScale2(frame_height, finalScale);
  249. bx = int(ax / finalScale)
  250. by = int(ay / finalScale)
  251. im = Image.new("RGBA", (bx, by))
  252. im.paste(frame_image, (0, 0, frame_image.size[0], frame_image.size[1]))
  253. frame_image = im.resize((ax, ay), Image.ANTIALIAS)
  254. frame_image = frame_image.crop((0,0,resAnim.frame_size[0], resAnim.frame_size[1]))
  255. trim = True
  256. if image_el.getAttribute("trim") == "0":
  257. trim = False
  258. if image.mode == "RGBA" and trim:
  259. r,g,b,a = frame_image.split()
  260. frame_bbox = a.getbbox()
  261. else:
  262. frame_bbox = frame_image.getbbox()
  263. if not frame_bbox:
  264. frame_bbox = (0,0,0,0)
  265. w = frame_bbox[2] - frame_bbox[0]
  266. h = frame_bbox[3] - frame_bbox[1]
  267. frame_image = frame_image.crop(frame_bbox)
  268. fr = frame(frame_image, frame_bbox, image_el, resAnim)
  269. if border:
  270. fr.border_left = fr.border_right = fr.border_top = fr.border_bottom = border
  271. frames.append(fr)
  272. resAnim.frames.append(fr)
  273. #sort frames by size
  274. #frames = sorted(frames, key = lambda fr: -fr.image.size[1])
  275. #frames = sorted(frames, key = lambda fr: -fr.image.size[0])
  276. frames = sorted(frames, key = lambda fr: -1 * max(fr.image.size[0], fr.image.size[1]) * max(fr.image.size[0], fr.image.size[1]))
  277. sq = 0
  278. for f in frames:
  279. sq += f.image.size[0] * f.image.size[1]
  280. compression = context.compression
  281. def get_aligned_frame_size(frame):
  282. def align_pixel(p):
  283. if compression == "no":
  284. return p + 1
  285. align = 4
  286. v = p % align
  287. if v == 0:
  288. p += align
  289. else:
  290. p += 8 - v
  291. return p
  292. sz = frame.image.size
  293. return align_pixel(sz[0] + frame.border_left + frame.border_right), align_pixel(sz[1] + frame.border_top + frame.border_bottom)
  294. def get_original_frame_size(frame):
  295. sz = frame.image.size
  296. return sz
  297. def set_node(frame, node):
  298. frame.node = node
  299. st = settings()
  300. st.get_size = get_aligned_frame_size
  301. st.set_node = set_node
  302. st.max_w = context.args.max_width
  303. st.max_h = context.args.max_height
  304. st.square = context.compression == "pvrtc"
  305. if len(frames) == 1:
  306. st.get_size = get_original_frame_size
  307. st.padding = 0
  308. pck(st, frames)
  309. #print "done"
  310. for atlas_id, atl in enumerate(st.atlasses):
  311. image = Image.new("RGBA", (atl.w, atl.h))
  312. for node in atl.nodes:
  313. fr = node.data
  314. x = node.rect.x + fr.border_left
  315. y = node.rect.y + fr.border_top
  316. sz = fr.image.size
  317. rect = (x, y, x + sz[0], y + sz[1])
  318. image.paste(fr.image, rect)
  319. fr.atlas_id = atlas_id
  320. image_atlas_el = walker.root_meta.ownerDocument.createElement("atlas")
  321. walker.root_meta.insertBefore(image_atlas_el, anims[0].walker.root_meta)
  322. #meta.appendChild(image_atlas_el)
  323. base_name = "%d_%d" % (self.atlas_group_id, atlas_id)
  324. ox_fmt = "r8g8b8a8"
  325. def compress(src, dest, fmt):
  326. cmd = context.helper.path_pvrtextool + " -i %s -f %s,UBN,lRGB -o %s" % (src, fmt, dest)
  327. cmd += " -l" #alpha bleed
  328. if context.args.quality == "best":
  329. cmd += " -q pvrtcbest"
  330. else:
  331. cmd += " -q pvrtcfast"
  332. if upscale or context.args.dither:
  333. cmd += " -dither"
  334. cmd += " -shh" #silent
  335. os.system(cmd)
  336. if compression == "etc1":
  337. #premultipliedAlpha(v)
  338. r, g, b, a = image.split()
  339. rgb = Image.merge("RGB", (r, g, b))
  340. alpha = Image.merge("RGB", (a,a,a))
  341. base_alpha_name = base_name + "_alpha"
  342. alpha_path = context.get_inner_dest(base_alpha_name + ".png")
  343. alpha.save(alpha_path)
  344. image_atlas_el.setAttribute("alpha", base_alpha_name + ".png")
  345. path_base = base_name + ".png"
  346. rs = ".pvr"
  347. rgb_path = context.get_inner_dest(path_base)
  348. path = context.get_inner_dest(path_base)
  349. rgb.save(path)
  350. pkm_rgb = base_name + rs
  351. pkm_alpha = base_alpha_name + rs
  352. def compress_etc1(src, dest):
  353. compress(src, dest, "etc1")
  354. #os.system(context.etc1tool + "%s -o %s -f etc1" %(src, dest))
  355. ox_fmt = "ETC1"
  356. compress_etc1(rgb_path, context.get_inner_dest() + pkm_rgb)
  357. os.remove(rgb_path)
  358. image_atlas_el.setAttribute("file", pkm_rgb)
  359. compress_etc1(alpha_path, context.get_inner_dest() + pkm_alpha)
  360. os.remove(alpha_path)
  361. image_atlas_el.setAttribute("alpha", pkm_alpha)
  362. else:
  363. if context.args.nopng:
  364. path_base = base_name + ".tga"
  365. else:
  366. path_base = base_name + ".png"
  367. path = context.get_inner_dest(path_base)
  368. image_atlas_el.setAttribute("file", path_base)
  369. image.save(path)
  370. if context.compression == "pvrtc":
  371. ox_fmt = "PVRTC_4RGBA"
  372. compress(path, context.get_inner_dest(base_name + ".pvr"), "PVRTC1_4")
  373. image_atlas_el.setAttribute("file", base_name + ".pvr")
  374. os.remove(path)
  375. image_atlas_el.setAttribute("format", ox_fmt)
  376. image_atlas_el.setAttribute("w", str(image.size[0]))
  377. image_atlas_el.setAttribute("h", str(image.size[1]))
  378. for anim in anims:
  379. if 0:
  380. anim = ResAnim()
  381. image_frames_el = anim.walker.root_meta
  382. image_frames_el.setAttribute("fs", "%d,%d,%d,%d,%f" % (anim.columns, anim.rows,
  383. anim.frame_size[0], anim.frame_size[1],
  384. anim.frame_scale))
  385. #meta.appendChild(image_frames_el)
  386. if context.debug:
  387. image_frames_el.setAttribute("debug_image", anim.name)
  388. data = ""
  389. for fr in anim.frames:
  390. data += "%d,%d,%d,%d,%d,%d,%d;" %(fr.atlas_id,
  391. fr.node.rect.x + fr.border_left, fr.node.rect.y + fr.border_top,
  392. fr.bbox[0], fr.bbox[1],
  393. fr.bbox[2] - fr.bbox[0], fr.bbox[3] - fr.bbox[1])
  394. text = image_frames_el.ownerDocument.createTextNode(data)
  395. image_frames_el.appendChild(text)