/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.tools.imagepacker;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.GdxRuntimeException;
/** Use {@link TexturePacker2}.
* @deprecated */
public class TexturePacker {
static Pattern indexPattern = Pattern.compile(".+_(\\d+)(_.*|$)");
static public boolean quiet;
ArrayList<Image> images = new ArrayList();
HashMap<String, Image> imageCrcs = new HashMap();
FileWriter writer;
int uncompressedSize, compressedSize;
int xPadding, yPadding;
final Filter filter;
int minWidth, minHeight;
int maxWidth, maxHeight;
final Settings settings;
/** Used by squeeze method when ignoreBlankImages is false to add empty region for blank input images during the pack. */
BufferedImage emptyImage = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR);
public TexturePacker (Settings settings) {
this.settings = settings;
this.filter = new Filter(Direction.none, null, null, -1, -1, null, null);
}
public TexturePacker (Settings settings, File inputDir, Filter filter, File outputDir, File packFile) throws IOException {
this.settings = settings;
this.filter = filter;
// Collect and squeeze images.
File[] files = inputDir.listFiles(filter);
if (files == null) return;
for (File file : files) {
if (file.isDirectory()) continue;
String imageName = file.getAbsolutePath().substring(inputDir.getAbsolutePath().length()) + "\n";
if (imageName.startsWith("/") || imageName.startsWith("\\")) imageName = imageName.substring(1);
int dotIndex = imageName.lastIndexOf('.');
if (dotIndex != -1) imageName = imageName.substring(0, dotIndex);
addImage(ImageIO.read(file), imageName);
}
if (images.isEmpty()) return;
log(inputDir.toString());
if (filter.format != null)
log("Format: " + filter.format);
else
log("Format: " + settings.defaultFormat + " (default)");
if (filter.minFilter != null && filter.magFilter != null)
log("Filter: " + filter.minFilter + ", " + filter.magFilter);
else
log("Filter: " + settings.defaultFilterMin + ", " + settings.defaultFilterMag + " (default)");
if (filter.direction != Direction.none) log("Repeat: " + filter.direction);
process(outputDir, packFile, inputDir.getName());
}
static void log (String message) {
if (!quiet) System.out.println(message);
}
public void addImage (BufferedImage image, String name) {
Image squeezed = squeeze(image, name, false);
if (squeezed != null) {
if (settings.alias) {
String crc = hash(squeezed);
Image existing = imageCrcs.get(crc);
if (existing != null) {
existing.aliases.add(squeezed);
return;
}
imageCrcs.put(crc, squeezed);
}
images.add(squeezed);
}
}
public void process (File outputDir, File packFile, String prefix) throws IOException {
if (images.isEmpty()) return;
minWidth = filter.width != -1 ? filter.width : settings.minWidth;
minHeight = filter.height != -1 ? filter.height : settings.minHeight;
maxWidth = filter.width != -1 ? filter.width : settings.maxWidth;
maxHeight = filter.height != -1 ? filter.height : settings.maxHeight;
if (settings.edgePadding) {
xPadding = !filter.direction.isX() ? settings.padding : 0;
yPadding = !filter.direction.isY() ? settings.padding : 0;
} else {
xPadding = images.size() > 1 && !filter.direction.isX() ? settings.padding : 0;
yPadding = images.size() > 1 && !filter.direction.isY() ? settings.padding : 0;
}
outputDir.mkdirs();
writer = new FileWriter(packFile, true);
try {
while (!images.isEmpty())
if (!writePage(prefix, outputDir)) break;
if (writer != null) {
log("Pixels eliminated: " + (1 - compressedSize / (float)uncompressedSize) * 100 + "%");
log("");
}
} finally {
writer.close();
}
}
private boolean writePage (String prefix, File outputDir) throws IOException {
// Try reasonably hard to pack images into the smallest POT size.
Comparator bestComparator = null;
Comparator secondBestComparator = imageComparators.get(0);
int bestWidth = 99999, bestHeight = 99999;
int secondBestWidth = 99999, secondBestHeight = 99999;
int bestUsedPixels = 0;
int width = minWidth, height = minHeight;
int grownPixels = 0, grownPixels2 = 0;
int i = 0, ii = 0;
while (true) {
for (Comparator comparator : imageComparators) {
// Pack as many images as possible, sorting the images different
// ways.
Collections.sort(images, comparator);
int usedPixels = insert(null, new ArrayList(images), width, height);
// Store the best pack, in case not all images fit on the max
// texture size.
if (usedPixels > bestUsedPixels) {
secondBestComparator = comparator;
secondBestWidth = width;
secondBestHeight = height;
}
// If all images fit and this sort is the best so far, take
// note.
if (usedPixels == -1) {
if (width * height < bestWidth * bestHeight) {
bestComparator = comparator;
bestWidth = width;
bestHeight = height;
}
}
}
if (width == maxWidth && height == maxHeight) break;
if (bestComparator != null) break;
if (settings.pot) {
// 64,64 -> 128,64 -> 256,64 etc 64,128 -> 64,256 etc -> 128,128
// -> 256,128 etc.
if (i % 3 == 0) {
grownPixels += MathUtils.nextPowerOfTwo(width + 1) - width;
width = MathUtils.nextPowerOfTwo(width + 1);
if (width > maxWidth) {
i++;
width -= grownPixels;
grownPixels = 0;
}
} else if (i % 3 == 1) {
grownPixels += MathUtils.nextPowerOfTwo(height + 1) - height;
height = MathUtils.nextPowerOfTwo(height + 1);
if (height > maxHeight) {
i++;
height -= grownPixels;
grownPixels = 0;
}
} else {
ii++;
if (ii % 2 == 1)
width = MathUtils.nextPowerOfTwo(width + 1);
else
height = MathUtils.nextPowerOfTwo(height + 1);
i++;
}
} else {
// 64-127,64 -> 64,64-127 -> 128-255,128 -> 128,128-255 etc.
int incr = 2;
if (i % 3 == 0) {
if (width + incr >= MathUtils.nextPowerOfTwo(width)) {
width -= grownPixels;
grownPixels = 0;
i++;
} else {
width += incr;
grownPixels += incr;
}
} else if (i % 3 == 1) {
if (height + incr >= MathUtils.nextPowerOfTwo(height)) {
height -= grownPixels;
grownPixels = 0;
i++;
} else {
height += incr;
grownPixels += incr;
}
} else {
if (width == MathUtils.nextPowerOfTwo(width) && height == MathUtils.nextPowerOfTwo(height)) ii++;
if (ii % 2 == 1)
width += incr;
else
height += incr;
i++;
}
}
width = Math.min(maxWidth, width);
height = Math.min(maxHeight, height);
}
if (bestComparator != null) {
Collections.sort(images, bestComparator);
} else {
Collections.sort(images, secondBestComparator);
bestWidth = secondBestWidth;
bestHeight = secondBestHeight;
}
width = bestWidth;
height = bestHeight;
if (settings.pot) {
width = MathUtils.nextPowerOfTwo(width);
height = MathUtils.nextPowerOfTwo(height);
}
if (width > maxWidth || height > maxHeight) {
System.out.println("ERROR: Images do not fit on max size: " + maxWidth + "x" + maxHeight);
return false;
}
int type;
switch (filter.format != null ? filter.format : settings.defaultFormat) {
case RGBA8888:
case RGBA4444:
type = BufferedImage.TYPE_INT_ARGB;
break;
case RGB565:
case RGB888:
type = BufferedImage.TYPE_INT_RGB;
break;
case Alpha:
type = BufferedImage.TYPE_BYTE_GRAY;
break;
default:
throw new RuntimeException("Luminance Alpha format is not supported.");
}
FileFormat fileFormat;
if (filter.fileFormat != null) {
fileFormat = filter.fileFormat;
} else {
fileFormat = settings.defaultFileFormat;
}
float imageQuality = settings.defaultImageQuality;
int imageNumber = 1;
File outputFile = new File(outputDir, prefix + imageNumber + "." + getFileExtension(fileFormat));
while (outputFile.exists())
outputFile = new File(outputDir, prefix + ++imageNumber + "." + getFileExtension(fileFormat));
writer.write("\n" + outputFile.getName() + "\n");
Format format;
if (filter.format != null) {
writer.write("format: " + filter.format + "\n");
format = filter.format;
} else {
writer.write("format: " + settings.defaultFormat + "\n");
format = settings.defaultFormat;
}
if (filter.minFilter == null || filter.magFilter == null)
writer.write("filter: " + settings.defaultFilterMin + "," + settings.defaultFilterMag + "\n");
else
writer.write("filter: " + filter.minFilter + "," + filter.magFilter + "\n");
writer.write("repeat: " + filter.direction + "\n");
BufferedImage canvas = new BufferedImage(width, height, type);
insert(canvas, images, bestWidth, bestHeight);
log("Writing " + canvas.getWidth() + "x" + canvas.getHeight() + ": " + outputFile);
if (fileFormat == FileFormat.JPEG) {
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
ImageWriter writer = (ImageWriter)writers.next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(imageQuality);
ImageOutputStream ios = ImageIO.createImageOutputStream(outputFile);
writer.setOutput(ios);
writer.write(null, new IIOImage(canvas, null, null), param);
if (!settings.pot) {
ios = ImageIO.createImageOutputStream(outputFile);
writer.setOutput(ios);
writer.write(null, new IIOImage(canvas, null, null), param);
}
} else {
ImageIO.write(canvas, getFileExtension(fileFormat), outputFile);
if (!settings.pot) ImageIO.write(squeeze(ImageIO.read(outputFile), "", true), getFileExtension(fileFormat), outputFile);
}
compressedSize += canvas.getWidth() * canvas.getHeight();
return true;
}
private static String getFileExtension (FileFormat fileFormat) {
String retVal = "";
switch (fileFormat) {
case PNG:
retVal = "png";
break;
case JPEG:
retVal = "jpg";
break;
}
return retVal;
}
private int insert (BufferedImage canvas, ArrayList<Image> images, int width, int height) throws IOException {
if (settings.debug && canvas != null) {
Graphics g = canvas.getGraphics();
g.setColor(Color.green);
g.drawRect(0, 0, width - 1, height - 1);
}
int x = 0, y = 0;
if (settings.edgePadding) {
if (!filter.direction.isX()) {
x = xPadding;
width -= xPadding;
}
if (!filter.direction.isY()) {
y = yPadding;
height -= yPadding;
}
} else {
// Pretend image is larger so padding on right and bottom edges is
// ignored.
if (!filter.direction.isX()) width += xPadding;
if (!filter.direction.isY()) height += yPadding;
}
Node root = new Node(x, y, width, height);
int usedPixels = 0;
for (int i = images.size() - 1; i >= 0; i--) {
Image image = images.get(i);
Node node = root.insert(image, false);
if (node == null) {
if (settings.rotate) node = root.insert(image, true);
if (node == null) continue;
}
usedPixels += image.getWidth() * image.getHeight();
images.remove(i);
if (canvas != null) {
node.writePackEntry();
Graphics2D g = (Graphics2D)canvas.getGraphics();
if (image.rotate) {
g.translate(node.left, node.top);
g.rotate(-90 * MathUtils.degreesToRadians);
g.translate(-node.left, -node.top);
g.translate(-image.getWidth(), 0);
}
if (settings.duplicatePadding) {
int amount = settings.padding / 2;
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
// Copy corner pixels to fill corners of the padding.
g.drawImage(image, node.left - amount, node.top - amount, node.left, node.top, 0, 0, 1, 1, null);
g.drawImage(image, node.left + imageWidth, node.top - amount, node.left + imageWidth + amount, node.top, 0, 0, 1,
1, null);
g.drawImage(image, node.left - amount, node.top + imageHeight, node.left, node.top + imageHeight + amount, 0, 0,
1, 1, null);
g.drawImage(image, node.left + imageWidth, node.top + imageHeight, node.left + imageWidth + amount, node.top
+ imageHeight + amount, 0, 0, 1, 1, null);
// Copy edge picels into padding.
g.drawImage(image, node.left, node.top - amount, node.left + imageWidth, node.top, 0, 0, imageWidth, 1, null);
g.drawImage(image, node.left, node.top + imageHeight, node.left + imageWidth, node.top + imageHeight + amount, 0,
imageHeight - 1, imageWidth, imageHeight, null);
g.drawImage(image, node.left - amount, node.top, node.left, node.top + imageHeight, 0, 0, 1, imageHeight, null);
g.drawImage(image, node.left + imageWidth, node.top, node.left + imageWidth + amount, node.top + imageHeight,
imageWidth - 1, 0, imageWidth, imageHeight, null);
}
g.drawImage(image, node.left, node.top, null);
if (image.rotate) {
g.translate(image.getWidth(), 0);
g.translate(node.left, node.top);
g.rotate(90 * MathUtils.degreesToRadians);
g.translate(-node.left, -node.top);
}
if (settings.debug) {
g.setColor(Color.magenta);
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
if (image.rotate)
g.drawRect(node.left, node.top, imageHeight - 1, imageWidth - 1);
else
g.drawRect(node.left, node.top, imageWidth - 1, imageHeight - 1);
}
}
}
return images.isEmpty() ? -1 : usedPixels;
}
private Image squeeze (BufferedImage source, String name, boolean skipTopLeft) {
if (source == null) return null;
if (!filter.accept(source)) return null;
uncompressedSize += source.getWidth() * source.getHeight();
WritableRaster alphaRaster = source.getAlphaRaster();
if (alphaRaster == null || !settings.stripWhitespace || name.contains("_ws"))
return new Image(name, source, 0, 0, source.getWidth(), source.getHeight());
final byte[] a = new byte[1];
int top = 0;
int bottom = source.getHeight();
if (!filter.direction.isY()) {
if (!skipTopLeft) {
outer:
for (int y = 0; y < source.getHeight(); y++) {
for (int x = 0; x < source.getWidth(); x++) {
alphaRaster.getDataElements(x, y, a);
int alpha = a[0];
if (alpha < 0) alpha += 256;
if (alpha > settings.alphaThreshold) break outer;
}
top++;
}
}
outer:
for (int y = source.getHeight(); --y >= top;) {
for (int x = 0; x < source.getWidth(); x++) {
alphaRaster.getDataElements(x, y, a);
int alpha = a[0];
if (alpha < 0) alpha += 256;
if (alpha > settings.alphaThreshold) break outer;
}
bottom--;
}
}
int left = 0;
int right = source.getWidth();
if (!filter.direction.isX()) {
if (!skipTopLeft) {
outer:
for (int x = 0; x < source.getWidth(); x++) {
for (int y = top; y < bottom; y++) {
alphaRaster.getDataElements(x, y, a);
int alpha = a[0];
if (alpha < 0) alpha += 256;
if (alpha > settings.alphaThreshold) break outer;
}
left++;
}
}
outer:
for (int x = source.getWidth(); --x >= left;) {
for (int y = top; y < bottom; y++) {
alphaRaster.getDataElements(x, y, a);
int alpha = a[0];
if (alpha < 0) alpha += 256;
if (alpha > settings.alphaThreshold) break outer;
}
right--;
}
}
int newWidth = right - left;
int newHeight = bottom - top;
if (newWidth <= 0 || newHeight <= 0) {
if (settings.ignoreBlankImages) {
log("Ignoring blank input image: " + name);
return null;
} else {
return new Image(name, emptyImage, 0, 0, 1, 1);
}
}
return new Image(name, source, left, top, newWidth, newHeight);
}
static private String hash (BufferedImage image) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA1");
WritableRaster raster = image.getRaster();
final byte[] pixel = new byte[4];
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
raster.getDataElements(x, y, pixel);
digest.update(pixel);
}
}
return new BigInteger(1, digest.digest()).toString(16);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
private class Node {
int left, top, width, height;
Node child1, child2;
Image image;
public Node (int left, int top, int width, int height) {
this.left = left;
this.top = top;
this.width = width;
this.height = height;
}
/** Returns true if the image was inserted. If canvas != null, an entry is written to the pack file. */
public Node insert (Image image, boolean rotate) throws IOException {
if (this.image != null) return null;
if (child1 != null) {
Node newNode = child1.insert(image, rotate);
if (newNode != null) return newNode;
return child2.insert(image, rotate);
}
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
if (rotate) {
int temp = imageWidth;
imageWidth = imageHeight;
imageHeight = temp;
}
int neededWidth = imageWidth + xPadding;
int neededHeight = imageHeight + yPadding;
if (neededWidth > width || neededHeight > height) return null;
if (neededWidth == width && neededHeight == height) {
this.image = image;
image.rotate = rotate;
return this;
}
int dw = width - neededWidth;
int dh = height - neededHeight;
if (dw > dh) {
child1 = new Node(left, top, neededWidth, height);
child2 = new Node(left + neededWidth, top, width - neededWidth, height);
} else {
child1 = new Node(left, top, width, neededHeight);
child2 = new Node(left, top + neededHeight, width, height - neededHeight);
}
return child1.insert(image, rotate);
}
void writePackEntry () throws IOException {
writePackEntry(image, null);
for (Image alias : image.aliases)
writePackEntry(alias, image);
}
/** @param image The Image to be written to the pack.
* @param source If the Image is an alias, the source is the original Image, otherwise null is expected. */
private void writePackEntry (Image image, Image source) throws IOException {
String imageName = image.name;
imageName = imageName.replace("\\", "/");
log("Packing... " + imageName + (source != null ? " (alias)" : ""));
Matcher matcher = indexPattern.matcher(imageName);
int index = -1;
if (matcher.matches()) index = Integer.parseInt(matcher.group(1));
int underscoreIndex = imageName.indexOf('_');
if (underscoreIndex != -1) imageName = imageName.substring(0, underscoreIndex);
boolean rotate = source != null ? source.rotate : image.rotate;
writer.write(imageName + "\n");
writer.write(" rotate: " + rotate + "\n");
writer.write(" xy: " + left + ", " + top + "\n");
writer.write(" size: " + image.getWidth() + ", " + image.getHeight() + "\n");
writer.write(" orig: " + image.originalWidth + ", " + image.originalHeight + "\n");
writer.write(" offset: " + image.offsetX + ", " + (image.originalHeight - image.getHeight() - image.offsetY) + "\n");
writer.write(" index: " + index + "\n");
}
}
static private class Image extends BufferedImage {
final String name;
final int offsetX, offsetY;
final int originalWidth, originalHeight;
boolean rotate;
ArrayList<Image> aliases = new ArrayList();
public Image (String name, BufferedImage src, int left, int top, int newWidth, int newHeight) {
super(src.getColorModel(), src.getRaster().createWritableChild(left, top, newWidth, newHeight, 0, 0, null), src
.getColorModel().isAlphaPremultiplied(), null);
this.name = name;
offsetX = left;
offsetY = top;
originalWidth = src.getWidth();
originalHeight = src.getHeight();
}
public String toString () {
return name;
}
}
static private ArrayList<Comparator> imageComparators = new ArrayList();
static {
imageComparators.add(new Comparator<Image>() {
public int compare (Image image1, Image image2) {
int diff = image1.getHeight() - image2.getHeight();
if (diff != 0) return diff;
return image1.getWidth() - image2.getWidth();
}
});
imageComparators.add(new Comparator<Image>() {
public int compare (Image image1, Image image2) {
int diff = image1.getWidth() - image2.getWidth();
if (diff != 0) return diff;
return image1.getHeight() - image2.getHeight();
}
});
imageComparators.add(new Comparator<Image>() {
public int compare (Image image1, Image image2) {
return image1.getWidth() * image1.getHeight() - image2.getWidth() * image2.getHeight();
}
});
}
public enum FileFormat {
PNG, JPEG;
}
static private class Filter implements FilenameFilter {
Direction direction;
FileFormat fileFormat;
Format format;
TextureFilter minFilter;
TextureFilter magFilter;
int width = -1;
int height = -1;
Settings settings;
public Filter (Direction direction, FileFormat fileFormat, Format format, int width, int height, TextureFilter minFilter,
TextureFilter magFilter) {
this.direction = direction;
this.fileFormat = fileFormat;
this.format = format;
this.width = width;
this.height = height;
this.minFilter = minFilter;
this.magFilter = magFilter;
}
public boolean accept (File dir, String name) {
switch (direction) {
case none:
if (name.contains("_x") || name.contains("_y")) return false;
break;
case x:
if (!name.contains("_x") || name.contains("_xy")) return false;
break;
case y:
if (!name.contains("_y") || name.contains("_xy")) return false;
break;
case xy:
if (!name.contains("_xy")) return false;
break;
}
if (format != null) {
if (!name.contains("_" + formatToAbbrev.get(format))) return false;
} else {
// Return if name has a format.
for (String f : formatToAbbrev.values())
if (name.contains("_" + f)) return false;
}
if (minFilter != null && magFilter != null) {
if (!name.contains("_" + filterToAbbrev.get(minFilter) + "," + filterToAbbrev.get(magFilter) + ".")
&& !name.contains("_" + filterToAbbrev.get(minFilter) + "," + filterToAbbrev.get(magFilter) + "_")) return false;
} else {
// Return if the name has a filter.
for (String f : filterToAbbrev.values()) {
String tag = "_" + f + ",";
int tagIndex = name.indexOf(tag);
if (tagIndex != -1) {
String rest = name.substring(tagIndex + tag.length());
for (String f2 : filterToAbbrev.values())
if (rest.startsWith(f2 + ".") || rest.startsWith(f2 + "_")) return false;
}
}
}
return true;
}
public boolean accept (BufferedImage image) {
if (width != -1 && image.getWidth() != width) return false;
if (height != -1 && image.getHeight() != height) return false;
return true;
}
}
static private enum Direction {
x, y, xy, none;
public boolean isX () {
return this == x || this == xy;
}
public boolean isY () {
return this == y || this == xy;
}
}
static final HashMap<TextureFilter, String> filterToAbbrev = new HashMap();
static {
filterToAbbrev.put(TextureFilter.Linear, "l");
filterToAbbrev.put(TextureFilter.Nearest, "n");
filterToAbbrev.put(TextureFilter.MipMap, "m");
filterToAbbrev.put(TextureFilter.MipMapLinearLinear, "mll");
filterToAbbrev.put(TextureFilter.MipMapLinearNearest, "mln");
filterToAbbrev.put(TextureFilter.MipMapNearestLinear, "mnl");
filterToAbbrev.put(TextureFilter.MipMapNearestNearest, "mnn");
}
static final HashMap<Format, String> formatToAbbrev = new HashMap();
static {
formatToAbbrev.put(Format.RGBA8888, "rgba8");
formatToAbbrev.put(Format.RGBA4444, "rgba4");
formatToAbbrev.put(Format.RGB565, "rgb565");
formatToAbbrev.put(Format.Alpha, "a");
}
/** Use {@link TexturePacker2}.
* @deprecated */
static public class Settings {
public Format defaultFormat = Format.RGBA8888;
public float defaultImageQuality = 0.9f;
public FileFormat defaultFileFormat = FileFormat.PNG;
public TextureFilter defaultFilterMin = TextureFilter.Nearest;
public TextureFilter defaultFilterMag = TextureFilter.Nearest;
public int alphaThreshold = 0;
public boolean pot = true;
public int padding = 2;
public boolean duplicatePadding = true;
public boolean debug = false;
public boolean rotate = false;
public int minWidth = 128;
public int minHeight = 128;
public int maxWidth = 1024;
public int maxHeight = 1024;
public boolean stripWhitespace = false;
public boolean incremental;
public boolean alias = true;
public boolean edgePadding = true;
/** True if blank images should be ignored when building the texture pack or false if empty regions should be created for
* them. */
public boolean ignoreBlankImages = true;
/** Specifies the crc file path used when incremental is enabled, if not specified the file is created in the user folder,
* inside the .texturepacker folder. */
public String incrementalFilePath = null;
HashMap<String, Long> crcs = new HashMap();
HashMap<String, String> packSections = new HashMap();
}
static private void process (Settings settings, File rootDir, File inputDir, File outputDir, File packFile) throws IOException {
if (inputDir.getName().startsWith(".")) return;
// Abort if nothing has changed.
boolean skip = false;
if (settings.incremental) {
File[] files = inputDir.listFiles();
if (files == null) return;
boolean noneHaveChanged = true;
int childCountNow = 0;
// added flag to generate the crc file using local paths in case the
// incrementalFilePath is
// specified.
boolean useAbsolutePaths = true;
if (settings.incrementalFilePath != null) useAbsolutePaths = false;
for (File file : files) {
if (file.isDirectory()) continue;
String path = file.getAbsolutePath();
if (!useAbsolutePaths) {
String rootFolderAbsolutePath = rootDir.getAbsolutePath();
if (isSubPath(rootFolderAbsolutePath, path)) path = removeSubPath(rootFolderAbsolutePath, path);
}
Long crcOld = settings.crcs.get(path);
long crcNow = crc(file);
if (crcOld == null || crcOld != crcNow) noneHaveChanged = false;
settings.crcs.put(path, crcNow);
childCountNow++;
}
String path = inputDir.getAbsolutePath();
if (!useAbsolutePaths) {
String rootFolderAbsolutePath = rootDir.getAbsolutePath();
if (isSubPath(rootFolderAbsolutePath, path)) path = removeSubPath(rootFolderAbsolutePath, path);
}
Long childCountOld = settings.crcs.get(path);
if (childCountOld == null || childCountNow != childCountOld) noneHaveChanged = false;
settings.crcs.put(path, (long)childCountNow);
if (outputDir.exists()) {
boolean foundPage = false;
String prefix = inputDir.getName();
for (File file : outputDir.listFiles()) {
if (file.getName().startsWith(prefix)) {
foundPage = true;
break;
}
}
if (!foundPage) noneHaveChanged = false;
}
String section = settings.packSections.get(inputDir.getName());
if (noneHaveChanged && section != null) {
FileWriter writer = new FileWriter(packFile, true);
writer.append(section);
writer.close();
log(inputDir.toString());
log("Skipping unchanged directory.");
log("");
skip = true;
}
}
if (!skip) {
// Clean existing page images.
if (outputDir.exists()) {
String prefix = inputDir.getName();
for (File file : outputDir.listFiles())
if (file.getName().startsWith(prefix)
&& file.getName().endsWith("." + getFileExtension(settings.defaultFileFormat))) file.delete();
}
// Just check all combinations, because we are extremely lazy.
ArrayList<TextureFilter> filters = new ArrayList();
filters.add(null);
filters.addAll(Arrays.asList(TextureFilter.values()));
ArrayList<Format> formats = new ArrayList();
formats.add(null);
formats.addAll(Arrays.asList(Format.values()));
for (int i = 0, n = formats.size(); i < n; i++) {
Format format = formats.get(i);
for (int ii = 0, nn = filters.size(); ii < nn; ii++) {
TextureFilter min = filters.get(ii);
for (int iii = 0; iii < nn; iii++) {
TextureFilter mag = filters.get(iii);
if ((min == null && mag != null) || (min != null && mag == null)) continue;
Filter filter = new Filter(Direction.none, settings.defaultFileFormat, format, -1, -1, min, mag);
new TexturePacker(settings, inputDir, filter, outputDir, packFile);
for (int width = settings.minWidth; width <= settings.maxWidth; width <<= 1) {
filter = new Filter(Direction.x, settings.defaultFileFormat, format, width, -1, min, mag);
new TexturePacker(settings, inputDir, filter, outputDir, packFile);
}
for (int height = settings.minHeight; height <= settings.maxHeight; height <<= 1) {
filter = new Filter(Direction.y, settings.defaultFileFormat, format, -1, height, min, mag);
new TexturePacker(settings, inputDir, filter, outputDir, packFile);
}
for (int width = settings.minWidth; width <= settings.maxWidth; width <<= 1) {
for (int height = settings.minHeight; height <= settings.maxHeight; height <<= 1) {
filter = new Filter(Direction.xy, settings.defaultFileFormat, format, width, height, min, mag);
new TexturePacker(settings, inputDir, filter, outputDir, packFile);
}
}
}
}
}
}
// Process subdirectories.
File[] files = inputDir.listFiles();
if (files == null) return;
for (File file : files)
if (file.isDirectory()) process(settings, rootDir, file, outputDir, packFile);
}
static public void process (Settings settings, String input, String output) {
process(settings, input, output, "pack");
}
static public void process (Settings settings, String input, String output, String packFileName) {
try {
File inputDir = new File(input);
File outputDir = new File(output);
if (!inputDir.isDirectory()) {
System.out.println("Not a directory: " + inputDir);
return;
}
File packFile = new File(outputDir, packFileName);
// Load incremental file.
File incrmentalFile = null;
if (settings.incremental && packFile.exists()) {
settings.crcs.clear();
// if localIncrementFile
String incrementalFilePath = settings.incrementalFilePath;
if (incrementalFilePath == null)
incrementalFilePath = System.getProperty("user.home") + "/.texturepacker/" + hash(inputDir.getAbsolutePath());
incrmentalFile = new File(incrementalFilePath);
if (incrmentalFile.exists()) {
BufferedReader reader = new BufferedReader(new FileReader(incrmentalFile));
while (true) {
String path = reader.readLine();
if (path == null) break;
String crc = reader.readLine();
if (crc == null) break;
settings.crcs.put(path, Long.parseLong(crc));
}
reader.close();
}
// Store the pack file text for each section.
BufferedReader reader = new BufferedReader(new FileReader(packFile));
StringBuilder buffer = new StringBuilder(2048);
while (true) {
String imageName = reader.readLine();
if (imageName == null) break;
if (imageName.length() == 0) continue;
String pageName = imageName.replaceAll("\\d+." + getFileExtension(settings.defaultFileFormat) + "$", "");
String section = settings.packSections.get(pageName);
if (section != null) buffer.append(section);
// buffer.append("****start\n");
buffer.append('\n');
buffer.append(imageName);
buffer.append('\n');
while (true) {
String line = reader.readLine();
if (line == null || line.length() == 0) break;
buffer.append(line);
buffer.append('\n');
}
settings.packSections.put(pageName, buffer.toString());
buffer.setLength(0);
}
reader.close();
}
// Clean pack file.
packFile.delete();
process(settings, inputDir, inputDir, outputDir, packFile);
// Write incrmental file.
if (incrmentalFile != null) {
incrmentalFile.getParentFile().mkdirs();
FileWriter writer = new FileWriter(incrmentalFile);
for (Entry<String, Long> entry : settings.crcs.entrySet()) {
writer.write(entry.getKey() + "\n");
writer.write(entry.getValue() + "\n");
}
writer.close();
}
} catch (IOException ex) {
throw new GdxRuntimeException("Error packing images: " + input, ex);
}
}
static private String hash (String value) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA1");
digest.update(value.getBytes());
return new BigInteger(1, digest.digest()).toString(16);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
static private long crc (File file) {
try {
FileInputStream input = new FileInputStream(file);
byte[] buffer = new byte[4096];
CRC32 crc32 = new CRC32();
while (true) {
int length = input.read(buffer);
if (length == -1) break;
crc32.update(buffer, 0, length);
}
input.close();
return crc32.getValue();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
static boolean isSubPath (String path, String subPath) {
if (subPath.length() < path.length()) return false;
String subPathSubString = subPath.substring(0, path.length());
return subPathSubString.equals(path);
}
static boolean isAbsolutePath (String path) {
return new File(path).isAbsolute();
}
static String removeSubPath (String path, String subPath) {
return subPath.replace(path, "");
}
static public void main (String[] args) throws Exception {
String input, output;
if (args.length != 2) {
System.out.println("Usage: INPUTDIR OUTPUTDIR");
return;
}
input = args[0];
output = args[1];
Settings settings = new Settings();
settings.alias = true;
process(settings, input, output);
}
}