Java tutorial
/******************************************************************************* * 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); } }