All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.threerings.media.tile.TileSet Maven / Gradle / Ivy

The newest version!
//
// Nenya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// https://github.com/threerings/nenya
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.media.tile;

import java.lang.ref.SoftReference;

import java.util.Iterator;
import java.util.Map;

import java.io.Serializable;

import java.awt.Rectangle;
import java.awt.image.BufferedImage;

import com.google.common.collect.Maps;

import com.samskivert.util.Throttle;

import com.threerings.media.image.BufferedMirage;
import com.threerings.media.image.Colorization;
import com.threerings.media.image.ImageUtil;
import com.threerings.media.image.Mirage;
import com.threerings.media.tile.Tile.Key;

import static com.threerings.media.Log.log;

/**
 * A tileset stores information on a single logical set of tiles. It provides a clean interface for
 * the {@link TileManager} or other entities to retrieve individual tiles from the tile set and
 * encapsulates the potentially sophisticated process of extracting the tile image from a composite
 * tileset image.
 *
 * 

Tiles are referenced by their tile id. The tile id is essentially the tile number, assuming * the tile at the top-left of the image is tile id zero and tiles are numbered, in ascending * order, left to right, top to bottom. * *

This class is serializable and will be serialized, so derived classes should be sure to mark * non-persistent fields as transient. */ public abstract class TileSet implements Cloneable, Serializable { /** Used to assign colorizations to tiles that require them. */ public static interface Colorizer { /** * Returns the colorization to be used for the specified named colorization class. * * @param index the index of the colorization being requested in the tileset's colorization * list. */ public Colorization getColorization (int index, String zation); } /** * Configures this tileset with an image provider that it can use to load its tileset image. * This will be called automatically when the tileset is fetched via the {@link TileManager}. */ public void setImageProvider (ImageProvider improv) { _improv = improv; } /** * Returns the tileset name. */ public String getName () { return (_name == null) ? _imagePath : _name; } /** * Specifies the tileset name. */ public void setName (String name) { _name = name; } /** * Sets the path to the image that will be used by this tileset. This must be called before the * first call to {@link #getTile}. */ public void setImagePath (String imagePath) { _imagePath = imagePath; } /** * Returns the path to the composite image used by this tileset. */ public String getImagePath () { return _imagePath; } /** * Returns the number of tiles in the tileset. */ public abstract int getTileCount (); /** * Creates a copy of this tileset which will apply the supplied colorizations to its tileset * image when creating tiles. */ public TileSet clone (Colorization[] zations) { try { TileSet tset = (TileSet)clone(); tset._zations = zations; return tset; } catch (CloneNotSupportedException cnse) { log.warning("Unable to clone tileset prior to colorization", "tset", this, "zations", zations, "error", cnse); return null; } } /** * Returns a new tileset that is a clone of this tileset with the image path updated to * reference the given path. Useful for configuring a single tileset and then generating * additional tilesets with new images with the same configuration. */ public TileSet clone (String imagePath) throws CloneNotSupportedException { TileSet dup = (TileSet)clone(); dup.setImagePath(imagePath); return dup; } /** * Computes and fills in the bounds for the specified tile based on the mechanism used by the * derived class to do such things. The width and height of the bounds should be the size of * the tile image and the x and y offset should be the offset in the tileset image for the * image data of the specified tile. * * @param tileIndex the index of the tile whose bounds are to be computed. * @param bounds the rectangle object into which to fill the bounds. * * @return the rectangle passed into the bounds parameter. */ public abstract Rectangle computeTileBounds (int tileIndex, Rectangle bounds); /** * Equivalent to {@link #getTile(int,Colorizer)} with a null Colorizer argument. */ public Tile getTile (int tileIndex) { return getTile(tileIndex, _zations); } /** * Creates a {@link Tile} object from this tileset corresponding to the specified tile id and * returns that tile. A null tile will never be returned, but one with an error image may be * returned if a problem occurs loading the underlying tileset image. * * @param tileIndex the index of the tile in the tileset. Tile indexes start with zero as the * upper left tile and increase by one as the tiles move left to right and top to bottom over * the source image. * @param rizer an entity that will be used to obtain colorizations for tilesets that are * recolorizable. Passing null if the tileset is known not to be recolorizable is valid. * * @return the tile object. */ public Tile getTile (int tileIndex, Colorizer rizer) { return getTile(tileIndex, getColorizations(tileIndex, rizer)); } /** * Creates a {@link Tile} object from this tileset corresponding to the specified tile id and * returns that tile. A null tile will never be returned, but one with an error image may be * returned if a problem occurs loading the underlying tileset image. * * @param tileIndex the index of the tile in the tileset. Tile indexes start with zero as the * upper left tile and increase by one as the tiles move left to right and top to bottom over * the source image. * @param zations colorizations to be applied to the tile image prior to returning it. These * may be null for uncolorized images. * * @return the tile object. */ public Tile getTile (int tileIndex, Colorization[] zations) { Tile tile = null; // first look in the active set; if it's in use by anyone or in the cache, it will be in // the active set synchronized (_atiles) { _key.tileSet = this; _key.tileIndex = tileIndex; _key.zations = zations; SoftReference sref = _atiles.get(_key); if (sref != null) { tile = sref.get(); } } // if it's not in the active set, it's not in memory; so load it if (tile == null) { tile = createTile(); tile.key = new Tile.Key(this, tileIndex, zations); initTile(tile, tileIndex, zations); synchronized (_atiles) { _atiles.put(tile.key, new SoftReference(tile)); } } // periodically report our image cache performance reportCachePerformance(); return tile; } /** * Returns a prepared version of the image that would be used by the tile at the specified * index. Because tilesets are often used simply to provide access to a collection of uniform * images, this method is provided to bypass the creation of a {@link Tile} object when all * that is desired is access to the underlying image. */ public Mirage getTileMirage (int tileIndex) { return getTileMirage(tileIndex, getColorizations(tileIndex, null)); } /** * Returns prepared versions of the images that would be used for each of the tiles. Because * tilesets are often used simply to provide access to a collection of uniform images, this * method is provided to bypass the creation of a {@link Tile} objects when all that is * desired is access to the underlying images. */ public Mirage[] getTileMirages () { Mirage[] mirages = new Mirage[getTileCount()]; for (int ii = 0; ii < mirages.length; ii++) { mirages[ii] = getTileMirage(ii); } return mirages; } /** * Returns a prepared version of the image that would be used by the tile at the specified * index. Because tilesets are often used simply to provide access to a collection of uniform * images, this method is provided to bypass the creation of a {@link Tile} object when all * that is desired is access to the underlying image. */ public Mirage getTileMirage (int tileIndex, Colorization[] zations) { Rectangle bounds = computeTileBounds(tileIndex, new Rectangle()); Mirage mirage = null; if (checkTileIndex(tileIndex)) { if (_improv == null) { log.warning("Aiya! Tile set missing image provider", "path", _imagePath); } else { mirage = _improv.getTileImage(_imagePath, bounds, zations); } } if (mirage == null) { mirage = new BufferedMirage(ImageUtil.createErrorImage(bounds.width, bounds.height)); } return mirage; } /** * Returns the entire, raw, uncut, unprepared tileset source image. Don't use this method * unless you know what you're doing! This image should not be rendered directly to the screen, * you should obtain a tile ({@link #getTile}), or a tile mirage ({@link #getTileMirage}). */ public BufferedImage getRawTileSetImage () { return _improv.getTileSetImage(_imagePath, _zations); } /** * Returns the raw (unprepared) image that would be used by the tile at the specified * index. Don't use this method unless you know what you're doing! If you're going to be * painting this image onto the screen directly, use {@link #getTileMirage} because that * prepares the image for display. Only use this if you're going to do further processing and * prepare the subsequent image for display onscreen. */ public BufferedImage getRawTileImage (int tileIndex) { Rectangle bounds = computeTileBounds(tileIndex, new Rectangle()); BufferedImage img = null; if (checkTileIndex(tileIndex)) { BufferedImage timg = getRawTileSetImage(); if (timg != null) { img = timg.getSubimage(bounds.x, bounds.y, bounds.width, bounds.height); } else { log.warning("Missing source image " + this); } } if (img == null) { img = ImageUtil.createErrorImage(bounds.width, bounds.height); } return img; } /** * Returns colorizations for the specified tile image. The default is to return any * colorizations associated with the tileset via a call to {@link #clone(Colorization[])}, * however derived classes may have dynamic colorization policies that look up colorization * assignments from the supplied colorizer. */ protected Colorization[] getColorizations (int tileIndex, Colorizer rizer) { return _zations; } /** * Used to ensure that the specified tile index is valid. */ protected boolean checkTileIndex (int tileIndex) { int tcount = getTileCount(); if (tileIndex >= 0 && tileIndex < tcount) { return true; } else { log.warning("Requested invalid tile [tset=" + this + ", index=" + tileIndex + "].", new Exception()); return false; } } /** * Creates a blank tile of the appropriate type for this tileset. * * @return a blank tile ready to be populated with its image and metadata. */ protected Tile createTile () { return new Tile(); } /** * Initializes the supplied tile. Derived classes can override this method to add in their own * tile information, but should be sure to call super.initTile(). * * @param tile the tile to initialize. * @param tileIndex the index of the tile. * @param zations the colorizations to be used when generating the tile image. */ protected void initTile (Tile tile, int tileIndex, Colorization[] zations) { if (_improv != null) { tile.setImage(getTileMirage(tileIndex, zations)); } } @Override public String toString () { StringBuilder buf = new StringBuilder("["); toString(buf); return buf.append("]").toString(); } /** * Reports statistics detailing the image manager cache performance and the current size of the * cached images. */ protected void reportCachePerformance () { if (/* Log.getLevel() != Log.log.DEBUG || */ _improv == null || _cacheStatThrottle.throttleOp()) { return; } // compute our estimated memory usage long amem = 0; int asize = 0; synchronized (_atiles) { // first total up the active tiles Iterator> iter = _atiles.values().iterator(); while (iter.hasNext()) { SoftReference sref = iter.next(); Tile tile = sref.get(); if (tile != null) { asize++; amem += tile.getEstimatedMemoryUsage(); } } } log.info("Tile caches", "amem", (amem / 1024) + "k", "tmem", (Tile._totalTileMemory / 1024) + "k", "seen", _atiles.size(), "asize", + asize); } /** * Derived classes can override this, calling super.toString(buf) and then * appending additional information to the buffer. */ protected void toString (StringBuilder buf) { buf.append("name=").append(_name); buf.append(", path=").append(_imagePath); } /** The path to the file containing the tile images. */ protected String _imagePath; /** The tileset name. */ protected String _name; /** Colorizations to be applied to tiles created from this tileset. */ protected transient Colorization[] _zations; /** The entity from which we obtain our tile image. */ protected transient ImageProvider _improv; /** Increase this value when object's serialized state is impacted by * a class change (modification of fields, inheritance). */ private static final long serialVersionUID = 1; /** A map containing weak references to all "active" tiles. */ protected static Map> _atiles = Maps.newHashMap(); /** A key used to look things up in the cache without creating craploads of keys unduly. */ protected static Tile.Key _key = new Tile.Key(null, 0, null); /** Throttle our cache status logging to once every 300 seconds. */ protected static Throttle _cacheStatThrottle = new Throttle(1, 300000L); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy