/*
* Tiled Map Editor, (c) 2004-2006
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Adam Turk <aturk@biggeruniverse.com>
* Bjorn Lindeijer <bjorn@lindeijer.nl>
*/
package com.onpositive.mapper.old;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Properties;
import java.util.Vector;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import tiled.core.Tile;
import tiled.core.TileSet;
import tiled.core.TilesetChangeListener;
import tiled.core.TilesetChangedEvent;
import tiled.mapeditor.util.cutter.BasicTileCutter;
import tiled.mapeditor.util.cutter.TileCutter;
import tiled.util.NumberedSet;
/**
* 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 TileSetOld
{
private String base;
private NumberedSet tiles, images;
private int firstGid;
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 RGB transparentColor;
// private RGB transparentColor = new RGB(255,255,255);
private Properties defaultTileProperties;
private Image tileSetImage;
private LinkedList<TilesetChangeListener> tilesetChangeListeners;
private HashMap<Image,Image> transparentImageCache = new HashMap();
private Tile whiteTile;
/**
* Default constructor
*/
public TileSet() {
tiles = new NumberedSet();
images = new NumberedSet();
tileDimensions = new Rectangle(0,0,0,0);
defaultTileProperties = new Properties();
tilesetChangeListeners = new LinkedList();
whiteTile = new Tile();
whiteTile.setImage(new Image(Display.getDefault(),new Rectangle(0,0,32,32)));
}
/**
* 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
{
this.tileCutter = cutter;
setTilesetImageFilename(imgFilename);
Image image;
try {
ImageData imageData = new ImageData(imgFilename);
if (transparentColor != null) {
// imageData.transparentPixel = imageData.palette.getPixel(transparentColor);
}
image = new Image(Display.getDefault(), imageData);
} catch (Exception e) {
image = new Image(Display.getDefault(), new Rectangle(0,0,16,16));
}
Image buffered = new Image(Display.getDefault(),image,SWT.IMAGE_COPY);
importTileBitmap(buffered, cutter);
}
/**
* Creates a tileset from a buffered image. Tiles are cut by the passed
* cutter.
*
* @param tilebmp the image to be used, must not be null
* @param cutter the tile cutter, must not be null
*/
private void importTileBitmap(Image tilebmp, TileCutter cutter)
{
assert tilebmp != null;
assert cutter != null;
tileCutter = cutter;
tileSetImage = tilebmp;
cutter.setImage(tilebmp);
Point size = cutter.getTileDimensions();
tileDimensions = new Rectangle(0,0,size.x,size.y);
if (cutter instanceof BasicTileCutter) {
BasicTileCutter basicTileCutter = (BasicTileCutter) cutter;
tileSpacing = basicTileCutter.getTileSpacing();
tileMargin = basicTileCutter.getTileMargin();
tilesPerRow = basicTileCutter.getTilesPerRow();
}
Image tile = cutter.getNextTile();
while (tile != null) {
Tile newTile = new Tile();
newTile.setImage(addImage(tile));
addNewTile(newTile);
tile = 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();
ImageData data = new ImageData(imgFilename);
if (transparentColor != null) {
data.transparentPixel = transparentColor.red << 16 + transparentColor.green << 8 + transparentColor.blue;
}
Image image = new Image(Display.getDefault(), data);
Rectangle origBounds = image.getBounds();
Image buffered = new Image(Display.getDefault(),origBounds.width,origBounds.height);
GC gc = new GC(buffered);
gc.drawImage(image,0,0);
gc.dispose();
refreshImportedTileBitmap(buffered);
}
/**
* Refreshes a tileset from a buffered image. Tiles are cut by the passed
* cutter.
*
* @param tilebmp the image to be used, must not be null
*/
private void refreshImportedTileBitmap(Image tilebmp) {
assert tilebmp != null;
tileCutter.reset();
tileCutter.setImage(tilebmp);
tileSetImage = tilebmp;
Point size = tileCutter.getTileDimensions();
tileDimensions = new Rectangle(0,0,size.x,size.y);
int id = 0;
Image tile = tileCutter.getNextTile();
while (tile != null) {
int imgId = getTile(id).tileImageId;
overlayImage(imgId, tile);
tile = tileCutter.getNextTile();
id++;
}
fireTilesetChanged();
}
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) {
String oldSource = externalSource;
externalSource = source;
fireSourceChanged(oldSource, 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 first global id used by this tileset.
*
* @param firstGid first global id
*/
public void setFirstGid(int firstGid) {
this.firstGid = firstGid;
}
/**
* Sets the name of this tileset.
*
* @param name the new name for this tileset
*/
public void setName(String name) {
String oldName = this.name;
this.name = name;
fireNameChanged(oldName, name);
}
/**
* Sets the transparent color in the tileset image.
*
* @param color
*/
public void setTransparentColor(RGB 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.getMaxId() + 1);
}
if (tileDimensions.width < t.getWidth()) {
tileDimensions.width = t.getWidth();
}
if (tileDimensions.height < t.getHeight()) {
tileDimensions.height = t.getHeight();
}
// Add any default properties
// TODO: use parent properties instead?
t.getProperties().putAll(defaultTileProperties);
tiles.put(t.getId(), t);
t.setTileSet(this);
fireTilesetChanged();
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>.
*
* todo: Fix the behaviour of this function? It actually does seem to
* todo: invalidate other tile indices due to implementation of
* todo: NumberedSet.
*
* @param i the index to remove
*/
public void removeTile(int i) {
tiles.remove(i);
fireTilesetChanged();
}
/**
* 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.getMaxId();
}
/**
* Returns an iterator over the tiles in this tileset.
*
* @return an iterator over the tiles in this tileset.
*/
public Iterator iterator() {
return tiles.iterator();
}
/**
* Generates a vector that removes the gaps that can occur if a tile is
* removed from the middle of a set of tiles. (Maps tiles contiguously)
*
* @return a {@link Vector} mapping ordered set location to the next
* non-null tile
*/
public Vector<Tile> generateGaplessVector() {
Vector<Tile> gapless = new Vector<Tile>();
for (int i = 0; i <= getMaxTileId(); i++) {
if (getTile(i) != null) gapless.add(getTile(i));
}
return gapless;
}
/**
* 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) {
if (tiles.size() <= i) {
if (whiteTile.getImage() == null) {
whiteTile.setImage(new Image(Display.getDefault(),tileDimensions));
}
return whiteTile;
}
try {
return (Tile) 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) {
}
}
return null;
}
/**
* Returns the first global id connected to this tileset.
*
* @return first global id
*/
public int getFirstGid() {
return firstGid;
}
/**
* @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 RGB getTransparentColor() {
return transparentColor;
}
/**
* @return the name of the tileset, and the total tiles
*/
public String toString() {
return getName() + " [" + size() + "]";
}
/**
* Returns the number of images in the set.
*
* @return the number of images in the set
*/
public int getTotalImages() {
return images.size();
}
/**
* @return an Enumeration of the image ids
*/
public Enumeration<String> getImageIds() {
Vector<String> v = new Vector();
for (int id = 0; id <= images.getMaxId(); ++id) {
if (images.containsId(id)) {
v.add(Integer.toString(id));
}
}
return v.elements();
}
// TILE IMAGE CODE
/**
* This function uses the CRC32 checksums to find the cached version of the
* image supplied.
*
* @param i an Image object
* @return returns the id of the given image, or -1 if the image is not in
* the set
*/
public int getIdByImage(Image i) {
return images.indexOf(i);
}
/**
* @param id
* @return the image identified by the key, or <code>null</code> when
* there is no such image
*/
public Image getImageById(int id) {
if (transparentColor != null)
return getTransparentImage((Image) images.get(id));
return (Image) images.get(id);
}
private Image getTransparentImage(Image image) {
if (image.getImageData().transparentPixel != -1)
return image;
Image result = transparentImageCache.get(image);
if (result == null) {
ImageData imageData = image.getImageData();
imageData.transparentPixel = imageData.palette.getPixel(transparentColor);
result = new Image(image.getDevice(),imageData);
transparentImageCache.put(image,result);
}
return result;
}
/**
* Overlays the image in the set referred to by the given key.
*
* @param id
* @param i
*/
public void overlayImage(int id, org.eclipse.swt.graphics.Image i) {
images.put(id, i);
}
/**
* Returns the dimensions of an image as specified by the id.
*
* @deprecated Unless somebody can explain the purpose of this function in
* its documentation, I consider this function deprecated. It
* is only used by tiles, but they should in my opinion just
* use their "internalImage". - Bjorn
* @param id the image id
* @return dimensions of image with referenced by given key
*/
public Point getImageDimensions(int id) {
Image img = (Image) images.get(id);
if (img != null) {
Rectangle bounds = img.getBounds();
return new Point(bounds.width, bounds.height);
} else {
return new Point(0, 0);
}
}
/**
* Adds the specified image to the image cache. If the image already exists
* in the cache, returns the id of the existing image. If it does not
* exist, this function adds the image and returns the new id.
*
* @param image the java.awt.Image to add to the image cache
* @return the id as an <code>int</code> of the image in the cache
*/
public int addImage(Image image) {
return images.findOrAdd(image);
}
public int addImage(Image image, int id) {
return images.put(id, image);
}
public void removeImage(int id) {
images.remove(id);
}
/**
* Returns whether the tileset is derived from a tileset image.
*
* @return tileSetImage != null
*/
public boolean isSetFromImage() {
return tileSetImage != null;
}
/**
* Checks whether each image has a one to one relationship with the tiles.
*
* @deprecated
* @return <code>true</code> if each image is associated with one and only
* one tile, <code>false</code> otherwise.
*/
public boolean isOneForOne() {
Iterator itr = iterator();
//[ATURK] I don't think that this check makes complete sense...
/*
while (itr.hasNext()) {
Tile t = (Tile)itr.next();
if (t.countAnimationFrames() != 1 || t.getImageId() != t.getId()
|| t.getImageOrientation() != 0) {
return false;
}
}
*/
for (int id = 0; id <= images.getMaxId(); ++id) {
int relations = 0;
itr = iterator();
while (itr.hasNext()) {
Tile t = (Tile) itr.next();
// todo: move the null check back into the iterator?
if (t != null && t.getImageId() == id) {
relations++;
}
}
if (relations != 1) {
return false;
}
}
return true;
}
public void setDefaultProperties(Properties defaultSetProperties) {
defaultTileProperties = defaultSetProperties;
}
public void addTilesetChangeListener(TilesetChangeListener listener) {
tilesetChangeListeners.add(listener);
}
public void removeTilesetChangeListener(TilesetChangeListener listener) {
tilesetChangeListeners.remove(listener);
}
private void fireTilesetChanged() {
TilesetChangedEvent event = new TilesetChangedEvent(this);
for (TilesetChangeListener listener : tilesetChangeListeners) {
listener.tilesetChanged(event);
}
}
private void fireNameChanged(String oldName, String newName) {
TilesetChangedEvent event = new TilesetChangedEvent(this);
for (TilesetChangeListener listener : tilesetChangeListeners) {
listener.nameChanged(event, oldName, newName);
}
}
private void fireSourceChanged(String oldSource, String newSource) {
TilesetChangedEvent event = new TilesetChangedEvent(this);
for (TilesetChangeListener listener : tilesetChangeListeners) {
listener.sourceChanged(event, oldSource, newSource);
}
}
/**
* Sets the tile spacing. Should be used only for tileset persisting properties.
* For actual tileset import use <code>importTileBitmap(Image, {@link TileCutter});
* @param tileSpacing
*/
public void setTileSpacing(int tileSpacing) {
this.tileSpacing = tileSpacing;
}
/**
* Sets the tile margin. Should be used only for tileset persisting properties.
* For actual tileset import use <code>importTileBitmap(Image, {@link TileCutter});
* @param tileSpacing
*/
public void setTileMargin(int tileMargin) {
this.tileMargin = tileMargin;
}
}