Initial commit

This commit is contained in:
Zukero
2015-02-23 22:43:19 +01:00
commit 59d8ad1cdb
212 changed files with 29432 additions and 0 deletions

111
hacked-libtiled/build.xml Normal file
View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
This is the Ant build script for rsyntaxtextarea.jar.
Available targets include:
1. compile: Compiles all org.fife classes into ${class-dir}.
2. make-jar: Creates the jar file.
3. make-source-zip: Creates a source zip file.
3. make-javadoc: Creates the javadoc for RSyntaxTextArea.
Author: Robert Futrell
Version: 1.5
Date: 11aug2013
-->
<project name="RSyntaxTextArea" default="make-jar" basedir=".">
<description>RSyntaxTextArea build file</description>
<!-- Set global properties for this build. -->
<property name="version" value="2.5.3"/>
<property name="source-dir" location="src"/>
<property name="class-dir" location="ant-classes"/>
<property name="dist-dir" location="dist"/>
<property name="doc-dir" location="javadoc"/>
<property name="debug" value="true"/>
<property name="debuglevel" value="lines,vars,source"/>
<property name="java-level" value="1.5"/>
<!-- Compiles the classes. -->
<target name="compile" description="Compile the source">
<echo>Compiling with java: ${java.runtime.version}</echo>
<delete includeEmptyDirs="true" quiet="true" dir="${class-dir}"/>
<mkdir dir="${class-dir}"/>
<javac srcdir="${source-dir}" destdir="${class-dir}"
deprecation="yes" debug="${debug}" debuglevel="${debuglevel}"
source="${java-level}" target="${java-level}" includeantruntime="false"/>
</target>
<!-- Creates the jar file. -->
<target name="make-jar" depends="compile"
description="Create RSyntaxTextArea jar">
<delete includeEmptyDirs="true" quiet="true" dir="${dist-dir}"/>
<mkdir dir="${dist-dir}"/>
<jar destfile="${dist-dir}/rsyntaxtextarea.jar">
<fileset dir="${class-dir}"/>
<fileset dir="i18n"/>
<fileset dir="${source-dir}">
<include name="theme.dtd"/>
</fileset>
<manifest>
<attribute name="Specification-Title" value="RSyntaxTextArea"/>
<attribute name="Specification-Version" value="${version}"/>
<attribute name="Implementation-Title" value="org.fife.ui"/>
<attribute name="Implementation-Version" value="${version}"/>
<section name="RTextArea">
<attribute name="Specification-Title" value="RTextArea"/>
<attribute name="Specification-Version" value="${version}"/>
<attribute name="Implementation-Title" value="org.fife.ui.rtextarea"/>
<attribute name="Implementation-Version" value="${version}"/>
</section>
<section name="RSyntaxTextArea">
<attribute name="Specification-Title" value="RSyntaxTextArea-Core"/>
<attribute name="Specification-Version" value="${version}"/>
<attribute name="Implementation-Title" value="org.fife.ui.rsyntaxtextarea"/>
<attribute name="Implementation-Version" value="${version}"/>
</section>
</manifest>
</jar>
<copy todir="${dist-dir}">
<fileset dir="distfiles"/>
</copy>
</target>
<!-- Builds the source zip file. -->
<target name="make-source-zip" description="Builds the source zip file">
<zip destfile="./rsyntaxtextarea_${version}_Source.zip">
<fileset dir=".">
<include name="distfiles/**"/>
<include name="src/**"/>
<include name="i18n/**"/>
<include name="test/**"/>
<include name="themes/**"/>
<include name="build.xml"/>
<include name=".project"/>
<include name=".classpath"/>
</fileset>
</zip>
</target>
<!-- Builds the javadoc. -->
<target name="make-javadoc" depends="compile">
<javadoc destdir="${doc-dir}" author="true" version="true"
breakiterator="yes">
<packageset dir="${source-dir}" defaultexcludes="yes">
<include name="org/**"/>
</packageset>
</javadoc>
</target>
</project>

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2004-2006, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.core;
/**
* Animated tiles take advantage of the Sprite class internally to handle
* animation using an array of tiles.
*
* @see tiled.core.Sprite
*/
public class AnimatedTile extends Tile
{
private Sprite sprite;
public AnimatedTile() {
}
public AnimatedTile(TileSet set) {
super(set);
}
public AnimatedTile(Tile[] frames) {
this();
sprite = new Sprite(frames);
}
public AnimatedTile(Sprite s) {
this();
setSprite(s);
}
public void setSprite(Sprite s) {
sprite = s;
}
public int countAnimationFrames() {
return sprite.getTotalFrames();
}
public int countKeys() {
return sprite.getTotalKeys();
}
public Sprite getSprite() {
return sprite;
}
}

View File

@@ -0,0 +1,469 @@
/*
* Copyright 2004-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.core;
import java.awt.Rectangle;
import java.util.*;
/**
* The Map class is the focal point of the <code>tiled.core</code> package.
*/
public class Map implements Iterable<MapLayer>
{
public static final int ORIENTATION_ORTHOGONAL = 1;
public static final int ORIENTATION_ISOMETRIC = 2;
public static final int ORIENTATION_HEXAGONAL = 4;
/** Shifted (used for iso and hex). */
public static final int ORIENTATION_SHIFTED = 5;
private Vector<MapLayer> layers;
private Vector<ObjectGroup> objectGroups;
private Vector<TileLayer> tileLayers;
private Vector<TileSet> tileSets;
private int tileWidth, tileHeight;
private int orientation = ORIENTATION_ORTHOGONAL;
private Properties properties;
private String filename;
protected Rectangle bounds; // in tiles
/**
* @param width the map width in tiles.
* @param height the map height in tiles.
*/
public Map(int width, int height) {
layers = new Vector<MapLayer>();
bounds = new Rectangle(width, height);
properties = new Properties();
tileSets = new Vector<TileSet>();
objectGroups = new Vector<ObjectGroup>();
tileLayers = new Vector<TileLayer>();
}
/**
* Returns the total number of layers.
*
* @return the size of the layer vector
*/
public int getLayerCount() {
return layers.size();
}
/**
* Changes the bounds of this plane to include all layers completely.
*/
public void fitBoundsToLayers() {
int width = 0;
int height = 0;
Rectangle layerBounds = new Rectangle();
for (int i = 0; i < layers.size(); i++) {
getLayer(i).getBounds(layerBounds);
if (width < layerBounds.width) width = layerBounds.width;
if (height < layerBounds.height) height = layerBounds.height;
}
setBounds(width, height);
}
/**
* Returns a <code>Rectangle</code> representing the maximum bounds in
* tiles.
* @return a new rectangle containing the maximum bounds of this plane
*/
public Rectangle getBounds() {
return new Rectangle(bounds);
}
public void setBounds(int width, int height) {
bounds.width = width;
bounds.height = height;
}
public MapLayer addLayer(MapLayer layer) {
layer.setMap(this);
layers.add(layer);
if (layer instanceof TileLayer) {
tileLayers.add((TileLayer) layer);
} else if (layer instanceof ObjectGroup) {
objectGroups.add((ObjectGroup) layer);
}
return layer;
}
public void setLayer(int index, MapLayer layer) {
layer.setMap(this);
layers.set(index, layer);
if (layer instanceof TileLayer) {
tileLayers.set(index - objectGroups.size(), (TileLayer) layer);
} else if (layer instanceof ObjectGroup) {
objectGroups.set(index, (ObjectGroup) layer);
}
}
public void insertLayer(int index, MapLayer layer) {
layer.setMap(this);
layers.add(index, layer);
if (layer instanceof TileLayer) {
tileLayers.add(index - objectGroups.size(), (TileLayer) layer);
} else if (layer instanceof ObjectGroup) {
objectGroups.add(index, (ObjectGroup) layer);
}
}
/**
* Removes the layer at the specified index. Layers above this layer will
* move down to fill the gap.
*
* @param index the index of the layer to be removed
* @return the layer that was removed from the list
*/
public MapLayer removeLayer(int index) {
if (index < objectGroups.size()) {
objectGroups.remove(index);
} else if (index - objectGroups.size() < tileLayers.size()){
tileLayers.remove(index - objectGroups.size());
}
return layers.remove(index);
}
/**
* Removes all layers from the plane.
*/
public void removeAllLayers() {
layers.removeAllElements();
objectGroups.removeAllElements();
tileLayers.removeAllElements();
}
/**
* Returns the layer vector.
*
* @return Vector the layer vector
*/
public Vector<MapLayer> getLayers() {
return layers;
}
/**
* Returns the tile layer vector.
*
* @return Vector the tile layer vector
*/
public Vector<TileLayer> getTileLayers() {
return tileLayers;
}
/**
* Returns the object group vector.
*
* @return Vector the object group vector
*/
public Vector<ObjectGroup> getObjectGroup() {
return objectGroups;
}
/**
* Sets the layer vector to the given java.util.Vector.
*
* @param layers the new set of layers
*/
public void setLayers(Vector<MapLayer> layers) {
this.layers = layers;
}
/**
* Returns the layer at the specified vector index.
*
* @param i the index of the layer to return
* @return the layer at the specified index, or null if the index is out of
* bounds
*/
public MapLayer getLayer(int i) {
try {
return layers.get(i);
} catch (ArrayIndexOutOfBoundsException e) {
}
return null;
}
/**
* Resizes this plane. The (dx, dy) pair determines where the original
* plane should be positioned on the new area. Only layers that exactly
* match the bounds of the map are resized, any other layers are moved by
* the given shift.
*
* @see tiled.core.MapLayer#resize
*
* @param width The new width of the map.
* @param height The new height of the map.
* @param dx The shift in x direction in tiles.
* @param dy The shift in y direction in tiles.
*/
public void resize(int width, int height, int dx, int dy) {
for (MapLayer layer : this) {
if (layer.bounds.equals(bounds)) {
layer.resize(width, height, dx, dy);
} else {
layer.setOffset(layer.bounds.x + dx, layer.bounds.y + dy);
}
}
bounds.width = width;
bounds.height = height;
}
/**
* Determines whether the point (x,y) falls within the plane.
*
* @param x
* @param y
* @return <code>true</code> if the point is within the plane,
* <code>false</code> otherwise
*/
public boolean inBounds(int x, int y) {
return x >= 0 && y >= 0 && x < bounds.width && y < bounds.height;
}
public Iterator<MapLayer> iterator() {
return layers.iterator();
}
/**
* Adds a Tileset to this Map. If the set is already attached to this map,
* <code>addTileset</code> simply returns.
*
* @param tileset a tileset to add
*/
public void addTileset(TileSet tileset) {
if (tileset == null || tileSets.indexOf(tileset) > -1) {
return;
}
Tile t = tileset.getTile(0);
if (t != null) {
int tw = t.getWidth();
int th = t.getHeight();
if (tw != tileWidth) {
if (tileWidth == 0) {
tileWidth = tw;
tileHeight = th;
}
}
}
tileSets.add(tileset);
}
/**
* Removes a {@link TileSet} from the map, and removes any tiles in the set
* from the map layers.
*
* @param tileset TileSet to remove
*/
public void removeTileset(TileSet tileset) {
// Sanity check
final int tilesetIndex = tileSets.indexOf(tileset);
if (tilesetIndex == -1)
return;
// Go through the map and remove any instances of the tiles in the set
for (Tile tile : tileset) {
for (MapLayer ml : this) {
if (ml instanceof TileLayer) {
((TileLayer) ml).removeTile(tile);
}
}
}
tileSets.remove(tileset);
}
/**
* @return the map properties
*/
public Properties getProperties() {
return properties;
}
public void setProperties(Properties prop) {
properties = prop;
}
public void setFilename(String filename) {
this.filename = filename;
}
/**
* Sets a new tile width.
*
* @param width the new tile width
*/
public void setTileWidth(int width) {
tileWidth = width;
}
/**
* Sets a new tile height.
*
* @param height the new tile height
*/
public void setTileHeight(int height) {
tileHeight = height;
}
public void setOrientation(int orientation) {
this.orientation = orientation;
}
public String getFilename() {
return filename;
}
/**
* Returns a vector with the currently loaded tileSets.
*
* @return Vector
*/
public Vector<TileSet> getTileSets() {
return tileSets;
}
/**
* Returns width of map in tiles.
*
* @return int
*/
public int getWidth() {
return bounds.width;
}
/**
* Returns height of map in tiles.
*
* @return int
*/
public int getHeight() {
return bounds.height;
}
/**
* Returns default tile width for this map.
*
* @return the default tile width
*/
public int getTileWidth() {
return tileWidth;
}
/**
* Returns default tile height for this map.
*
* @return the default tile height
*/
public int getTileHeight() {
return tileHeight;
}
/**
* Returns wether the given tile coordinates fall within the map
* boundaries.
*
* @param x The tile-space x-coordinate
* @param y The tile-space y-coordinate
* @return <code>true</code> if the point is within the map boundaries,
* <code>false</code> otherwise
*/
public boolean contains(int x, int y) {
return x >= 0 && y >= 0 && x < bounds.width && y < bounds.height;
}
/**
* Returns the maximum tile height. This is the height of the highest tile
* in all tileSets or the tile height used by this map if it's smaller.
*
* @return int The maximum tile height
*/
public int getTileHeightMax() {
int maxHeight = tileHeight;
for (TileSet tileset : tileSets) {
int height = tileset.getTileHeight();
if (height > maxHeight) {
maxHeight = height;
}
}
return maxHeight;
}
/**
* Swaps the tile sets at the given indices.
*/
public void swapTileSets(int index0, int index1) {
if (index0 == index1) return;
TileSet set = tileSets.get(index0);
tileSets.set(index0, tileSets.get(index1));
tileSets.set(index1, set);
}
/**
* Returns the orientation of this map. Orientation will be one of
* {@link Map#ORIENTATION_ISOMETRIC}, {@link Map#ORIENTATION_ORTHOGONAL},
* {@link Map#ORIENTATION_HEXAGONAL} and {@link Map#ORIENTATION_SHIFTED}.
*
* @return The orientation from the enumerated set
*/
public int getOrientation() {
return orientation;
}
/**
* Returns string describing the map. The form is <code>Map[width x height
* x layers][tileWidth x tileHeight]</code>, for example <code>
* Map[64x64x2][24x24]</code>.
*
* @return string describing map
*/
public String toString() {
return "Map[" + bounds.width + "x" + bounds.height + "x" +
getLayerCount() + "][" + tileWidth + "x" +
tileHeight + "]";
}
public boolean containsLayer(MapLayer layer) {
return layers.contains(layer);
}
public int getLayerIndex(MapLayer layer) {
return layers.indexOf(layer);
}
}

View File

