atlas.py 6.6 KB

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