interior.py 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. # coding: utf-8
  2. import typing
  3. from PIL.PngImagePlugin import PngImageFile
  4. from synergine2_xyz.map import TMXMap
  5. from synergine2_xyz.utils import get_direct_around_positions_of_position
  6. class InteriorMapConfiguration(object):
  7. def __init__(
  8. self,
  9. layer_name: str='interiors',
  10. exterior_id: str='ext',
  11. interior_id: str='int',
  12. separator_id: str='sep',
  13. ) -> None:
  14. self.layer_name = layer_name
  15. self.exterior_id = exterior_id
  16. self.interior_id = interior_id
  17. self.separator_id = separator_id
  18. class InteriorManager(object):
  19. def __init__(
  20. self,
  21. map_: TMXMap,
  22. original_image: PngImageFile,
  23. configuration: InteriorMapConfiguration=None,
  24. ) -> None:
  25. self.interiors = []
  26. self.map = map_
  27. self.original_image = original_image
  28. self.configuration = configuration or InteriorMapConfiguration()
  29. self.interiors = self._compute_interiors()
  30. self._cache = {} # type: typing.Dict[str, PngImageFile]
  31. def _compute_interiors(self) -> typing.List[typing.List[typing.Tuple[int, int]]]:
  32. interiors = []
  33. layer_tiles = self.map.layer_tiles(self.configuration.layer_name)
  34. for tile_xy, tile in layer_tiles.items():
  35. if tile.property('id') == self.configuration.interior_id:
  36. x, y = map(int, tile_xy.split('.'))
  37. if not any([(x, y) in i for i in interiors]):
  38. new_interior = [(x, y)]
  39. positions_to_parse = []
  40. possible_positions_xyz = get_direct_around_positions_of_position((x, y, 0))
  41. possible_positions_xy = [(p[0], p[1]) for p in possible_positions_xyz]
  42. positions_to_parse.extend(possible_positions_xy)
  43. for possible_position_xyz in positions_to_parse:
  44. test_tile = None
  45. new_tile_x = possible_position_xyz[0]
  46. new_tile_y = possible_position_xyz[1]
  47. possible_position_key = '{}.{}'.format(new_tile_x, new_tile_y)
  48. if (new_tile_x, new_tile_y) in new_interior:
  49. continue
  50. try:
  51. test_tile = layer_tiles[possible_position_key]
  52. except KeyError:
  53. continue
  54. if test_tile.property('id') not in [
  55. self.configuration.interior_id,
  56. self.configuration.separator_id,
  57. ]:
  58. continue
  59. new_interior.append((new_tile_x, new_tile_y))
  60. if not test_tile.property('id') == self.configuration.separator_id:
  61. new_position_neighbour = get_direct_around_positions_of_position((new_tile_x, new_tile_y, 0))
  62. positions_to_parse.extend(new_position_neighbour)
  63. interiors.append(new_interior)
  64. return interiors
  65. def get_interiors(
  66. self,
  67. where_positions: typing.Iterable[typing.Tuple[int, int]]=None,
  68. ) -> typing.List[typing.List[typing.Tuple[int, int]]]:
  69. if where_positions is None:
  70. return self.interiors
  71. interiors = []
  72. for interior in self.interiors:
  73. for where_position in where_positions:
  74. if where_position in interior and interior not in interiors:
  75. interiors.append(interior)
  76. return interiors
  77. def _get_interior_unique_key(
  78. self,
  79. interiors: typing.List[typing.List[typing.Tuple[int, int]]],
  80. ) -> str:
  81. """
  82. Compute a key for given interior list. WARNING: For performance reasons,
  83. actual unique key is interior list ID concatenation:
  84. So, if same interior list is given, but in different python object, key will be
  85. different !
  86. :param interiors: Interior list to build unique key
  87. :return: String or Int who id unique key of given interiors
  88. """
  89. return '.'.join([str(id(i)) for i in interiors])
  90. def update_image_for_interiors(
  91. self,
  92. interiors: typing.List[typing.List[typing.Tuple[int, int]]],
  93. tile_width: int,
  94. tile_height: int,
  95. invert_y: bool=True,
  96. ) -> PngImageFile:
  97. """
  98. :param interiors: List of list of interior tile positions
  99. :param tile_width: Width of a tile
  100. :param tile_height: Height of a tile
  101. :param invert_y: If y reference is top left instead bottom left
  102. :return: Image givan at __init__ with all tile pixels replaces by transparent
  103. pixel
  104. """
  105. try:
  106. return self._cache[self._get_interior_unique_key(interiors)]
  107. except KeyError:
  108. pass # compute it
  109. image = self.original_image.copy()
  110. image_height = image.height
  111. pixels = image.load()
  112. for interior in interiors:
  113. for tile_x, tile_y in interior:
  114. start_x = tile_x * tile_width
  115. start_y = tile_y * tile_height
  116. for x in range(start_x, start_x+tile_width):
  117. for y in range(start_y, start_y+tile_height):
  118. real_y = y
  119. if invert_y:
  120. real_y = image_height - 1 - y
  121. pixels[x, real_y] = (0, 0, 0, 0)
  122. self._cache[self._get_interior_unique_key(interiors)] = image
  123. return image