@@ -0,0 +1,310 @@
/*
* Copyright 2004-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.core;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.util.Properties;
/**
* A layer of a map.
*
* @see Map
*/
public abstract class MapLayer implements Cloneable
{
/** MIRROR_HORIZONTAL */
public static final int MIRROR_HORIZONTAL = 1;
/** MIRROR_VERTICAL */
public static final int MIRROR_VERTICAL = 2;
/** ROTATE_90 */
public static final int ROTATE_90 = 90;
/** ROTATE_180 */
public static final int ROTATE_180 = 180;
/** ROTATE_270 */
public static final int ROTATE_270 = 270;
protected String name;
protected boolean isVisible = true;
protected Map myMap;
protected float opacity = 1.0f;
protected Rectangle bounds;
private Properties properties = new Properties();
public MapLayer() {
bounds = new Rectangle();
setMap(null);
}
/**
* @param w width in tiles
* @param h height in tiles
*/
public MapLayer(int w, int h) {
this(new Rectangle(0, 0, w, h));
}
public MapLayer(Rectangle r) {
this();
setBounds(r);
}
/**
* @param map the map this layer is part of
*/
MapLayer(Map map) {
this();
setMap(map);
}
/**
* @param map the map this layer is part of
* @param w width in tiles
* @param h height in tiles
*/
public MapLayer(Map map, int w, int h) {
this(w, h);
setMap(map);
}
/**
* Performs a linear translation of this layer by (<i>dx, dy</i>).
*
* @param dx distance over x axis
* @param dy distance over y axis
*/
public void translate(int dx, int dy) {
bounds.x += dx;
bounds.y += dy;
}
public abstract void rotate(int angle);
public abstract void mirror(int dir);
/**
* Sets the bounds (in tiles) to the specified Rectangle.
*
* @param bounds
*/
protected void setBounds(Rectangle bounds) {
this.bounds = new Rectangle(bounds);
}
/**
* Sets the name of this layer.
*
* @param name the new name
*/
public void setName(String name) {
this.name = name;
}
/**
* Sets the map this layer is part of.
*
* @param map the Map object
*/
public void setMap(Map map) {
myMap = map;
}
public Map getMap() {
return myMap;
}
/**
* Sets layer opacity. If it is different from the previous value and the
* layer is visible, a MapChangedEvent is fired.
*
* @param opacity the new opacity for this layer
*/
public void setOpacity(float opacity) {
this.opacity = opacity;
}
/**
* Sets the visibility of this map layer. If it changes from its current
* value, a MapChangedEvent is fired.
*
* @param visible <code>true</code> to make the layer visible;
* <code>false</code> to make it invisible
*/
public void setVisible(boolean visible) {
isVisible = visible;
}
/**
* Sets the offset of this map layer. The offset is a distance by which to
* shift this layer from the origin of the map.
*
* @param xOff x offset in tiles
* @param yOff y offset in tiles
*/
public void setOffset(int xOff, int yOff) {
bounds.x = xOff;
bounds.y = yOff;
}
/**
* Returns the name of this layer.
* @return the name of the layer
*/
public String getName() {
return name;
}
/**
* Returns layer width in tiles.
* @return layer width in tiles.
*/
public int getWidth() {
return bounds.width;
}
/**
* Returns layer height in tiles.
* @return layer height in tiles.
*/
public int getHeight() {
return bounds.height;
}
/**
* Returns the layer bounds in tiles.
* @return the layer bounds in tiles
*/
public Rectangle getBounds() {
return new Rectangle(bounds);
}
/**
* Assigns the layer bounds in tiles to the given rectangle.
* @param rect the rectangle to which the layer bounds are assigned
*/
public void getBounds(Rectangle rect) {
rect.setBounds(bounds);
}
/**
* A convenience method to check if a point in tile-space is within
* the layer boundaries.
*
* @param x the x-coordinate of the point
* @param y the y-coordinate of the point
* @return <code>true</code> if the point (x,y) is within the layer
* boundaries, <code>false</code> otherwise.
*/
public boolean contains(int x, int y) {
return bounds.contains(x, y);
}
/**
* Returns layer opacity.
*
* @return layer opacity, ranging from 0.0 to 1.0
*/
public float getOpacity() {
return opacity;
}
/**
* Returns whether this layer is visible.
*
* @return <code>true</code> if the layer is visible, <code>false</code>
* otherwise.
*/
public boolean isVisible() {
return isVisible;
}
/**
* Merges the tile data of this layer with the specified layer. The calling
* layer is considered the significant layer, and will overwrite the data
* of the argument layer. At cells where the calling layer has no data, the
* argument layer data is preserved.
*
* @param other the insignificant layer to merge with
*/
public abstract void mergeOnto(MapLayer other);
public abstract void maskedMergeOnto(MapLayer other, Area mask);
public abstract void copyFrom(MapLayer other);
public abstract void maskedCopyFrom(MapLayer other, Area mask);
public abstract MapLayer createDiff(MapLayer ml);
/**
* Unlike mergeOnto, copyTo includes the null tile when merging
*
* @see MapLayer#copyFrom
* @see MapLayer#mergeOnto
* @param other the layer to copy this layer to
*/
public abstract void copyTo(MapLayer other);
public abstract boolean isEmpty();
/**
* Creates a copy of this layer.
*
* @see Object#clone
* @return a clone of this layer, as complete as possible
* @exception CloneNotSupportedException
*/
public Object clone() throws CloneNotSupportedException {
MapLayer clone = (MapLayer) super.clone();
// Create a new bounds object
clone.bounds = new Rectangle(bounds);
clone.properties = (Properties) properties.clone();
return clone;
}
/**
* @param width the new width of the layer
* @param height the new height of the layer
* @param dx the shift in x direction
* @param dy the shift in y direction
*/
public abstract void resize(int width, int height, int dx, int dy);
public void setProperties(Properties p) {
properties.clear();
properties.putAll(p);
}
public Properties getProperties() {
return properties;
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2004-2008, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2008, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.core;
import java.awt.*;
import java.util.Properties;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* An object occupying an {@link ObjectGroup}.
*/
public class MapObject implements Cloneable
{
private Properties properties = new Properties();
private ObjectGroup objectGroup;
private Rectangle bounds = new Rectangle();
private String name = "Object";
private String type = "";
private String imageSource = "";
private Image image;
private Image scaledImage;
public MapObject(int x, int y, int width, int height) {
bounds = new Rectangle(x, y, width, height);
}
public Object clone() throws CloneNotSupportedException {
MapObject clone = (MapObject) super.clone();
clone.bounds = new Rectangle(bounds);
clone.properties = (Properties) properties.clone();
return clone;
}
/**
* @return the object group this object is part of
*/
public ObjectGroup getObjectGroup() {
return objectGroup;
}
/**
* Sets the object group this object is part of. Should only be called by
* the object group.
*
* @param objectGroup the object group this object is part of
*/
public void setObjectGroup(ObjectGroup objectGroup) {
this.objectGroup = objectGroup;
}
public Rectangle getBounds() {
return bounds;
}
public void setBounds(Rectangle bounds) {
this.bounds = bounds;
}
public String getImageSource() {
return imageSource;
}
public void setImageSource(String source) {
if (imageSource.equals(source))
return;
imageSource = source;
// Attempt to read the image
if (imageSource.length() > 0) {
try {
image = ImageIO.read(new File(imageSource));
} catch (IOException e) {
image = null;
}
} else {
image = null;
}
scaledImage = null;
}
/**
* Returns the image to be used when drawing this object. This image is
* scaled to the size of the object.
*
* @param zoom the requested zoom level of the image
* @return the image to be used when drawing this object
*/
public Image getImage(double zoom) {
if (image == null)
return null;
final int zoomedWidth = (int) (getWidth() * zoom);
final int zoomedHeight = (int) (getHeight() * zoom);
if (scaledImage == null || scaledImage.getWidth(null) != zoomedWidth
|| scaledImage.getHeight(null) != zoomedHeight)
{
scaledImage = image.getScaledInstance(zoomedWidth, zoomedHeight,
Image.SCALE_SMOOTH);
}
return scaledImage;
}
public int getX() {
return bounds.x;
}
public void setX(int x) {
bounds.x = x;
}
public int getY() {
return bounds.y;
}
public void setY(int y) {
bounds.y = y;
}
public void translate(int dx, int dy) {
bounds.translate(dx, dy);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public int getWidth() {
return bounds.width;
}
public void setWidth(int width) {
bounds.width = width;
}
public void setHeight(int height) {
bounds.height = height;
}
public int getHeight() {
return bounds.height;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties p) {
properties = p;
}
public String toString() {
return type + " (" + getX() + "," + getY() + ")";
}
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright 2004-2006, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.core;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
/**
* A layer containing {@link MapObject map objects}.
*/
public class ObjectGroup extends MapLayer
{
private LinkedList<MapObject> objects = new LinkedList<MapObject>();
/**
* Default constructor.
*/
public ObjectGroup() {
}
/**
* @param map the map this object group is part of
*/
public ObjectGroup(Map map) {
super(map);
}
/**
* Creates an object group that is part of the given map and has the given
* origin.
*
* @param map the map this object group is part of
* @param origX the x origin of this layer
* @param origY the y origin of this layer
*/
public ObjectGroup(Map map, int origX, int origY) {
super(map);
setBounds(new Rectangle(origX, origY, 0, 0));
}
/**
* Creates an object group with a given area. The size of area is
* irrelevant, just its origin.
*
* @param area the area of the object group
*/
public ObjectGroup(Rectangle area) {
super(area);
}
/**
* @see MapLayer#rotate(int)
*/
public void rotate(int angle) {
// TODO: Implement rotating an object group
}
/**
* @see MapLayer#mirror(int)
*/
public void mirror(int dir) {
// TODO: Implement mirroring an object group
}
public void mergeOnto(MapLayer other) {
// TODO: Implement merging with another object group
}
public void maskedMergeOnto(MapLayer other, Area mask) {
// TODO: Figure out what object group should do with this method
}
public void copyFrom(MapLayer other) {
// TODO: Implement copying from another object group (same as merging)
}
public void maskedCopyFrom(MapLayer other, Area mask) {
// TODO: Figure out what object group should do with this method
}
public void copyTo(MapLayer other) {
// TODO: Implement copying to another object group (same as merging)
}
/**
* @see MapLayer#resize(int,int,int,int)
*/
public void resize(int width, int height, int dx, int dy) {
// TODO: Translate contained objects by the change of origin
}
public boolean isEmpty() {
return objects.isEmpty();
}
public Object clone() throws CloneNotSupportedException {
ObjectGroup clone = (ObjectGroup) super.clone();
clone.objects = new LinkedList<MapObject>();
for (MapObject object : objects) {
final MapObject objectClone = (MapObject) object.clone();
clone.objects.add(objectClone);
objectClone.setObjectGroup(clone);
}
return clone;
}
/**
* @deprecated
*/
public MapLayer createDiff(MapLayer ml) {
return null;
}
public void addObject(MapObject o) {
objects.add(o);
o.setObjectGroup(this);
}
public void removeObject(MapObject o) {
objects.remove(o);
o.setObjectGroup(null);
}
public Iterator<MapObject> getObjects() {
return objects.iterator();
}
public List<MapObject> getObjectsList() {
return objects;
}
public MapObject getObjectAt(int x, int y) {
for (MapObject obj : objects) {
// Attempt to get an object bordering the point that has no width
if (obj.getWidth() == 0 && obj.getX() + bounds.x == x) {
return obj;
}
// Attempt to get an object bordering the point that has no height
if (obj.getHeight() == 0 && obj.getY() + bounds.y == y) {
return obj;
}
Rectangle rect = new Rectangle(obj.getX() + bounds.x * myMap.getTileWidth(),
obj.getY() + bounds.y * myMap.getTileHeight(),
obj.getWidth(), obj.getHeight());
if (rect.contains(x, y)) {
return obj;
}
}
return null;
}
// This method will work at any zoom level, provided you provide the correct zoom factor. It also adds a one pixel buffer (that doesn't change with zoom).
public MapObject getObjectNear(int x, int y, double zoom) {
Rectangle2D mouse = new Rectangle2D.Double(x - zoom - 1, y - zoom - 1, 2 * zoom + 1, 2 * zoom + 1);
Shape shape;
for (MapObject obj : objects) {
if (obj.getWidth() == 0 && obj.getHeight() == 0) {
shape = new Ellipse2D.Double(obj.getX() * zoom, obj.getY() * zoom, 10 * zoom, 10 * zoom);
} else {
shape = new Rectangle2D.Double(obj.getX() + bounds.x * myMap.getTileWidth(),
obj.getY() + bounds.y * myMap.getTileHeight(),
obj.getWidth() > 0 ? obj.getWidth() : zoom,
obj.getHeight() > 0 ? obj.getHeight() : zoom);
}
if (shape.intersects(mouse)) {
return obj;
}
}
return null;
}
public void clear() {
objects.clear();
}
}

View File

@@ -0,0 +1,378 @@
/*
* Copyright 2004-2006, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.core;
import java.awt.Image;
import java.awt.Rectangle;
import java.util.Iterator;
import java.util.Vector;
public class Sprite
{
private Vector<KeyFrame> keys;
private int borderWidth = 0;
private int fpl = 0;
private int totalKeys = -1;
private float currentFrame = 0;
private Rectangle frameSize;
private boolean bPlaying = true;
public class KeyFrame
{
public static final int MASK_ANIMATION = 0x0000000F;
public static final int KEY_LOOP = 0x01;
public static final int KEY_STOP = 0x02;
public static final int KEY_AUTO = 0x04;
public static final int KEY_REVERSE = 0x08;
public static final int KEY_NAME_LENGTH_MAX = 32;
private String name = null;
private int id = -1;
private int flags = KEY_LOOP;
private float frameRate = 1.0f; //one fps
private Tile[] frames;
public KeyFrame() {
flags = KEY_LOOP;
}
public KeyFrame(String name) {
this();
this.name = name;
}
public KeyFrame(String name, Tile[] tile) {
this(name);
frames = tile;
}
public void setName(String name) {
this.name = name;
}
public void setFrameRate(float r) {
frameRate = r;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public int getLastFrame() {
return frames.length - 1;
}
public boolean isFrameLast(int frame) {
return frames.length - 1 == frame;
}
public void setFlags(int f) {
flags = f;
}
public int getFlags() {
return flags;
}
public String getName() {
return name;
}
public Tile getFrame(int f) {
if (f > 0 && f < frames.length) {
return frames[f];
}
return null;
}
public float getFrameRate() {
return frameRate;
}
public int getTotalFrames() {
return frames.length;
}
public boolean equalsIgnoreCase(String n) {
return name != null && name.equalsIgnoreCase(n);
}
public String toString() {
return "(" + name + ")" + id + ": @ " + frameRate;
}
}
private KeyFrame currentKey = null;
public Sprite() {
frameSize = new Rectangle();
keys = new Vector<KeyFrame>();
}
public Sprite(Tile[] frames) {
setFrames(frames);
}
public Sprite(Image image, int fpl, int border, int totalFrames) {
Tile[] frames = null;
this.fpl = fpl;
borderWidth = border;
//TODO: break up the image into tiles
//given this information, extrapolate the rest...
frameSize.width = image.getWidth(null) / (fpl + borderWidth * fpl);
frameSize.height = (int) (image.getHeight(null) / (Math.ceil(totalFrames / fpl) + Math.ceil(totalFrames / fpl) * borderWidth));
createKey("", frames, KeyFrame.KEY_LOOP);
}
public void setFrames(Tile[] frames) {
frameSize = new Rectangle(0, 0, frames[0].getWidth(), frames[0].getHeight());
createKey("", frames, KeyFrame.KEY_LOOP);
}
public void setFrameSize(int w, int h) {
frameSize.width = w;
frameSize.height = h;
}
public void setBorderWidth(int b) {
borderWidth = b;
}
public void setFpl(int f) {
fpl = f;
}
public void setCurrentFrame(float c) {
if (c < 0) {
switch (currentKey.flags & KeyFrame.MASK_ANIMATION) {
case KeyFrame.KEY_LOOP:
currentFrame = currentKey.getLastFrame();
break;
case KeyFrame.KEY_AUTO:
currentKey = getPreviousKey();
currentFrame = currentKey.getLastFrame();
break;
case KeyFrame.KEY_REVERSE:
currentKey.setFrameRate(-currentKey.getFrameRate());
currentFrame = 0;
break;
case KeyFrame.KEY_STOP:
bPlaying = false;
currentFrame = 0;
break;
}
} else if (c > currentKey.getLastFrame()) {
switch (currentKey.flags & KeyFrame.MASK_ANIMATION) {
case KeyFrame.KEY_LOOP:
currentFrame = 0;
break;
case KeyFrame.KEY_AUTO:
currentFrame = 0;
currentKey = getNextKey();
break;
case KeyFrame.KEY_REVERSE:
currentKey.setFrameRate(-currentKey.getFrameRate());
currentFrame = currentKey.getLastFrame();
break;
case KeyFrame.KEY_STOP:
bPlaying = false;
currentFrame = currentKey.getLastFrame();
break;
}
} else {
currentFrame = c;
}
}
public void setTotalKeys(int t) {
totalKeys = t;
}
public Rectangle getFrameSize() {
return frameSize;
}
public int getTotalFrames() {
int total = 0;
for (KeyFrame key : keys) {
total += key.getTotalFrames();
}
return total;
}
public int getBorderWidth() {
return borderWidth;
}
public Tile getCurrentFrame() {
return currentKey.getFrame((int) currentFrame);
}
public KeyFrame getNextKey() {
Iterator<KeyFrame> itr = keys.iterator();
while (itr.hasNext()) {
KeyFrame k = itr.next();
if (k == currentKey) {
if (itr.hasNext())
return itr.next();
}
}
return keys.get(0);
}
public KeyFrame getPreviousKey() {
//TODO: this
return null;
}
public KeyFrame getCurrentKey() {
return currentKey;
}
public int getFPL() {
return fpl;
}
public int getTotalKeys() {
return keys.size();
}
public void setKeyFrameTo(String name) {
for (KeyFrame k : keys) {
if (k.equalsIgnoreCase(name)) {
currentKey = k;
break;
}
}
}
public void addKey(KeyFrame k) {
keys.add(k);
}
public void removeKey(String name) {
keys.remove(getKey(name));
}
public void createKey(String name, Tile[] frames, int flags) {
KeyFrame kf = new KeyFrame(name, frames);
kf.setName(name);
kf.setFlags(flags);
addKey(kf);
}
public void iterateFrame() {
if (currentKey != null) {
if (bPlaying) {
setCurrentFrame(currentFrame + currentKey.getFrameRate());
}
}
}
/**
* Sets the current frame relative to the starting frame of the
* current key.
*
* @param c
*/
public void keySetFrame(int c) {
setCurrentFrame(c);
}
public void play() {
bPlaying = true;
}
public void stop() {
bPlaying = false;
}
public void keyStepBack(int amt) {
setCurrentFrame(currentFrame - amt);
}
public void keyStepForward(int amt) {
setCurrentFrame(currentFrame + amt);
}
public KeyFrame getKey(String keyName) {
for (KeyFrame k : keys) {
if (k != null && k.equalsIgnoreCase(keyName)) {
return k;
}
}
return null;
}
public KeyFrame getKey(int i) {
return keys.get(i);
}
public Iterator<KeyFrame> getKeys() throws Exception {
return keys.iterator();
}
public Rectangle getCurrentFrameRect() {
int x = 0, y = 0;
if (frameSize.height > 0 && frameSize.width > 0) {
y = ((int) currentFrame / fpl) * (frameSize.height + borderWidth);
x = ((int) currentFrame % fpl) * (frameSize.width + borderWidth);
}
return new Rectangle(x, y, frameSize.width, frameSize.height);
}
/**
* @see Object#toString()
*/
public String toString() {
return "Frame: (" + frameSize.width + "x" + frameSize.height + ")\n" +
"Border: " + borderWidth + "\n" +
"FPL: " + fpl + "\n" +
"Total Frames: " + getTotalFrames() + "\n" +
"Total keys: " + totalKeys;
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2004-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.core;
import java.awt.*;
import java.util.Properties;
/**
* The core class for our tiles.
*/
public class Tile
{
private Image image;
private int id = -1;
private Properties properties;
private TileSet tileset;
public Tile() {
// properties = new Properties();
}
public Tile(TileSet set) {
this();
setTileSet(set);
}
/**
* Copy constructor
*
* @param t tile to copy
*/
public Tile(Tile t) {
if (t.properties != null) properties = (Properties) t.properties.clone();
tileset = t.tileset;
}
/**
* Sets the id of the tile as long as it is at least 0.
*
* @param i The id of the tile
*/
public void setId(int i) {
if (i >= 0) {
id = i;
}
}
/**
* Sets the image of the tile.
*
* @param i the new image of the tile
*/
public void setImage(Image i) {
image = i;
}
/**
* Sets the parent tileset for a tile.
*
* @param set
*/
public void setTileSet(TileSet set) {
tileset = set;
}
public void setProperties(Properties p) {
properties = p;
}
public Properties getProperties() {
if (properties == null) return new Properties();
return properties;
}
/**
* Returns the tile id of this tile, relative to tileset.
*
* @return id
*/
public int getId() {
return id;
}
/**
* Returns the {@link tiled.core.TileSet} that this tile is part of.
*
* @return TileSet
*/
public TileSet getTileSet() {
return tileset;
}
public int getWidth() {
if (image != null)
return image.getWidth(null);
return 0;
}
public int getHeight() {
if (image != null)
return image.getHeight(null);
return 0;
}
/**
* Returns the tile image for this Tile.
*
* @return Image
*/
public Image getImage() {
if (tileset != null && tileset.sheet != null) return tileset.sheet.getImage(getId());
return image;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return "Tile " + id + " (" + getWidth() + "x" + getHeight() + ")";
}
}

View File

@@ -0,0 +1,482 @@
/*
* Copyright 2004-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.core;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.util.HashMap;
import java.util.Properties;
/**
* A TileLayer is a specialized MapLayer, used for tracking two dimensional
* tile data.
*/
public class TileLayer extends MapLayer
{
protected Tile[][] map;
protected HashMap<Object, Properties> tileInstanceProperties = new HashMap<Object, Properties>();
public Properties getTileInstancePropertiesAt(int x, int y) {
if (!bounds.contains(x, y)) {
return null;
}
Object key = new Point(x, y);
return tileInstanceProperties.get(key);
}
public void setTileInstancePropertiesAt(int x, int y, Properties tip) {
if (bounds.contains(x, y)) {
Object key = new Point(x, y);
tileInstanceProperties.put(key, tip);
}
}
/**
* Default constructor.
*/
public TileLayer() {
}
/**
* Construct a TileLayer from the given width and height.
*
* @param w width in tiles
* @param h height in tiles
*/
public TileLayer(int w, int h) {
super(w, h);
}
/**
* Create a tile layer using the given bounds.
*
* @param r the bounds of the tile layer.
*/
public TileLayer(Rectangle r) {
super(r);
}
/**
* @param m the map this layer is part of
*/
TileLayer(Map m) {
super(m);
}
/**
* @param m the map this layer is part of
* @param w width in tiles
* @param h height in tiles
*/
public TileLayer(Map m, int w, int h) {
super(w, h);
setMap(m);
}
/**
* Rotates the layer by the given Euler angle.
*
* @param angle The Euler angle (0-360) to rotate the layer array data by.
* @see MapLayer#rotate(int)
*/
public void rotate(int angle) {
Tile[][] trans;
int xtrans = 0, ytrans = 0;
switch (angle) {
case ROTATE_90:
trans = new Tile[bounds.width][bounds.height];
xtrans = bounds.height - 1;
break;
case ROTATE_180:
trans = new Tile[bounds.height][bounds.width];
xtrans = bounds.width - 1;
ytrans = bounds.height - 1;
break;
case ROTATE_270:
trans = new Tile[bounds.width][bounds.height];
ytrans = bounds.width - 1;
break;
default:
// System.out.println("Unsupported rotation (" + angle + ")");
return;
}
double ra = Math.toRadians(angle);
int cos_angle = (int)Math.round(Math.cos(ra));
int sin_angle = (int)Math.round(Math.sin(ra));
for (int y = 0; y < bounds.height; y++) {
for (int x = 0; x < bounds.width; x++) {
int xrot = x * cos_angle - y * sin_angle;
int yrot = x * sin_angle + y * cos_angle;
trans[yrot + ytrans][xrot + xtrans] = getTileAt(x+bounds.x, y+bounds.y);
}
}
bounds.width = trans[0].length;
bounds.height = trans.length;
map = trans;
}
/**
* Performs a mirroring function on the layer data. Two orientations are
* allowed: vertical and horizontal.
*
* Example: <code>layer.mirror(MapLayer.MIRROR_VERTICAL);</code> will
* mirror the layer data around a horizontal axis.
*
* @param dir the axial orientation to mirror around
*/
public void mirror(int dir) {
Tile[][] mirror = new Tile[bounds.height][bounds.width];
for (int y = 0; y < bounds.height; y++) {
for (int x = 0; x < bounds.width; x++) {
if (dir == MIRROR_VERTICAL) {
mirror[y][x] = map[bounds.height - 1 - y][x];
} else {
mirror[y][x] = map[y][bounds.width - 1 - x];
}
}
}
map = mirror;
}
/**
* Checks to see if the given Tile is used anywhere in the layer.
*
* @param t a Tile object to check for
* @return <code>true</code> if the Tile is used at least once,
* <code>false</code> otherwise.
*/
public boolean isUsed(Tile t) {
for (int y = 0; y < bounds.height; y++) {
for (int x = 0; x < bounds.width; x++) {
if (map[y][x] == t) {
return true;
}
}
}
return false;
}
public boolean isEmpty() {
for (int p = 0; p < 2; p++) {
for (int y = 0; y < bounds.height; y++) {
for (int x = p; x < bounds.width; x += 2) {
if (map[y][x] != null)
return false;
}
}
}
return true;
}
/**
* Sets the bounds (in tiles) to the specified Rectangle. <b>Caution:</b>
* this causes a reallocation of the data array, and all previous data is
* lost.
*
* @param bounds new new bounds of this tile layer (in tiles)
* @see MapLayer#setBounds
*/
protected void setBounds(Rectangle bounds) {
super.setBounds(bounds);
map = new Tile[bounds.height][bounds.width];
// Tile instance properties is null when this method is called from
// the constructor of MapLayer
if (tileInstanceProperties != null) {
tileInstanceProperties.clear();
}
}
/**
* Creates a diff of the two layers, <code>ml</code> is considered the
* significant difference.
*
* @param ml
* @return A new MapLayer that represents the difference between this
* layer, and the argument, or <b>null</b> if no difference exists.
*/
public MapLayer createDiff(MapLayer ml) {
if (ml == null) { return null; }
if (ml instanceof TileLayer) {
Rectangle r = null;
for (int y = bounds.y; y < bounds.height + bounds.y; y++) {
for (int x = bounds.x; x < bounds.width + bounds.x; x++) {
if (((TileLayer)ml).getTileAt(x, y) != getTileAt(x, y)) {
if (r != null) {
r.add(x, y);
} else {
r = new Rectangle(new Point(x, y));
}
}
}
}
if (r != null) {
MapLayer diff = new TileLayer(
new Rectangle(r.x, r.y, r.width + 1, r.height + 1));
diff.copyFrom(ml);
return diff;
} else {
return new TileLayer();
}
} else {
return null;
}
}
/**
* Removes any occurences of the given tile from this map layer. If layer
* is locked, an exception is thrown.
*
* @param tile the Tile to be removed
*/
public void removeTile(Tile tile) {
for (int y = 0; y < bounds.height; y++) {
for (int x = 0; x < bounds.width; x++) {
if (map[y][x] == tile) {
setTileAt(x + bounds.x, y + bounds.y, null);
}
}
}
}
/**
* Sets the tile at the specified position. Does nothing if (tx, ty) falls
* outside of this layer.
*
* @param tx x position of tile
* @param ty y position of tile
* @param ti the tile object to place
*/
public void setTileAt(int tx, int ty, Tile ti) {
if (bounds.contains(tx, ty)) {
map[ty - bounds.y][tx - bounds.x] = ti;
}
}
/**
* Returns the tile at the specified position.
*
* @param tx Tile-space x coordinate
* @param ty Tile-space y coordinate
* @return tile at position (tx, ty) or <code>null</code> when (tx, ty) is
* outside this layer
*/
public Tile getTileAt(int tx, int ty) {
return (bounds.contains(tx, ty)) ?
map[ty - bounds.y][tx - bounds.x] : null;
}
/**
* Returns the first occurrence (using top down, left to right search) of
* the given tile.
*
* @param t the {@link Tile} to look for
* @return A java.awt.Point instance of the first instance of t, or
* <code>null</code> if it is not found
*/
public Point locationOf(Tile t) {
for (int y = bounds.y; y < bounds.height + bounds.y; y++) {
for (int x = bounds.x; x < bounds.width + bounds.x; x++) {
if (getTileAt(x, y) == t) {
return new Point(x, y);
}
}
}
return null;
}
/**
* Replaces all occurrences of the Tile <code>find</code> with the Tile
* <code>replace</code> in the entire layer
*
* @param find the tile to replace
* @param replace the replacement tile
*/
public void replaceTile(Tile find, Tile replace) {
for (int y = bounds.y; y < bounds.y + bounds.height; y++) {
for (int x = bounds.x; x < bounds.x + bounds.width; x++) {
if(getTileAt(x,y) == find) {
setTileAt(x, y, replace);
}
}
}
}
/**
* @inheritDoc MapLayer#mergeOnto(MapLayer)
*/
public void mergeOnto(MapLayer other) {
for (int y = bounds.y; y < bounds.y + bounds.height; y++) {
for (int x = bounds.x; x < bounds.x + bounds.width; x++) {
Tile tile = getTileAt(x, y);
if (tile != null) {
((TileLayer) other).setTileAt(x, y, tile);
}
}
}
}
/**
* Like mergeOnto, but will only copy the area specified.
*
* @see TileLayer#mergeOnto(MapLayer)
* @param other
* @param mask
*/
public void maskedMergeOnto(MapLayer other, Area mask) {
Rectangle boundBox = mask.getBounds();
for (int y = boundBox.y; y < boundBox.y + boundBox.height; y++) {
for (int x = boundBox.x; x < boundBox.x + boundBox.width; x++) {
Tile tile = ((TileLayer) other).getTileAt(x, y);
if (mask.contains(x, y) && tile != null) {
setTileAt(x, y, tile);
}
}
}
}
/**
* Copy data from another layer onto this layer. Unlike mergeOnto,
* copyFrom() copies the empty cells as well.
*
* @see MapLayer#mergeOnto
* @param other
*/
public void copyFrom(MapLayer other) {
for (int y = bounds.y; y < bounds.y + bounds.height; y++) {
for (int x = bounds.x; x < bounds.x + bounds.width; x++) {
setTileAt(x, y, ((TileLayer) other).getTileAt(x, y));
}
}
}
/**
* Like copyFrom, but will only copy the area specified.
*
* @see TileLayer#copyFrom(MapLayer)
* @param other
* @param mask
*/
public void maskedCopyFrom(MapLayer other, Area mask) {
Rectangle boundBox = mask.getBounds();
for (int y = boundBox.y; y < boundBox.y + boundBox.height; y++) {
for (int x = boundBox.x; x < boundBox.x + boundBox.width; x++) {
if (mask.contains(x,y)) {
setTileAt(x, y, ((TileLayer) other).getTileAt(x, y));
}
}
}
}
/**
* Unlike mergeOnto, copyTo includes the null tile when merging.
*
* @see MapLayer#copyFrom
* @see MapLayer#mergeOnto
* @param other the layer to copy this layer to
*/
public void copyTo(MapLayer other) {
for (int y = bounds.y; y < bounds.y + bounds.height; y++) {
for (int x = bounds.x; x < bounds.x + bounds.width; x++) {
((TileLayer) other).setTileAt(x, y, getTileAt(x, y));
}
}
}
/**
* Creates a copy of this layer.
*
* @see Object#clone
* @return a clone of this layer, as complete as possible
* @exception CloneNotSupportedException
*/
public Object clone() throws CloneNotSupportedException {
TileLayer clone = (TileLayer) super.clone();
// Clone the layer data
clone.map = new Tile[map.length][];
clone.tileInstanceProperties = new HashMap<Object, Properties>();
for (int i = 0; i < map.length; i++) {
clone.map[i] = new Tile[map[i].length];
System.arraycopy(map[i], 0, clone.map[i], 0, map[i].length);
for (int j = 0; j < map[i].length; j++) {
Properties p = getTileInstancePropertiesAt(i, j);
if (p != null) {
Integer key = i + j * bounds.width;
clone.tileInstanceProperties.put(key, (Properties) p.clone());
}
}
}
return clone;
}
/**
* @param width the new width of the layer
* @param height the new height of the layer
* @param dx the shift in x direction
* @param dy the shift in y direction
*/
public void resize(int width, int height, int dx, int dy) {
Tile[][] newMap = new Tile[height][width];
HashMap<Object, Properties> newTileInstanceProperties = new HashMap<Object, Properties>();
int maxX = Math.min(width, bounds.width + dx);
int maxY = Math.min(height, bounds.height + dy);
for (int x = Math.max(0, dx); x < maxX; x++) {
for (int y = Math.max(0, dy); y < maxY; y++) {
newMap[y][x] = getTileAt(x - dx, y - dy);
Properties tip = getTileInstancePropertiesAt(x - dx, y - dy);
if (tip != null) {
newTileInstanceProperties.put(new Point(x, y), tip);
}
}
}
map = newMap;
tileInstanceProperties = newTileInstanceProperties;
bounds.width = width;
bounds.height = height;
}
}

View File

@@ -0,0 +1,523 @@
/*
* Copyright 2004-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.core;
import java.awt.Color;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Vector;
import javax.imageio.ImageIO;
import tiled.util.BasicTileCutter;
import tiled.util.TileCutter;
import tiled.util.TransparentImageFilter;
import com.gpl.rpg.atcontentstudio.model.maps.TMXMap;
import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet;
/**
* todo: Update documentation
* <p>TileSet handles operations on tiles as a set, or group. It has several
* advanced internal functions aimed at reducing unnecessary data replication.
* A 'tile' is represented internally as two distinct pieces of data. The
* first and most important is a {@link Tile} object, and these are held in
* a {@link Vector}.</p>
*
* <p>The other is the tile image.</p>
*/
public class TileSet implements Iterable<Tile>
{
private String base;
final private Vector<Tile> tiles = new Vector<Tile>();
private long tilebmpFileLastModified;
private TileCutter tileCutter;
private Rectangle tileDimensions;
private int tileSpacing;
private int tileMargin;
private int tilesPerRow;
private String externalSource;
private File tilebmpFile;
private String name;
private Color transparentColor;
private Image tileSetImage;
public Spritesheet sheet = null;
/**
* Default constructor
*/
public TileSet() {
tileDimensions = new Rectangle();
}
/**
* Creates a tileset from a tileset image file.
*
* @param imgFilename
* @param cutter
* @throws IOException
* @see TileSet#importTileBitmap(BufferedImage, TileCutter)
*/
public void importTileBitmap(String imgFilename, TileCutter cutter)
throws IOException
{
setTilesetImageFilename(imgFilename);
File f = new File(imgFilename);
Image image = ImageIO.read(f.getCanonicalFile());
if (image == null) {
throw new IOException("Failed to load " + tilebmpFile);
}
Toolkit tk = Toolkit.getDefaultToolkit();
if (transparentColor != null) {
int rgb = transparentColor.getRGB();
image = tk.createImage(
new FilteredImageSource(image.getSource(),
new TransparentImageFilter(rgb)));
}
BufferedImage buffered = new BufferedImage(
image.getWidth(null),
image.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
buffered.getGraphics().drawImage(image, 0, 0, null);
importTileBitmap(buffered, cutter);
}
public void loadFromProject(String name, TMXMap tmxMap) {
sheet = tmxMap.getProject().getSpritesheet(name);
int i = 0;
Image tileImage = sheet.getImage(i);
while (tileImage != null) {
Tile tile = new Tile();
// tile.setImage(tileImage);
addNewTile(tile);
i++;
tileImage = sheet.getImage(i);
}
}
/**
* Creates a tileset from a buffered image. Tiles are cut by the passed
* cutter.
*
* @param tileBitmap the image to be used, must not be null
* @param cutter the tile cutter, must not be null
*/
private void importTileBitmap(BufferedImage tileBitmap, TileCutter cutter)
{
assert tileBitmap != null;
assert cutter != null;
tileCutter = cutter;
tileSetImage = tileBitmap;
cutter.setImage(tileBitmap);
tileDimensions = new Rectangle(cutter.getTileDimensions());
if (cutter instanceof BasicTileCutter) {
BasicTileCutter basicTileCutter = (BasicTileCutter) cutter;
tileSpacing = basicTileCutter.getTileSpacing();
tileMargin = basicTileCutter.getTileMargin();
tilesPerRow = basicTileCutter.getTilesPerRow();
}
Image tileImage = cutter.getNextTile();
while (tileImage != null) {
Tile tile = new Tile();
tile.setImage(tileImage);
addNewTile(tile);
tileImage = cutter.getNextTile();
}
}
/**
* Refreshes a tileset from a tileset image file.
*
* @throws IOException
* @see TileSet#importTileBitmap(BufferedImage,TileCutter)
*/
private void refreshImportedTileBitmap()
throws IOException
{
String imgFilename = tilebmpFile.getPath();
Image image = ImageIO.read(new File(imgFilename));
if (image == null) {
throw new IOException("Failed to load " + tilebmpFile);
}
Toolkit tk = Toolkit.getDefaultToolkit();
if (transparentColor != null) {
int rgb = transparentColor.getRGB();
image = tk.createImage(
new FilteredImageSource(image.getSource(),
new TransparentImageFilter(rgb)));
}
BufferedImage buffered = new BufferedImage(
image.getWidth(null),
image.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
buffered.getGraphics().drawImage(image, 0, 0, null);
refreshImportedTileBitmap(buffered);
}
/**
* Refreshes a tileset from a buffered image. Tiles are cut by the passed
* cutter.
*
* @param tileBitmap the image to be used, must not be null
*/
private void refreshImportedTileBitmap(BufferedImage tileBitmap) {
assert tileBitmap != null;
tileCutter.reset();
tileCutter.setImage(tileBitmap);
tileSetImage = tileBitmap;
tileDimensions = new Rectangle(tileCutter.getTileDimensions());
int id = 0;
Image tileImage = tileCutter.getNextTile();
while (tileImage != null) {
Tile tile = getTile(id);
tile.setImage(tileImage);
tileImage = tileCutter.getNextTile();
id++;
}
}
public void checkUpdate() throws IOException {
if (tilebmpFile != null &&
tilebmpFile.lastModified() > tilebmpFileLastModified)
{
refreshImportedTileBitmap();
tilebmpFileLastModified = tilebmpFile.lastModified();
}
}
/**
* Sets the URI path of the external source of this tile set. By setting
* this, the set is implied to be external in all other operations.
*
* @param source a URI of the tileset image file
*/
public void setSource(String source) {
externalSource = source;
}
/**
* Sets the base directory for the tileset
*
* @param base a String containing the native format directory
*/
public void setBaseDir(String base) {
this.base = base;
}
/**
* Sets the filename of the tileset image. Doesn't change the tileset in
* any other way.
*
* @param name
*/
public void setTilesetImageFilename(String name) {
if (name != null) {
tilebmpFile = new File(name);
tilebmpFileLastModified = tilebmpFile.lastModified();
}
else {
tilebmpFile = null;
}
}
/**
* Sets the name of this tileset.
*
* @param name the new name for this tileset
*/
public void setName(String name) {
this.name = name;
}
/**
* Sets the transparent color in the tileset image.
*
* @param color
*/
public void setTransparentColor(Color color) {
transparentColor = color;
}
/**
* Adds the tile to the set, setting the id of the tile only if the current
* value of id is -1.
*
* @param t the tile to add
* @return int The <b>local</b> id of the tile
*/
public int addTile(Tile t) {
if (t.getId() < 0)
t.setId(tiles.size());
if (tileDimensions.width < t.getWidth())
tileDimensions.width = t.getWidth();
if (tileDimensions.height < t.getHeight())
tileDimensions.height = t.getHeight();
tiles.add(t);
t.setTileSet(this);
return t.getId();
}
/**
* This method takes a new Tile object as argument, and in addition to
* the functionality of <code>addTile()</code>, sets the id of the tile
* to -1.
*
* @see TileSet#addTile(Tile)
* @param t the new tile to add.
*/
public void addNewTile(Tile t) {
t.setId(-1);
addTile(t);
}
/**
* Removes a tile from this tileset. Does not invalidate other tile
* indices. Removal is simply setting the reference at the specified
* index to <b>null</b>.
*
* @param i the index to remove
*/
public void removeTile(int i) {
tiles.set(i, null);
}
/**
* Returns the amount of tiles in this tileset.
*
* @return the amount of tiles in this tileset
*/
public int size() {
return tiles.size();
}
/**
* Returns the maximum tile id.
*
* @return the maximum tile id, or -1 when there are no tiles
*/
public int getMaxTileId() {
return tiles.size() - 1;
}
/**
* Returns an iterator over the tiles in this tileset.
*
* @return an iterator over the tiles in this tileset.
*/
public Iterator<Tile> iterator() {
return tiles.iterator();
}
/**
* Returns the width of tiles in this tileset. All tiles in a tileset
* should be the same width, and the same as the tile width of the map the
* tileset is used with.
*
* @return int - The maximum tile width
*/
public int getTileWidth() {
return tileDimensions.width;
}
/**
* Returns the tile height of tiles in this tileset. Not all tiles in a
* tileset are required to have the same height, but the height should be
* at least the tile height of the map the tileset is used with.
*
* If there are tiles with varying heights in this tileset, the returned
* height will be the maximum.
*
* @return the max height of the tiles in the set
*/
public int getTileHeight() {
return tileDimensions.height;
}
/**
* Returns the spacing between the tiles on the tileset image.
* @return the spacing in pixels between the tiles on the tileset image
*/
public int getTileSpacing() {
return tileSpacing;
}
/**
* Returns the margin around the tiles on the tileset image.
* @return the margin in pixels around the tiles on the tileset image
*/
public int getTileMargin() {
return tileMargin;
}
/**
* Returns the number of tiles per row in the original tileset image.
* @return the number of tiles per row in the original tileset image.
*/
public int getTilesPerRow() {
return tilesPerRow;
}
/**
* Gets the tile with <b>local</b> id <code>i</code>.
*
* @param i local id of tile
* @return A tile with local id <code>i</code> or <code>null</code> if no
* tile exists with that id
*/
public Tile getTile(int i) {
try {
return tiles.get(i);
} catch (ArrayIndexOutOfBoundsException a) {}
return null;
}
/**
* Returns the first non-null tile in the set.
*
* @return The first tile in this tileset, or <code>null</code> if none
* exists.
*/
public Tile getFirstTile() {
Tile ret = null;
int i = 0;
while (ret == null && i <= getMaxTileId()) {
ret = getTile(i);
i++;
}
return ret;
}
/**
* Returns the source of this tileset.
*
* @return a filename if tileset is external or <code>null</code> if
* tileset is internal.
*/
public String getSource() {
return externalSource;
}
/**
* Returns the base directory for the tileset
*
* @return a directory in native format as given in the tileset file or tag
*/
public String getBaseDir() {
return base;
}
/**
* Returns the filename of the tileset image.
*
* @return the filename of the tileset image, or <code>null</code> if this
* tileset doesn't reference a tileset image
*/
public String getTilebmpFile() {
if (tilebmpFile != null) {
try {
return tilebmpFile.getCanonicalPath();
} catch (IOException e) {
}
} else if (sheet != null) {
try {
return sheet.spritesheetFile.getCanonicalPath();
} catch (IOException e) {}
}
return null;
}
/**
* @return the name of this tileset.
*/
public String getName() {
return name;
}
/**
* Returns the transparent color of the tileset image, or <code>null</code>
* if none is set.
*
* @return Color - The transparent color of the set
*/
public Color getTransparentColor() {
return transparentColor;
}
/**
* @return the name of the tileset, and the total tiles
*/
public String toString() {
return getName() + " [" + size() + "]";
}
// TILE IMAGE CODE
/**
* Returns whether the tileset is derived from a tileset image.
*
* @return tileSetImage != null
*/
public boolean isSetFromImage() {
return tileSetImage != null;
}
public Spritesheet getSpritesheet() {
return this.sheet;
}
}

View File

@@ -0,0 +1,963 @@
/*
* Copyright 2004-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.io;
import java.awt.Color;
import java.awt.Image;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Properties;
import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import tiled.core.AnimatedTile;
import tiled.core.Map;
import tiled.core.MapLayer;
import tiled.core.MapObject;
import tiled.core.ObjectGroup;
import tiled.core.Tile;
import tiled.core.TileLayer;
import tiled.core.TileSet;
import tiled.util.Base64;
import tiled.util.BasicTileCutter;
import tiled.util.ImageHelper;
import com.gpl.rpg.atcontentstudio.model.maps.TMXMap;
/**
* The standard map reader for TMX files. Supports reading .tmx, .tmx.gz and *.tsx files.
*/
public class TMXMapReader
{
private Map map;
private String xmlPath;
private String error;
private final EntityResolver entityResolver = new MapEntityResolver();
private TreeMap<Integer, TileSet> tilesetPerFirstGid;
public final TMXMapReaderSettings settings = new TMXMapReaderSettings();
private final HashMap<String, TileSet> cachedTilesets = new HashMap<String, TileSet>();
public static final class TMXMapReaderSettings {
public boolean reuseCachedTilesets = false;
}
public TMXMapReader() {
}
String getError() {
return error;
}
private static String makeUrl(String filename) throws MalformedURLException {
final String url;
if (filename.indexOf("://") > 0 || filename.startsWith("file:")) {
url = filename;
} else {
url = new File(filename).toURI().toString();
}
return url;
}
private static int reflectFindMethodByName(Class c, String methodName) {
Method[] methods = c.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().equalsIgnoreCase(methodName)) {
return i;
}
}
return -1;
}
private void reflectInvokeMethod(Object invokeVictim, Method method,
String[] args) throws Exception
{
Class[] parameterTypes = method.getParameterTypes();
Object[] conformingArguments = new Object[parameterTypes.length];
if (args.length < parameterTypes.length) {
throw new Exception("Insufficient arguments were supplied");
}
for (int i = 0; i < parameterTypes.length; i++) {
if ("int".equalsIgnoreCase(parameterTypes[i].getName())) {
conformingArguments[i] = new Integer(args[i]);
} else if ("float".equalsIgnoreCase(parameterTypes[i].getName())) {
conformingArguments[i] = new Float(args[i]);
} else if (parameterTypes[i].getName().endsWith("String")) {
conformingArguments[i] = args[i];
} else if ("boolean".equalsIgnoreCase(parameterTypes[i].getName())) {
conformingArguments[i] = Boolean.valueOf(args[i]);
} else {
// Unsupported argument type, defaulting to String
conformingArguments[i] = args[i];
}
}
method.invoke(invokeVictim,conformingArguments);
}
private void setOrientation(String o) {
if ("isometric".equalsIgnoreCase(o)) {
map.setOrientation(Map.ORIENTATION_ISOMETRIC);
} else if ("orthogonal".equalsIgnoreCase(o)) {
map.setOrientation(Map.ORIENTATION_ORTHOGONAL);
} else if ("hexagonal".equalsIgnoreCase(o)) {
map.setOrientation(Map.ORIENTATION_HEXAGONAL);
} else if ("shifted".equalsIgnoreCase(o)) {
map.setOrientation(Map.ORIENTATION_SHIFTED);
} else {
// System.out.println("Unknown orientation '" + o + "'");
}
}
private static String getAttributeValue(Node node, String attribname) {
final NamedNodeMap attributes = node.getAttributes();
String value = null;
if (attributes != null) {
Node attribute = attributes.getNamedItem(attribname);
if (attribute != null) {
value = attribute.getNodeValue();
}
}
return value;
}
private static int getAttribute(Node node, String attribname, int def) {
final String attr = getAttributeValue(node, attribname);
if (attr != null) {
return Integer.parseInt(attr);
} else {
return def;
}
}
private Object unmarshalClass(Class reflector, Node node)
throws InstantiationException, IllegalAccessException,
InvocationTargetException {
Constructor cons = null;
try {
cons = reflector.getConstructor((Class[]) null);
} catch (SecurityException e1) {
e1.printStackTrace();
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
return null;
}
Object o = cons.newInstance((Object[]) null);
Node n;
Method[] methods = reflector.getMethods();
NamedNodeMap nnm = node.getAttributes();
if (nnm != null) {
for (int i = 0; i < nnm.getLength(); i++) {
n = nnm.item(i);
try {
int j = reflectFindMethodByName(reflector,
"set" + n.getNodeName());
if (j >= 0) {
reflectInvokeMethod(o,methods[j],
new String [] {n.getNodeValue()});
} else {
// System.out.println("Unsupported attribute '" +
// n.getNodeName() + "' on <" +
// node.getNodeName() + "> tag");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
return o;
}
private Image unmarshalImage(Node t, String baseDir) throws IOException
{
Image img = null;
String source = getAttributeValue(t, "source");
if (source != null) {
if (checkRoot(source)) {
source = makeUrl(source);
} else {
source = makeUrl(baseDir + source);
}
img = ImageIO.read(new URL(source));
} else {
NodeList nl = t.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if ("data".equals(node.getNodeName())) {
Node cdata = node.getFirstChild();
if (cdata != null) {
String sdata = cdata.getNodeValue();
char[] charArray = sdata.trim().toCharArray();
byte[] imageData = Base64.decode(charArray);
img = ImageHelper.bytesToImage(imageData);
// Deriving a scaled instance, even if it has the same
// size, somehow makes drawing of the tiles a lot
// faster on various systems (seen on Linux, Windows
// and MacOS X).
img = img.getScaledInstance(
img.getWidth(null), img.getHeight(null),
Image.SCALE_FAST);
}
break;
}
}
}
return img;
}
private TileSet unmarshalTilesetFile(InputStream in, String filename, TMXMap tmxMap)
throws Exception
{
TileSet set = null;
Node tsNode;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
//builder.setErrorHandler(new XMLErrorHandler());
Document tsDoc = builder.parse(in, ".");
String xmlPathSave = xmlPath;
if (filename.indexOf(File.separatorChar) >= 0) {
xmlPath = filename.substring(0,
filename.lastIndexOf(File.separatorChar) + 1);
}
NodeList tsNodeList = tsDoc.getElementsByTagName("tileset");
// There can be only one tileset in a .tsx file.
tsNode = tsNodeList.item(0);
if (tsNode != null) {
set = unmarshalTileset(tsNode, tmxMap);
if (set.getSource() != null) {
// System.out.println("Recursive external tilesets are not supported.");
}
set.setSource(filename);
}
xmlPath = xmlPathSave;
} catch (SAXException e) {
error = "Failed while loading " + filename + ": " +
e.getLocalizedMessage();
}
return set;
}
private TileSet unmarshalTileset(Node t, TMXMap tmxMap) throws Exception {
String source = getAttributeValue(t, "source");
String basedir = getAttributeValue(t, "basedir");
int firstGid = getAttribute(t, "firstgid", 1);
String tilesetBaseDir = xmlPath;
if (basedir != null) {
tilesetBaseDir = basedir; //makeUrl(basedir);
}
if (source != null) {
String filename = tilesetBaseDir + source;
//if (checkRoot(source)) {
// filename = makeUrl(source);
//}
TileSet ext = null;
try {
InputStream in = new URL(makeUrl(filename)).openStream();
ext = unmarshalTilesetFile(in, filename, tmxMap);
setFirstGidForTileset(ext, firstGid);
} catch (FileNotFoundException fnf) {
error = "Could not find external tileset file " + filename;
}
if (ext == null) {
error = "Tileset " + source + " was not loaded correctly!";
}
return ext;
}
else {
final int tileWidth = getAttribute(t, "tilewidth", map != null ? map.getTileWidth() : 0);
final int tileHeight = getAttribute(t, "tileheight", map != null ? map.getTileHeight() : 0);
final int tileSpacing = getAttribute(t, "spacing", 0);
final int tileMargin = getAttribute(t, "margin", 0);
final String name = getAttributeValue(t, "name");
TileSet set;
if (settings.reuseCachedTilesets) {
set = cachedTilesets.get(name);
if (set != null) {
setFirstGidForTileset(set, firstGid);
return set;
}
set = new TileSet();
cachedTilesets.put(name, set);
} else {
set = new TileSet();
}
set.setName(name);
set.setBaseDir(basedir);
setFirstGidForTileset(set, firstGid);
boolean hasTilesetImage = false;
NodeList children = t.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeName().equalsIgnoreCase("image")) {
if (hasTilesetImage) {
// System.out.println("Ignoring illegal image element after tileset image.");
continue;
}
String imgSource = getAttributeValue(child, "source");
String transStr = getAttributeValue(child, "trans");
if (imgSource != null) {
// Not a shared image, but an entire set in one image
// file. There should be only one image element in this
// case.
if (tmxMap.getProject().getSpritesheet(name) != null) {
set.loadFromProject(name, tmxMap);
} else {
hasTilesetImage = true;
// FIXME: importTileBitmap does not fully support URLs
String sourcePath = imgSource;
if (! new File(imgSource).isAbsolute()) {
sourcePath = tilesetBaseDir + imgSource;
}
if (transStr != null) {
if (transStr.startsWith("#"))
transStr = transStr.substring(1);
int colorInt = Integer.parseInt(transStr, 16);
Color color = new Color(colorInt);
set.setTransparentColor(color);
}
set.importTileBitmap(sourcePath, new BasicTileCutter(
tileWidth, tileHeight, tileSpacing, tileMargin));
}
}
}
else if (child.getNodeName().equalsIgnoreCase("tile")) {
Tile tile = unmarshalTile(set, child, tilesetBaseDir);
if (!hasTilesetImage || tile.getId() > set.getMaxTileId()) {
set.addTile(tile);
} else {
Tile myTile = set.getTile(tile.getId());
myTile.setProperties(tile.getProperties());
//TODO: there is the possibility here of overlaying images,
// which some people may want
}
}
}
return set;
}
}
private MapObject readMapObject(Node t) throws Exception {
final String name = getAttributeValue(t, "name");
final String type = getAttributeValue(t, "type");
final int x = getAttribute(t, "x", 0);
final int y = getAttribute(t, "y", 0);
final int width = getAttribute(t, "width", 0);
final int height = getAttribute(t, "height", 0);
MapObject obj = new MapObject(x, y, width, height);
if (name != null)
obj.setName(name);
if (type != null)
obj.setType(type);
NodeList children = t.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if ("image".equalsIgnoreCase(child.getNodeName())) {
String source = getAttributeValue(child, "source");
if (source != null) {
if (! new File(source).isAbsolute()) {
source = xmlPath + source;
}
obj.setImageSource(source);
}
break;
}
}
Properties props = new Properties();
readProperties(children, props);
obj.setProperties(props);
return obj;
}
/**
* Reads properties from amongst the given children. When a "properties"
* element is encountered, it recursively calls itself with the children
* of this node. This function ensures backward compatibility with tmx
* version 0.99a.
*
* Support for reading property values stored as character data was added
* in Tiled 0.7.0 (tmx version 0.99c).
*
* @param children the children amongst which to find properties
* @param props the properties object to set the properties of
*/
private static void readProperties(NodeList children, Properties props) {
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if ("property".equalsIgnoreCase(child.getNodeName())) {
final String key = getAttributeValue(child, "name");
String value = getAttributeValue(child, "value");
if (value == null) {
Node grandChild = child.getFirstChild();
if (grandChild != null) {
value = grandChild.getNodeValue();
if (value != null)
value = value.trim();
}
}
if (value != null)
props.setProperty(key, value);
}
else if ("properties".equals(child.getNodeName())) {
readProperties(child.getChildNodes(), props);
}
}
}
private Tile unmarshalTile(TileSet set, Node t, String baseDir)
throws Exception
{
Tile tile = null;
NodeList children = t.getChildNodes();
boolean isAnimated = false;
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if ("animation".equalsIgnoreCase(child.getNodeName())) {
isAnimated = true;
break;
}
}
try {
if (isAnimated) {
tile = (Tile) unmarshalClass(AnimatedTile.class, t);
} else {
tile = (Tile) unmarshalClass(Tile.class, t);
}
} catch (Exception e) {
error = "Failed creating tile: " + e.getLocalizedMessage();
return tile;
}
tile.setTileSet(set);
readProperties(children, tile.getProperties());
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if ("image".equalsIgnoreCase(child.getNodeName())) {
int id = getAttribute(child, "id", -1);
Image img = unmarshalImage(child, baseDir);
tile.setImage(img);
} else if ("animation".equalsIgnoreCase(child.getNodeName())) {
// TODO: fill this in once TMXMapWriter is complete
}
}
return tile;
}
private MapLayer unmarshalObjectGroup(Node t) throws Exception {
ObjectGroup og = null;
try {
og = (ObjectGroup)unmarshalClass(ObjectGroup.class, t);
} catch (Exception e) {
e.printStackTrace();
return og;
}
final int offsetX = getAttribute(t, "x", 0);
final int offsetY = getAttribute(t, "y", 0);
og.setOffset(offsetX, offsetY);
// Add all objects from the objects group
NodeList children = t.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if ("object".equalsIgnoreCase(child.getNodeName())) {
og.addObject(readMapObject(child));
}
}
Properties props = new Properties();
readProperties(children, props);
og.setProperties(props);
return og;
}
/**
* Loads a map layer from a layer node.
* @param t the node representing the "layer" element
* @return the loaded map layer
* @throws Exception
*/
private MapLayer readLayer(Node t, TMXMap tmxMap) throws Exception {
final int layerWidth = getAttribute(t, "width", map.getWidth());
final int layerHeight = getAttribute(t, "height", map.getHeight());
TileLayer ml = new TileLayer(layerWidth, layerHeight);
final int offsetX = getAttribute(t, "x", 0);
final int offsetY = getAttribute(t, "y", 0);
final int visible = getAttribute(t, "visible", 1);
String opacity = getAttributeValue(t, "opacity");
ml.setName(getAttributeValue(t, "name"));
if (opacity != null) {
ml.setOpacity(Float.parseFloat(opacity));
}
readProperties(t.getChildNodes(), ml.getProperties());
for (Node child = t.getFirstChild(); child != null;
child = child.getNextSibling())
{
String nodeName = child.getNodeName();
if ("data".equalsIgnoreCase(nodeName)) {
String encoding = getAttributeValue(child, "encoding");
if (encoding != null && "base64".equalsIgnoreCase(encoding)) {
Node cdata = child.getFirstChild();
if (cdata != null) {
char[] enc = cdata.getNodeValue().trim().toCharArray();
byte[] dec = Base64.decode(enc);
ByteArrayInputStream bais = new ByteArrayInputStream(dec);
InputStream is;
String comp = getAttributeValue(child, "compression");
if ("gzip".equalsIgnoreCase(comp)) {
final int len = layerWidth * layerHeight * 4;
is = new GZIPInputStream(bais, len);
} else if ("zlib".equalsIgnoreCase(comp)) {
is = new InflaterInputStream(bais);
} else if (comp != null && !comp.isEmpty()) {
throw new IOException("Unrecognized compression method \"" + comp + "\" for map layer " + ml.getName());
} else {
is = bais;
}
for (int y = 0; y < ml.getHeight(); y++) {
for (int x = 0; x < ml.getWidth(); x++) {
int tileId = 0;
tileId |= is.read();
tileId |= is.read() << 8;
tileId |= is.read() << 16;
tileId |= is.read() << 24;
java.util.Map.Entry<Integer, TileSet> ts = findTileSetForTileGID(tileId);
if (ts != null) {
ml.setTileAt(x, y,
ts.getValue().getTile(tileId - ts.getKey()));
if (ts.getValue().getSpritesheet() != null) {
tmxMap.usedSpritesheets.add(ts.getValue().getSpritesheet());
ts.getValue().getSpritesheet().addBacklink(tmxMap);
}
} else {
ml.setTileAt(x, y, null);
}
}
}
}
} else {
int x = 0, y = 0;
for (Node dataChild = child.getFirstChild();
dataChild != null;
dataChild = dataChild.getNextSibling())
{
if ("tile".equalsIgnoreCase(dataChild.getNodeName())) {
int tileId = getAttribute(dataChild, "gid", -1);
java.util.Map.Entry<Integer, TileSet> ts = findTileSetForTileGID(tileId);
if (ts != null) {
ml.setTileAt(x, y,
ts.getValue().getTile(tileId - ts.getKey()));
if (ts.getValue().getSpritesheet() != null) {
tmxMap.usedSpritesheets.add(ts.getValue().getSpritesheet());
ts.getValue().getSpritesheet().addBacklink(tmxMap);
}
} else {
ml.setTileAt(x, y, null);
}
x++;
if (x == ml.getWidth()) {
x = 0; y++;
}
if (y == ml.getHeight()) { break; }
}
}
}
} else if ("tileproperties".equalsIgnoreCase(nodeName)) {
for (Node tpn = child.getFirstChild();
tpn != null;
tpn = tpn.getNextSibling())
{
if ("tile".equalsIgnoreCase(tpn.getNodeName())) {
int x = getAttribute(tpn, "x", -1);
int y = getAttribute(tpn, "y", -1);
Properties tip = new Properties();
readProperties(tpn.getChildNodes(), tip);
ml.setTileInstancePropertiesAt(x, y, tip);
}
}
}
}
// This is done at the end, otherwise the offset is applied during
// the loading of the tiles.
ml.setOffset(offsetX, offsetY);
// Invisible layers are automatically locked, so it is important to
// set the layer to potentially invisible _after_ the layer data is
// loaded.
// todo: Shouldn't this be just a user interface feature, rather than
// todo: something to keep in mind at this level?
ml.setVisible(visible == 1);
return ml;
}
private void buildMap(Document doc, TMXMap tmxMap) throws Exception {
Node item, mapNode;
mapNode = doc.getDocumentElement();
if (!"map".equals(mapNode.getNodeName())) {
throw new Exception("Not a valid tmx map file.");
}
// Get the map dimensions and create the map
int mapWidth = getAttribute(mapNode, "width", 0);
int mapHeight = getAttribute(mapNode, "height", 0);
if (mapWidth > 0 && mapHeight > 0) {
map = new Map(mapWidth, mapHeight);
} else {
// Maybe this map is still using the dimensions element
NodeList l = doc.getElementsByTagName("dimensions");
for (int i = 0; (item = l.item(i)) != null; i++) {
if (item.getParentNode() == mapNode) {
mapWidth = getAttribute(item, "width", 0);
mapHeight = getAttribute(item, "height", 0);
if (mapWidth > 0 && mapHeight > 0) {
map = new Map(mapWidth, mapHeight);
}
}
}
}
if (map == null) {
throw new Exception("Couldn't locate map dimensions.");
}
// Load other map attributes
String orientation = getAttributeValue(mapNode, "orientation");
int tileWidth = getAttribute(mapNode, "tilewidth", 0);
int tileHeight = getAttribute(mapNode, "tileheight", 0);
if (tileWidth > 0) {
map.setTileWidth(tileWidth);
}
if (tileHeight > 0) {
map.setTileHeight(tileHeight);
}
if (orientation != null) {
setOrientation(orientation);
} else {
setOrientation("orthogonal");
}
// Load properties
readProperties(mapNode.getChildNodes(), map.getProperties());
// Load tilesets first, in case order is munged
tilesetPerFirstGid = new TreeMap<Integer, TileSet>();
NodeList l = doc.getElementsByTagName("tileset");
for (int i = 0; (item = l.item(i)) != null; i++) {
map.addTileset(unmarshalTileset(item, tmxMap));
}
// Load the layers and objectgroups
for (Node sibs = mapNode.getFirstChild(); sibs != null;
sibs = sibs.getNextSibling())
{
if ("layer".equals(sibs.getNodeName())) {
MapLayer layer = readLayer(sibs, tmxMap);
if (layer != null) {
map.addLayer(layer);
}
}
else if ("objectgroup".equals(sibs.getNodeName())) {
MapLayer layer = unmarshalObjectGroup(sibs);
if (layer != null) {
map.addLayer(layer);
}
}
}
tilesetPerFirstGid = null;
}
private Map unmarshal(Reader reader, TMXMap tmxMap) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc;
try {
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
InputSource insrc = new InputSource(reader);
insrc.setSystemId(xmlPath);
insrc.setEncoding("UTF-8");
doc = builder.parse(insrc);
} catch (SAXException e) {
e.printStackTrace();
throw new Exception("Error while parsing map file: " +
e.toString());
}
buildMap(doc, tmxMap);
return map;
}
private Map unmarshal(InputStream in, TMXMap tmxMap) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc;
try {
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
InputSource insrc = new InputSource(in);
insrc.setSystemId(xmlPath);
insrc.setEncoding("UTF-8");
doc = builder.parse(insrc);
} catch (SAXException e) {
e.printStackTrace();
throw new Exception("Error while parsing map file: " +
e.toString());
}
buildMap(doc, tmxMap);
return map;
}
public Map readMap(String filename, TMXMap tmxMap) throws Exception {
xmlPath = filename.substring(0,
filename.lastIndexOf(File.separatorChar) + 1);
String xmlFile = makeUrl(filename);
//xmlPath = makeUrl(xmlPath);
URL url = new URL(xmlFile);
InputStream is = url.openStream();
// Wrap with GZIP decoder for .tmx.gz files
if (filename.endsWith(".gz")) {
is = new GZIPInputStream(is);
}
Map unmarshalledMap = unmarshal(is, tmxMap);
unmarshalledMap.setFilename(filename);
map = null;
return unmarshalledMap;
}
public Map readMap(InputStream in, TMXMap tmxMap) throws Exception {
xmlPath = makeUrl(".");
Map unmarshalledMap = unmarshal(in, tmxMap);
//unmarshalledMap.setFilename(xmlFile)
//
return unmarshalledMap;
}
public Map readMap(Reader reader, TMXMap tmxMap) throws Exception {
xmlPath = makeUrl(".");
Map unmarshalledMap = unmarshal(reader, tmxMap);
//unmarshalledMap.setFilename(xmlFile)
//
return unmarshalledMap;
}
public TileSet readTileset(String filename, TMXMap tmxMap) throws Exception {
String xmlFile = filename;
xmlPath = filename.substring(0,
filename.lastIndexOf(File.separatorChar) + 1);
xmlFile = makeUrl(xmlFile);
xmlPath = makeUrl(xmlPath);
URL url = new URL(xmlFile);
return unmarshalTilesetFile(url.openStream(), filename, tmxMap);
}
public TileSet readTileset(InputStream in, TMXMap tmxMap) throws Exception {
return unmarshalTilesetFile(in, ".", tmxMap);
}
public boolean accept(File pathName) {
try {
String path = pathName.getCanonicalPath();
if (path.endsWith(".tmx") || path.endsWith(".tsx") ||
path.endsWith(".tmx.gz")) {
return true;
}
} catch (IOException e) {}
return false;
}
private class MapEntityResolver implements EntityResolver
{
public InputSource resolveEntity(String publicId, String systemId) {
if (systemId.equals("http://mapeditor.org/dtd/1.0/map.dtd")) {
return new InputSource(TMXMapReader.class.getResourceAsStream(
"resources/map.dtd"));
}
return null;
}
}
/**
* This utility function will check the specified string to see if it
* starts with one of the OS root designations. (Ex.: '/' on Unix, 'C:' on
* Windows)
*
* @param filename a filename to check for absolute or relative path
* @return <code>true</code> if the specified filename starts with a
* filesystem root, <code>false</code> otherwise.
*/
public static boolean checkRoot(String filename) {
File[] roots = File.listRoots();
for (File root : roots) {
try {
String canonicalRoot = root.getCanonicalPath().toLowerCase();
if (filename.toLowerCase().startsWith(canonicalRoot)) {
return true;
}
} catch (IOException e) {
// Do we care?
}
}
return false;
}
/**
* Get the tile set and its corresponding firstgid that matches the given
* global tile id.
*
*
* @param gid a global tile id
* @return the tileset containing the tile with the given global tile id,
* or <code>null</code> when no such tileset exists
*/
private java.util.Map.Entry<Integer, TileSet> findTileSetForTileGID(int gid) {
return tilesetPerFirstGid.floorEntry(gid);
}
private void setFirstGidForTileset(TileSet tileset, int firstGid) {
tilesetPerFirstGid.put(firstGid, tileset);
}
}

