/*
* $Id$
*
* Copyright (c) 2007 by Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.tools.imageop;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* The abstract base class for {@link ImageOp}s which support multiple tiles.
*
* @since 3.1.0
* @author Joel Uckelman
*/
public abstract class AbstractTiledOpImpl extends AbstractOpImpl {
private static final Dimension DEFAULT_TILE_SIZE = new Dimension(256, 256);
/** The standard size of this <code>ImageOp</code>s tiles. */
protected Dimension tileSize;
/** The number of tiles along the x-axis. */
protected int numXTiles;
/** The number of tiles along the y-axis. */
protected int numYTiles;
/** The tiles already created, stored as <code>y*numXTiles + x</code>. */
protected ImageOp[] tiles;
/**
* Sets the <code>tileSize</code> which is used by {@link getTileSize},
* {@link getTileHeight}, {@link getTileWidth}, {@link getNumXTiles},
* {@link getNumYTiles}, and all other tile methods.
*/
protected void fixTileSize() {
if (size == null) fixSize();
tileSize = DEFAULT_TILE_SIZE;
numXTiles = (int) Math.ceil((double)size.width/tileSize.width);
numYTiles = (int) Math.ceil((double)size.height/tileSize.height);
tiles = new ImageOp[numXTiles*numYTiles];
}
/** {@inheritDoc} */
public Dimension getTileSize() {
if (tileSize == null) fixTileSize();
return new Dimension(tileSize);
}
/** {@inheritDoc} */
public int getTileHeight() {
if (tileSize == null) fixTileSize();
return tileSize.height;
}
/** {@inheritDoc} */
public int getTileWidth() {
if (tileSize == null) fixTileSize();
return tileSize.width;
}
/** {@inheritDoc} */
public int getNumXTiles() {
if (tileSize == null) fixTileSize();
return numXTiles;
}
/** {@inheritDoc} */
public int getNumYTiles() {
if (tileSize == null) fixTileSize();
return numYTiles;
}
/**
* Returns the <code>ImageOp</code> which produces tile
* <code>(tileX,tileY)</code>, creating it if necessary.
*
* @return the <code>ImageOp</code> for tile <code>(tileX,tileY)</code>
*/
public ImageOp getTileOp(int tileX, int tileY) {
ImageOp top = tiles[tileY*numXTiles + tileX];
if (top == null) {
top = tiles[tileY*numXTiles + tileX] = createTileOp(tileX, tileY);
}
return top;
}
protected abstract ImageOp createTileOp(int tileX, int tileY);
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException unless {@code 0 <= tileX < numXTiles}
* and {@code 0 <= tileY < numYTiles}.
*/
public BufferedImage getTile(int tileX, int tileY, ImageOpObserver obs)
throws CancellationException, InterruptedException, ExecutionException
{
if (tileX < 0 || tileX >= numXTiles ||
tileY < 0 || tileY >= numYTiles)
throw new IndexOutOfBoundsException();
return getTileOp(tileX, tileY).getImage(obs);
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException unless {@code 0 <= tileX < numXTiles}
* and {@code 0 <= tileY < numYTiles}.
*/
public Future<BufferedImage> getFutureTile(
int tileX, int tileY, ImageOpObserver obs) throws ExecutionException
{
if (tileX < 0 || tileX >= numXTiles ||
tileY < 0 || tileY >= numYTiles)
throw new IndexOutOfBoundsException();
return getTileOp(tileX, tileY).getFutureImage(obs);
}
/**
* {@inheritDoc}
*
* @throws IllegalArgumentException if <code>rect == null</code>.
*/
public Point[] getTileIndices(Rectangle rect) {
if (rect == null) throw new IllegalArgumentException();
if (size == null || tileSize == null) fixTileSize();
// FIXME: maybe do this without creating new Rectangles
rect = rect.intersection(new Rectangle(size));
if (rect.isEmpty()) {
return new Point[0];
}
final int minTileX = rect.x/tileSize.width;
final int minTileY = rect.y/tileSize.height;
final int maxTileX = (rect.x + rect.width - 1)/tileSize.width;
final int maxTileY = (rect.y + rect.height - 1)/tileSize.height;
final Point[] tilesInRect =
new Point[(maxTileX-minTileX+1)*(maxTileY-minTileY+1)];
// FIXME: Maybe do this by keeping a MRU cache of Points.
// Maybe not, profiling shows that this isn't causing the gc to run much.
int offset = 0;
for (int ty = minTileY; ty <= maxTileY; ++ty) {
for (int tx = minTileX; tx <= maxTileX; ++tx) {
tilesInRect[offset++] = new Point(tx,ty);
}
}
return tilesInRect;
}
}