123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917 |
- # -*- coding: utf-8 -*-
- """
- TileMap loader for python for Tiled, a generic tile map editor
- from http://mapeditor.org/ .
- It loads the \*.tmx files produced by Tiled.
- """
- # Versioning scheme based on: http://en.wikipedia.org/wiki/Versioning#Designating_development_stage
- #
- # +-- api change, probably incompatible with older versions
- # | +-- enhancements but no api change
- # | |
- # major.minor[.build[.revision]]
- # |
- # +-|* 0 for alpha (status)
- # |* 1 for beta (status)
- # |* 2 for release candidate
- # |* 3 for (public) release
- #
- # For instance:
- # * 1.2.0.1 instead of 1.2-a
- # * 1.2.1.2 instead of 1.2-b2 (beta with some bug fixes)
- # * 1.2.2.3 instead of 1.2-rc (release candidate)
- # * 1.2.3.0 instead of 1.2-r (commercial distribution)
- # * 1.2.3.5 instead of 1.2-r5 (commercial distribution with many bug fixes)
- __revision__ = "$Rev: 115 $"
- __version__ = "3.1.0." + __revision__[6:-2]
- __author__ = 'DR0ID @ 2009-2011'
- # import logging
- # #the following few lines are needed to use logging if this module used without
- # # a previous call to logging.basicConfig()
- # if 0 == len(logging.root.handlers):
- # logging.basicConfig(level=logging.DEBUG)
- # _LOGGER = logging.getLogger('tiledtmxloader')
- # if __debug__:
- # _LOGGER.debug('%s loading ...' % (__name__))
- # -----------------------------------------------------------------------------
- import sys
- from xml.dom import minidom, Node
- try:
- import StringIO
- from StringIO import StringIO
- except:
- from io import StringIO
- import os.path
- import struct
- import array
- # -----------------------------------------------------------------------------
- class TileMap(object):
- """
- The TileMap holds all the map data.
- :Ivariables:
- orientation : string
- orthogonal or isometric or hexagonal or shifted
- tilewidth : int
- width of the tiles (for all layers)
- tileheight : int
- height of the tiles (for all layers)
- width : int
- width of the map (number of tiles)
- height : int
- height of the map (number of tiles)
- version : string
- version of the map format
- tile_sets : list
- list of TileSet
- properties : dict
- the propertis set in the editor, name-value pairs, strings
- pixel_width : int
- width of the map in pixels
- pixel_height : int
- height of the map in pixels
- layers : list
- list of TileLayer
- map_file_name : dict
- file name of the map
- named_layers : dict of string:TledLayer
- dict containing {name : TileLayer}
- named_tile_sets : dict
- dict containing {name : TileSet}
- """
- def __init__(self):
- # This is the top container for all data. The gid is the global id
- # (for a image).
- # Before calling convert most of the values are strings. Some additional
- # values are also calculated, see convert() for details. After calling
- # convert, most values are integers or floats where appropriat.
- """
- The TileMap holds all the map data.
- """
- # set through parser
- self.orientation = None
- self.tileheight = 0
- self.tilewidth = 0
- self.width = 0
- self.height = 0
- self.version = 0
- self.tile_sets = [] # TileSet
- # ISSUE 9: object groups should be in the same order as layers
- self.layers = [] # WorldTileLayer <- what order? back to front (guessed)
- # self.object_groups = []
- self.properties = {} # {name: value}
- # additional info
- self.pixel_width = 0
- self.pixel_height = 0
- self.named_layers = {} # {name: layer}
- self.named_tile_sets = {} # {name: tile_set}
- self.map_file_name = ""
- def convert(self):
- """
- Converts numerical values from strings to numerical values.
- It also calculates or set additional data:
- pixel_width
- pixel_height
- named_layers
- named_tile_sets
- """
- self.tilewidth = int(self.tilewidth)
- self.tileheight = int(self.tileheight)
- self.width = int(self.width)
- self.height = int(self.height)
- self.pixel_width = self.width * self.tilewidth
- self.pixel_height = self.height * self.tileheight
- for layer in self.layers:
- # ISSUE 9
- if not layer.is_object_group:
- layer.tilewidth = self.tilewidth
- layer.tileheight = self.tileheight
- self.named_layers[layer.name] = layer
- layer.convert()
- for tile_set in self.tile_sets:
- self.named_tile_sets[tile_set.name] = tile_set
- tile_set.spacing = int(tile_set.spacing)
- tile_set.margin = int(tile_set.margin)
- for img in tile_set.images:
- if img.trans:
- img.trans = (int(img.trans[:2], 16), \
- int(img.trans[2:4], 16), \
- int(img.trans[4:], 16))
- def decode(self):
- """
- Decodes the TileLayer encoded_content and saves it in decoded_content.
- """
- for layer in self.layers:
- if not layer.is_object_group:
- layer.decode()
- # -----------------------------------------------------------------------------
- class TileSet(object):
- """
- A tileset holds the tiles and its images.
- :Ivariables:
- firstgid : int
- the first gid of this tileset
- name : string
- the name of this TileSet
- images : list
- list of TileImages
- tiles : list
- list of Tiles
- indexed_images : dict
- after calling load() it is dict containing id: image
- spacing : int
- the spacing between tiles
- marging : int
- the marging of the tiles
- properties : dict
- the propertis set in the editor, name-value pairs
- tilewidth : int
- the actual width of the tile, can be different from the tilewidth
- of the map
- tilehight : int
- the actual hight of th etile, can be different from the tilehight
- of the map
- """
- def __init__(self):
- self.firstgid = 0
- self.name = None
- self.images = [] # TileImage
- self.tiles = [] # Tile
- self.indexed_images = {} # {id:image}
- self.spacing = 0
- self.margin = 0
- self.properties = {}
- self.tileheight = 0
- self.tilewidth = 0
- # -----------------------------------------------------------------------------
- class TileImage(object):
- """
- An image of a tile or just an image.
- :Ivariables:
- id : int
- id of this image (has nothing to do with gid)
- format : string
- the format as string, only 'png' at the moment
- source : string
- filename of the image. either this is set or the content
- encoding : string
- encoding of the content
- trans : tuple of (r,g,b)
- the colorkey color, raw as hex, after calling convert just a
- (r,g,b) tuple
- properties : dict
- the propertis set in the editor, name-value pairs
- image : TileImage
- after calling load the pygame surface
- """
- def __init__(self):
- self.id = 0
- self.format = None
- self.source = None
- self.encoding = None # from <data>...</data>
- self.content = None # from <data>...</data>
- self.image = None
- self.trans = None
- self.properties = {} # {name: value}
- # -----------------------------------------------------------------------------
- class Tile(object):
- """
- A single tile.
- :Ivariables:
- id : int
- id of the tile gid = TileSet.firstgid + Tile.id
- images : list of :class:TileImage
- list of TileImage, either its 'id' or 'image data' will be set
- properties : dict of name:value
- the propertis set in the editor, name-value pairs
- """
- # [20:22] DR0ID_: to sum up: there are two use cases,
- # if the tile element has a child element 'image' then tile is
- # standalone with its own id and
- # the other case where a tileset is present then it
- # referes to the image with that id in the tileset
- def __init__(self):
- self.id = 0
- self.images = [] # uses TileImage but either only id will be set or image data
- self.properties = {} # {name: value}
- # -----------------------------------------------------------------------------
- class TileLayer(object):
- """
- A layer of the world.
- :Ivariables:
- x : int
- position of layer in the world in number of tiles (not pixels)
- y : int
- position of layer in the world in number of tiles (not pixels)
- width : int
- number of tiles in x direction
- height : int
- number of tiles in y direction
- pixel_width : int
- width of layer in pixels
- pixel_height : int
- height of layer in pixels
- name : string
- name of this layer
- opacity : float
- float from 0 (full transparent) to 1.0 (opaque)
- decoded_content : list
- list of graphics id going through the map::
- e.g [1, 1, 1, ]
- where decoded_content[0] is (0,0)
- decoded_content[1] is (1,0)
- ...
- decoded_content[w] is (width,0)
- decoded_content[w+1] is (0,1)
- ...
- decoded_content[w * h] is (width,height)
- usage: graphics id = decoded_content[tile_x + tile_y * width]
- content2D : list
- list of list, usage: graphics id = content2D[x][y]
- """
- def __init__(self):
- self.width = 0
- self.height = 0
- self.x = 0
- self.y = 0
- self.pixel_width = 0
- self.pixel_height = 0
- self.name = None
- self.opacity = -1
- self.encoding = None
- self.compression = None
- self.encoded_content = None
- self.decoded_content = []
- self.visible = True
- self.properties = {} # {name: value}
- self.content2D = None
- self.is_object_group = False # ISSUE 9
- def decode(self):
- """
- Converts the contents in a list of integers which are the gid of the
- used tiles. If necessairy it decodes and uncompresses the contents.
- """
- self.decoded_content = []
- if self.encoded_content:
- content = self.encoded_content
- if self.encoding:
- if self.encoding.lower() == 'base64':
- content = decode_base64(content)
- elif self.encoding.lower() == 'csv':
- list_of_lines = content.split()
- for line in list_of_lines:
- self.decoded_content.extend(line.split(','))
- self.decoded_content = list(map(int, \
- [val for val in self.decoded_content if val]))
- content = ""
- else:
- raise Exception('unknown data encoding %s' % \
- (self.encoding))
- else:
- # in the case of xml the encoded_content already contains a
- # list of integers
- self.decoded_content = list(map(int, self.encoded_content))
- content = ""
- if self.compression:
- if self.compression == 'gzip':
- content = decompress_gzip(content)
- elif self.compression == 'zlib':
- content = decompress_zlib(content)
- else:
- raise Exception('unknown data compression %s' % \
- (self.compression))
- else:
- raise Exception('no encoded content to decode')
- struc = struct.Struct("<" + "I" * self.width)
- struc_unpack_from = struc.unpack_from
- self_decoded_content_extend = self.decoded_content.extend
- for idx in range(0, len(content), 4 * self.width):
- val = struc_unpack_from(content, idx)
- self_decoded_content_extend(val)
- arr = array.array('I')
- arr.fromlist(self.decoded_content)
- self.decoded_content = arr
- # TODO: generate property grid here??
- self._gen_2D()
- def _gen_2D(self):
- self.content2D = []
- # generate the needed lists and fill them
- for xpos in range(self.width):
- self.content2D.append(array.array('I'))
- for ypos in range(self.height):
- self.content2D[xpos].append( \
- self.decoded_content[xpos + ypos * self.width])
- def pretty_print(self):
- num = 0
- for y in range(int(self.height)):
- output = ""
- for x in range(int(self.width)):
- output += str(self.decoded_content[num])
- num += 1
- print(output)
- def convert(self):
- self.opacity = float(self.opacity)
- self.x = int(self.x)
- self.y = int(self.y)
- self.width = int(self.width)
- self.height = int(self.height)
- self.pixel_width = self.width * self.tilewidth
- self.pixel_height = self.height * self.tileheight
- self.visible = bool(int(self.visible))
- # def get_visible_tile_range(self, xmin, ymin, xmax, ymax):
- # tile_w = self.pixel_width / self.width
- # tile_h = self.pixel_height / self.height
- # left = int(round(float(xmin) / tile_w)) - 1
- # right = int(round(float(xmax) / tile_w)) + 2
- # top = int(round(float(ymin) / tile_h)) - 1
- # bottom = int(round(float(ymax) / tile_h)) + 2
- # return (left, top, left - right, top - bottom)
- # def get_tiles(self, xmin, ymin, xmax, ymax):
- # tiles = []
- # if self.visible:
- # for ypos in range(ymin, ymax):
- # for xpos in range(xmin, xmax):
- # try:
- # img_idx = self.content2D[xpos][ypos]
- # if img_idx:
- # tiles.append((xpos, ypos, img_idx))
- # except IndexError:
- # pass
- # return tiles
- # -----------------------------------------------------------------------------
- class MapObjectGroupLayer(object):
- """
- Group of objects on the map.
- :Ivariables:
- x : int
- the x position
- y : int
- the y position
- width : int
- width of the bounding box (usually 0, so no use)
- height : int
- height of the bounding box (usually 0, so no use)
- name : string
- name of the group
- objects : list
- list of the map objects
- """
- def __init__(self):
- self.width = 0
- self.height = 0
- self.name = None
- self.objects = []
- self.x = 0
- self.y = 0
- self.visible = True
- self.properties = {} # {name: value}
- self.is_object_group = True # ISSUE 9
- def convert(self):
- self.x = int(self.x)
- self.y = int(self.y)
- self.width = int(self.width)
- self.height = int(self.height)
- for map_obj in self.objects:
- map_obj.x = int(float(map_obj.x))
- map_obj.y = int(float(map_obj.y))
- map_obj.width = int(float(map_obj.width))
- map_obj.height = int(float(map_obj.height))
- # -----------------------------------------------------------------------------
- class MapObject(object):
- """
- A single object on the map.
- :Ivariables:
- x : int
- x position relative to group x position
- y : int
- y position relative to group y position
- width : int
- width of this object
- height : int
- height of this object
- type : string
- the type of this object
- image_source : string
- source path of the image for this object
- image : :class:TileImage
- after loading this is the pygame surface containing the image
- """
- def __init__(self):
- self.name = None
- self.x = 0
- self.y = 0
- self.width = 0
- self.height = 0
- self.type = None
- self.image_source = None
- self.image = None
- self.properties = {} # {name: value}
- # -----------------------------------------------------------------------------
- def decode_base64(in_str):
- """
- Decodes a base64 string and returns it.
- :Parameters:
- in_str : string
- base64 encoded string
- :returns: decoded string
- """
- import base64
- return base64.decodestring(in_str.encode('latin-1'))
- # -----------------------------------------------------------------------------
- def decompress_gzip(in_str):
- """
- Uncompresses a gzip string and returns it.
- :Parameters:
- in_str : string
- gzip compressed string
- :returns: uncompressed string
- """
- import gzip
-
- if sys.version_info > (2, ):
- from io import BytesIO
- copmressed_stream = BytesIO(in_str)
- else:
- # gzip can only handle file object therefore using StringIO
- copmressed_stream = StringIO(in_str.decode("latin-1"))
- gzipper = gzip.GzipFile(fileobj=copmressed_stream)
- content = gzipper.read()
- gzipper.close()
- return content
- # -----------------------------------------------------------------------------
- def decompress_zlib(in_str):
- """
- Uncompresses a zlib string and returns it.
- :Parameters:
- in_str : string
- zlib compressed string
- :returns: uncompressed string
- """
- import zlib
- content = zlib.decompress(in_str)
- return content
- # -----------------------------------------------------------------------------
- def printer(obj, ident=''):
- """
- Helper function, prints a hirarchy of objects.
- """
- import inspect
- print(ident + obj.__class__.__name__.upper())
- ident += ' '
- lists = []
- for name in dir(obj):
- elem = getattr(obj, name)
- if isinstance(elem, list) and name != 'decoded_content':
- lists.append(elem)
- elif not inspect.ismethod(elem):
- if not name.startswith('__'):
- if name == 'data' and elem:
- print(ident + 'data = ')
- printer(elem, ident + ' ')
- else:
- print(ident + '%s\t= %s' % (name, getattr(obj, name)))
- for objt_list in lists:
- for _obj in objt_list:
- printer(_obj, ident + ' ')
- # -----------------------------------------------------------------------------
- class VersionError(Exception): pass
- # -----------------------------------------------------------------------------
- class TileMapParser(object):
- """
- Allows to parse and decode map files for 'Tiled', a open source map editor
- written in java. It can be found here: http://mapeditor.org/
- """
- def _build_tile_set(self, tile_set_node, world_map):
- tile_set = TileSet()
- self._set_attributes(tile_set_node, tile_set)
- if hasattr(tile_set, "source"):
- tile_set = self._parse_tsx(tile_set.source, tile_set, world_map)
- else:
- tile_set = self._get_tile_set(tile_set_node, tile_set, \
- self.map_file_name)
- world_map.tile_sets.append(tile_set)
- def _parse_tsx(self, file_name, tile_set, world_map):
- # ISSUE 5: the *.tsx file is probably relative to the *.tmx file
- if not os.path.isabs(file_name):
- # print "map file name", self.map_file_name
- file_name = self._get_abs_path(self.map_file_name, file_name)
- # print "tsx filename: ", file_name
- # would be more elegant to use "with open(file_name, "rb") as file:"
- # but that is python 2.6
- file = None
- try:
- file = open(file_name, "rb")
- dom = minidom.parseString(file.read())
- finally:
- if file:
- file.close()
- for node in self._get_nodes(dom.childNodes, 'tileset'):
- tile_set = self._get_tile_set(node, tile_set, file_name)
- break
- return tile_set
- def _get_tile_set(self, tile_set_node, tile_set, base_path):
- for node in self._get_nodes(tile_set_node.childNodes, 'image'):
- self._build_tile_set_image(node, tile_set, base_path)
- for node in self._get_nodes(tile_set_node.childNodes, 'tile'):
- self._build_tile_set_tile(node, tile_set)
- self._set_attributes(tile_set_node, tile_set)
- return tile_set
- def _build_tile_set_image(self, image_node, tile_set, base_path):
- image = TileImage()
- self._set_attributes(image_node, image)
- # id of TileImage has to be set! -> Tile.TileImage will only have id set
- for node in self._get_nodes(image_node.childNodes, 'data'):
- self._set_attributes(node, image)
- image.content = node.childNodes[0].nodeValue
- image.source = self._get_abs_path(base_path, image.source) # ISSUE 5
- tile_set.images.append(image)
- def _get_abs_path(self, base, relative):
- if os.path.isabs(relative):
- return relative
- if os.path.isfile(base):
- base = os.path.dirname(base)
- return os.path.abspath(os.path.join(base, relative))
- def _build_tile_set_tile(self, tile_set_node, tile_set):
- tile = Tile()
- self._set_attributes(tile_set_node, tile)
- for node in self._get_nodes(tile_set_node.childNodes, 'image'):
- self._build_tile_set_tile_image(node, tile)
- tile_set.tiles.append(tile)
- def _build_tile_set_tile_image(self, tile_node, tile):
- tile_image = TileImage()
- self._set_attributes(tile_node, tile_image)
- for node in self._get_nodes(tile_node.childNodes, 'data'):
- self._set_attributes(node, tile_image)
- tile_image.content = node.childNodes[0].nodeValue
- tile.images.append(tile_image)
- def _build_layer(self, layer_node, world_map):
- layer = TileLayer()
- self._set_attributes(layer_node, layer)
- for node in self._get_nodes(layer_node.childNodes, 'data'):
- self._set_attributes(node, layer)
- if layer.encoding:
- layer.encoded_content = node.lastChild.nodeValue
- else:
- #print 'has childnodes', node.hasChildNodes()
- layer.encoded_content = []
- for child in node.childNodes:
- if child.nodeType == Node.ELEMENT_NODE and \
- child.nodeName == "tile":
- val = child.attributes["gid"].nodeValue
- #print child, val
- layer.encoded_content.append(val)
- world_map.layers.append(layer)
- def _build_world_map(self, world_node):
- world_map = TileMap()
- self._set_attributes(world_node, world_map)
- if world_map.version != "1.0":
- raise VersionError('this parser was made for maps of version 1.0, found version %s' % world_map.version)
- for node in self._get_nodes(world_node.childNodes, 'tileset'):
- self._build_tile_set(node, world_map)
- for node in self._get_nodes(world_node.childNodes, 'layer'):
- self._build_layer(node, world_map)
- for node in self._get_nodes(world_node.childNodes, 'objectgroup'):
- self._build_object_groups(node, world_map)
- return world_map
- def _build_object_groups(self, object_group_node, world_map):
- object_group = MapObjectGroupLayer()
- self._set_attributes(object_group_node, object_group)
- for node in self._get_nodes(object_group_node.childNodes, 'object'):
- tiled_object = MapObject()
- self._set_attributes(node, tiled_object)
- for p in self._get_nodes(node.childNodes, 'polyline'):
- def xy(s):
- (x, y) = [int(c) for c in s.split(",")]
- return (int(tiled_object.x) + x, int(tiled_object.y) + y)
- tiled_object.polyline = \
- [xy(s) for s in p.attributes['points'].nodeValue.split()]
- for img_node in self._get_nodes(node.childNodes, 'image'):
- tiled_object.image_source = \
- img_node.attributes['source'].nodeValue
- object_group.objects.append(tiled_object)
- # ISSUE 9
- world_map.layers.append(object_group)
- # -- helpers -- #
- def _get_nodes(self, nodes, name):
- for node in nodes:
- if node.nodeType == Node.ELEMENT_NODE and node.nodeName == name:
- yield node
- def _set_attributes(self, node, obj):
- attrs = node.attributes
- for attr_name in list(attrs.keys()):
- setattr(obj, attr_name, attrs.get(attr_name).nodeValue)
- self._get_properties(node, obj)
- def _get_properties(self, node, obj):
- props = {}
- for properties_node in self._get_nodes(node.childNodes, 'properties'):
- for property_node in self._get_nodes(properties_node.childNodes, 'property'):
- try:
- props[property_node.attributes['name'].nodeValue] = \
- property_node.attributes['value'].nodeValue
- except KeyError:
- props[property_node.attributes['name'].nodeValue] = \
- property_node.lastChild.nodeValue
- obj.properties.update(props)
- # -- parsers -- #
- def parse(self, file_name):
- """
- Parses the given map. Does no decoding nor loading of the data.
- :return: instance of TileMap
- """
- # would be more elegant to use
- # "with open(file_name, "rb") as tmx_file:" but that is python 2.6
- self.map_file_name = os.path.abspath(file_name)
- tmx_file = None
- try:
- tmx_file = open(self.map_file_name, "rb")
- dom = minidom.parseString(tmx_file.read())
- finally:
- if tmx_file:
- tmx_file.close()
- for node in self._get_nodes(dom.childNodes, 'map'):
- world_map = self._build_world_map(node)
- break
- world_map.map_file_name = self.map_file_name
- world_map.convert()
- return world_map
- def parse_decode(self, file_name):
- """
- Parses the map but additionally decodes the data.
- :return: instance of TileMap
- """
- world_map = self.parse(file_name)
- world_map.decode()
- return world_map
- # -----------------------------------------------------------------------------
- class AbstractResourceLoader(object):
- """
- Abstract base class for the resource loader.
- """
- FLIP_X = 1 << 31
- FLIP_Y = 1 << 30
- def __init__(self):
- self.indexed_tiles = {} # {gid: (offsetx, offsety, image}
- self.world_map = None
- self._img_cache = {}
- def _load_image(self, filename, colorkey=None): # -> image
- """
- Load a single image.
- :Parameters:
- filename : string
- Path to the file to be loaded.
- colorkey : tuple
- The (r, g, b) color that should be used as colorkey
- (or magic color).
- Default: None
- :rtype: image
- """
- raise NotImplementedError('This should be implemented in a inherited class')
- def _load_image_file_like(self, file_like_obj, colorkey=None): # -> image
- """
- Load a image from a file like object.
- :Parameters:
- file_like_obj : file
- This is the file like object to load the image from.
- colorkey : tuple
- The (r, g, b) color that should be used as colorkey
- (or magic color).
- Default: None
- :rtype: image
- """
- raise NotImplementedError('This should be implemented in a inherited class')
- def _load_image_parts(self, filename, margin, spacing, tilewidth, tileheight, colorkey=None): #-> [images]
- """
- Load different tile images from one source image.
- :Parameters:
- filename : string
- Path to image to be loaded.
- margin : int
- The margin around the image.
- spacing : int
- The space between the tile images.
- tilewidth : int
- The width of a single tile.
- tileheight : int
- The height of a single tile.
- colorkey : tuple
- The (r, g, b) color that should be used as colorkey
- (or magic color).
- Default: None
- Luckily that iteration is so easy in python::
- ...
- w, h = image_size
- for y in xrange(margin, h, tileheight + spacing):
- for x in xrange(margin, w, tilewidth + spacing):
- ...
- :rtype: a list of images
- """
- raise NotImplementedError('This should be implemented in a inherited class')
- def load(self, tile_map):
- """
- """
- self.world_map = tile_map
- for tile_set in tile_map.tile_sets:
- # do images first, because tiles could reference it
- for img in tile_set.images:
- if img.source:
- self._load_image_from_source(tile_map, tile_set, img)
- else:
- tile_set.indexed_images[img.id] = self._load_tile_image(img)
- # tiles
- for tile in tile_set.tiles:
- for img in tile.images:
- if not img.content and not img.source:
- # only image id set
- indexed_img = tile_set.indexed_images[img.id]
- self.indexed_tiles[int(tile_set.firstgid) + int(tile.id)] = (0, 0, indexed_img)
- else:
- if img.source:
- self._load_image_from_source(tile_map, tile_set, img)
- else:
- indexed_img = self._load_tile_image(img)
- self.indexed_tiles[int(tile_set.firstgid) + int(tile.id)] = (0, 0, indexed_img)
- def _load_image_from_source(self, tile_map, tile_set, a_tile_image):
- # relative path to file
- img_path = os.path.join(os.path.dirname(tile_map.map_file_name), \
- a_tile_image.source)
- tile_width = int(tile_map.tilewidth)
- tile_height = int(tile_map.tileheight)
- if tile_set.tileheight:
- tile_width = int(tile_set.tilewidth)
- if tile_set.tilewidth:
- tile_height = int(tile_set.tileheight)
- offsetx = 0
- offsety = 0
- # the offset is used for pygame because the origin is topleft in pygame
- if tile_height > tile_map.tileheight:
- offsety = tile_height - tile_map.tileheight
- idx = 0
- for image in self._load_image_parts(img_path, \
- tile_set.margin, tile_set.spacing, \
- tile_width, tile_height, a_tile_image.trans):
- self.indexed_tiles[int(tile_set.firstgid) + idx] = \
- (offsetx, -offsety, image)
- idx += 1
- def _load_tile_image(self, a_tile_image):
- img_str = a_tile_image.content
- if a_tile_image.encoding:
- if a_tile_image.encoding == 'base64':
- img_str = decode_base64(a_tile_image.content)
- else:
- raise Exception('unknown image encoding %s' % a_tile_image.encoding)
- sio = StringIO(img_str)
- new_image = self._load_image_file_like(sio, a_tile_image.trans)
- return new_image
- # -----------------------------------------------------------------------------
|