Support fancier packing of trimmed tilesets.
By default, it still does the same thing, but now you can pass in a fancier packer. Provided a SLIGHTLY fancier one that's still pretty dumb and wasteful, but allows row wrapping instead of gluing everything together into one super wide image; I've got some that otherwise wind up SO insanely wide that it causes problems decoding them.
This commit is contained in:
committed by
Michael Bayne
parent
bce83d858e
commit
ddf07dc905
@@ -178,6 +178,16 @@ public class TrimmedObjectTileSet extends TileSet
|
|||||||
return trimObjectTileSet(source, destImage, FastImageIO.FILE_SUFFIX);
|
return trimObjectTileSet(source, destImage, FastImageIO.FILE_SUFFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to trim the tile set to a file using the simplest packer.
|
||||||
|
*/
|
||||||
|
public static TrimmedObjectTileSet trimObjectTileSet (
|
||||||
|
ObjectTileSet source, OutputStream destImage, String imgFormat)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
return trimObjectTileSet (source, destImage, imgFormat, new TileSetTrimmer.StripPacker());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a trimmed object tileset from the supplied source object tileset. The image path
|
* Creates a trimmed object tileset from the supplied source object tileset. The image path
|
||||||
* must be set by hand to the appropriate path based on where the image data that is written to
|
* must be set by hand to the appropriate path based on where the image data that is written to
|
||||||
@@ -186,7 +196,8 @@ public class TrimmedObjectTileSet extends TileSet
|
|||||||
* information.
|
* information.
|
||||||
*/
|
*/
|
||||||
public static TrimmedObjectTileSet trimObjectTileSet (
|
public static TrimmedObjectTileSet trimObjectTileSet (
|
||||||
ObjectTileSet source, OutputStream destImage, String imgFormat)
|
ObjectTileSet source, OutputStream destImage, String imgFormat,
|
||||||
|
TileSetTrimmer.Packer packer)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
final TrimmedObjectTileSet tset = new TrimmedObjectTileSet();
|
final TrimmedObjectTileSet tset = new TrimmedObjectTileSet();
|
||||||
@@ -247,7 +258,7 @@ public class TrimmedObjectTileSet extends TileSet
|
|||||||
tset._bounds[tileIndex] = new Rectangle(imageX, imageY, trimWidth, trimHeight);
|
tset._bounds[tileIndex] = new Rectangle(imageX, imageY, trimWidth, trimHeight);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
TileSetTrimmer.trimTileSet(source, destImage, tmr, imgFormat);
|
TileSetTrimmer.trimTileSet(source, destImage, tmr, imgFormat, packer);
|
||||||
|
|
||||||
// Log.info("Trimmed object tileset [bounds=" + StringUtil.toString(tset._bounds) +
|
// Log.info("Trimmed object tileset [bounds=" + StringUtil.toString(tset._bounds) +
|
||||||
// ", metrics=" + StringUtil.toString(tset._ometrics) + "].");
|
// ", metrics=" + StringUtil.toString(tset._ometrics) + "].");
|
||||||
|
|||||||
@@ -70,6 +70,16 @@ public class TrimmedTileSet extends TileSet
|
|||||||
return trimTileSet(source, destImage, FastImageIO.FILE_SUFFIX);
|
return trimTileSet(source, destImage, FastImageIO.FILE_SUFFIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to trim the tile set to a file using the simplest packer.
|
||||||
|
*/
|
||||||
|
public static TrimmedTileSet trimTileSet (
|
||||||
|
TileSet source, OutputStream destImage, String imgFormat)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
return trimTileSet(source, destImage, imgFormat, new TileSetTrimmer.StripPacker());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a trimmed tileset from the supplied source tileset. The image path must be set by
|
* Creates a trimmed tileset from the supplied source tileset. The image path must be set by
|
||||||
* hand to the appropriate path based on where the image data that is written to the
|
* hand to the appropriate path based on where the image data that is written to the
|
||||||
@@ -77,8 +87,8 @@ public class TrimmedTileSet extends TileSet
|
|||||||
* indicateds how the resulting image should be saved. If null, we save using FastImageIO
|
* indicateds how the resulting image should be saved. If null, we save using FastImageIO
|
||||||
* See {@link TileSetTrimmer#trimTileSet} for further information.
|
* See {@link TileSetTrimmer#trimTileSet} for further information.
|
||||||
*/
|
*/
|
||||||
public static TrimmedTileSet trimTileSet (TileSet source, OutputStream destImage,
|
public static TrimmedTileSet trimTileSet (
|
||||||
String imgFormat)
|
TileSet source, OutputStream destImage, String imgFormat, TileSetTrimmer.Packer packer)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
final TrimmedTileSet tset = new TrimmedTileSet();
|
final TrimmedTileSet tset = new TrimmedTileSet();
|
||||||
@@ -101,7 +111,7 @@ public class TrimmedTileSet extends TileSet
|
|||||||
tset._obounds[tileIndex] = new Rectangle(imageX, imageY, trimWidth, trimHeight);
|
tset._obounds[tileIndex] = new Rectangle(imageX, imageY, trimWidth, trimHeight);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
TileSetTrimmer.trimTileSet(source, destImage, tmr, imgFormat);
|
TileSetTrimmer.trimTileSet(source, destImage, tmr, imgFormat, packer);
|
||||||
|
|
||||||
return tset;
|
return tset;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,17 +19,27 @@
|
|||||||
|
|
||||||
package com.threerings.media.tile.util;
|
package com.threerings.media.tile.util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.awt.Dimension;
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.geom.Area;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.Raster;
|
import java.awt.image.Raster;
|
||||||
import java.awt.image.RasterFormatException;
|
import java.awt.image.RasterFormatException;
|
||||||
import java.awt.image.WritableRaster;
|
import java.awt.image.WritableRaster;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import com.google.common.collect.ComparisonChain;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
import com.threerings.resource.FastImageIO;
|
import com.threerings.resource.FastImageIO;
|
||||||
|
|
||||||
import com.threerings.media.image.ImageUtil;
|
import com.threerings.media.image.ImageUtil;
|
||||||
@@ -40,7 +50,7 @@ import com.threerings.media.tile.TileSet;
|
|||||||
* which means that each tile is converted to an image that contains the
|
* which means that each tile is converted to an image that contains the
|
||||||
* smallest rectangular region of the original image that contains all
|
* smallest rectangular region of the original image that contains all
|
||||||
* non-transparent pixels. These trimmed images are then written out to a
|
* non-transparent pixels. These trimmed images are then written out to a
|
||||||
* single image, packed together left to right.
|
* single image, packed together.
|
||||||
*/
|
*/
|
||||||
public class TileSetTrimmer
|
public class TileSetTrimmer
|
||||||
{
|
{
|
||||||
@@ -70,14 +80,29 @@ public class TileSetTrimmer
|
|||||||
int trimWidth, int trimHeight);
|
int trimWidth, int trimHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to pack the trimmed tiles into the final image.
|
||||||
|
*/
|
||||||
|
public static interface Packer
|
||||||
|
{
|
||||||
|
/** Add trimmed bounds for a tile. */
|
||||||
|
void addTile (int tileIndex, int width, int height);
|
||||||
|
|
||||||
|
/** Do the deed and return dimensions of the packaged layout. */
|
||||||
|
Dimension pack ();
|
||||||
|
|
||||||
|
/** Get the packed bounds of the given tile. */
|
||||||
|
Rectangle getPosition (int tileIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience function to trim the tile set using FastImageIO to save the result.
|
* Convenience function to trim the tile set using FastImageIO to save the result.
|
||||||
*/
|
*/
|
||||||
public static void trimTileSet (
|
public static void trimTileSet (
|
||||||
TileSet source, OutputStream destImage, TrimMetricsReceiver tmr)
|
TileSet source, OutputStream destImage, TrimMetricsReceiver tmr, Packer packer)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
trimTileSet(source, destImage, tmr, FastImageIO.FILE_SUFFIX);
|
trimTileSet(source, destImage, tmr, FastImageIO.FILE_SUFFIX, packer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -95,7 +120,8 @@ public class TileSetTrimmer
|
|||||||
* @param imgFormat the format in which to write the image file - or if null, use FastImageIO.
|
* @param imgFormat the format in which to write the image file - or if null, use FastImageIO.
|
||||||
*/
|
*/
|
||||||
public static void trimTileSet (
|
public static void trimTileSet (
|
||||||
TileSet source, OutputStream destImage, TrimMetricsReceiver tmr, String imgFormat)
|
TileSet source, OutputStream destImage, TrimMetricsReceiver tmr, String imgFormat,
|
||||||
|
Packer packer)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
int tcount = source.getTileCount();
|
int tcount = source.getTileCount();
|
||||||
@@ -106,50 +132,50 @@ public class TileSetTrimmer
|
|||||||
Rectangle[] tbounds = new Rectangle[tcount];
|
Rectangle[] tbounds = new Rectangle[tcount];
|
||||||
|
|
||||||
// compute some tile metrics
|
// compute some tile metrics
|
||||||
int nextx = 0, maxy = 0;
|
|
||||||
for (int ii = 0; ii < tcount; ii++) {
|
for (int ii = 0; ii < tcount; ii++) {
|
||||||
// extract the image from the original tileset
|
// extract the image from the original tileset
|
||||||
try {
|
try {
|
||||||
timgs[ii] = source.getRawTileImage(ii);
|
timgs[ii] = source.getRawTileImage(ii);
|
||||||
} catch (RasterFormatException rfe) {
|
} catch (RasterFormatException rfe) {
|
||||||
throw new IOException("Failed to get tile image " +
|
throw new IOException("Failed to get tile image " +
|
||||||
"[tidx=" + ii + ", tset=" + source +
|
"[tidx=" + ii + ", tset=" + source + ", rfe=" + rfe + "].");
|
||||||
", rfe=" + rfe + "].");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// figure out how tightly we can trim it
|
// figure out how tightly we can trim it
|
||||||
tbounds[ii] = new Rectangle();
|
tbounds[ii] = new Rectangle();
|
||||||
ImageUtil.computeTrimmedBounds(timgs[ii], tbounds[ii]);
|
ImageUtil.computeTrimmedBounds(timgs[ii], tbounds[ii]);
|
||||||
|
|
||||||
// let our caller know what we did
|
packer.addTile(ii, tbounds[ii].width, tbounds[ii].height);
|
||||||
tmr.trimmedTile(ii, nextx, 0, tbounds[ii].x, tbounds[ii].y,
|
}
|
||||||
tbounds[ii].width, tbounds[ii].height);
|
|
||||||
|
|
||||||
// adjust the new tileset image dimensions
|
Dimension bounds = packer.pack();
|
||||||
maxy = Math.max(maxy, tbounds[ii].height);
|
|
||||||
nextx += tbounds[ii].width;
|
for (int ii = 0; ii < tcount; ii++) {
|
||||||
|
// let our caller know what we did
|
||||||
|
Rectangle rect = packer.getPosition(ii);
|
||||||
|
tmr.trimmedTile(ii, rect.x, rect.y, tbounds[ii].x, tbounds[ii].y,
|
||||||
|
tbounds[ii].width, tbounds[ii].height);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the new tileset image
|
// create the new tileset image
|
||||||
BufferedImage image = null;
|
BufferedImage image = null;
|
||||||
try {
|
try {
|
||||||
image = ImageUtil.createCompatibleImage(source.getRawTileSetImage(), nextx, maxy);
|
image = ImageUtil.createCompatibleImage(source.getRawTileSetImage(),
|
||||||
|
bounds.width, bounds.height);
|
||||||
|
|
||||||
} catch (RasterFormatException rfe) {
|
} catch (RasterFormatException rfe) {
|
||||||
throw new IOException("Failed to create trimmed tileset image " +
|
throw new IOException("Failed to create trimmed tileset image " +
|
||||||
"[wid=" + nextx + ", hei=" + maxy +
|
"[bounds=" + bounds + ", tset=" + source + ", rfe=" + rfe + "].");
|
||||||
", tset=" + source + ", rfe=" + rfe + "].");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy the tile data
|
// copy the tile data
|
||||||
WritableRaster drast = image.getRaster();
|
WritableRaster drast = image.getRaster();
|
||||||
int xoff = 0;
|
|
||||||
for (int ii = 0; ii < tcount; ii++) {
|
for (int ii = 0; ii < tcount; ii++) {
|
||||||
|
Rectangle pos = packer.getPosition(ii);
|
||||||
Rectangle tb = tbounds[ii];
|
Rectangle tb = tbounds[ii];
|
||||||
Raster srast = timgs[ii].getRaster().createChild(
|
Raster srast = timgs[ii].getRaster().createChild(
|
||||||
tb.x, tb.y, tb.width, tb.height, 0, 0, null);
|
tb.x, tb.y, tb.width, tb.height, 0, 0, null);
|
||||||
drast.setRect(xoff, 0, srast);
|
drast.setRect(pos.x, pos.y, srast);
|
||||||
xoff += tb.width;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destImage != null) {
|
if (destImage != null) {
|
||||||
@@ -161,4 +187,99 @@ public class TileSetTrimmer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Includes some boilerplate to handle bits of the packing process. */
|
||||||
|
public static abstract class BasePacker implements Packer
|
||||||
|
{
|
||||||
|
public void addTile (int tileIndex, int width, int height) {
|
||||||
|
if (_packed) {
|
||||||
|
throw new IllegalStateException("Cannot add tile after packing.");
|
||||||
|
}
|
||||||
|
_tiles.put(tileIndex, new Rectangle(0, 0, width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
final public Dimension pack () {
|
||||||
|
if (_packed) {
|
||||||
|
throw new IllegalStateException("Cannot pack repeatedly.");
|
||||||
|
}
|
||||||
|
if (_tiles.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Cannot pack empty tileset");
|
||||||
|
}
|
||||||
|
|
||||||
|
_packed = true;
|
||||||
|
return doPack();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Dimension doPack ();
|
||||||
|
|
||||||
|
public Rectangle getPosition (int tileIndex) {
|
||||||
|
if (!_packed) {
|
||||||
|
throw new IllegalStateException("Cannot get tile position before packing.");
|
||||||
|
}
|
||||||
|
return _tiles.get(tileIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Map<Integer, Rectangle> _tiles = Maps.newHashMap();
|
||||||
|
protected boolean _packed;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packs the tiles together in a wide strip in tile order.
|
||||||
|
*/
|
||||||
|
public static class StripPacker extends RowPacker
|
||||||
|
{
|
||||||
|
public StripPacker () {
|
||||||
|
super(Integer.MAX_VALUE, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packs into rows of the given maximum width, optionally shuffling tiles around to help
|
||||||
|
* things pack a little less wastefully if the tiles vary in height.
|
||||||
|
*/
|
||||||
|
public static class RowPacker extends BasePacker
|
||||||
|
{
|
||||||
|
public final int maxWidth;
|
||||||
|
public final boolean sort;
|
||||||
|
|
||||||
|
public RowPacker (int maxWidth, boolean sort) {
|
||||||
|
this.maxWidth = maxWidth;
|
||||||
|
this.sort = sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected Dimension doPack () {
|
||||||
|
Dimension dim = new Dimension(0, 0);
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
|
||||||
|
Collection<Rectangle> rects = _tiles.values();
|
||||||
|
if (sort) {
|
||||||
|
rects = Lists.newArrayList(_tiles.values());
|
||||||
|
Collections.sort((List<Rectangle>)rects, new Comparator<Rectangle>() {
|
||||||
|
@Override public int compare (Rectangle a, Rectangle b) {
|
||||||
|
return ComparisonChain.start().
|
||||||
|
compare(b.height, a.height).
|
||||||
|
compare(b.width, a.width).
|
||||||
|
result();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Rectangle rect : rects) {
|
||||||
|
if (x + rect.width > maxWidth) {
|
||||||
|
x = 0;
|
||||||
|
y = dim.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect.x = x;
|
||||||
|
x += rect.width;
|
||||||
|
rect.y = y;
|
||||||
|
|
||||||
|
dim.height = Math.max(dim.height, y + rect.height);
|
||||||
|
dim.width = Math.max(dim.width, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dim;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import java.io.OutputStream;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
import net.sf.json.JSONArray;
|
import net.sf.json.JSONArray;
|
||||||
@@ -56,6 +57,7 @@ import com.threerings.media.tile.TrimmedObjectTileSet;
|
|||||||
import com.threerings.media.tile.bundle.BundleUtil;
|
import com.threerings.media.tile.bundle.BundleUtil;
|
||||||
import com.threerings.media.tile.bundle.TileSetBundle;
|
import com.threerings.media.tile.bundle.TileSetBundle;
|
||||||
import com.threerings.media.tile.tools.xml.TileSetRuleSet;
|
import com.threerings.media.tile.tools.xml.TileSetRuleSet;
|
||||||
|
import com.threerings.media.tile.util.TileSetTrimmer;
|
||||||
|
|
||||||
import static com.threerings.media.Log.log;
|
import static com.threerings.media.Log.log;
|
||||||
|
|
||||||
@@ -153,6 +155,15 @@ public class TileSetBundler
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Packer to use when trimming.
|
||||||
|
*/
|
||||||
|
public Writer usePacker (Supplier<TileSetTrimmer.Packer> packer)
|
||||||
|
{
|
||||||
|
this.packer = packer;
|
||||||
|
return trimImages(packer != null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether or not we write out raw images.
|
* Sets whether or not we write out raw images.
|
||||||
*/
|
*/
|
||||||
@@ -194,6 +205,7 @@ public class TileSetBundler
|
|||||||
|
|
||||||
boolean trim = true;
|
boolean trim = true;
|
||||||
boolean raw = true;
|
boolean raw = true;
|
||||||
|
Supplier<TileSetTrimmer.Packer> packer;
|
||||||
String imageBase;
|
String imageBase;
|
||||||
JSONConversion.Config json;
|
JSONConversion.Config json;
|
||||||
}
|
}
|
||||||
@@ -429,7 +441,7 @@ public class TileSetBundler
|
|||||||
// write the trimmed tileset image to the target file
|
// write the trimmed tileset image to the target file
|
||||||
TrimmedObjectTileSet tset =
|
TrimmedObjectTileSet tset =
|
||||||
TrimmedObjectTileSet.trimObjectTileSet((ObjectTileSet)set, dest,
|
TrimmedObjectTileSet.trimObjectTileSet((ObjectTileSet)set, dest,
|
||||||
target.raw ? FastImageIO.FILE_SUFFIX : "png");
|
target.raw ? FastImageIO.FILE_SUFFIX : "png", target.packer.get());
|
||||||
tset.setImagePath(imagePath);
|
tset.setImagePath(imagePath);
|
||||||
// replace the original set with the trimmed
|
// replace the original set with the trimmed
|
||||||
// tileset in the tileset bundle
|
// tileset in the tileset bundle
|
||||||
|
|||||||
Reference in New Issue
Block a user