/*
* Copyright (c) 2002 Shaven Puppy Ltd
* 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 'Shaven Puppy' 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.
*/
package com.shavenpuppy.jglib.tools;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.imageio.ImageIO;
import org.lwjgl.util.Rectangle;
import com.shavenpuppy.jglib.Image;
import com.shavenpuppy.jglib.util.ImprovedStringTokenizer;
/**
* The texturepacker takes a selection of .PNG images and tries to cram
* them all into a single .PNG image with dimensions which are a power of 2
* (ie. an OpenGL texture). All "whitespace" pixels are stripped off the
* source .PNGs to reduce their size to the minimum.
*
* Usage:
* TexturePacker <sourcedir> <destdir> <nameprefix>
*
* This will recursively find all .PNGs in <sourcedir> and generate a number
* of .PNG and .XML files in <destdir>. PNGs will be names <nameprefix>n.PNG,
* XMLs will be named <nameprefix>n.XML. In each XML file there will be an
* imagebank named <nameprefix>n.imagebank.
*/
public class TexturePacker {
/** Default maximum texture size */
private static final int DEFAULT_MAX_SIZE = 256;
/** Maximum texture size */
private int maxSize = DEFAULT_MAX_SIZE;
/** The source directory */
private String sourceDir;
/** Image filter to apply */
private ImageFilterDelegator imageFilter;
/** The destination directory */
private String destDir;
/** The name prefix */
private String namePrefix;
/** The output XML file */
private String outputFile;
/** The hotspot file */
private String hotspotFile;
/** The style file */
private String styleFile;
/** The directory excludes file */
private String excludesFile;
/** The default styles */
private final String defaultStyleRGBA = "transparent.style";
private final String defaultStyleRGB = "opaque.style";
/** A mapping of filenames to Points which are hotspots */
private HashMap hotspotMap = new HashMap();
/** A mapping of filenames to styles */
private HashMap styleMap = new HashMap();
/** The list of directories to exclude */
private ArrayList excludesList = new ArrayList();
/** Use jpeg compression */
private boolean jpeg;
/** Use linear texture magnification */
private boolean linear;
/** Prefix appended to output location */
private String classpathPrefix;
/** A quadtree of used and unused bits of texture */
private class Quad {
int x, y, w, h;
Quad[] child;
boolean allocated;
/**
* Construct a Quad
*/
Quad(int x, int y, int w, int h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
/**
* Allocate a chunk.
* Picks the smallest Quad in the tree below that fits.
* @param width The desired width
* @param height The desired height
* @return the allocated Quad or null if there was no room
*/
Quad allocate(int width, int height) {
Quad smallest = findSmallest(width, height);
if (smallest == null) {
return null;
}
return smallest.doAllocate(width, height);
}
/**
* Performs the allocation. The quad is split into up to three smaller
* quads; the quad in the top left is allocated.
*/
private Quad doAllocate(int neww, int newh) {
assert !allocated;
if (neww == w && newh == h) {
// Can't make any children
allocated = true;
return this;
}
if (neww == w) {
// Only one child, at the bottom
child = new Quad[2];
child[0] = new Quad(x, y, neww, newh);
child[1] = new Quad(x, y + newh, neww, h - newh);
child[0].allocated = true;
return child[0];
}
if (newh == h) {
// Only one child, at the right
child = new Quad[2];
child[0] = new Quad(x, y, neww, newh);
child[1] = new Quad(x + neww, y, w - neww, newh);
child[0].allocated = true;
return child[0];
}
child = new Quad[3];
child[0] = new Quad(x, y, neww, newh);
child[0].allocated = true;
if (w - neww > h - newh) {
child[1] = new Quad(x + neww, y, w - neww, h);
child[2] = new Quad(x, y + newh, neww, h - newh);
} else {
child[1] = new Quad(x, y + newh, w, h - newh);
child[2] = new Quad(x + neww, y, w - neww, newh);
}
return child[0];
}
/**
* Find the smallest chunk that satisfies the specified required
* dimensions.
* @param width The desired width
* @param height The desired height
* @return the smallest Quad or null if there was none
*/
Quad findSmallest(int width, int height) {
// Any quad already allocated or not big enough is useless
if (allocated || this.w < width || this.h < height) {
return null;
}
if (child == null) {
// We are the smallest quad.
return this;
}
Quad smallest = null;
for (int i = 0; i < child.length; i ++) {
Quad ret = child[i].findSmallest(width, height);
if (smallest == null || (ret != null && ret.w * ret.h < smallest.w * smallest.h)) {
smallest = ret;
}
}
return smallest;
}
void draw(Graphics2D g2d) {
if (child != null) {
for (int i = 0; i < child.length; i ++) {
child[i].draw(g2d);
}
} else {
g2d.setColor(allocated ? Color.red : Color.green);
g2d.drawRect(x, y, w, h);
}
}
}
int counter = 0;
/**
* Packed Sprite
*/
private class PackedSprite {
// Sprite source
// final File file;
// Directory, relative to root path
final String dir;
// Sprite image name
final String name;
// Size
final int width, height;
// Source image
BufferedImage image;
// Rectangle offset
final Rectangle offset = new Rectangle();
// The quad we end up placed in
Quad pos;
// Have we got alpha?
boolean alpha;
PackedSprite(LoadedImage loadedImg) throws Exception
{
this.name = loadedImg.name;
this.dir = loadedImg.dir.replace(File.separatorChar, '/');
assert (loadedImg.img != null);
// Do filter processing
BufferedImage im = imageFilter.process(loadedImg.img, offset);
// Squeeze and chop off any dead space in the image
this.image = squeeze(im);
this.width = image.getWidth();
this.height = image.getHeight();
}
/**
* Squeeze an image down to its smallest possible size. Only 32 bit RGBA
* images can be squeezed in this way.
* @param src The source image
* @return the squeezed image, the original source image, or null if the image is empty.
*/
BufferedImage squeeze(BufferedImage src) throws Exception {
offset.setLocation(0, 0);
// We'll use the alpha channel:
WritableRaster alphaRaster = src.getAlphaRaster();
if (alphaRaster == null) {
Point hotspot = getHotspot(dir, name);
if (hotspot != null) {
System.out.println("Applied hotspot to "+name);
offset.setLocation(hotspot.x, hotspot.y);
}
return src;
} else {
this.alpha = true;
}
// Scan across the top:
final byte[] a = new byte[1];
int top = 0;
outer0: for (int y = 0; y < src.getHeight(); y ++) {
for (int x = 0; x < src.getWidth(); x ++) {
alphaRaster.getDataElements(x, y, a);
if (a[0] != 0) {
break outer0;
}
}
top ++;
}
int bottom = src.getHeight() - 1;
outer1: for (int y = src.getHeight(); --y >= top; ) {
for (int x = 0; x < src.getWidth(); x ++) {
alphaRaster.getDataElements(x, y, a);
if (a[0] != 0) {
break outer1;
}
}
bottom --;
}
int left = 0;
outer2: for (int x = 0; x < src.getWidth(); x ++) {
for (int y = top; y <= bottom; y ++) {
alphaRaster.getDataElements(x, y, a);
if (a[0] != 0) {
break outer2;
}
}
left ++;
}
int right = src.getWidth() - 1;
outer3: for (int x = src.getWidth(); --x >= left; ) {
for (int y = top; y <= bottom; y ++) {
alphaRaster.getDataElements(x, y, a);
if (a[0] != 0) {
break outer3;
}
}
right --;
}
int newWidth = 1 + right - left;
int newHeight = 1 + bottom - top;
if (newWidth <= 0 || newHeight <= 0) {
throw new Exception("Image is completely alpha'd away!");
}
BufferedImage dest = src.getSubimage(left, top, newWidth, newHeight);
// Use defined hotspot if there is one; otherwise use the centre of the image
Point hotspot = getHotspot(dir, name);
if (hotspot != null) {
System.out.println("Applied hotspot to "+name);
int yDec = src.getHeight() - 2 - bottom;
offset.setLocation(hotspot.x - left, hotspot.y - yDec - 1);
} else {
offset.setLocation(src.getWidth() / 2 - left, bottom - src.getHeight() / 2);
}
offset.setSize(1 + right - left, 1 + bottom - top);
return dest;
}
}
private Point getHotspot(String dir, String name) {
Point hotspot;
if (name != null) {
hotspot = (Point) hotspotMap.get(name);
if (hotspot != null) {
return hotspot;
}
}
return (Point) hotspotMap.get(dir);
// hotspot = (Point) hotspotMap.get(dir);
// if (hotspot != null) {
// return hotspot;
// }
// // Strip path
// int idx = dir.indexOf('/');
// if (idx != -1) {
// dir = dir.substring(0, idx);
// System.out.println("Check directory "+dir);
// return getHotspot(dir, null);
// }
//
// return null;
}
/**
* A PackedTexture
*/
private class PackedTexture {
// Marks the used areas
final Quad area = new Quad(0, 0, maxSize, maxSize);
// The image we're writing to
BufferedImage image;
// List of stuff stashed so far
final ArrayList packedSprites = new ArrayList();
int count;
/** Pixels used so far */
private int used;
/** Type: RGB or RGBA */
private final String type;
PackedTexture(String type) {
this.type = type;
if (type.equals("RGB")) {
ColorModel cm = new DirectColorModel(24, 0xFF, 0xFF00, 0xFF0000);//.;ColorSpace.getInstance(ColorSpace.CS_sRGB), true, false, ColorModel.OPAQUE, DataBuffer.TYPE_BYTE);
WritableRaster wr = cm.createCompatibleWritableRaster(maxSize, maxSize);
image = new BufferedImage(cm, wr, false, null);
} else {
ColorModel cm = new DirectColorModel(32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000);//new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), true, false, ColorModel.TRANSLUCENT, DataBuffer.TYPE_BYTE);
WritableRaster wr = cm.createCompatibleWritableRaster(maxSize, maxSize);
image = new BufferedImage(cm, wr, false, null);
}
count = counter ++;
}
/**
* Attempt to pack an image into this packedtexture. If successful
* return true, otherwise return false.
* @return true if the image fit
*/
boolean pack(PackedSprite sprite) {
// If the image is gone, we're no longer accepting any more sprites...
if (image == null) {
return false;
}
int w = sprite.image.getWidth();
int h = sprite.image.getHeight();
if (w < maxSize) {
w ++;
}
if (h < maxSize) {
h ++;
}
sprite.pos = area.allocate(w, h);
if (sprite.pos == null) {
return false;
}
Graphics2D g2d = (Graphics2D) image.getGraphics();
g2d.drawImage(sprite.image, sprite.pos.x, sprite.pos.y, null);
packedSprites.add(sprite);
used += w * h;
return true;
}
/**
* Have we got an image?
* @return true if we're still accepting stuff
*/
boolean hasImage() {
return image != null;
}
/**
* @return true if we're > 95% full
*/
boolean isFull() {
return image == null || (used > (int)(image.getWidth() * image.getHeight() * 0.95));
}
void writeImage() throws Exception {
String name = namePrefix+count;
System.out.println("Writing "+name+".jgimage : space wasted "+(100.0f - (100.0f * used / (image.getWidth() * image.getHeight()) ))+"%");
Image convertedImage = ImageConverter.convert(image, jpeg);
Image.write(convertedImage, new BufferedOutputStream(new FileOutputStream(new File(destDir+File.separatorChar+name+".jgimage"))));
// JFrame f = new JFrame();
// f.getContentPane().add(new JLabel(new ImageIcon(image)));
// f.setSize(image.getWidth(), image.getHeight());
// f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
// f.setVisible(true);
// Save some RAM
image.flush();
image = null;
for (Iterator i = packedSprites.iterator(); i.hasNext(); ) {
PackedSprite ps = ((PackedSprite) i.next());
ps.image.flush();
ps.image = null;
}
}
void writeXML(BufferedWriter bw) throws Exception {
if (hasImage()) {
writeImage();
}
String name = namePrefix+count;
// And the XML snippet
// Write out textureimage:
// Write out texture definition:
bw.write("\t<texture name=\"");
bw.write(name);
bw.write(".texture\" url=\"classpath:");
bw.write(classpathPrefix);
bw.write(name);
bw.write(".jgimage\" dst=\"GL_");
bw.write(type);
bw.write("\" target=\"GL_TEXTURE_2D\" min=\"GL_LINEAR\" mag=\"");
if (linear) {
bw.write("GL_LINEAR");
} else {
bw.write("GL_NEAREST");
}
bw.write("\" wrap=\"GL_FALSE\" />\n");
// Write out sprite image bank:
bw.write("\t<imagebank name=\"");
bw.write(name);
bw.write(".imagebank\" texture=\"");
bw.write(name);
if (type.equals("RGB")) {
bw.write(".texture\" defaultstyle=\""+defaultStyleRGB+"\">\n");
} else {
bw.write(".texture\" defaultstyle=\""+defaultStyleRGBA+"\">\n");
}
for (Iterator i = packedSprites.iterator(); i.hasNext(); ) {
PackedSprite sprite = (PackedSprite) i.next();
bw.write("\t\t<spriteimage name=\"spriteimage.");
bw.write(sprite.name);
bw.write("\" x=\"");
bw.write(String.valueOf(sprite.pos.x));
bw.write("\" y=\"");
bw.write(String.valueOf(sprite.pos.y));
bw.write("\" w=\"");
bw.write(String.valueOf(sprite.width));
bw.write("\" h=\"");
bw.write(String.valueOf(sprite.height));
bw.write("\" hx=\"");
bw.write(String.valueOf(sprite.offset.getX()));
bw.write("\" hy=\"");
bw.write(String.valueOf(sprite.offset.getY()));
bw.write("\" ");
// Write style if it's available
String style = (String) styleMap.get(sprite.name);
if (style == null) {
style = (String) styleMap.get(sprite.dir);
}
if (style != null) {
bw.write("style=\"");
bw.write(style);
bw.write("\" ");
System.out.println("Style for "+sprite.dir+":"+sprite.name+" is "+style);
}
bw.write("/>\n");
}
bw.write("\t</imagebank>\n");
}
}
/**
* Constructor for TexturePacker.
*/
public TexturePacker(String sourceDir, ImageFilterDelegator filterDel, String destDir, String namePrefix, String outputFile, String hotspotFile, String styleFile, String excludesFile, String maxSize, boolean jpeg, boolean linear, String classpathPrefix)
{
this.sourceDir = sourceDir;
this.imageFilter = filterDel;
this.destDir = destDir;
this.namePrefix = namePrefix;
this.outputFile = outputFile;
this.hotspotFile = hotspotFile;
this.styleFile = styleFile;
this.excludesFile = excludesFile;
this.maxSize = Integer.parseInt(maxSize);
this.jpeg = jpeg;
this.linear = linear;
this.classpathPrefix = classpathPrefix;
}
/**
* Recursively find a list of source .PNGs
* @param dir The directory to recurse
* @param ret An arraylist to store the resulting files in
*/
private void getSourcePNGs(File dir, ArrayList ret) throws Exception {
// Get the list of source .PNGs
System.out.println("Scanning "+dir);
if (!dir.exists()) {
throw new Exception("Source directory '"+dir+"' does not exist.");
}
if (!dir.isDirectory()) {
throw new Exception("Source '"+sourceDir+"' is not a directory.");
}
if (excludesList.contains(dir.getName())) {
return;
}
File[] file = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
String name = pathname.getName();
if (pathname.isDirectory()) {
return !pathname.equals("CVS");
} else if
(
name.endsWith(".png")
|| name.endsWith(".tga")
)
{
return imageFilter.getFileFilter().accept(pathname);
} else {
return false;
}
}
});
for (int i = 0; i < file.length; i ++) {
if (file[i].isFile()) {
System.out.println("Adding "+file[i]);
ret.add(file[i]);
} else {
getSourcePNGs(file[i], ret);
}
}
}
/**
* Reads in the hotspot control file, which is a list of filenames followed by coordinates
*/
private void readHotspots() throws Exception {
BufferedReader br = new BufferedReader(new FileReader(hotspotFile));
String line;
while ( (line = br.readLine()) != null) {
line.trim();
// Ignore comments
if (line.startsWith("//") || line.startsWith("\'") || line.startsWith("#") || line.startsWith(";") || line.equals("")) {
continue;
}
ImprovedStringTokenizer st = new ImprovedStringTokenizer(line);
String fileName = st.nextToken();
Point hotspot = new Point(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()));
hotspotMap.put(fileName, hotspot);
}
br.close();
}
/**
* Reads in the rendering style control file, which is a list of filenames followed by rendering styles
*/
private void readStyles() throws Exception {
BufferedReader br = new BufferedReader(new FileReader(styleFile));
String line;
while ( (line = br.readLine()) != null) {
line.trim();
// Ignore comments
if (line.startsWith("//") || line.startsWith("\'") || line.startsWith("#") || line.startsWith(";") || line.equals("")) {
continue;
}
ImprovedStringTokenizer st = new ImprovedStringTokenizer(line);
String fileName = st.nextToken();
String style = st.nextToken();
styleMap.put(fileName, style);
}
br.close();
}
/**
* Reads in the excludes control file, which is a list of directory names to exclude
*/
private void readExcludes() throws Exception {
BufferedReader br = new BufferedReader(new FileReader(excludesFile));
String line;
while ( (line = br.readLine()) != null) {
line.trim();
// Ignore comments
if (line.startsWith("//") || line.startsWith("\'") || line.startsWith("#") || line.startsWith(";") || line.equals("")) {
continue;
}
excludesList.add(line);
}
br.close();
}
/**
* Perform the packing operation
*/
private void pack() throws Exception {
Image.setCompressor(new JPEGCompressor());
// Read the hotspot control file
readHotspots();
// Read style control file
readStyles();
// Read exclusions
readExcludes();
// Get the list of source .PNGs
File sourceDirF = new File(sourceDir);
ArrayList sourcePNGs = new ArrayList(128);
getSourcePNGs(sourceDirF, sourcePNGs);
// Load into a list of LoadedImage objects
ArrayList loadedImgs = loadImageFiles(sourcePNGs);
// Load each one
ArrayList packedSpritesRGB = new ArrayList(loadedImgs.size());
ArrayList packedSpritesRGBA = new ArrayList(loadedImgs.size());
for (Iterator i = loadedImgs.iterator(); i.hasNext(); )
{
LoadedImage img = (LoadedImage)i.next();
try {
PackedSprite ps = new PackedSprite(img);
if (ps.alpha) {
packedSpritesRGBA.add(ps);
} else {
packedSpritesRGB.add(ps);
}
} catch (Exception e) {
System.out.println("FAILED to pack "+img.name+": "+e);
}
}
// Here's the array of PackedTextures
ArrayList packedTexturesRGB = new ArrayList();
ArrayList packedTexturesRGBA = new ArrayList();
// stash("RGB4", packedSpritesRGB, packedTexturesRGB);
// stash("RGBA4", packedSpritesRGBA, packedTexturesRGBA);
stash("RGB", packedSpritesRGB, packedTexturesRGB);
stash("RGBA", packedSpritesRGBA, packedTexturesRGBA);
// Output the XML snippet
File outputXMLf = new File(outputFile);
outputXMLf.getAbsoluteFile().getParentFile().mkdirs();
System.out.println("Writing to "+outputFile);
BufferedWriter bw = new BufferedWriter(new FileWriter(outputXMLf));
bw.write("<?xml version='1.0' encoding='utf-8'?>\n<resources>\n");
for (Iterator i = packedTexturesRGB.iterator(); i.hasNext(); ) {
PackedTexture pt = (PackedTexture) i.next();
pt.writeXML(bw);
}
for (Iterator i = packedTexturesRGBA.iterator(); i.hasNext(); ) {
PackedTexture pt = (PackedTexture) i.next();
pt.writeXML(bw);
}
bw.write("</resources>\n");
bw.flush();
bw.close();
}
private void stash(String type, ArrayList packedSprites, ArrayList packedTextures) throws Exception {
// For each image, attempt to pack it into a PackedTexture
for (int i = 0; i < packedSprites.size(); i ++) {
boolean stashed = false;
PackedSprite packedSprite = (PackedSprite) packedSprites.get(i);
/*
for (Iterator store = packedTextures.iterator(); store.hasNext(); ) {
PackedTexture pt = (PackedTexture) store.next();
stashed = pt.pack(packedSprite);
if (stashed)
break;
}
*/
if (packedTextures.size() > 0) {
PackedTexture pt = (PackedTexture) packedTextures.get(packedTextures.size() - 1);
stashed = pt.pack(packedSprite);
if (!stashed && pt.hasImage()) {
pt.writeImage();
}
}
if (!stashed) {
PackedTexture pt2 = new PackedTexture(type);
stashed = pt2.pack(packedSprite);
if (!stashed) {
throw new Exception("Image "+packedSprite.name+" is too big");
}
packedTextures.add(pt2);
}
}
}
/** Accepts an ArrayList of File objects which are image files to load.
* Returns an ArrayList of LoadedImage objects for packing.
*/
private ArrayList loadImageFiles(ArrayList filesList)
{
ArrayList loadedImages = new ArrayList();
// Loop over all input files
for (int i=0; i<filesList.size(); i++)
{
try
{
File file = (File)filesList.get(i);
// System.out.println("File:"+file);
// Get base name and image
String baseName = file.getName().substring(0, file.getName().length() - 4);
BufferedImage baseImage = ImageIO.read(file);
String dir = "";
File parentDir = file.getParentFile();
if (parentDir != null)
{
if ( !parentDir.equals( new File(sourceDir) ) )
{
int offset = sourceDir.length() + 1;
dir = parentDir.getPath().substring(offset);
}
}
loadedImages.add( new LoadedImage(baseName, dir, baseImage));
}
catch (Exception e)
{
e.printStackTrace();
}
}
return imageFilter.preProcess(loadedImages);
}
/**
* Start the application
* Usage:
* TexturePacker <sourcedir> [shadows|sprites] <destdir> <nameprefix> <outputxmlfile> <hotspotfile> <stylefile> <excludesfile>
*/
public static void main(String[] args) {
if (args.length < 11) {
System.err.println("Usage:");
System.err.println("\tTexturePacker <sourcedir> <imagefilter> <destdir> <nameprefix> <outputxmlfile> <hotspotfile> <stylefile> <excludesfile> <maxsize> [jpeg | *] [linear | nearest] [ classpathDir ]");
System.exit(-1);
}
ImageIO.setUseCache(false);
try
{
ImageFilterDelegator filterDel = new ImageFilterDelegator(args[1]);
boolean isJpeg = args[9].equals("jpeg");
boolean isLinear = args[10].equals("linear");
String classpathPrefix;
if (args.length >= 12) {
classpathPrefix = args[11];
} else {
classpathPrefix = "";
}
new TexturePacker(args[0], filterDel, args[2], args[3], args[4], args[5], args[6], args[7], args[8], isJpeg, isLinear, classpathPrefix).pack();
}
catch (Exception e)
{
e.printStackTrace(System.err);
System.exit(-1);
}
}
}