1 # -*- coding: utf-8 -*-
4 TileMap loader for python for Tiled, a generic tile map editor
5 from http://mapeditor.org/ .
6 It loads the \*.tmx files produced by Tiled.
11 # Versioning scheme based on: http://en.wikipedia.org/wiki/Versioning#Designating_development_stage
13 # +-- api change, probably incompatible with older versions
14 # | +-- enhancements but no api change
16 # major.minor[.build[.revision]]
18 # +-|* 0 for alpha (status)
19 # |* 1 for beta (status)
20 # |* 2 for release candidate
21 # |* 3 for (public) release
24 # * 1.2.0.1 instead of 1.2-a
25 # * 1.2.1.2 instead of 1.2-b2 (beta with some bug fixes)
26 # * 1.2.2.3 instead of 1.2-rc (release candidate)
27 # * 1.2.3.0 instead of 1.2-r (commercial distribution)
28 # * 1.2.3.5 instead of 1.2-r5 (commercial distribution with many bug fixes)
30 __revision__
= "$Rev$"
31 __version__
= "3.1.0." + __revision__
[6:-2]
32 __author__
= 'DR0ID @ 2009-2011'
35 # #the following few lines are needed to use logging if this module used without
36 # # a previous call to logging.basicConfig()
37 # if 0 == len(logging.root.handlers):
38 # logging.basicConfig(level=logging.DEBUG)
40 # _LOGGER = logging.getLogger('tiledtmxloader')
42 # _LOGGER.debug('%s loading ...' % (__name__))
43 # -----------------------------------------------------------------------------
47 from xml
.dom
import minidom
, Node
51 from StringIO
import StringIO
54 from io
import StringIO
59 # -----------------------------------------------------------------------------
60 class TileMap(object):
63 The TileMap holds all the map data.
67 orthogonal or isometric or hexagonal or shifted
69 width of the tiles (for all layers)
71 height of the tiles (for all layers)
73 width of the map (number of tiles)
75 height of the map (number of tiles)
77 version of the map format
81 the propertis set in the editor, name-value pairs, strings
83 width of the map in pixels
85 height of the map in pixels
90 named_layers : dict of string:TledLayer
91 dict containing {name : TileLayer}
92 named_tile_sets : dict
93 dict containing {name : TileSet}
99 # This is the top container for all data. The gid is the global id
101 # Before calling convert most of the values are strings. Some additional
102 # values are also calculated, see convert() for details. After calling
103 # convert, most values are integers or floats where appropriat.
105 The TileMap holds all the map data.
108 self
.orientation
= None
114 self
.tile_sets
= [] # TileSet
115 # ISSUE 9: object groups should be in the same order as layers
116 self
.layers
= [] # WorldTileLayer <- what order? back to front (guessed)
117 # self.object_groups = []
118 self
.properties
= {} # {name: value}
121 self
.pixel_height
= 0
122 self
.named_layers
= {} # {name: layer}
123 self
.named_tile_sets
= {} # {name: tile_set}
124 self
.map_file_name
= ""
128 Converts numerical values from strings to numerical values.
129 It also calculates or set additional data:
135 self
.tilewidth
= int(self
.tilewidth
)
136 self
.tileheight
= int(self
.tileheight
)
137 self
.width
= int(self
.width
)
138 self
.height
= int(self
.height
)
139 self
.pixel_width
= self
.width
* self
.tilewidth
140 self
.pixel_height
= self
.height
* self
.tileheight
142 for layer
in self
.layers
:
144 if not layer
.is_object_group
:
145 layer
.tilewidth
= self
.tilewidth
146 layer
.tileheight
= self
.tileheight
147 self
.named_layers
[layer
.name
] = layer
150 for tile_set
in self
.tile_sets
:
151 self
.named_tile_sets
[tile_set
.name
] = tile_set
152 tile_set
.spacing
= int(tile_set
.spacing
)
153 tile_set
.margin
= int(tile_set
.margin
)
154 for img
in tile_set
.images
:
156 img
.trans
= (int(img
.trans
[:2], 16), \
157 int(img
.trans
[2:4], 16), \
158 int(img
.trans
[4:], 16))
162 Decodes the TileLayer encoded_content and saves it in decoded_content.
164 for layer
in self
.layers
:
165 if not layer
.is_object_group
:
166 self
._decode
_layer
(layer
)
169 def _decode_layer(self
, layer
):
171 Converts the contents in a list of integers which are the gid of the
172 used tiles. If necessairy it decodes and uncompresses the contents.
174 layer
.decoded_content
= []
175 if layer
.encoded_content
:
176 content
= layer
.encoded_content
178 if layer
.encoding
.lower() == 'base64':
179 content
= decode_base64(content
)
180 elif layer
.encoding
.lower() == 'csv':
181 list_of_lines
= content
.split()
182 for line
in list_of_lines
:
183 layer
.decoded_content
.extend(line
.split(','))
184 self
._fill
_decoded
_content
(layer
, list(map(int, \
185 [val
for val
in layer
.decoded_content
if val
])))
188 raise Exception('unknown data encoding %s' % \
191 # in the case of xml the encoded_content already contains a
193 self
._fill
_decoded
_content
(layer
, list(map(int, layer
.encoded_content
)))
196 if layer
.compression
:
197 if layer
.compression
== 'gzip':
198 content
= decompress_gzip(content
)
199 elif layer
.compression
== 'zlib':
200 content
= decompress_zlib(content
)
202 raise Exception('unknown data compression %s' % \
205 raise Exception('no encoded content to decode')
207 # struc = struct.Struct("<" + "I" * layer.width)
208 # struc_unpack_from = struc.unpack_from
209 # layer_decoded_content_extend = layer.decoded_content.extend
210 # for idx in range(0, len(content), 4 * layer.width):
211 # val = struc_unpack_from(content, idx)
212 # layer_decoded_content_extend(val)
215 struc
= struct
.Struct("<" + "I" * layer
.width
* layer
.height
)
216 val
= struc
.unpack(content
) # make Cell
217 # layer.decoded_content.extend(val)
220 # layer.decoded_content = array.array('I')
221 # layer.decoded_content.extend(val)
222 self
._fill
_decoded
_content
(layer
, val
)
225 # arr = array.array('I')
226 # arr.fromlist(layer.decoded_content)
227 # layer.decoded_content = arr
229 # TODO: generate property grid here??
231 def _fill_decoded_content(self
, layer
, gid_list
):
232 layer
.decoded_content
= array
.array('I')
233 layer
.decoded_content
.extend(gid_list
)
238 # -----------------------------------------------------------------------------
241 class TileSet(object):
243 A tileset holds the tiles and its images.
247 the first gid of this tileset
249 the name of this TileSet
254 indexed_images : dict
255 after calling load() it is dict containing id: image
257 the spacing between tiles
259 the marging of the tiles
261 the propertis set in the editor, name-value pairs
263 the actual width of the tile, can be different from the tilewidth
266 the actual hight of th etile, can be different from the tilehight
274 self
.images
= [] # TileImage
275 self
.tiles
= [] # Tile
276 self
.indexed_images
= {} # {id:image}
283 # -----------------------------------------------------------------------------
285 class TileImage(object):
287 An image of a tile or just an image.
291 id of this image (has nothing to do with gid)
293 the format as string, only 'png' at the moment
295 filename of the image. either this is set or the content
297 encoding of the content
298 trans : tuple of (r,g,b)
299 the colorkey color, raw as hex, after calling convert just a
302 the propertis set in the editor, name-value pairs
304 after calling load the pygame surface
311 self
.encoding
= None # from <data>...</data>
312 self
.content
= None # from <data>...</data>
315 self
.properties
= {} # {name: value}
317 # -----------------------------------------------------------------------------
325 id of the tile gid = TileSet.firstgid + Tile.id
326 images : list of :class:TileImage
327 list of TileImage, either its 'id' or 'image data' will be set
328 properties : dict of name:value
329 the propertis set in the editor, name-value pairs
332 # [20:22] DR0ID_: to sum up: there are two use cases,
333 # if the tile element has a child element 'image' then tile is
334 # standalone with its own id and
335 # the other case where a tileset is present then it
336 # referes to the image with that id in the tileset
340 self
.images
= [] # uses TileImage but either only id will be set or image data
341 self
.properties
= {} # {name: value}
343 # -----------------------------------------------------------------------------
347 def __init__(self
, idx
):
349 self
.porperties
= None
351 # -----------------------------------------------------------------------------
353 class TileLayer(object):
355 A layer of the world.
359 position of layer in the world in number of tiles (not pixels)
361 position of layer in the world in number of tiles (not pixels)
363 number of tiles in x direction
365 number of tiles in y direction
367 width of layer in pixels
369 height of layer in pixels
373 float from 0 (full transparent) to 1.0 (opaque)
374 decoded_content : list
375 list of graphics id going through the map::
378 where decoded_content[0] is (0,0)
379 decoded_content[1] is (1,0)
381 decoded_content[w] is (width,0)
382 decoded_content[w+1] is (0,1)
384 decoded_content[w * h] is (width,height)
386 usage: graphics id = decoded_content[tile_x + tile_y * width]
388 list of list, usage: graphics id = content2D[x][y]
398 self
.pixel_height
= 0
402 self
.compression
= None
403 self
.encoded_content
= None
404 self
.decoded_content
= []
406 self
.properties
= {} # {name: value}
407 self
.is_object_group
= False # ISSUE 9
408 self
._content
2D
= None
412 # Converts the contents in a list of integers which are the gid of the
413 # used tiles. If necessairy it decodes and uncompresses the contents.
415 # self.decoded_content = []
416 # if self.encoded_content:
417 # content = self.encoded_content
419 # if self.encoding.lower() == 'base64':
420 # content = decode_base64(content)
421 # elif self.encoding.lower() == 'csv':
422 # list_of_lines = content.split()
423 # for line in list_of_lines:
424 # self.decoded_content.extend(line.split(','))
425 # self.decoded_content = list(map(int, \
426 # [val for val in self.decoded_content if val]))
429 # raise Exception('unknown data encoding %s' % \
432 # # in the case of xml the encoded_content already contains a
434 # self.decoded_content = list(map(int, self.encoded_content))
436 # if self.compression:
437 # if self.compression == 'gzip':
438 # content = decompress_gzip(content)
439 # elif self.compression == 'zlib':
440 # content = decompress_zlib(content)
442 # raise Exception('unknown data compression %s' % \
443 # (self.compression))
445 # raise Exception('no encoded content to decode')
447 # # struc = struct.Struct("<" + "I" * self.width)
448 # # struc_unpack_from = struc.unpack_from
449 # # self_decoded_content_extend = self.decoded_content.extend
450 # # for idx in range(0, len(content), 4 * self.width):
451 # # val = struc_unpack_from(content, idx)
452 # # self_decoded_content_extend(val)
454 # struc = struct.Struct("<" + "I" * self.width * self.height)
455 # val = struc.unpack(content) # make Cell
456 # # self.decoded_content.extend(val)
459 # self.decoded_content = array.array('I')
460 # self.decoded_content.extend(val)
463 # # arr = array.array('I')
464 # # arr.fromlist(self.decoded_content)
465 # # self.decoded_content = arr
467 # # TODO: generate property grid here??
471 def generate_2D(self
):
474 # generate the needed lists and fill them
475 for xpos
in range(self
.width
):
476 self
.content2D
.append(array
.array('I'))
477 for ypos
in range(self
.height
):
478 self
.content2D
[xpos
].append( \
479 self
.decoded_content
[xpos
+ ypos
* self
.width
])
481 def pretty_print(self
):
483 for y
in range(int(self
.height
)):
485 for x
in range(int(self
.width
)):
486 output
+= str(self
.decoded_content
[num
])
491 self
.opacity
= float(self
.opacity
)
494 self
.width
= int(self
.width
)
495 self
.height
= int(self
.height
)
496 self
.pixel_width
= self
.width
* self
.tilewidth
497 self
.pixel_height
= self
.height
* self
.tileheight
498 self
.visible
= bool(int(self
.visible
))
500 # def get_visible_tile_range(self, xmin, ymin, xmax, ymax):
501 # tile_w = self.pixel_width / self.width
502 # tile_h = self.pixel_height / self.height
503 # left = int(round(float(xmin) / tile_w)) - 1
504 # right = int(round(float(xmax) / tile_w)) + 2
505 # top = int(round(float(ymin) / tile_h)) - 1
506 # bottom = int(round(float(ymax) / tile_h)) + 2
507 # return (left, top, left - right, top - bottom)
509 # def get_tiles(self, xmin, ymin, xmax, ymax):
512 # for ypos in range(ymin, ymax):
513 # for xpos in range(xmin, xmax):
515 # img_idx = self.content2D[xpos][ypos]
517 # tiles.append((xpos, ypos, img_idx))
522 # -----------------------------------------------------------------------------
525 class MapObjectGroupLayer(object):
527 Group of objects on the map.
535 width of the bounding box (usually 0, so no use)
537 height of the bounding box (usually 0, so no use)
541 list of the map objects
553 self
.properties
= {} # {name: value}
554 self
.is_object_group
= True # ISSUE 9
559 self
.width
= int(self
.width
)
560 self
.height
= int(self
.height
)
561 for map_obj
in self
.objects
:
562 map_obj
.x
= int(map_obj
.x
)
563 map_obj
.y
= int(map_obj
.y
)
564 map_obj
.width
= int(map_obj
.width
)
565 map_obj
.height
= int(map_obj
.height
)
567 # -----------------------------------------------------------------------------
569 class MapObject(object):
571 A single object on the map.
575 x position relative to group x position
577 y position relative to group y position
581 height of this object
583 the type of this object
584 image_source : string
585 source path of the image for this object
586 image : :class:TileImage
587 after loading this is the pygame surface containing the image
596 self
.image_source
= None
598 self
.properties
= {} # {name: value}
600 # -----------------------------------------------------------------------------
601 def decode_base64(in_str
):
603 Decodes a base64 string and returns it.
607 base64 encoded string
609 :returns: decoded string
612 return base64
.decodestring(in_str
.encode('latin-1'))
614 # -----------------------------------------------------------------------------
615 def decompress_gzip(in_str
):
617 Uncompresses a gzip string and returns it.
621 gzip compressed string
623 :returns: uncompressed string
627 if sys
.version_info
> (2, ):
628 from io
import BytesIO
629 copmressed_stream
= BytesIO(in_str
)
631 # gzip can only handle file object therefore using StringIO
632 copmressed_stream
= StringIO(in_str
.decode("latin-1"))
633 gzipper
= gzip
.GzipFile(fileobj
=copmressed_stream
)
634 content
= gzipper
.read()
638 # -----------------------------------------------------------------------------
639 def decompress_zlib(in_str
):
641 Uncompresses a zlib string and returns it.
645 zlib compressed string
647 :returns: uncompressed string
650 content
= zlib
.decompress(in_str
)
652 # -----------------------------------------------------------------------------
653 def printer(obj
, ident
=''):
655 Helper function, prints a hirarchy of objects.
658 print(ident
+ obj
.__class
__.__name
__.upper())
661 for name
in dir(obj
):
662 elem
= getattr(obj
, name
)
663 if isinstance(elem
, list) and name
!= 'decoded_content':
665 elif not inspect
.ismethod(elem
):
666 if not name
.startswith('__'):
667 if name
== 'data' and elem
:
668 print(ident
+ 'data = ')
669 printer(elem
, ident
+ ' ')
671 print(ident
+ '%s\t= %s' % (name
, getattr(obj
, name
)))
672 for objt_list
in lists
:
673 for _obj
in objt_list
:
674 printer(_obj
, ident
+ ' ')
676 # -----------------------------------------------------------------------------
678 class VersionError(Exception): pass
680 # -----------------------------------------------------------------------------
681 class TileMapParser(object):
683 Allows to parse and decode map files for 'Tiled', a open source map editor
684 written in java. It can be found here: http://mapeditor.org/
687 def _build_tile_set(self
, tile_set_node
, world_map
):
689 self
._set
_attributes
(tile_set_node
, tile_set
)
690 if hasattr(tile_set
, "source"):
691 tile_set
= self
._parse
_tsx
(tile_set
.source
, tile_set
, world_map
)
693 tile_set
= self
._get
_tile
_set
(tile_set_node
, tile_set
, \
695 world_map
.tile_sets
.append(tile_set
)
697 def _parse_tsx(self
, file_name
, tile_set
, world_map
):
698 # ISSUE 5: the *.tsx file is probably relative to the *.tmx file
699 if not os
.path
.isabs(file_name
):
700 # print "map file name", self.map_file_name
701 file_name
= self
._get
_abs
_path
(self
.map_file_name
, file_name
)
702 # print "tsx filename: ", file_name
703 # would be more elegant to use "with open(file_name, "rb") as file:"
704 # but that is python 2.6
707 file = open(file_name
, "rb")
708 dom
= minidom
.parseString(file.read())
712 for node
in self
._get
_nodes
(dom
.childNodes
, 'tileset'):
713 tile_set
= self
._get
_tile
_set
(node
, tile_set
, file_name
)
717 def _get_tile_set(self
, tile_set_node
, tile_set
, base_path
):
718 for node
in self
._get
_nodes
(tile_set_node
.childNodes
, 'image'):
719 self
._build
_tile
_set
_image
(node
, tile_set
, base_path
)
720 for node
in self
._get
_nodes
(tile_set_node
.childNodes
, 'tile'):
721 self
._build
_tile
_set
_tile
(node
, tile_set
)
722 self
._set
_attributes
(tile_set_node
, tile_set
)
725 def _build_tile_set_image(self
, image_node
, tile_set
, base_path
):
727 self
._set
_attributes
(image_node
, image
)
728 # id of TileImage has to be set! -> Tile.TileImage will only have id set
729 for node
in self
._get
_nodes
(image_node
.childNodes
, 'data'):
730 self
._set
_attributes
(node
, image
)
731 image
.content
= node
.childNodes
[0].nodeValue
732 image
.source
= self
._get
_abs
_path
(base_path
, image
.source
) # ISSUE 5
733 tile_set
.images
.append(image
)
735 def _get_abs_path(self
, base
, relative
):
736 if os
.path
.isabs(relative
):
738 if os
.path
.isfile(base
):
739 base
= os
.path
.dirname(base
)
740 return os
.path
.abspath(os
.path
.join(base
, relative
))
742 def _build_tile_set_tile(self
, tile_set_node
, tile_set
):
744 self
._set
_attributes
(tile_set_node
, tile
)
745 for node
in self
._get
_nodes
(tile_set_node
.childNodes
, 'image'):
746 self
._build
_tile
_set
_tile
_image
(node
, tile
)
747 tile_set
.tiles
.append(tile
)
749 def _build_tile_set_tile_image(self
, tile_node
, tile
):
750 tile_image
= TileImage()
751 self
._set
_attributes
(tile_node
, tile_image
)
752 for node
in self
._get
_nodes
(tile_node
.childNodes
, 'data'):
753 self
._set
_attributes
(node
, tile_image
)
754 tile_image
.content
= node
.childNodes
[0].nodeValue
755 tile
.images
.append(tile_image
)
757 def _build_layer(self
, layer_node
, world_map
):
759 self
._set
_attributes
(layer_node
, layer
)
760 for node
in self
._get
_nodes
(layer_node
.childNodes
, 'data'):
761 self
._set
_attributes
(node
, layer
)
763 layer
.encoded_content
= node
.lastChild
.nodeValue
765 #print 'has childnodes', node.hasChildNodes()
766 layer
.encoded_content
= []
767 for child
in node
.childNodes
:
768 if child
.nodeType
== Node
.ELEMENT_NODE
and \
769 child
.nodeName
== "tile":
770 val
= child
.attributes
["gid"].nodeValue
772 layer
.encoded_content
.append(val
)
773 world_map
.layers
.append(layer
)
775 def _build_world_map(self
, world_node
):
776 world_map
= TileMap()
777 self
._set
_attributes
(world_node
, world_map
)
778 if world_map
.version
!= "1.0":
779 raise VersionError('this parser was made for maps of version 1.0, found version %s' % world_map
.version
)
780 for node
in self
._get
_nodes
(world_node
.childNodes
, 'tileset'):
781 self
._build
_tile
_set
(node
, world_map
)
782 for node
in self
._get
_nodes
(world_node
.childNodes
, 'layer'):
783 self
._build
_layer
(node
, world_map
)
784 for node
in self
._get
_nodes
(world_node
.childNodes
, 'objectgroup'):
785 self
._build
_object
_groups
(node
, world_map
)
788 def _build_object_groups(self
, object_group_node
, world_map
):
789 object_group
= MapObjectGroupLayer()
790 self
._set
_attributes
(object_group_node
, object_group
)
791 for node
in self
._get
_nodes
(object_group_node
.childNodes
, 'object'):
792 tiled_object
= MapObject()
793 self
._set
_attributes
(node
, tiled_object
)
794 for img_node
in self
._get
_nodes
(node
.childNodes
, 'image'):
795 tiled_object
.image_source
= \
796 img_node
.attributes
['source'].nodeValue
797 object_group
.objects
.append(tiled_object
)
799 world_map
.layers
.append(object_group
)
802 def _get_nodes(self
, nodes
, name
):
804 if node
.nodeType
== Node
.ELEMENT_NODE
and node
.nodeName
== name
:
807 def _set_attributes(self
, node
, obj
):
808 attrs
= node
.attributes
809 for attr_name
in list(attrs
.keys()):
810 setattr(obj
, attr_name
, attrs
.get(attr_name
).nodeValue
)
811 self
._get
_properties
(node
, obj
)
813 def _get_properties(self
, node
, obj
):
815 for properties_node
in self
._get
_nodes
(node
.childNodes
, 'properties'):
816 for property_node
in self
._get
_nodes
(properties_node
.childNodes
, 'property'):
818 props
[property_node
.attributes
['name'].nodeValue
] = \
819 property_node
.attributes
['value'].nodeValue
821 props
[property_node
.attributes
['name'].nodeValue
] = \
822 property_node
.lastChild
.nodeValue
823 obj
.properties
.update(props
)
827 def parse(self
, file_name
):
829 Parses the given map. Does no decoding nor loading of the data.
830 :return: instance of TileMap
832 # would be more elegant to use
833 # "with open(file_name, "rb") as tmx_file:" but that is python 2.6
834 self
.map_file_name
= os
.path
.abspath(file_name
)
837 tmx_file
= open(self
.map_file_name
, "rb")
838 dom
= minidom
.parseString(tmx_file
.read())
842 for node
in self
._get
_nodes
(dom
.childNodes
, 'map'):
843 world_map
= self
._build
_world
_map
(node
)
845 world_map
.map_file_name
= self
.map_file_name
849 def parse_decode(self
, file_name
):
851 Parses the map but additionally decodes the data.
852 :return: instance of TileMap
854 world_map
= self
.parse(file_name
)
859 # -----------------------------------------------------------------------------
861 class AbstractResourceLoader(object):
863 Abstract base class for the resource loader.
871 self
.indexed_tiles
= {} # {gid: (offsetx, offsety, image}
872 self
.world_map
= None
875 def _load_image(self
, filename
, colorkey
=None): # -> image
881 Path to the file to be loaded.
883 The (r, g, b) color that should be used as colorkey
890 raise NotImplementedError('This should be implemented in a inherited class')
892 def _load_image_file_like(self
, file_like_obj
, colorkey
=None): # -> image
894 Load a image from a file like object.
898 This is the file like object to load the image from.
900 The (r, g, b) color that should be used as colorkey
906 raise NotImplementedError('This should be implemented in a inherited class')
908 def _load_image_parts(self
, filename
, margin
, spacing
, tilewidth
, tileheight
, colorkey
=None): #-> [images]
910 Load different tile images from one source image.
914 Path to image to be loaded.
916 The margin around the image.
918 The space between the tile images.
920 The width of a single tile.
922 The height of a single tile.
924 The (r, g, b) color that should be used as colorkey
928 Luckily that iteration is so easy in python::
932 for y in xrange(margin, h, tileheight + spacing):
933 for x in xrange(margin, w, tilewidth + spacing):
936 :rtype: a list of images
938 raise NotImplementedError('This should be implemented in a inherited class')
940 def load(self
, tile_map
):
943 self
.world_map
= tile_map
944 for tile_set
in tile_map
.tile_sets
:
945 # do images first, because tiles could reference it
946 for img
in tile_set
.images
:
948 self
._load
_image
_from
_source
(tile_map
, tile_set
, img
)
950 tile_set
.indexed_images
[img
.id] = self
._load
_tile
_image
(img
)
952 for tile
in tile_set
.tiles
:
953 for img
in tile
.images
:
954 if not img
.content
and not img
.source
:
956 indexed_img
= tile_set
.indexed_images
[img
.id]
957 self
.indexed_tiles
[int(tile_set
.firstgid
) + int(tile
.id)] = (0, 0, indexed_img
)
960 self
._load
_image
_from
_source
(tile_map
, tile_set
, img
)
962 indexed_img
= self
._load
_tile
_image
(img
)
963 self
.indexed_tiles
[int(tile_set
.firstgid
) + int(tile
.id)] = (0, 0, indexed_img
)
965 def _load_image_from_source(self
, tile_map
, tile_set
, a_tile_image
):
966 # relative path to file
967 img_path
= os
.path
.join(os
.path
.dirname(tile_map
.map_file_name
), \
969 tile_width
= int(tile_map
.tilewidth
)
970 tile_height
= int(tile_map
.tileheight
)
971 if tile_set
.tileheight
:
972 tile_width
= int(tile_set
.tilewidth
)
973 if tile_set
.tilewidth
:
974 tile_height
= int(tile_set
.tileheight
)
977 # the offset is used for pygame because the origin is topleft in pygame
978 if tile_height
> tile_map
.tileheight
:
979 offsety
= tile_height
- tile_map
.tileheight
981 for image
in self
._load
_image
_parts
(img_path
, \
982 tile_set
.margin
, tile_set
.spacing
, \
983 tile_width
, tile_height
, a_tile_image
.trans
):
984 self
.indexed_tiles
[int(tile_set
.firstgid
) + idx
] = \
985 (offsetx
, -offsety
, image
)
988 def _load_tile_image(self
, a_tile_image
):
989 img_str
= a_tile_image
.content
990 if a_tile_image
.encoding
:
991 if a_tile_image
.encoding
== 'base64':
992 img_str
= decode_base64(a_tile_image
.content
)
994 raise Exception('unknown image encoding %s' % a_tile_image
.encoding
)
995 sio
= StringIO(img_str
)
996 new_image
= self
._load
_image
_file
_like
(sio
, a_tile_image
.trans
)
999 # -----------------------------------------------------------------------------