process_atlas.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from __future__ import absolute_import
  4. from __future__ import division
  5. from __future__ import print_function
  6. try:
  7. import Image
  8. import ImageChops
  9. except ImportError:
  10. from PIL import Image
  11. from PIL import ImageChops
  12. import os
  13. import struct
  14. import base64
  15. from . import atlas
  16. from . import process
  17. def isImagesIdentical(im1, im2):
  18. if im1.size[0] == 0 or im1.size[1] == 0:
  19. return False
  20. if im2.size[0] == 0 or im2.size[1] == 0:
  21. return False
  22. bbox = ImageChops.difference(im1, im2).getbbox()
  23. r = bbox is None
  24. return r
  25. def as_int(attr, df=0):
  26. if not attr:
  27. return df
  28. return int(attr)
  29. def as_float(attr, df=0):
  30. if not attr:
  31. return df
  32. return float(attr)
  33. def as_bool(attr, df=False):
  34. if not attr:
  35. return df
  36. lw = attr.lower()
  37. return lw == "true" or lw == "1"
  38. def fixImage_unused(image):
  39. if image.mode != "RGBA":
  40. return image
  41. data = image.load()
  42. for y in range(image.size[1]):
  43. for x in range(image.size[0]):
  44. a = data[x, y][3]
  45. # if a == 0 or a == 1:
  46. if a == 0:
  47. data[x, y] = (0, 0, 0, 0)
  48. return image
  49. def premultipliedAlpha(image):
  50. if image.mode != "RGBA":
  51. return image
  52. data = image.load()
  53. for y in range(image.size[1]):
  54. for x in range(image.size[0]):
  55. dt = data[x, y]
  56. a = dt[3]
  57. data[x, y] = ((dt[0] * a) / 255, (dt[1] * a) /
  58. 255, (dt[2] * a) / 255, a)
  59. return image
  60. def bbox(x, y, w, h):
  61. return (x, y, x + w, y + h)
  62. class alphaData(object):
  63. def __init__(self, w, h, data):
  64. self.w = w
  65. self.h = h
  66. self.data = data
  67. class frame(object):
  68. def __init__(self, image, bbox, image_element, rs, adata, extend):
  69. self.image = image
  70. self.image_element = image_element
  71. self.adata = adata
  72. self.extend = extend
  73. self.node = None
  74. self.resanim = rs
  75. self.border_top = self.border_left = 2
  76. self.border_right = self.border_bottom = 1
  77. self.identicalWith = None
  78. if not bbox:
  79. bbox = (0, 0, 1, 1)
  80. self.bbox = bbox
  81. class ResAnim(object):
  82. def __init__(self):
  83. self.frames = []
  84. self.name = ""
  85. self.frame_size2 = (1, 1) # original size * scale_factor
  86. self.frame_scale2 = 1.0
  87. self.columns = 0
  88. self.rows = 0
  89. self.walker = None
  90. def frame_cmp_sort(f1, f2):
  91. return f2.image.size[0] - f1.image.size[0]
  92. def applyScale(intVal, scale):
  93. v = int(intVal * scale + 0.5)
  94. if v < 1:
  95. v = 1
  96. return v
  97. def applyScale2(x, scale):
  98. initialX = x
  99. best = None
  100. while 1:
  101. X = x * scale
  102. d = abs(X - int(X))
  103. if not best:
  104. best = (d, X)
  105. if best[0] > d:
  106. best = (d, X)
  107. eps = 0.00000001
  108. if d < eps:
  109. return int(X)
  110. if x > initialX * 2:
  111. return int(best[1])
  112. x += 1
  113. def nextPOT(v):
  114. v = v - 1
  115. v = v | (v >> 1)
  116. v = v | (v >> 2)
  117. v = v | (v >> 4)
  118. v = v | (v >> 8)
  119. v = v | (v >> 16)
  120. return v + 1
  121. class settings(object):
  122. def __init__(self):
  123. self.get_size = None
  124. self.set_node = None
  125. self.padding = 1
  126. self.max_w = 2048
  127. self.max_h = 2048
  128. self.atlasses = []
  129. self.square = False
  130. self.npot = False
  131. def makeAlpha(a):
  132. def roundUp(v, multiple):
  133. if multiple == 0:
  134. return v
  135. rem = v % multiple
  136. if rem == 0:
  137. return v
  138. res = v + multiple - rem
  139. return res
  140. try:
  141. asmall = a.resize(
  142. (int(a.size[0] / 4), int(a.size[1] / 4)), Image.ANTIALIAS)
  143. except ValueError:
  144. return None
  145. b = asmall.getextrema()
  146. if not b:
  147. return None
  148. if b[0] > 10:
  149. return None
  150. asmall_size = asmall.size
  151. BITS = 32
  152. val = roundUp(asmall_size[0], BITS)
  153. lineLen = val // BITS
  154. buff = b''
  155. for y in range(asmall_size[1]):
  156. line = [0 for x in range(lineLen)]
  157. for x in range(asmall_size[0]):
  158. p = asmall.getpixel((x, y))
  159. if p > 5:
  160. n = x // BITS
  161. b = x % BITS
  162. line[n] |= 1 << b
  163. for v in line:
  164. buff += struct.pack("<I", v)
  165. adata = alphaData(asmall_size[0], asmall_size[1], buff)
  166. return adata
  167. def pack(st, frames, sw, sh):
  168. atl = atlas.Atlas(st.padding, sw, sh)
  169. not_packed = []
  170. for fr in frames:
  171. if fr.identicalWith:
  172. continue
  173. ns = st.get_size(fr)
  174. node = atl.add(ns[0], ns[1], fr)
  175. if not node:
  176. not_packed.append(fr)
  177. else:
  178. st.set_node(fr, node)
  179. # atl.add(250, 250)
  180. # atl.save()
  181. return not_packed, atl
  182. def get_pow2list(npot, mn, mx):
  183. # ls = []
  184. if npot:
  185. while 1:
  186. yield mn
  187. mn += 64
  188. if mn > mx:
  189. yield mx
  190. break
  191. else:
  192. mn = nextPOT(mn)
  193. mx = nextPOT(mx)
  194. while 1:
  195. yield mn
  196. mn *= 2
  197. if mn > mx:
  198. break
  199. def pck(st, frames):
  200. if 0:
  201. st = settings()
  202. while frames:
  203. sq = 0
  204. min_w = 64
  205. min_h = 64
  206. for f in frames:
  207. size = st.get_size(f)
  208. sq += size[0] * size[1]
  209. min_w = max(min_w, size[0])
  210. min_h = max(min_h, size[1])
  211. max_w = st.max_w
  212. max_h = st.max_h
  213. if st.square:
  214. min_w = min_h = max(min_w, min_h)
  215. max_w = max_h = max(max_w, max_h)
  216. sizes_w = list(get_pow2list(st.npot, min_w, max_w))
  217. sizes_h = list(get_pow2list(st.npot, min_h, max_h))
  218. for sw in sizes_w:
  219. for sh in sizes_h:
  220. end = sh == sizes_h[-1] and sw == sizes_w[-1]
  221. if st.square and sw != sh:
  222. continue
  223. if sw * sh < sq and not end:
  224. continue
  225. not_packed, bn = pack(st, frames, sw, sh)
  226. st.atlasses.append(bn)
  227. if not not_packed:
  228. return
  229. if end:
  230. frames = not_packed
  231. else:
  232. st.atlasses.pop()
  233. def processRS(context, walker):
  234. image_el = walker.root
  235. image_name = image_el.getAttribute("file")
  236. if not image_name:
  237. return None
  238. file_path = walker.getPath("file")
  239. # print image_path
  240. image = None
  241. # fn = self._getExistsFile(image_path)
  242. # virtual_width = 1
  243. # virtual_height = 1
  244. path = context.src_data + file_path
  245. try:
  246. image = Image.open(path)
  247. except IOError:
  248. pass
  249. if image:
  250. # virtual_width = int(image.size[0] * scale + 0.001)
  251. # virtual_height= int(image.size[1] * scale + 0.001)
  252. pass
  253. else:
  254. context.error("can't find image:\n%s\n" % (path, ))
  255. image = Image.new("RGBA", (0, 0))
  256. resAnim = ResAnim()
  257. resAnim.walker = walker
  258. resAnim.image = image
  259. resAnim.name = image_name
  260. columns = as_int(image_el.getAttribute("cols"))
  261. frame_width = as_int(image_el.getAttribute("frame_width"))
  262. rows = as_int(image_el.getAttribute("rows"))
  263. frame_height = as_int(image_el.getAttribute("frame_height"))
  264. border_attr = image_el.getAttribute("border")
  265. trim = as_bool(image_el.getAttribute("trim"), True)
  266. extend = as_bool(image_el.getAttribute("extend"), False)
  267. if not extend:
  268. pass
  269. # sq = as_float(image_el.getAttribute("scale_quality"), 1)
  270. # next.scale_quality *= sq
  271. if not columns:
  272. columns = 1
  273. if not rows:
  274. rows = 1
  275. if frame_width:
  276. columns = image.size[0] / frame_width
  277. else:
  278. frame_width = image.size[0] / columns
  279. if frame_height:
  280. rows = image.size[1] / frame_height
  281. else:
  282. frame_height = image.size[1] / rows
  283. size_warning = False
  284. if frame_width * columns != image.size[0]:
  285. size_warning = True
  286. context.warning("image has width %d and %d columns:" %
  287. (image.size[0], columns))
  288. if frame_height * rows != image.size[1]:
  289. size_warning = True
  290. context.warning("<image has height %d and %d rows:" %
  291. (image.size[1], rows))
  292. if size_warning:
  293. context.warnings += 1
  294. scale_factor = walker.scale_factor
  295. resAnim.frame_scale2 = scale_factor
  296. finalScale = 1
  297. upscale = False
  298. if context.args.resize:
  299. max_scale = 1.0 / scale_factor
  300. finalScale = context.scale * walker.scale_quality
  301. if finalScale > max_scale:
  302. if not context.args.upscale:
  303. finalScale = max_scale
  304. else:
  305. upscale = True
  306. resAnim.frame_scale2 = 1.0 / finalScale
  307. finalScale = finalScale * scale_factor
  308. frame_size = (applyScale(frame_width, finalScale),
  309. applyScale(frame_height, finalScale))
  310. resAnim.frame_size2 = (applyScale(frame_width, scale_factor),
  311. applyScale(frame_height, scale_factor))
  312. resAnim.columns = columns
  313. resAnim.rows = rows
  314. for row in range(rows):
  315. for col in range(columns):
  316. rect = (int(col * frame_width),
  317. int(row * frame_height),
  318. int((col + 1) * frame_width),
  319. int((row + 1) * frame_height))
  320. frame_image = image.crop(rect)
  321. def resize():
  322. ax = applyScale2(frame_width, finalScale)
  323. ay = applyScale2(frame_height, finalScale)
  324. bx = int(ax / finalScale)
  325. by = int(ay / finalScale)
  326. im = Image.new("RGBA", (bx, by))
  327. im.paste(frame_image, (0, 0, frame_image.size[
  328. 0], frame_image.size[1]))
  329. frame_image = im.resize((ax, ay), Image.ANTIALIAS)
  330. frame_image = frame_image.crop(
  331. (0, 0, frame_size[0], frame_size[1]))
  332. resize_filter = Image.ANTIALIAS
  333. if upscale:
  334. resize_filter = Image.BICUBIC
  335. if context.args.resize:
  336. if as_bool(image_el.getAttribute("trueds")) or (not trim and not extend) or context.args.simple_downsample:
  337. frame_image = frame_image.resize(
  338. (frame_size[0], frame_size[1]), resize_filter)
  339. else:
  340. ax = applyScale2(frame_width, finalScale)
  341. ay = applyScale2(frame_height, finalScale)
  342. bx = int(ax / finalScale)
  343. by = int(ay / finalScale)
  344. im = Image.new("RGBA", (bx, by))
  345. im.paste(frame_image, (0, 0, frame_image.size[
  346. 0], frame_image.size[1]))
  347. frame_image = im.resize((ax, ay), resize_filter)
  348. frame_image = frame_image.crop(
  349. (0, 0, frame_size[0], frame_size[1]))
  350. adata = None
  351. if image.mode == "RGBA" and trim:
  352. r, g, b, a = frame_image.split()
  353. tt = context.args.trim_threshold
  354. if tt:
  355. a = a.point(lambda p: p - tt)
  356. if walker.hit_test:
  357. adata = makeAlpha(a)
  358. frame_bbox = a.getbbox()
  359. else:
  360. frame_bbox = (0, 0, frame_image.size[0], frame_image.size[1])
  361. if not frame_bbox:
  362. frame_bbox = (0, 0, 0, 0)
  363. frame_image = frame_image.crop(frame_bbox)
  364. fr = frame(frame_image, frame_bbox, image_el, resAnim, adata, extend)
  365. if border_attr:
  366. fr.border_left = fr.border_right = fr.border_top = fr.border_bottom = as_int(border_attr)
  367. #if not extend:
  368. # fr.border_left = fr.border_right = fr.border_top = fr.border_bottom = 0
  369. for f in resAnim.frames:
  370. if isImagesIdentical(f.image, fr.image):
  371. fr.identicalWith = f
  372. if f.identicalWith:
  373. fr.identicalWith = f.identicalWith
  374. resAnim.frames.append(fr)
  375. return resAnim
  376. class atlas_Processor(process.Process):
  377. node_id = "atlas"
  378. def __init__(self):
  379. self.atlas_group_id = 0
  380. def process(self, context, walker):
  381. self.atlas_group_id += 1
  382. # meta = context.add_meta()
  383. anims = []
  384. frames = []
  385. alphaData = ""
  386. while True:
  387. next = walker.next()
  388. if 0:
  389. import xml_processor
  390. next = xml_processor.XmlWalker()
  391. if not next:
  392. break
  393. anim = processRS(context, next)
  394. if anim:
  395. anims.append(anim)
  396. frames.extend(anim.frames)
  397. # sort frames by size
  398. #frames = sorted(frames, key = lambda fr: -fr.image.size[1])
  399. #frames = sorted(frames, key = lambda fr: -fr.image.size[0])
  400. frames = sorted(frames, key=lambda fr: -1 * max(
  401. fr.image.size[0], fr.image.size[1]) * max(fr.image.size[0], fr.image.size[1]))
  402. sq = 0
  403. for f in frames:
  404. sq += f.image.size[0] * f.image.size[1]
  405. compression = context.compression
  406. def get_aligned_frame_size(frame):
  407. def align_pixel(p):
  408. if not compression or compression == "no":
  409. return p + 1
  410. align = 4
  411. v = p % align
  412. if v == 0:
  413. p += align
  414. else:
  415. p += 8 - v
  416. return p
  417. sz = frame.image.size
  418. return align_pixel(sz[0] + frame.border_left + frame.border_right), align_pixel(sz[1] + frame.border_top + frame.border_bottom)
  419. def get_original_frame_size(frame):
  420. sz = frame.image.size
  421. return sz
  422. def set_node(frame, node):
  423. frame.node = node
  424. st = settings()
  425. st.npot = context._npot
  426. st.get_size = get_aligned_frame_size
  427. st.set_node = set_node
  428. st.max_w = context.args.max_width
  429. st.max_h = context.args.max_height
  430. st.square = context.compression == "pvrtc"
  431. st.padding = 0
  432. if len(frames) == 1:
  433. st.get_size = get_original_frame_size
  434. pck(st, frames)
  435. # print "done"
  436. for atlas_id, atl in enumerate(st.atlasses):
  437. image = Image.new("RGBA", (atl.w, atl.h))
  438. for node in atl.nodes:
  439. fr = node.data
  440. x = node.rect.x + fr.border_left
  441. y = node.rect.y + fr.border_top
  442. sz = fr.image.size
  443. rect = bbox(x, y, sz[0], sz[1])
  444. if fr.extend:
  445. part = fr.image.crop(bbox(0, 0, sz[0], 1))
  446. image.paste(part, bbox(x, y - 1, sz[0], 1))
  447. part = fr.image.crop(bbox(0, sz[1] - 1, sz[0], 1))
  448. image.paste(part, bbox(x, y + sz[1], sz[0], 1))
  449. part = fr.image.crop(bbox(0, 0, 1, sz[1]))
  450. image.paste(part, bbox(x - 1, y, 1, sz[1]))
  451. part = fr.image.crop(bbox(sz[0] - 1, 0, 1, sz[1]))
  452. image.paste(part, bbox(x + sz[0], y, 1, sz[1]))
  453. image.paste(fr.image, rect)
  454. fr.atlas_id = atlas_id
  455. image_atlas_el = walker.root_meta.ownerDocument.createElement(
  456. "atlas")
  457. walker.root_meta.insertBefore(
  458. image_atlas_el, anims[0].walker.root_meta)
  459. # meta.appendChild(image_atlas_el)
  460. base_name = "%d_%d" % (self.atlas_group_id, atlas_id)
  461. ox_fmt = "r8g8b8a8"
  462. def compress(src, dest, fmt):
  463. cmd = context.helper.path_pvrtextool + \
  464. " -i %s -f %s,UBN,lRGB -o %s" % (src, fmt, dest)
  465. cmd += " -l" # alpha bleed
  466. if context.args.quality == "best":
  467. cmd += " -q pvrtcbest"
  468. else:
  469. cmd += " -q pvrtcfast"
  470. if context.args.dither:
  471. cmd += " -dither"
  472. cmd += " -shh" # silent
  473. os.system(cmd)
  474. if compression == "etc1":
  475. # premultipliedAlpha(v)
  476. r, g, b, a = image.split()
  477. rgb = Image.merge("RGB", (r, g, b))
  478. alpha = Image.merge("RGB", (a, a, a))
  479. base_alpha_name = base_name + "_alpha"
  480. alpha_path = context.get_inner_dest(base_alpha_name + ".png")
  481. alpha.save(alpha_path)
  482. image_atlas_el.setAttribute("alpha", base_alpha_name + ".png")
  483. path_base = base_name + ".png"
  484. rs = ".pvr"
  485. rgb_path = context.get_inner_dest(path_base)
  486. path = context.get_inner_dest(path_base)
  487. rgb.save(path)
  488. pkm_rgb = base_name + rs
  489. pkm_alpha = base_alpha_name + rs
  490. def compress_etc1(src, dest):
  491. compress(src, dest, "etc1")
  492. #os.system(context.etc1tool + "%s -o %s -f etc1" %(src, dest))
  493. ox_fmt = "ETC1"
  494. compress_etc1(rgb_path, context.get_inner_dest() + pkm_rgb)
  495. os.remove(rgb_path)
  496. image_atlas_el.setAttribute("file", pkm_rgb)
  497. compress_etc1(alpha_path, context.get_inner_dest() + pkm_alpha)
  498. os.remove(alpha_path)
  499. image_atlas_el.setAttribute("alpha", pkm_alpha)
  500. else:
  501. if context.args.nopng:
  502. path_base = base_name + ".tga"
  503. else:
  504. path_base = base_name + ".png"
  505. path = context.get_inner_dest(path_base)
  506. image_atlas_el.setAttribute("file", path_base)
  507. image.save(path)
  508. if context.compression == "pvrtc":
  509. ox_fmt = "PVRTC_4RGBA"
  510. compress(path, context.get_inner_dest(base_name + ".pvr"), "PVRTC1_4")
  511. image_atlas_el.setAttribute("file", base_name + ".pvr")
  512. os.remove(path)
  513. if context.compression == "pvrtc2":
  514. ox_fmt = "PVRTC2_4RGBA"
  515. compress(path, context.get_inner_dest(base_name + ".pvr"), "PVRTC2_4")
  516. image_atlas_el.setAttribute("file", base_name + ".pvr")
  517. os.remove(path)
  518. if context.compression == "etc2":
  519. ox_fmt = "etc2"
  520. compress(path, context.get_inner_dest(base_name + ".pvr"), "ETC2_RGBA")
  521. image_atlas_el.setAttribute("file", base_name + ".pvr")
  522. os.remove(path)
  523. image_atlas_el.setAttribute("format", ox_fmt)
  524. image_atlas_el.setAttribute("w", str(image.size[0]))
  525. image_atlas_el.setAttribute("h", str(image.size[1]))
  526. alpha = b''
  527. for anim in anims:
  528. if 0:
  529. anim = ResAnim()
  530. image_frames_el = anim.walker.root_meta
  531. image_frames_el.setAttribute("fs", "%d,%d,%d,%d,%f" % (anim.columns, anim.rows,
  532. anim.frame_size2[
  533. 0], anim.frame_size2[1],
  534. anim.frame_scale2))
  535. adata = anim.frames[0].adata
  536. if adata:
  537. image_frames_el.setAttribute("ht", "%d,%d,%d,%d" % (
  538. len(alpha), len(adata.data), adata.w, adata.h))
  539. if context.debug:
  540. image_frames_el.setAttribute("debug_image", anim.name)
  541. data = ""
  542. for item in anim.frames:
  543. fr = item
  544. if item.identicalWith:
  545. fr = item.identicalWith
  546. data += "%d,%d,%d,%d,%d,%d,%d;" % (fr.atlas_id,
  547. fr.node.rect.x + fr.border_left, fr.node.rect.y + fr.border_top,
  548. fr.bbox[0], fr.bbox[1],
  549. fr.bbox[2] - fr.bbox[0], fr.bbox[3] - fr.bbox[1])
  550. if fr.adata:
  551. alpha += fr.adata.data
  552. text = image_frames_el.ownerDocument.createTextNode(data)
  553. image_frames_el.appendChild(text)
  554. if alpha:
  555. doc = walker.root_meta.ownerDocument
  556. alpha_el = doc.createElement("ht")
  557. adata_str = base64.b64encode(alpha)
  558. text = doc.createTextNode(adata_str.decode("utf-8"))
  559. alpha_el.setAttribute("len", str(len(adata_str)))
  560. alpha_el.appendChild(text)
  561. walker.root_meta.appendChild(alpha_el)