View File

@@ -0,0 +1,613 @@
/*
* Copyright 2004-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2008, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.io;
import java.awt.Color;
import java.awt.Rectangle;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;
import tiled.core.*;
import tiled.core.Map;
import tiled.io.xml.XMLWriter;
import tiled.util.Base64;
/**
* A writer for Tiled's TMX map format.
*/
public class TMXMapWriter
{
private static final int LAST_BYTE = 0x000000FF;
private static final boolean encodeLayerData = true;
private static final boolean compressLayerData = encodeLayerData;
private HashMap<String, Integer> firstGidPerTileset;
public static class Settings {
public static final String LAYER_COMPRESSION_METHOD_GZIP = "gzip";
public static final String LAYER_COMPRESSION_METHOD_ZLIB = "zlib";
public String layerCompressionMethod = LAYER_COMPRESSION_METHOD_GZIP;
}
public Settings settings = new Settings();
/**
* Saves a map to an XML file.
*
* @param filename the filename of the map file
*/
public void writeMap(Map map, String filename) throws Exception {
OutputStream os = new FileOutputStream(filename);
if (filename.endsWith(".tmx.gz")) {
os = new GZIPOutputStream(os);
}
Writer writer = new OutputStreamWriter(os, Charset.forName("UTF-8"));
XMLWriter xmlWriter = new XMLWriter(writer);
xmlWriter.startDocument();
writeMap(map, xmlWriter, filename);
xmlWriter.endDocument();
writer.flush();
if (os instanceof GZIPOutputStream) {
((GZIPOutputStream)os).finish();
}
}
/**
* Saves a tileset to an XML file.
*
* @param filename the filename of the tileset file
*/
public void writeTileset(TileSet set, String filename) throws Exception {
OutputStream os = new FileOutputStream(filename);
Writer writer = new OutputStreamWriter(os, Charset.forName("UTF-8"));
XMLWriter xmlWriter = new XMLWriter(writer);
xmlWriter.startDocument();
writeTileset(set, xmlWriter, filename);
xmlWriter.endDocument();
writer.flush();
}
public void writeMap(Map map, OutputStream out) throws Exception {
writeMap(map, out, "/.");
}
public void writeMap(Map map, OutputStream out, String workingDirectory) throws Exception {
Writer writer = new OutputStreamWriter(out,Charset.forName("UTF-8"));
XMLWriter xmlWriter = new XMLWriter(writer);
xmlWriter.startDocument();
writeMap(map, xmlWriter, workingDirectory);
xmlWriter.endDocument();
writer.flush();
}
public void writeTileset(TileSet set, OutputStream out) throws Exception {
Writer writer = new OutputStreamWriter(out, Charset.forName("UTF-8"));
XMLWriter xmlWriter = new XMLWriter(writer);
xmlWriter.startDocument();
writeTileset(set, xmlWriter, "/.");
xmlWriter.endDocument();
writer.flush();
}
private void writeMap(Map map, XMLWriter w, String wp) throws IOException {
w.writeDocType("map", null, "http://mapeditor.org/dtd/1.0/map.dtd");
w.startElement("map");
w.writeAttribute("version", "1.0");
switch (map.getOrientation()) {
case Map.ORIENTATION_ORTHOGONAL:
w.writeAttribute("orientation", "orthogonal"); break;
case Map.ORIENTATION_ISOMETRIC:
w.writeAttribute("orientation", "isometric"); break;
case Map.ORIENTATION_HEXAGONAL:
w.writeAttribute("orientation", "hexagonal"); break;
case Map.ORIENTATION_SHIFTED:
w.writeAttribute("orientation", "shifted"); break;
}
w.writeAttribute("width", map.getWidth());
w.writeAttribute("height", map.getHeight());
w.writeAttribute("tilewidth", map.getTileWidth());
w.writeAttribute("tileheight", map.getTileHeight());
writeProperties(map.getProperties(), w);
firstGidPerTileset = new HashMap<String, Integer>();
int firstgid = 1;
for (TileSet tileset : map.getTileSets()) {
setFirstGidForTileset(tileset, firstgid);
writeTilesetReference(tileset, w, wp);
firstgid += tileset.getMaxTileId() + 1;
}
for (MapLayer layer : map) {
writeMapLayer(layer, w, wp);
}
firstGidPerTileset = null;
w.endElement();
}
private static void writeProperties(Properties props, XMLWriter w) throws
IOException
{
if (!props.isEmpty()) {
final SortedSet<Object> propertyKeys = new TreeSet<Object>();
propertyKeys.addAll(props.keySet());
w.startElement("properties");
for (Object propertyKey : propertyKeys) {
final String key = (String) propertyKey;
final String property = props.getProperty(key);
w.startElement("property");
w.writeAttribute("name", key);
if (property.indexOf('\n') == -1) {
w.writeAttribute("value", property);
} else {
// Save multiline values as character data
w.writeCDATA(property);
}
w.endElement();
}
w.endElement();
}
}
/**
* Writes a reference to an external tileset into a XML document. In the
* case where the tileset is not stored in an external file, writes the
* contents of the tileset instead.
*
* @param set the tileset to write a reference to
* @param w the XML writer to write to
* @param wp the working directory of the map
* @throws java.io.IOException
*/
private void writeTilesetReference(TileSet set, XMLWriter w, String wp)
throws IOException {
String source = set.getSource();
if (source == null) {
writeTileset(set, w, wp);
} else {
w.startElement("tileset");
w.writeAttribute("firstgid", getFirstGidForTileset(set));
w.writeAttribute("source", getRelativePath(wp, source));
if (set.getBaseDir() != null) {
w.writeAttribute("basedir", set.getBaseDir());
}
w.endElement();
}
}
private void writeTileset(TileSet set, XMLWriter w, String wp)
throws IOException {
String tileBitmapFile = set.getTilebmpFile();
String name = set.getName();
w.startElement("tileset");
w.writeAttribute("firstgid", getFirstGidForTileset(set));
if (name != null) {
w.writeAttribute("name", name);
}
if (tileBitmapFile != null) {
w.writeAttribute("tilewidth", set.getTileWidth());
w.writeAttribute("tileheight", set.getTileHeight());
final int tileSpacing = set.getTileSpacing();
final int tileMargin = set.getTileMargin();
if (tileSpacing != 0) {
w.writeAttribute("spacing", tileSpacing);
}
if (tileMargin != 0) {
w.writeAttribute("margin", tileMargin);
}
}
if (set.getBaseDir() != null) {
w.writeAttribute("basedir", set.getBaseDir());
}
if (tileBitmapFile != null) {
w.startElement("image");
w.writeAttribute("source", getRelativePath(wp, tileBitmapFile));
Color trans = set.getTransparentColor();
if (trans != null) {
w.writeAttribute("trans", Integer.toHexString(
trans.getRGB()).substring(2));
}
w.endElement();
// Write tile properties when necessary.
for (Tile tile : set) {
// todo: move the null check back into the iterator?
if (tile != null && !tile.getProperties().isEmpty()) {
w.startElement("tile");
w.writeAttribute("id", tile.getId());
writeProperties(tile.getProperties(), w);
w.endElement();
}
}
} else {
// Check to see if there is a need to write tile elements
boolean needWrite = false;
// As long as one has properties, they all need to be written.
// TODO: This shouldn't be necessary
for (Tile tile : set) {
if (!tile.getProperties().isEmpty()) {
needWrite = true;
break;
}
}
if (needWrite) {
for (Tile tile : set) {
// todo: move this check back into the iterator?
if (tile != null) {
writeTile(tile, w);
}
}
}
}
w.endElement();
}
private static void writeObjectGroup(ObjectGroup o, XMLWriter w, String wp)
throws IOException
{
Iterator<MapObject> itr = o.getObjects();
while (itr.hasNext()) {
writeMapObject(itr.next(), w, wp);
}
}
/**
* Writes this layer to an XMLWriter. This should be done <b>after</b> the
* first global ids for the tilesets are determined, in order for the right
* gids to be written to the layer data.
*/
private void writeMapLayer(MapLayer l, XMLWriter w, String wp) throws IOException {
Rectangle bounds = l.getBounds();
if (l instanceof ObjectGroup) {
w.startElement("objectgroup");
} else {
w.startElement("layer");
}
w.writeAttribute("name", l.getName());
if (bounds.width != 0) {
w.writeAttribute("width", bounds.width);
}
if (bounds.height != 0) {
w.writeAttribute("height", bounds.height);
}
if (bounds.x != 0) {
w.writeAttribute("x", bounds.x);
}
if (bounds.y != 0) {
w.writeAttribute("y", bounds.y);
}
if (!l.isVisible()) {
w.writeAttribute("visible", "0");
}
if (l.getOpacity() < 1.0f) {
w.writeAttribute("opacity", l.getOpacity());
}
writeProperties(l.getProperties(), w);
if (l instanceof ObjectGroup){
writeObjectGroup((ObjectGroup) l, w, wp);
} else if (l instanceof TileLayer) {
final TileLayer tl = (TileLayer) l;
w.startElement("data");
if (encodeLayerData) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream out;
w.writeAttribute("encoding", "base64");
DeflaterOutputStream dos;
if (compressLayerData) {
if (Settings.LAYER_COMPRESSION_METHOD_ZLIB.equalsIgnoreCase(settings.layerCompressionMethod)) {
dos = new DeflaterOutputStream(baos);
} else if (Settings.LAYER_COMPRESSION_METHOD_GZIP.equalsIgnoreCase(settings.layerCompressionMethod)) {
dos = new GZIPOutputStream(baos);
} else {
throw new IOException("Unrecognized compression method \"" + settings.layerCompressionMethod + "\" for map layer " + l.getName());
}
out = dos;
w.writeAttribute("compression", settings.layerCompressionMethod);
} else {
out = baos;
}
for (int y = 0; y < l.getHeight(); y++) {
for (int x = 0; x < l.getWidth(); x++) {
Tile tile = tl.getTileAt(x + bounds.x,
y + bounds.y);
int gid = 0;
if (tile != null) {
gid = getGid(tile);
}
out.write(gid & LAST_BYTE);
out.write(gid >> 8 & LAST_BYTE);
out.write(gid >> 16 & LAST_BYTE);
out.write(gid >> 24 & LAST_BYTE);
}
}
if (compressLayerData && dos != null) {
dos.finish();
}
w.writeCDATA(Base64.encodeToString(baos.toByteArray(), false));
} else {
for (int y = 0; y < l.getHeight(); y++) {
for (int x = 0; x < l.getWidth(); x++) {
Tile tile = tl.getTileAt(x + bounds.x, y + bounds.y);
int gid = 0;
if (tile != null) {
gid = getGid(tile);
}
w.startElement("tile");
w.writeAttribute("gid", gid);
w.endElement();
}
}
}
w.endElement();
boolean tilePropertiesElementStarted = false;
for (int y = 0; y < l.getHeight(); y++) {
for (int x = 0; x < l.getWidth(); x++) {
Properties tip = tl.getTileInstancePropertiesAt(x, y);
if (tip != null && !tip.isEmpty()) {
if (!tilePropertiesElementStarted) {
w.startElement("tileproperties");
tilePropertiesElementStarted = true;
}
w.startElement("tile");
w.writeAttribute("x", x);
w.writeAttribute("y", y);
writeProperties(tip, w);
w.endElement();
}
}
}
if (tilePropertiesElementStarted)
w.endElement();
}
w.endElement();
}
/**
* Used to write tile elements for tilesets not based on a tileset image.
*
* @param tile the tile instance that should be written
* @param w the writer to write to
* @throws IOException when an io error occurs
*/
private void writeTile(Tile tile, XMLWriter w) throws IOException {
w.startElement("tile");
w.writeAttribute("id", tile.getId());
writeProperties(tile.getProperties(), w);
if (tile instanceof AnimatedTile)
writeAnimation(((AnimatedTile)tile).getSprite(), w);
w.endElement();
}
private void writeAnimation(Sprite s, XMLWriter w) throws IOException {
w.startElement("animation");
for (int k = 0; k < s.getTotalKeys(); k++) {
Sprite.KeyFrame key = s.getKey(k);
w.startElement("keyframe");
w.writeAttribute("name", key.getName());
for (int it = 0; it < key.getTotalFrames(); it++) {
Tile stile = key.getFrame(it);
w.startElement("tile");
w.writeAttribute("gid", getGid(stile));
w.endElement();
}
w.endElement();
}
w.endElement();
}
private static void writeMapObject(MapObject mapObject, XMLWriter w, String wp)
throws IOException
{
w.startElement("object");
w.writeAttribute("name", mapObject.getName());
if (mapObject.getType().length() != 0)
w.writeAttribute("type", mapObject.getType());
w.writeAttribute("x", mapObject.getX());
w.writeAttribute("y", mapObject.getY());
if (mapObject.getWidth() != 0)
w.writeAttribute("width", mapObject.getWidth());
if (mapObject.getHeight() != 0)
w.writeAttribute("height", mapObject.getHeight());
writeProperties(mapObject.getProperties(), w);
if (mapObject.getImageSource().length() > 0) {
w.startElement("image");
w.writeAttribute("source",
getRelativePath(wp, mapObject.getImageSource()));
w.endElement();
}
w.endElement();
}
/**
* Returns the relative path from one file to the other. The function
* expects absolute paths, relative paths will be converted to absolute
* using the working directory.
*
* @param from the path of the origin file
* @param to the path of the destination file
* @return the relative path from origin to destination
*/
public static String getRelativePath(String from, String to) {
if(!(new File(to)).isAbsolute())
return to;
// Make the two paths absolute and unique
try {
from = new File(from).getCanonicalPath();
to = new File(to).getCanonicalPath();
} catch (IOException e) {
}
File fromFile = new File(from);
File toFile = new File(to);
Vector<String> fromParents = new Vector<String>();
Vector<String> toParents = new Vector<String>();
// Iterate to find both parent lists
while (fromFile != null) {
fromParents.add(0, fromFile.getName());
fromFile = fromFile.getParentFile();
}
while (toFile != null) {
toParents.add(0, toFile.getName());
toFile = toFile.getParentFile();
}
// Iterate while parents are the same
int shared = 0;
int maxShared = Math.min(fromParents.size(), toParents.size());
for (shared = 0; shared < maxShared; shared++) {
String fromParent = fromParents.get(shared);
String toParent = toParents.get(shared);
if (!fromParent.equals(toParent)) {
break;
}
}
// Append .. for each remaining parent in fromParents
StringBuffer relPathBuf = new StringBuffer();
for (int i = shared; i < fromParents.size() - 1; i++) {
relPathBuf.append(".." + File.separator);
}
// Add the remaining part in toParents
for (int i = shared; i < toParents.size() - 1; i++) {
relPathBuf.append(toParents.get(i) + File.separator);
}
relPathBuf.append(new File(to).getName());
String relPath = relPathBuf.toString();
// Turn around the slashes when path is relative
try {
String absPath = new File(relPath).getCanonicalPath();
if (!absPath.equals(relPath)) {
// Path is not absolute, turn slashes around
// Assumes: \ does not occur in file names
relPath = relPath.replace('\\', '/');
}
} catch (IOException e) {
}
return relPath;
}
public boolean accept(File pathName) {
try {
String path = pathName.getCanonicalPath();
if (path.endsWith(".tmx") || path.endsWith(".tsx") || path.endsWith(".tmx.gz")) {
return true;
}
} catch (IOException e) {}
return false;
}
/**
* Returns the global tile id of the given tile.
*
* @return global tile id of the given tile
*/
private int getGid(Tile tile) {
TileSet tileset = tile.getTileSet();
if (tileset != null) {
return tile.getId() + getFirstGidForTileset(tileset);
}
return tile.getId();
}
private void setFirstGidForTileset(TileSet tileset, int firstGid) {
firstGidPerTileset.put(tileset.getName(), firstGid);
}
private int getFirstGidForTileset(TileSet tileset) {
return firstGidPerTileset.get(tileset.getName());
}
}

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
format 0.7.1 by
Tiled Developers (mapeditor.org)
documented in dtd form originally by
Olivier.Beaton@quadir.net
Creative Commons Attribution 3.0
http://creativecommons.org/licenses/by/3.0/
last updated on
2008-08-05
-->
<!ELEMENT map (properties?, tileset*, (layer | objectgroup)*)>
<!ATTLIST map
xmlns CDATA #IMPLIED
xmlns:xsi CDATA #IMPLIED
xsi:schemaLocation CDATA #IMPLIED
version CDATA #REQUIRED
orientation (orthogonal | isometric | hexagonal | shifted) #REQUIRED
width CDATA #REQUIRED
height CDATA #REQUIRED
tilewidth CDATA #REQUIRED
tileheight CDATA #REQUIRED
>
<!ELEMENT properties (property*)>
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!--
data is required when a child of tilset
data is not valid when a child of tile
-->
<!ELEMENT image (data?)>
<!--
format is required when a child of tileset
format is not valid when a child of tile
source here is required when tileset tileheight/tilewidth -> image is used and you are referencing an outside image
-->
<!ATTLIST image
format CDATA #IMPLIED
id CDATA #IMPLIED
source CDATA #IMPLIED
trans CDATA #IMPLIED
>
<!--
#PCDATA when data is child of image
tile* when data is child of layer without compression
-->
<!ELEMENT data (#PCDATA | tile)*>
<!ATTLIST data
encoding CDATA #IMPLIED
compression CDATA #IMPLIED
>
<!ELEMENT tileset (image*, tile*)>
<!--
name REQUIRED only if source tsx not present
source here refers to a TSX
-->
<!ATTLIST tileset
name CDATA #IMPLIED
firstgid CDATA #REQUIRED
source CDATA #IMPLIED
tilewidth CDATA #IMPLIED
tileheight CDATA #IMPLIED
spacing CDATA #IMPLIED
margin CDATA #IMPLIED
>
<!--
image required when child of all but layer -> data
image not valid when child of layer -> data
-->
<!ELEMENT tile (properties?, image?)>
<!--
id required when child of all but layer -> data
id not valid when child of layer -> data
gid required when child of layer -> data
gid not valid when not child of layer -> data
-->
<!ATTLIST tile
id CDATA #IMPLIED
gid CDATA #IMPLIED
>
<!ELEMENT layer (properties?, data)>
<!ATTLIST layer
name CDATA #REQUIRED
width CDATA #REQUIRED
height CDATA #REQUIRED
x CDATA #IMPLIED
y CDATA #IMPLIED
opacity CDATA #IMPLIED
visible (0 | 1) #IMPLIED
>
<!ELEMENT objectgroup (object*)>
<!ATTLIST objectgroup
name CDATA #REQUIRED
width CDATA #IMPLIED
height CDATA #IMPLIED
x CDATA #IMPLIED
y CDATA #IMPLIED
>
<!ELEMENT object (properties?, image?)>
<!ATTLIST object
name CDATA #REQUIRED
type CDATA #REQUIRED
x CDATA #REQUIRED
y CDATA #REQUIRED
width CDATA #IMPLIED
height CDATA #IMPLIED
>

View File

@@ -0,0 +1,206 @@
/*
* Copyright 2004-2006, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.io.xml;
import java.lang.String;
import java.io.Writer;
import java.io.IOException;
import java.util.Stack;
/**
* A simple helper class to write an XML file, based on
* http://www.xmlsoft.org/html/libxml-xmlwriter.html
*/
public class XMLWriter
{
private boolean bIndent = true;
private String indentString = " ";
private String newLine = "\n";
private final Writer w;
private final Stack<String> openElements;
private boolean bStartTagOpen;
private boolean bDocumentOpen;
public XMLWriter(Writer writer) {
openElements = new Stack<String>();
w = writer;
}
public void setIndent(boolean bIndent) {
this.bIndent = bIndent;
newLine = bIndent ? "\n" : "";
}
public void setIndentString(String indentString) {
this.indentString = indentString;
}
public void startDocument() throws IOException {
startDocument("1.0");
}
public void startDocument(String version) throws IOException {
w.write("<?xml version=\"" + version + "\" encoding=\"UTF-8\"?>"
+ newLine);
bDocumentOpen = true;
}
public void writeDocType(String name, String pubId, String sysId)
throws IOException, XMLWriterException {
if (!bDocumentOpen) {
throw new XMLWriterException(
"Can't write DocType, no open document.");
} else if (!openElements.isEmpty()) {
throw new XMLWriterException(
"Can't write DocType, open elements exist.");
}
w.write("<!DOCTYPE " + name + " ");
if (pubId != null) {
w.write("PUBLIC \"" + pubId + "\"");
if (sysId != null) {
w.write(" \"" + sysId + "\"");
}
} else if (sysId != null) {
w.write("SYSTEM \"" + sysId + "\"");
}
w.write(">" + newLine);
}
public void startElement(String name)
throws IOException, XMLWriterException {
if (!bDocumentOpen) {
throw new XMLWriterException(
"Can't start new element, no open document.");
}
if (bStartTagOpen) {
w.write(">" + newLine);
}
writeIndent();
w.write("<" + name);
openElements.push(name);
bStartTagOpen = true;
}
public void endDocument() throws IOException {
// End all open elements.
while (!openElements.isEmpty()) {
endElement();
}
w.flush(); //writers do not always flush automatically...
}
public void endElement() throws IOException {
String name = openElements.pop();
// If start tag still open, end with />, else with </name>.
if (bStartTagOpen) {
w.write("/>" + newLine);
bStartTagOpen = false;
} else {
writeIndent();
w.write("</" + name + ">" + newLine);
}
// Set document closed when last element is closed
if (openElements.isEmpty()) {
bDocumentOpen = false;
}
}
public void writeAttribute(String name, String content)
throws IOException, XMLWriterException {
if (bStartTagOpen) {
String escapedContent = (content != null) ?
content.replaceAll("\"", "&quot;") : "";
w.write(" " + name + "=\"" + escapedContent + "\"");
} else {
throw new XMLWriterException(
"Can't write attribute without open start tag.");
}
}
public void writeAttribute(String name, int content)
throws IOException, XMLWriterException {
writeAttribute(name, String.valueOf(content));
}
public void writeAttribute(String name, float content)
throws IOException, XMLWriterException {
writeAttribute(name, String.valueOf(content));
}
public void writeCDATA(String content) throws IOException {
if (bStartTagOpen) {
w.write(">" + newLine);
bStartTagOpen = false;
}
writeIndent();
w.write(content + newLine);
}
public void writeComment(String content) throws IOException {
if (bStartTagOpen) {
w.write(">" + newLine);
bStartTagOpen = false;
}
writeIndent();
w.write("<!-- " + content + " -->" + newLine);
}
public void writeElement(String name, String content)
throws IOException, XMLWriterException {
startElement(name);
writeCDATA(content);
endElement();
}
private void writeIndent() throws IOException {
if (bIndent) {
for (int i = 0; i < openElements.size(); i++) {
w.write(indentString);
}
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2004-2006, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.io.xml;
public class XMLWriterException extends RuntimeException
{
public XMLWriterException(String error) {
super(error);
}
}

View File

@@ -0,0 +1,575 @@
package tiled.util;
import java.util.Arrays;
/** A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance
* with RFC 2045.<br><br>
* On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster
* on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes)
* compared to <code>sun.misc.Encoder()/Decoder()</code>.<br><br>
*
* On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and
* about 50% faster for decoding large arrays. This implementation is about twice as fast on very small
* arrays (&lt 30 bytes). If source/destination is a <code>String</code> this
* version is about three times as fast due to the fact that the Commons Codec result has to be recoded
* to a <code>String</code> from <code>byte[]</code>, which is very expensive.<br><br>
*
* This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only
* allocates the resulting array. This produces less garbage and it is possible to handle arrays twice
* as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown
* whether Sun's <code>sun.misc.Encoder()/Decoder()</code> produce temporary arrays but since performance
* is quite low it probably does.<br><br>
*
* The encoder produces the same output as the Sun one except that the Sun's encoder appends
* a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the
* length and is probably a side effect. Both are in conformance with RFC 2045 though.<br>
* Commons codec seem to always att a trailing line separator.<br><br>
*
* <b>Note!</b>
* The encode/decode method pairs (types) come in three versions with the <b>exact</b> same algorithm and
* thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different
* format types. The methods not used can simply be commented out.<br><br>
*
* There is also a "fast" version of all decode methods that works the same way as the normal ones, but
* har a few demands on the decoded input. Normally though, these fast verions should be used if the source if
* the input is known and it hasn't bee tampered with.<br><br>
*
* If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com.
*
* Licence (BSD):
* ==============
*
* Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
* Neither the name of the MiG InfoCom AB nor the names of its contributors may be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* @version 2.2
* @author Mikael Grev
* Date: 2004-aug-02
* Time: 11:31:11
*/
public class Base64
{
private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
private static final int[] IA = new int[256];
static {
Arrays.fill(IA, -1);
for (int i = 0, iS = CA.length; i < iS; i++)
IA[CA[i]] = i;
IA['='] = 0;
}
// ****************************************************************************************
// * char[] version
// ****************************************************************************************
/** Encodes a raw byte array into a BASE64 <code>char[]</code> representation i accordance with RFC 2045.
* @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
* little faster.
* @return A BASE64 encoded array. Never <code>null</code>.
*/
public final static char[] encodeToChar(byte[] sArr, boolean lineSep)
{
// Check special case
int sLen = sArr != null ? sArr.length : 0;
if (sLen == 0)
return new char[0];
int eLen = (sLen / 3) * 3; // Length of even 24-bits.
int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count
int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array
char[] dArr = new char[dLen];
// Encode even 24-bits
for (int s = 0, d = 0, cc = 0; s < eLen;) {
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
// Encode the int into four chars
dArr[d++] = CA[(i >>> 18) & 0x3f];
dArr[d++] = CA[(i >>> 12) & 0x3f];
dArr[d++] = CA[(i >>> 6) & 0x3f];
dArr[d++] = CA[i & 0x3f];
// Add optional line separator
if (lineSep && ++cc == 19 && d < dLen - 2) {
dArr[d++] = '\r';
dArr[d++] = '\n';
cc = 0;
}
}
// Pad and encode last bits if source isn't even 24 bits.
int left = sLen - eLen; // 0 - 2.
if (left > 0) {
// Prepare the int
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
// Set last four chars
dArr[dLen - 4] = CA[i >> 12];
dArr[dLen - 3] = CA[(i >>> 6) & 0x3f];
dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '=';
dArr[dLen - 1] = '=';
}
return dArr;
}
/** Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with
* and without line separators.
* @param sArr The source array. <code>null</code> or length 0 will return an empty array.
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
*/
public final static byte[] decode(char[] sArr)
{
// Check special case
int sLen = sArr != null ? sArr.length : 0;
if (sLen == 0)
return new byte[0];
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
// so we don't have to reallocate & copy it later.
int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
if (IA[sArr[i]] < 0)
sepCnt++;
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
if ((sLen - sepCnt) % 4 != 0)
return null;
int pad = 0;
for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0;)
if (sArr[i] == '=')
pad++;
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
for (int s = 0, d = 0; d < len;) {
// Assemble three bytes into an int from four "valid" characters.
int i = 0;
for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
int c = IA[sArr[s++]];
if (c >= 0)
i |= c << (18 - j * 6);
else
j--;
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++]= (byte) (i >> 8);
if (d < len)
dArr[d++] = (byte) i;
}
}
return dArr;
}
/** Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as
* fast as {@link #decode(char[])}. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
* @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0.
*/
public final static byte[] decodeFast(char[] sArr)
{
// Check special case
int sLen = sArr.length;
if (sLen == 0)
return new byte[0];
int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IA[sArr[sIx]] < 0)
sIx++;
// Trim illegal chars from end
while (eIx > 0 && IA[sArr[eIx]] < 0)
eIx--;
// get the padding count (=) (0, 1 or 2)
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
int cCnt = eIx - sIx + 1; // Content count including possible separators
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
// Assemble three bytes into an int from four "valid" characters.
int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++)
i |= IA[sArr[sIx++]] << (18 - j * 6);
for (int r = 16; d < len; r -= 8)
dArr[d++] = (byte) (i >> r);
}
return dArr;
}
// ****************************************************************************************
// * byte[] version
// ****************************************************************************************
/** Encodes a raw byte array into a BASE64 <code>byte[]</code> representation i accordance with RFC 2045.
* @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
* little faster.
* @return A BASE64 encoded array. Never <code>null</code>.
*/
public final static byte[] encodeToByte(byte[] sArr, boolean lineSep)
{
// Check special case
int sLen = sArr != null ? sArr.length : 0;
if (sLen == 0)
return new byte[0];
int eLen = (sLen / 3) * 3; // Length of even 24-bits.
int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count
int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array
byte[] dArr = new byte[dLen];
// Encode even 24-bits
for (int s = 0, d = 0, cc = 0; s < eLen;) {
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
// Encode the int into four chars
dArr[d++] = (byte) CA[(i >>> 18) & 0x3f];
dArr[d++] = (byte) CA[(i >>> 12) & 0x3f];
dArr[d++] = (byte) CA[(i >>> 6) & 0x3f];
dArr[d++] = (byte) CA[i & 0x3f];
// Add optional line separator
if (lineSep && ++cc == 19 && d < dLen - 2) {
dArr[d++] = '\r';
dArr[d++] = '\n';
cc = 0;
}
}
// Pad and encode last bits if source isn't an even 24 bits.
int left = sLen - eLen; // 0 - 2.
if (left > 0) {
// Prepare the int
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
// Set last four chars
dArr[dLen - 4] = (byte) CA[i >> 12];
dArr[dLen - 3] = (byte) CA[(i >>> 6) & 0x3f];
dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '=';
dArr[dLen - 1] = '=';
}
return dArr;
}
/** Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
* and without line separators.
* @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
*/
public final static byte[] decode(byte[] sArr)
{
// Check special case
int sLen = sArr.length;
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
// so we don't have to reallocate & copy it later.
int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
if (IA[sArr[i] & 0xff] < 0)
sepCnt++;
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
if ((sLen - sepCnt) % 4 != 0)
return null;
int pad = 0;
for (int i = sLen; i > 1 && IA[sArr[--i] & 0xff] <= 0;)
if (sArr[i] == '=')
pad++;
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
for (int s = 0, d = 0; d < len;) {
// Assemble three bytes into an int from four "valid" characters.
int i = 0;
for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
int c = IA[sArr[s++] & 0xff];
if (c >= 0)
i |= c << (18 - j * 6);
else
j--;
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++]= (byte) (i >> 8);
if (d < len)
dArr[d++] = (byte) i;
}
}
return dArr;
}
/** Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as
* fast as {@link #decode(byte[])}. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
* @param sArr The source array. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0.
*/
public final static byte[] decodeFast(byte[] sArr)
{
// Check special case
int sLen = sArr.length;
if (sLen == 0)
return new byte[0];
int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0)
sIx++;
// Trim illegal chars from end
while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0)
eIx--;
// get the padding count (=) (0, 1 or 2)
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end.
int cCnt = eIx - sIx + 1; // Content count including possible separators
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
// Assemble three bytes into an int from four "valid" characters.
int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++)
i |= IA[sArr[sIx++]] << (18 - j * 6);
for (int r = 16; d < len; r -= 8)
dArr[d++] = (byte) (i >> r);
}
return dArr;
}
// ****************************************************************************************
// * String version
// ****************************************************************************************
/** Encodes a raw byte array into a BASE64 <code>String</code> representation i accordance with RFC 2045.
* @param sArr The bytes to convert. If <code>null</code> or length 0 an empty array will be returned.
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
* little faster.
* @return A BASE64 encoded array. Never <code>null</code>.
*/
public final static String encodeToString(byte[] sArr, boolean lineSep)
{
// Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower.
return new String(encodeToChar(sArr, lineSep));
}
/** Decodes a BASE64 encoded <code>String</code>. All illegal characters will be ignored and can handle both strings with
* and without line separators.<br>
* <b>Note!</b> It can be up to about 2x the speed to call <code>decode(str.toCharArray())</code> instead. That
* will create a temporary array though. This version will use <code>str.charAt(i)</code> to iterate the string.
* @param str The source string. <code>null</code> or length 0 will return an empty array.
* @return The decoded array of bytes. May be of length 0. Will be <code>null</code> if the legal characters
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
*/
public final static byte[] decode(String str)
{
// Check special case
int sLen = str != null ? str.length() : 0;
if (sLen == 0)
return new byte[0];
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
// so we don't have to reallocate & copy it later.
int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...)
for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
if (IA[str.charAt(i)] < 0)
sepCnt++;
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
if ((sLen - sepCnt) % 4 != 0)
return null;
// Count '=' at end
int pad = 0;
for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0;)
if (str.charAt(i) == '=')
pad++;
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
for (int s = 0, d = 0; d < len;) {
// Assemble three bytes into an int from four "valid" characters.
int i = 0;
for (int j = 0; j < 4; j++) { // j only increased if a valid char was found.
int c = IA[str.charAt(s++)];
if (c >= 0)
i |= c << (18 - j * 6);
else
j--;
}
// Add the bytes
dArr[d++] = (byte) (i >> 16);
if (d < len) {
dArr[d++]= (byte) (i >> 8);
if (d < len)
dArr[d++] = (byte) i;
}
}
return dArr;
}
/** Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as
* fast as {@link #decode(String)}. The preconditions are:<br>
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
* + Line separator must be "\r\n", as specified in RFC 2045
* + The array must not contain illegal characters within the encoded string<br>
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
* @param s The source string. Length 0 will return an empty array. <code>null</code> will throw an exception.
* @return The decoded array of bytes. May be of length 0.
*/
public final static byte[] decodeFast(String s)
{
// Check special case
int sLen = s.length();
if (sLen == 0)
return new byte[0];
int sIx = 0, eIx = sLen - 1; // Start and end index after trimming.
// Trim illegal chars from start
while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0)
sIx++;
// Trim illegal chars from end
while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0)
eIx--;
// get the padding count (=) (0, 1 or 2)
int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end.
int cCnt = eIx - sIx + 1; // Content count including possible separators
int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0;
int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes
byte[] dArr = new byte[len]; // Preallocate byte[] of exact length
// Decode all but the last 0 - 2 bytes.
int d = 0;
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
// Assemble three bytes into an int from four "valid" characters.
int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)];
// Add the bytes
dArr[d++] = (byte) (i >> 16);
dArr[d++] = (byte) (i >> 8);
dArr[d++] = (byte) i;
// If line separator, jump over it.
if (sepCnt > 0 && ++cc == 19) {
sIx += 2;
cc = 0;
}
}
if (d < len) {
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
int i = 0;
for (int j = 0; sIx <= eIx - pad; j++)
i |= IA[s.charAt(sIx++)] << (18 - j * 6);
for (int r = 16; d < len; r -= 8)
dArr[d++] = (byte) (i >> r);
}
return dArr;
}
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2004-2006, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.util;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
/**
* Cuts tiles from a tileset image according to a regular rectangular pattern.
* Supports a variable spacing between tiles and a margin around them.
*/
public class BasicTileCutter implements TileCutter
{
private int nextX, nextY;
private BufferedImage image;
private final int tileWidth;
private final int tileHeight;
private final int tileSpacing;
private final int tileMargin;
public BasicTileCutter(int tileWidth, int tileHeight, int tileSpacing,
int tileMargin)
{
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.tileSpacing = tileSpacing;
this.tileMargin = tileMargin;
reset();
}
public String getName() {
return "Basic";
}
public void setImage(BufferedImage image) {
this.image = image;
}
public Image getNextTile() {
if (nextY + tileHeight + tileMargin <= image.getHeight()) {
BufferedImage tile =
image.getSubimage(nextX, nextY, tileWidth, tileHeight);
nextX += tileWidth + tileSpacing;
if (nextX + tileWidth + tileMargin > image.getWidth()) {
nextX = tileMargin;
nextY += tileHeight + tileSpacing;
}
return tile;
}
return null;
}
public void reset() {
nextX = tileMargin;
nextY = tileMargin;
}
public Dimension getTileDimensions() {
return new Dimension(tileWidth, tileHeight);
}
/**
* Returns the spacing between tile images.
* @return the spacing between tile images.
*/
public int getTileSpacing() {
return tileSpacing;
}
/**
* Returns the margin around the tile images.
* @return the margin around the tile images.
*/
public int getTileMargin() {
return tileMargin;
}
/**
* Returns the number of tiles per row in the tileset image.
* @return the number of tiles per row in the tileset image.
*/
public int getTilesPerRow() {
return (image.getWidth() - 2 * tileMargin + tileSpacing) /
(tileWidth + tileSpacing);
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2004-2008, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2008, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.util;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* This class provides functions to help out with saving/loading images.
*/
public class ImageHelper
{
/**
* Converts an image to a PNG stored in a byte array.
*
* @param image
* @return a byte array with the PNG data
*/
static public byte[] imageToPNG(Image image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
BufferedImage buffer = new BufferedImage(
image.getWidth(null), image.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
buffer.createGraphics().drawImage(image, 0, 0, null);
ImageIO.write(buffer, "PNG", baos);
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
return baos.toByteArray();
}
/**
* Converts a byte array into an image. The byte array must include the
* image header, so that a decision about format can be made.
*
* @param imageData The byte array of the data to convert.
* @return Image The image instance created from the byte array
* @throws IOException
* @see java.awt.Toolkit#createImage(byte[] imagedata)
*/
static public BufferedImage bytesToImage(byte[] imageData) throws IOException {
return ImageIO.read(new ByteArrayInputStream(imageData));
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2004-2006, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.util;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
/**
* A generic interface to a class that implements tile cutting behavior.
*/
public interface TileCutter
{
/**
* Sets the image that this cutter should cut in tile images.
* @param image the image that this cutter should cut
*/
public void setImage(BufferedImage image);
/**
* Retrieves the next tile image.
* @return the next tile image, or <code>null</code> when no more tile
* images are available
*/
public Image getNextTile();
/**
* Resets the tile cutter so that the next call to <code>getNextTile</code>
* will return the first tile.
*/
void reset();
/**
* Returns the default tile dimensions of tiles cut by this cutter.
* @return the default tile dimensions of tiles cut by this cutter.
*/
public Dimension getTileDimensions();
/**
* Returns the name of this tile cutter.
* @return the name of this tile cutter
*/
public String getName();
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2004-2006, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
* Copyright 2004-2006, Adam Turk <aturk@biggeruniverse.com>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.util;
import java.awt.image.RGBImageFilter;
/**
* This filter is used for filtering out a given "transparent" color from an
* image. Sometimes known as magic pink.
*/
public class TransparentImageFilter extends RGBImageFilter
{
int trans;
/**
* @param col the color to make transparent
*/
public TransparentImageFilter(int col) {
trans = col;
// The filter doesn't depend on pixel location
canFilterIndexColorModel = true;
}
/**
* Filters the given pixel. It returns a transparent pixel for pixels that
* match the transparency color, or the existing pixel for anything else.
*/
public int filterRGB(int x, int y, int rgb) {
if (rgb == trans) {
return 0;
} else {
return rgb;
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.view;
import tiled.core.TileLayer;
import java.awt.*;
/**
* An interface defining methods to render a map.
*/
public interface MapRenderer
{
/**
* Calculates the dimensions of the map this renderer applies to.
*
* @return the dimensions of the given map in pixels
*/
public Dimension getMapSize();
/**
* Paints the given tile layer, taking into account the clip rect of the
* given graphics context.
*
* @param g the graphics context to paint to
* @param layer the layer to paint
*/
public void paintTileLayer(Graphics2D g, TileLayer layer);
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
*
* This file is part of libtiled-java.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tiled.view;
import tiled.core.Map;
import tiled.core.Tile;
import tiled.core.TileLayer;
import java.awt.*;
/**
* The orthogonal map renderer. This is the most basic map renderer, dealing
* with maps that use rectangular tiles.
*/
public class OrthogonalRenderer implements MapRenderer
{
private final Map map;
public OrthogonalRenderer(Map map) {
this.map = map;
}
public Dimension getMapSize() {
return new Dimension(
map.getWidth() * map.getTileWidth(),
map.getHeight() * map.getTileHeight());
}
public void paintTileLayer(Graphics2D g, TileLayer layer) {
final Rectangle clip = g.getClipBounds();
final int tileWidth = map.getTileWidth();
final int tileHeight = map.getTileHeight();
final Rectangle bounds = layer.getBounds();
g.translate(bounds.x * tileWidth, bounds.y * tileHeight);
clip.translate(-bounds.x * tileWidth, -bounds.y * tileHeight);
clip.height += map.getTileHeightMax();
final int startX = Math.max(0, clip.x / tileWidth);
final int startY = Math.max(0, clip.y / tileHeight);
final int endX = Math.min(layer.getWidth(),
(int) Math.ceil(clip.getMaxX() / tileWidth));
final int endY = Math.min(layer.getHeight(),
(int) Math.ceil(clip.getMaxY() / tileHeight));
for (int x = startX; x < endX; ++x) {
for (int y = startY; y < endY; ++y) {
final Tile tile = layer.getTileAt(x, y);
if (tile == null)
continue;
final Image image = tile.getImage();
if (image == null)
continue;
g.drawImage(
image,
x * tileWidth,
(y + 1) * tileHeight - image.getHeight(null),
null);
}
}
g.translate(-bounds.x * tileWidth, -bounds.y * tileHeight);
}
}