atlas.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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. # SCALE = 50
  7. # OFFSET = 5
  8. SCALE = 1
  9. OFFSET = 0
  10. def conv(*xyxy):
  11. return xyxy[0] * SCALE, xyxy[1] * SCALE, xyxy[2] * SCALE, xyxy[3] * SCALE
  12. def conv_in(*xyxy):
  13. return xyxy[0] * SCALE + OFFSET, xyxy[1] * SCALE + OFFSET, \
  14. xyxy[2] * SCALE - OFFSET, xyxy[3] * SCALE - OFFSET
  15. class rect(object):
  16. def __init__(self, x=0, y=0, w=0, h=0):
  17. self.x = x
  18. self.y = y
  19. self.w = w
  20. self.h = h
  21. def size(self):
  22. return self.w, self.h
  23. def pos(self):
  24. return self.x, self.y
  25. def xyxy(self):
  26. return self.x, self.y, self.get_right(), self.get_bottom()
  27. def get_left(self):
  28. return self.x
  29. def get_right(self):
  30. return self.x + self.w
  31. def get_bottom(self):
  32. return self.y + self.h
  33. def get_top(self):
  34. return self.y
  35. def is_empty(self):
  36. return self.w <= 0 or self.h <= 0
  37. def is_contains(self, r):
  38. return r.x >= self.x and r.y >= self.y and\
  39. r.get_bottom() <= self.get_bottom() and\
  40. r.get_right() <= self.get_right()
  41. def is_contained(self, r):
  42. return self.x >= r.x and self.y >= r.y and\
  43. self.get_bottom() <= r.get_bottom() and\
  44. self.get_right() <= r.get_right()
  45. def get_intersection(self, rc):
  46. c = rect(self.x, self.y, self.w, self.h)
  47. c.x = max(self.x, rc.x)
  48. c.y = max(self.y, rc.y)
  49. r = min(self.get_right(), rc.get_right())
  50. b = min(self.get_bottom(), rc.get_bottom())
  51. c.w = r - c.x
  52. c.h = b - c.y
  53. return c
  54. def get_clipX(self, x):
  55. r1 = rect(self.x, self.y, x - self.x, self.h)
  56. r2 = rect(x, self.y, self.get_right() - x, self.h)
  57. r1.w = min(self.w, max(0, r1.w))
  58. r2.w = min(self.w, max(0, r2.w))
  59. return r1, r2
  60. def get_clipY(self, y):
  61. r1 = rect(self.x, self.y, self.w, y - self.y)
  62. r2 = rect(self.x, y, self.w, self.get_bottom() - y)
  63. r1.h = min(self.h, max(0, r1.h))
  64. r2.h = min(self.h, max(0, r2.h))
  65. return r1, r2
  66. def __repr__(self):
  67. return "(%d, %d, %d, %d)" % (self.x, self.y, self.w, self.h)
  68. class frame(object):
  69. def __init__(self, w, h):
  70. self.w = w
  71. self.h = h
  72. class MyException(Exception):
  73. def __init__(self, free):
  74. self.free = free
  75. class Node(object):
  76. def __init__(self, rect, atl, data):
  77. self.rect = rect
  78. self.atlas = atl
  79. self.data = data
  80. class Atlas(object):
  81. n = 0
  82. def __init__(self, padding, w, h):
  83. self.w = w
  84. self.h = h
  85. self.free_rects = []
  86. self.free_rects.append(
  87. rect(padding, padding, w - padding, h - padding))
  88. self.nodes = []
  89. self.bounds = rect()
  90. Atlas.n += 1
  91. @staticmethod
  92. def optimize(clipped):
  93. res = []
  94. append = res.append
  95. for r in clipped:
  96. add = True
  97. is_contained = r.is_contained
  98. for t in clipped:
  99. if t == r:
  100. continue
  101. if is_contained(t):
  102. add = False
  103. break
  104. if add:
  105. append(r)
  106. return res
  107. def save(self):
  108. import Image
  109. import ImageDraw
  110. im = Image.new("RGBA", (self.w * SCALE, self.h * SCALE))
  111. draw = ImageDraw.Draw(im)
  112. draw.rectangle(conv(0, 0, self.w, self.h), fill="white")
  113. for src_rect in self.rects:
  114. draw.rectangle(conv(*src_rect.xyxy()), fill="red", outline="black")
  115. imcopy = im.copy()
  116. ImageDraw.Draw(imcopy)
  117. for fr in self.free_rects:
  118. rim = Image.new("RGBA", im.size)
  119. rimdraw = ImageDraw.Draw(rim)
  120. rimdraw.rectangle(conv_in(*fr.xyxy()),
  121. fill="green", outline="black")
  122. mask = Image.new("RGBA", im.size, "white")
  123. maskdraw = ImageDraw.Draw(mask)
  124. maskdraw.rectangle(conv_in(*fr.xyxy()), fill="#c01010")
  125. mask = mask.split()[0]
  126. imcopy = Image.composite(imcopy, rim, mask)
  127. imcopy.save("png/%s.png" % (self.n, ))
  128. def add(self, w, h, data):
  129. dest_rect = None
  130. mn = 0xffffffffffffffff
  131. for r in self.free_rects:
  132. if r.w >= w and r.h >= h:
  133. # v = min((r.w - w), (r.h - h))
  134. # v = r.w * r.h - w * h
  135. v = r.y * r.x * r.x
  136. if v < mn:
  137. # if 1:
  138. mn = v
  139. dest_rect = r
  140. # break
  141. # break
  142. if not dest_rect:
  143. # self.save()
  144. return None
  145. src_rect = rect(dest_rect.x, dest_rect.y, w, h)
  146. clipped = []
  147. append = clipped.append
  148. for r in self.free_rects:
  149. if r.get_intersection(src_rect).is_empty():
  150. append(r)
  151. continue
  152. r1, _ = r.get_clipX(src_rect.get_left())
  153. _, r2 = r.get_clipX(src_rect.get_right())
  154. r3, _ = r.get_clipY(src_rect.get_top())
  155. _, r4 = r.get_clipY(src_rect.get_bottom())
  156. if not r1.is_empty():
  157. append(r1)
  158. if not r2.is_empty():
  159. append(r2)
  160. if not r3.is_empty():
  161. append(r3)
  162. if not r4.is_empty():
  163. append(r4)
  164. self.free_rects = self.optimize(clipped)
  165. node = Node(src_rect, self, data)
  166. self.nodes.append(node)
  167. return node
  168. if __name__ == "__main__":
  169. import random
  170. r = rect(0, 0, 10, 10)
  171. fr = []
  172. random.seed(0)
  173. for x in range(200):
  174. fr.append(frame(random.randint(10, 60), random.randint(10, 60)))
  175. fr = [frame(1, 2), frame(2, 3), frame(2, 3), frame(3, 3), frame(8, 2),
  176. frame(4, 1), frame(4, 2), frame(1, 1), frame(3, 3), frame(3, 3),
  177. frame(3, 3), ]
  178. # fr = [frame(2, 2), frame(3, 3), frame(2, 2), ]
  179. # fr = [frame(1, 1), frame(6, 3), frame(8, 1), frame(2,2)]
  180. # fr = fr #30223
  181. # fr = sorted(fr, key = lambda v: -max(v.h, v.w)) #21450
  182. # fr = sorted(fr, key = lambda v: -v.h * v.w) #22492
  183. # fr = sorted(fr, key = lambda v: -v.h) #21880
  184. # fr = sorted(fr, key = lambda v: -v.w) #20573
  185. im = Image.new("RGBA", (r.w * SCALE, r.h * SCALE))
  186. draw = ImageDraw.Draw(im)
  187. draw.rectangle(conv(0, 0, r.w, r.h), fill="white")
  188. exc = ""
  189. atlas = Atlas(0, r.w, r.h)
  190. for f in fr:
  191. node = atlas.add(f.w, f.h)
  192. if not node:
  193. break
  194. s = 0
  195. for f in fr:
  196. s += f.w * f.h
  197. print("left: " + str(s))
  198. im.save("image-%s.png" % (s, ))