1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.tools.texturepacker; 18 19 import java.awt.Color; 20 import java.awt.Graphics2D; 21 import java.awt.image.BufferedImage; 22 import java.io.File; 23 import java.io.FileOutputStream; 24 import java.io.FileReader; 25 import java.io.FileWriter; 26 import java.io.IOException; 27 import java.io.OutputStreamWriter; 28 import java.io.Writer; 29 import java.util.Comparator; 30 import java.util.HashSet; 31 import java.util.Iterator; 32 import java.util.Set; 33 34 import javax.imageio.IIOImage; 35 import javax.imageio.ImageIO; 36 import javax.imageio.ImageWriteParam; 37 import javax.imageio.ImageWriter; 38 import javax.imageio.stream.ImageOutputStream; 39 40 import com.badlogic.gdx.files.FileHandle; 41 import com.badlogic.gdx.graphics.Pixmap.Format; 42 import com.badlogic.gdx.graphics.Texture.TextureFilter; 43 import com.badlogic.gdx.graphics.Texture.TextureWrap; 44 import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData; 45 import com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData.Region; 46 import com.badlogic.gdx.math.MathUtils; 47 import com.badlogic.gdx.utils.Array; 48 import com.badlogic.gdx.utils.GdxRuntimeException; 49 import com.badlogic.gdx.utils.Json; 50 51 /** @author Nathan Sweet */ 52 public class TexturePacker { 53 private final Settings settings; 54 private final Packer packer; 55 private final ImageProcessor imageProcessor; 56 private final Array<InputImage> inputImages = new Array(); 57 private File rootDir; 58 59 /** @param rootDir Used to strip the root directory prefix from image file names, can be null. */ TexturePacker(File rootDir, Settings settings)60 public TexturePacker (File rootDir, Settings settings) { 61 this.rootDir = rootDir; 62 this.settings = settings; 63 64 if (settings.pot) { 65 if (settings.maxWidth != MathUtils.nextPowerOfTwo(settings.maxWidth)) 66 throw new RuntimeException("If pot is true, maxWidth must be a power of two: " + settings.maxWidth); 67 if (settings.maxHeight != MathUtils.nextPowerOfTwo(settings.maxHeight)) 68 throw new RuntimeException("If pot is true, maxHeight must be a power of two: " + settings.maxHeight); 69 } 70 71 if (settings.grid) 72 packer = new GridPacker(settings); 73 else 74 packer = new MaxRectsPacker(settings); 75 imageProcessor = new ImageProcessor(rootDir, settings); 76 } 77 TexturePacker(Settings settings)78 public TexturePacker (Settings settings) { 79 this(null, settings); 80 } 81 addImage(File file)82 public void addImage (File file) { 83 InputImage inputImage = new InputImage(); 84 inputImage.file = file; 85 inputImages.add(inputImage); 86 } 87 addImage(BufferedImage image, String name)88 public void addImage (BufferedImage image, String name) { 89 InputImage inputImage = new InputImage(); 90 inputImage.image = image; 91 inputImage.name = name; 92 inputImages.add(inputImage); 93 } 94 pack(File outputDir, String packFileName)95 public void pack (File outputDir, String packFileName) { 96 if (packFileName.endsWith(settings.atlasExtension)) 97 packFileName = packFileName.substring(0, packFileName.length() - settings.atlasExtension.length()); 98 outputDir.mkdirs(); 99 100 for (int i = 0, n = settings.scale.length; i < n; i++) { 101 imageProcessor.setScale(settings.scale[i]); 102 for (InputImage inputImage : inputImages) { 103 if (inputImage.file != null) 104 imageProcessor.addImage(inputImage.file); 105 else 106 imageProcessor.addImage(inputImage.image, inputImage.name); 107 } 108 109 Array<Page> pages = packer.pack(imageProcessor.getImages()); 110 111 String scaledPackFileName = settings.getScaledPackFileName(packFileName, i); 112 writeImages(outputDir, scaledPackFileName, pages); 113 try { 114 writePackFile(outputDir, scaledPackFileName, pages); 115 } catch (IOException ex) { 116 throw new RuntimeException("Error writing pack file.", ex); 117 } 118 imageProcessor.clear(); 119 } 120 } 121 writeImages(File outputDir, String scaledPackFileName, Array<Page> pages)122 private void writeImages (File outputDir, String scaledPackFileName, Array<Page> pages) { 123 File packFileNoExt = new File(outputDir, scaledPackFileName); 124 File packDir = packFileNoExt.getParentFile(); 125 String imageName = packFileNoExt.getName(); 126 127 int fileIndex = 0; 128 for (Page page : pages) { 129 int width = page.width, height = page.height; 130 int paddingX = settings.paddingX; 131 int paddingY = settings.paddingY; 132 if (settings.duplicatePadding) { 133 paddingX /= 2; 134 paddingY /= 2; 135 } 136 width -= settings.paddingX; 137 height -= settings.paddingY; 138 if (settings.edgePadding) { 139 page.x = paddingX; 140 page.y = paddingY; 141 width += paddingX * 2; 142 height += paddingY * 2; 143 } 144 if (settings.pot) { 145 width = MathUtils.nextPowerOfTwo(width); 146 height = MathUtils.nextPowerOfTwo(height); 147 } 148 width = Math.max(settings.minWidth, width); 149 height = Math.max(settings.minHeight, height); 150 page.imageWidth = width; 151 page.imageHeight = height; 152 153 File outputFile; 154 while (true) { 155 outputFile = new File(packDir, imageName + (fileIndex++ == 0 ? "" : fileIndex) + "." + settings.outputFormat); 156 if (!outputFile.exists()) break; 157 } 158 new FileHandle(outputFile).parent().mkdirs(); 159 page.imageName = outputFile.getName(); 160 161 BufferedImage canvas = new BufferedImage(width, height, getBufferedImageType(settings.format)); 162 Graphics2D g = (Graphics2D)canvas.getGraphics(); 163 164 if (!settings.silent) System.out.println("Writing " + canvas.getWidth() + "x" + canvas.getHeight() + ": " + outputFile); 165 166 for (Rect rect : page.outputRects) { 167 BufferedImage image = rect.getImage(imageProcessor); 168 int iw = image.getWidth(); 169 int ih = image.getHeight(); 170 int rectX = page.x + rect.x, rectY = page.y + page.height - rect.y - rect.height; 171 if (settings.duplicatePadding) { 172 int amountX = settings.paddingX / 2; 173 int amountY = settings.paddingY / 2; 174 if (rect.rotated) { 175 // Copy corner pixels to fill corners of the padding. 176 for (int i = 1; i <= amountX; i++) { 177 for (int j = 1; j <= amountY; j++) { 178 plot(canvas, rectX - j, rectY + iw - 1 + i, image.getRGB(0, 0)); 179 plot(canvas, rectX + ih - 1 + j, rectY + iw - 1 + i, image.getRGB(0, ih - 1)); 180 plot(canvas, rectX - j, rectY - i, image.getRGB(iw - 1, 0)); 181 plot(canvas, rectX + ih - 1 + j, rectY - i, image.getRGB(iw - 1, ih - 1)); 182 } 183 } 184 // Copy edge pixels into padding. 185 for (int i = 1; i <= amountY; i++) { 186 for (int j = 0; j < iw; j++) { 187 plot(canvas, rectX - i, rectY + iw - 1 - j, image.getRGB(j, 0)); 188 plot(canvas, rectX + ih - 1 + i, rectY + iw - 1 - j, image.getRGB(j, ih - 1)); 189 } 190 } 191 for (int i = 1; i <= amountX; i++) { 192 for (int j = 0; j < ih; j++) { 193 plot(canvas, rectX + j, rectY - i, image.getRGB(iw - 1, j)); 194 plot(canvas, rectX + j, rectY + iw - 1 + i, image.getRGB(0, j)); 195 } 196 } 197 } else { 198 // Copy corner pixels to fill corners of the padding. 199 for (int i = 1; i <= amountX; i++) { 200 for (int j = 1; j <= amountY; j++) { 201 plot(canvas, rectX - i, rectY - j, image.getRGB(0, 0)); 202 plot(canvas, rectX - i, rectY + ih - 1 + j, image.getRGB(0, ih - 1)); 203 plot(canvas, rectX + iw - 1 + i, rectY - j, image.getRGB(iw - 1, 0)); 204 plot(canvas, rectX + iw - 1 + i, rectY + ih - 1 + j, image.getRGB(iw - 1, ih - 1)); 205 } 206 } 207 // Copy edge pixels into padding. 208 for (int i = 1; i <= amountY; i++) { 209 copy(image, 0, 0, iw, 1, canvas, rectX, rectY - i, rect.rotated); 210 copy(image, 0, ih - 1, iw, 1, canvas, rectX, rectY + ih - 1 + i, rect.rotated); 211 } 212 for (int i = 1; i <= amountX; i++) { 213 copy(image, 0, 0, 1, ih, canvas, rectX - i, rectY, rect.rotated); 214 copy(image, iw - 1, 0, 1, ih, canvas, rectX + iw - 1 + i, rectY, rect.rotated); 215 } 216 } 217 } 218 copy(image, 0, 0, iw, ih, canvas, rectX, rectY, rect.rotated); 219 if (settings.debug) { 220 g.setColor(Color.magenta); 221 g.drawRect(rectX, rectY, rect.width - settings.paddingX - 1, rect.height - settings.paddingY - 1); 222 } 223 } 224 225 if (settings.bleed && !settings.premultiplyAlpha && !(settings.outputFormat.equalsIgnoreCase("jpg") || settings.outputFormat.equalsIgnoreCase("jpeg"))) { 226 canvas = new ColorBleedEffect().processImage(canvas, 2); 227 g = (Graphics2D)canvas.getGraphics(); 228 } 229 230 if (settings.debug) { 231 g.setColor(Color.magenta); 232 g.drawRect(0, 0, width - 1, height - 1); 233 } 234 235 ImageOutputStream ios = null; 236 try { 237 if (settings.outputFormat.equalsIgnoreCase("jpg") || settings.outputFormat.equalsIgnoreCase("jpeg")) { 238 BufferedImage newImage = new BufferedImage(canvas.getWidth(), canvas.getHeight(), BufferedImage.TYPE_3BYTE_BGR); 239 newImage.getGraphics().drawImage(canvas, 0, 0, null); 240 canvas = newImage; 241 242 Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg"); 243 ImageWriter writer = writers.next(); 244 ImageWriteParam param = writer.getDefaultWriteParam(); 245 param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); 246 param.setCompressionQuality(settings.jpegQuality); 247 ios = ImageIO.createImageOutputStream(outputFile); 248 writer.setOutput(ios); 249 writer.write(null, new IIOImage(canvas, null, null), param); 250 } else { 251 if (settings.premultiplyAlpha) canvas.getColorModel().coerceData(canvas.getRaster(), true); 252 ImageIO.write(canvas, "png", outputFile); 253 } 254 } catch (IOException ex) { 255 throw new RuntimeException("Error writing file: " + outputFile, ex); 256 } finally { 257 if (ios != null) { 258 try { 259 ios.close(); 260 } catch (Exception ignored) { 261 } 262 } 263 } 264 } 265 } 266 plot(BufferedImage dst, int x, int y, int argb)267 static private void plot (BufferedImage dst, int x, int y, int argb) { 268 if (0 <= x && x < dst.getWidth() && 0 <= y && y < dst.getHeight()) dst.setRGB(x, y, argb); 269 } 270 copy(BufferedImage src, int x, int y, int w, int h, BufferedImage dst, int dx, int dy, boolean rotated)271 static private void copy (BufferedImage src, int x, int y, int w, int h, BufferedImage dst, int dx, int dy, boolean rotated) { 272 if (rotated) { 273 for (int i = 0; i < w; i++) 274 for (int j = 0; j < h; j++) 275 plot(dst, dx + j, dy + w - i - 1, src.getRGB(x + i, y + j)); 276 } else { 277 for (int i = 0; i < w; i++) 278 for (int j = 0; j < h; j++) 279 plot(dst, dx + i, dy + j, src.getRGB(x + i, y + j)); 280 } 281 } 282 writePackFile(File outputDir, String scaledPackFileName, Array<Page> pages)283 private void writePackFile (File outputDir, String scaledPackFileName, Array<Page> pages) throws IOException { 284 File packFile = new File(outputDir, scaledPackFileName + settings.atlasExtension); 285 File packDir = packFile.getParentFile(); 286 packDir.mkdirs(); 287 288 if (packFile.exists()) { 289 // Make sure there aren't duplicate names. 290 TextureAtlasData textureAtlasData = new TextureAtlasData(new FileHandle(packFile), new FileHandle(packFile), false); 291 for (Page page : pages) { 292 for (Rect rect : page.outputRects) { 293 String rectName = Rect.getAtlasName(rect.name, settings.flattenPaths); 294 for (Region region : textureAtlasData.getRegions()) { 295 if (region.name.equals(rectName)) { 296 throw new GdxRuntimeException("A region with the name \"" + rectName + "\" has already been packed: " 297 + rect.name); 298 } 299 } 300 } 301 } 302 } 303 304 Writer writer = new OutputStreamWriter(new FileOutputStream(packFile, true), "UTF-8"); 305 for (Page page : pages) { 306 writer.write("\n" + page.imageName + "\n"); 307 writer.write("size: " + page.imageWidth + "," + page.imageHeight + "\n"); 308 writer.write("format: " + settings.format + "\n"); 309 writer.write("filter: " + settings.filterMin + "," + settings.filterMag + "\n"); 310 writer.write("repeat: " + getRepeatValue() + "\n"); 311 312 page.outputRects.sort(); 313 for (Rect rect : page.outputRects) { 314 writeRect(writer, page, rect, rect.name); 315 Array<Alias> aliases = new Array(rect.aliases.toArray()); 316 aliases.sort(); 317 for (Alias alias : aliases) { 318 Rect aliasRect = new Rect(); 319 aliasRect.set(rect); 320 alias.apply(aliasRect); 321 writeRect(writer, page, aliasRect, alias.name); 322 } 323 } 324 } 325 writer.close(); 326 } 327 writeRect(Writer writer, Page page, Rect rect, String name)328 private void writeRect (Writer writer, Page page, Rect rect, String name) throws IOException { 329 writer.write(Rect.getAtlasName(name, settings.flattenPaths) + "\n"); 330 writer.write(" rotate: " + rect.rotated + "\n"); 331 writer.write(" xy: " + (page.x + rect.x) + ", " + (page.y + page.height - rect.height - rect.y) + "\n"); 332 333 writer.write(" size: " + rect.regionWidth + ", " + rect.regionHeight + "\n"); 334 if (rect.splits != null) { 335 writer.write(" split: " // 336 + rect.splits[0] + ", " + rect.splits[1] + ", " + rect.splits[2] + ", " + rect.splits[3] + "\n"); 337 } 338 if (rect.pads != null) { 339 if (rect.splits == null) writer.write(" split: 0, 0, 0, 0\n"); 340 writer.write(" pad: " + rect.pads[0] + ", " + rect.pads[1] + ", " + rect.pads[2] + ", " + rect.pads[3] + "\n"); 341 } 342 writer.write(" orig: " + rect.originalWidth + ", " + rect.originalHeight + "\n"); 343 writer.write(" offset: " + rect.offsetX + ", " + (rect.originalHeight - rect.regionHeight - rect.offsetY) + "\n"); 344 writer.write(" index: " + rect.index + "\n"); 345 } 346 getRepeatValue()347 private String getRepeatValue () { 348 if (settings.wrapX == TextureWrap.Repeat && settings.wrapY == TextureWrap.Repeat) return "xy"; 349 if (settings.wrapX == TextureWrap.Repeat && settings.wrapY == TextureWrap.ClampToEdge) return "x"; 350 if (settings.wrapX == TextureWrap.ClampToEdge && settings.wrapY == TextureWrap.Repeat) return "y"; 351 return "none"; 352 } 353 getBufferedImageType(Format format)354 private int getBufferedImageType (Format format) { 355 switch (settings.format) { 356 case RGBA8888: 357 case RGBA4444: 358 return BufferedImage.TYPE_INT_ARGB; 359 case RGB565: 360 case RGB888: 361 return BufferedImage.TYPE_INT_RGB; 362 case Alpha: 363 return BufferedImage.TYPE_BYTE_GRAY; 364 default: 365 throw new RuntimeException("Unsupported format: " + settings.format); 366 } 367 } 368 369 /** @author Nathan Sweet */ 370 static public class Page { 371 public String imageName; 372 public Array<Rect> outputRects, remainingRects; 373 public float occupancy; 374 public int x, y, width, height, imageWidth, imageHeight; 375 } 376 377 /** @author Regnarock 378 * @author Nathan Sweet */ 379 static public class Alias implements Comparable<Alias> { 380 public String name; 381 public int index; 382 public int[] splits; 383 public int[] pads; 384 public int offsetX, offsetY, originalWidth, originalHeight; 385 Alias(Rect rect)386 public Alias (Rect rect) { 387 name = rect.name; 388 index = rect.index; 389 splits = rect.splits; 390 pads = rect.pads; 391 offsetX = rect.offsetX; 392 offsetY = rect.offsetY; 393 originalWidth = rect.originalWidth; 394 originalHeight = rect.originalHeight; 395 } 396 apply(Rect rect)397 public void apply (Rect rect) { 398 rect.name = name; 399 rect.index = index; 400 rect.splits = splits; 401 rect.pads = pads; 402 rect.offsetX = offsetX; 403 rect.offsetY = offsetY; 404 rect.originalWidth = originalWidth; 405 rect.originalHeight = originalHeight; 406 } 407 compareTo(Alias o)408 public int compareTo (Alias o) { 409 return name.compareTo(o.name); 410 } 411 } 412 413 /** @author Nathan Sweet */ 414 static public class Rect implements Comparable<Rect> { 415 public String name; 416 public int offsetX, offsetY, regionWidth, regionHeight, originalWidth, originalHeight; 417 public int x, y; 418 public int width, height; // Portion of page taken by this region, including padding. 419 public int index; 420 public boolean rotated; 421 public Set<Alias> aliases = new HashSet<Alias>(); 422 public int[] splits; 423 public int[] pads; 424 public boolean canRotate = true; 425 426 private boolean isPatch; 427 private BufferedImage image; 428 private File file; 429 int score1, score2; 430 Rect(BufferedImage source, int left, int top, int newWidth, int newHeight, boolean isPatch)431 Rect (BufferedImage source, int left, int top, int newWidth, int newHeight, boolean isPatch) { 432 image = new BufferedImage(source.getColorModel(), source.getRaster().createWritableChild(left, top, newWidth, newHeight, 433 0, 0, null), source.getColorModel().isAlphaPremultiplied(), null); 434 offsetX = left; 435 offsetY = top; 436 regionWidth = newWidth; 437 regionHeight = newHeight; 438 originalWidth = source.getWidth(); 439 originalHeight = source.getHeight(); 440 width = newWidth; 441 height = newHeight; 442 this.isPatch = isPatch; 443 } 444 445 /** Clears the image for this rect, which will be loaded from the specified file by {@link #getImage(ImageProcessor)}. */ unloadImage(File file)446 public void unloadImage (File file) { 447 this.file = file; 448 image = null; 449 } 450 getImage(ImageProcessor imageProcessor)451 public BufferedImage getImage (ImageProcessor imageProcessor) { 452 if (image != null) return image; 453 454 BufferedImage image; 455 try { 456 image = ImageIO.read(file); 457 } catch (IOException ex) { 458 throw new RuntimeException("Error reading image: " + file, ex); 459 } 460 if (image == null) throw new RuntimeException("Unable to read image: " + file); 461 String name = this.name; 462 if (isPatch) name += ".9"; 463 return imageProcessor.processImage(image, name).getImage(null); 464 } 465 Rect()466 Rect () { 467 } 468 Rect(Rect rect)469 Rect (Rect rect) { 470 x = rect.x; 471 y = rect.y; 472 width = rect.width; 473 height = rect.height; 474 } 475 set(Rect rect)476 void set (Rect rect) { 477 name = rect.name; 478 image = rect.image; 479 offsetX = rect.offsetX; 480 offsetY = rect.offsetY; 481 regionWidth = rect.regionWidth; 482 regionHeight = rect.regionHeight; 483 originalWidth = rect.originalWidth; 484 originalHeight = rect.originalHeight; 485 x = rect.x; 486 y = rect.y; 487 width = rect.width; 488 height = rect.height; 489 index = rect.index; 490 rotated = rect.rotated; 491 aliases = rect.aliases; 492 splits = rect.splits; 493 pads = rect.pads; 494 canRotate = rect.canRotate; 495 score1 = rect.score1; 496 score2 = rect.score2; 497 file = rect.file; 498 isPatch = rect.isPatch; 499 } 500 compareTo(Rect o)501 public int compareTo (Rect o) { 502 return name.compareTo(o.name); 503 } 504 505 @Override equals(Object obj)506 public boolean equals (Object obj) { 507 if (this == obj) return true; 508 if (obj == null) return false; 509 if (getClass() != obj.getClass()) return false; 510 Rect other = (Rect)obj; 511 if (name == null) { 512 if (other.name != null) return false; 513 } else if (!name.equals(other.name)) return false; 514 return true; 515 } 516 517 @Override toString()518 public String toString () { 519 return name + "[" + x + "," + y + " " + width + "x" + height + "]"; 520 } 521 getAtlasName(String name, boolean flattenPaths)522 static public String getAtlasName (String name, boolean flattenPaths) { 523 return flattenPaths ? new FileHandle(name).name() : name; 524 } 525 } 526 527 /** @author Nathan Sweet */ 528 static public class Settings { 529 public boolean pot = true; 530 public int paddingX = 2, paddingY = 2; 531 public boolean edgePadding = true; 532 public boolean duplicatePadding = false; 533 public boolean rotation; 534 public int minWidth = 16, minHeight = 16; 535 public int maxWidth = 1024, maxHeight = 1024; 536 public boolean square = false; 537 public boolean stripWhitespaceX, stripWhitespaceY; 538 public int alphaThreshold; 539 public TextureFilter filterMin = TextureFilter.Nearest, filterMag = TextureFilter.Nearest; 540 public TextureWrap wrapX = TextureWrap.ClampToEdge, wrapY = TextureWrap.ClampToEdge; 541 public Format format = Format.RGBA8888; 542 public boolean alias = true; 543 public String outputFormat = "png"; 544 public float jpegQuality = 0.9f; 545 public boolean ignoreBlankImages = true; 546 public boolean fast; 547 public boolean debug; 548 public boolean silent; 549 public boolean combineSubdirectories; 550 public boolean flattenPaths; 551 public boolean premultiplyAlpha; 552 public boolean useIndexes = true; 553 public boolean bleed = true; 554 public boolean limitMemory = true; 555 public boolean grid; 556 public float[] scale = {1}; 557 public String[] scaleSuffix = {""}; 558 public String atlasExtension = ".atlas"; 559 Settings()560 public Settings () { 561 } 562 Settings(Settings settings)563 public Settings (Settings settings) { 564 fast = settings.fast; 565 rotation = settings.rotation; 566 pot = settings.pot; 567 minWidth = settings.minWidth; 568 minHeight = settings.minHeight; 569 maxWidth = settings.maxWidth; 570 maxHeight = settings.maxHeight; 571 paddingX = settings.paddingX; 572 paddingY = settings.paddingY; 573 edgePadding = settings.edgePadding; 574 duplicatePadding = settings.duplicatePadding; 575 alphaThreshold = settings.alphaThreshold; 576 ignoreBlankImages = settings.ignoreBlankImages; 577 stripWhitespaceX = settings.stripWhitespaceX; 578 stripWhitespaceY = settings.stripWhitespaceY; 579 alias = settings.alias; 580 format = settings.format; 581 jpegQuality = settings.jpegQuality; 582 outputFormat = settings.outputFormat; 583 filterMin = settings.filterMin; 584 filterMag = settings.filterMag; 585 wrapX = settings.wrapX; 586 wrapY = settings.wrapY; 587 debug = settings.debug; 588 silent = settings.silent; 589 combineSubdirectories = settings.combineSubdirectories; 590 flattenPaths = settings.flattenPaths; 591 premultiplyAlpha = settings.premultiplyAlpha; 592 square = settings.square; 593 useIndexes = settings.useIndexes; 594 bleed = settings.bleed; 595 limitMemory = settings.limitMemory; 596 grid = settings.grid; 597 scale = settings.scale; 598 scaleSuffix = settings.scaleSuffix; 599 atlasExtension = settings.atlasExtension; 600 } 601 getScaledPackFileName(String packFileName, int scaleIndex)602 public String getScaledPackFileName (String packFileName, int scaleIndex) { 603 // Use suffix if not empty string. 604 if (scaleSuffix[scaleIndex].length() > 0) 605 packFileName += scaleSuffix[scaleIndex]; 606 else { 607 // Otherwise if scale != 1 or multiple scales, use subdirectory. 608 float scaleValue = scale[scaleIndex]; 609 if (scale.length != 1) { 610 packFileName = (scaleValue == (int)scaleValue ? Integer.toString((int)scaleValue) : Float.toString(scaleValue)) 611 + "/" + packFileName; 612 } 613 } 614 return packFileName; 615 } 616 } 617 618 /** Packs using defaults settings. 619 * @see TexturePacker#process(Settings, String, String, String) */ process(String input, String output, String packFileName)620 static public void process (String input, String output, String packFileName) { 621 process(new Settings(), input, output, packFileName); 622 } 623 624 /** @param input Directory containing individual images to be packed. 625 * @param output Directory where the pack file and page images will be written. 626 * @param packFileName The name of the pack file. Also used to name the page images. */ process(Settings settings, String input, String output, String packFileName)627 static public void process (Settings settings, String input, String output, String packFileName) { 628 try { 629 TexturePackerFileProcessor processor = new TexturePackerFileProcessor(settings, packFileName); 630 // Sort input files by name to avoid platform-dependent atlas output changes. 631 processor.setComparator(new Comparator<File>() { 632 public int compare (File file1, File file2) { 633 return file1.getName().compareTo(file2.getName()); 634 } 635 }); 636 processor.process(new File(input), new File(output)); 637 } catch (Exception ex) { 638 throw new RuntimeException("Error packing images.", ex); 639 } 640 } 641 642 /** @return true if the output file does not yet exist or its last modification date is before the last modification date of the 643 * input file */ isModified(String input, String output, String packFileName, Settings settings)644 static public boolean isModified (String input, String output, String packFileName, Settings settings) { 645 String packFullFileName = output; 646 647 if (!packFullFileName.endsWith("/")) { 648 packFullFileName += "/"; 649 } 650 651 // Check against the only file we know for sure will exist and will be changed if any asset changes: 652 // the atlas file 653 packFullFileName += packFileName; 654 packFullFileName += settings.atlasExtension; 655 File outputFile = new File(packFullFileName); 656 657 if (!outputFile.exists()) { 658 return true; 659 } 660 661 File inputFile = new File(input); 662 if (!inputFile.exists()) { 663 throw new IllegalArgumentException("Input file does not exist: " + inputFile.getAbsolutePath()); 664 } 665 666 return inputFile.lastModified() > outputFile.lastModified(); 667 } 668 processIfModified(String input, String output, String packFileName)669 static public void processIfModified (String input, String output, String packFileName) { 670 // Default settings (Needed to access the default atlas extension string) 671 Settings settings = new Settings(); 672 673 if (isModified(input, output, packFileName, settings)) { 674 process(settings, input, output, packFileName); 675 } 676 } 677 processIfModified(Settings settings, String input, String output, String packFileName)678 static public void processIfModified (Settings settings, String input, String output, String packFileName) { 679 if (isModified(input, output, packFileName, settings)) { 680 process(settings, input, output, packFileName); 681 } 682 } 683 684 static public interface Packer { pack(Array<Rect> inputRects)685 public Array<Page> pack (Array<Rect> inputRects); 686 } 687 688 static final class InputImage { 689 File file; 690 String name; 691 BufferedImage image; 692 } 693 main(String[] args)694 static public void main (String[] args) throws Exception { 695 Settings settings = null; 696 String input = null, output = null, packFileName = "pack.atlas"; 697 698 switch (args.length) { 699 case 4: 700 settings = new Json().fromJson(Settings.class, new FileReader(args[3])); 701 case 3: 702 packFileName = args[2]; 703 case 2: 704 output = args[1]; 705 case 1: 706 input = args[0]; 707 break; 708 default: 709 System.out.println("Usage: inputDir [outputDir] [packFileName] [settingsFileName]"); 710 System.exit(0); 711 } 712 713 if (output == null) { 714 File inputFile = new File(input); 715 output = new File(inputFile.getParentFile(), inputFile.getName() + "-packed").getAbsolutePath(); 716 } 717 if (settings == null) 718 settings = new Settings(); 719 720 process(settings, input, output, packFileName); 721 } 722 } 723