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 com.badlogic.gdx.tools.FileProcessor; 20 import com.badlogic.gdx.tools.texturepacker.TexturePacker.Settings; 21 import com.badlogic.gdx.utils.GdxRuntimeException; 22 import com.badlogic.gdx.utils.Json; 23 import com.badlogic.gdx.utils.JsonReader; 24 import com.badlogic.gdx.utils.ObjectMap; 25 import com.badlogic.gdx.utils.ObjectSet; 26 27 import java.io.File; 28 import java.io.FileReader; 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.Comparator; 32 import java.util.regex.Matcher; 33 import java.util.regex.Pattern; 34 35 /** @author Nathan Sweet */ 36 public class TexturePackerFileProcessor extends FileProcessor { 37 private final Settings defaultSettings; 38 private ObjectMap<File, Settings> dirToSettings = new ObjectMap(); 39 private Json json = new Json(); 40 private String packFileName; 41 private File root; 42 ArrayList<File> ignoreDirs = new ArrayList(); 43 44 public TexturePackerFileProcessor () { 45 this(new Settings(), "pack.atlas"); 46 } 47 48 public TexturePackerFileProcessor (Settings defaultSettings, String packFileName) { 49 this.defaultSettings = defaultSettings; 50 51 if (packFileName.toLowerCase().endsWith(defaultSettings.atlasExtension.toLowerCase())) 52 packFileName = packFileName.substring(0, packFileName.length() - defaultSettings.atlasExtension.length()); 53 this.packFileName = packFileName; 54 55 setFlattenOutput(true); 56 addInputSuffix(".png", ".jpg", ".jpeg"); 57 } 58 59 public ArrayList<Entry> process (File inputFile, File outputRoot) throws Exception { 60 root = inputFile; 61 62 // Collect pack.json setting files. 63 final ArrayList<File> settingsFiles = new ArrayList(); 64 FileProcessor settingsProcessor = new FileProcessor() { 65 protected void processFile (Entry inputFile) throws Exception { 66 settingsFiles.add(inputFile.inputFile); 67 } 68 }; 69 settingsProcessor.addInputRegex("pack\\.json"); 70 settingsProcessor.process(inputFile, null); 71 // Sort parent first. 72 Collections.sort(settingsFiles, new Comparator<File>() { 73 public int compare (File file1, File file2) { 74 return file1.toString().length() - file2.toString().length(); 75 } 76 }); 77 for (File settingsFile : settingsFiles) { 78 // Find first parent with settings, or use defaults. 79 Settings settings = null; 80 File parent = settingsFile.getParentFile(); 81 while (true) { 82 if (parent.equals(root)) break; 83 parent = parent.getParentFile(); 84 settings = dirToSettings.get(parent); 85 if (settings != null) { 86 settings = new Settings(settings); 87 break; 88 } 89 } 90 if (settings == null) settings = new Settings(defaultSettings); 91 // Merge settings from current directory. 92 merge(settings, settingsFile); 93 dirToSettings.put(settingsFile.getParentFile(), settings); 94 } 95 96 // Do actual processing. 97 return super.process(inputFile, outputRoot); 98 } 99 100 private void merge (Settings settings, File settingsFile) { 101 try { 102 json.readFields(settings, new JsonReader().parse(new FileReader(settingsFile))); 103 } catch (Exception ex) { 104 throw new GdxRuntimeException("Error reading settings file: " + settingsFile, ex); 105 } 106 } 107 108 public ArrayList<Entry> process (File[] files, File outputRoot) throws Exception { 109 // Delete pack file and images. 110 if (outputRoot.exists()) { 111 // Load root settings to get scale. 112 File settingsFile = new File(root, "pack.json"); 113 Settings rootSettings = defaultSettings; 114 if (settingsFile.exists()) { 115 rootSettings = new Settings(rootSettings); 116 merge(rootSettings, settingsFile); 117 } 118 119 for (int i = 0, n = rootSettings.scale.length; i < n; i++) { 120 FileProcessor deleteProcessor = new FileProcessor() { 121 protected void processFile (Entry inputFile) throws Exception { 122 inputFile.inputFile.delete(); 123 } 124 }; 125 deleteProcessor.setRecursive(false); 126 127 String scaledPackFileName = rootSettings.getScaledPackFileName(packFileName, i); 128 File packFile = new File(scaledPackFileName); 129 130 String prefix = packFile.getName(); 131 int dotIndex = prefix.lastIndexOf('.'); 132 if (dotIndex != -1) prefix = prefix.substring(0, dotIndex); 133 deleteProcessor.addInputRegex("(?i)" + prefix + "\\d*\\.(png|jpg|jpeg)"); 134 deleteProcessor.addInputRegex("(?i)" + prefix + "\\.atlas"); 135 136 String dir = packFile.getParent(); 137 if (dir == null) 138 deleteProcessor.process(outputRoot, null); 139 else if (new File(outputRoot + "/" + dir).exists()) // 140 deleteProcessor.process(outputRoot + "/" + dir, null); 141 } 142 } 143 return super.process(files, outputRoot); 144 } 145 146 protected void processDir (Entry inputDir, ArrayList<Entry> files) throws Exception { 147 if (ignoreDirs.contains(inputDir.inputFile)) return; 148 149 // Find first parent with settings, or use defaults. 150 Settings settings = null; 151 File parent = inputDir.inputFile; 152 while (true) { 153 settings = dirToSettings.get(parent); 154 if (settings != null) break; 155 if (parent.equals(root)) break; 156 parent = parent.getParentFile(); 157 } 158 if (settings == null) settings = defaultSettings; 159 160 if (settings.combineSubdirectories) { 161 // Collect all files under subdirectories and ignore subdirectories so they won't be packed twice. 162 files = new FileProcessor(this) { 163 protected void processDir (Entry entryDir, ArrayList<Entry> files) { 164 ignoreDirs.add(entryDir.inputFile); 165 } 166 167 protected void processFile (Entry entry) { 168 addProcessedFile(entry); 169 } 170 }.process(inputDir.inputFile, null); 171 } 172 173 if (files.isEmpty()) return; 174 175 // Sort by name using numeric suffix, then alpha. 176 Collections.sort(files, new Comparator<Entry>() { 177 final Pattern digitSuffix = Pattern.compile("(.*?)(\\d+)$"); 178 179 public int compare (Entry entry1, Entry entry2) { 180 String full1 = entry1.inputFile.getName(); 181 int dotIndex = full1.lastIndexOf('.'); 182 if (dotIndex != -1) full1 = full1.substring(0, dotIndex); 183 184 String full2 = entry2.inputFile.getName(); 185 dotIndex = full2.lastIndexOf('.'); 186 if (dotIndex != -1) full2 = full2.substring(0, dotIndex); 187 188 String name1 = full1, name2 = full2; 189 int num1 = 0, num2 = 0; 190 191 Matcher matcher = digitSuffix.matcher(full1); 192 if (matcher.matches()) { 193 try { 194 num1 = Integer.parseInt(matcher.group(2)); 195 name1 = matcher.group(1); 196 } catch (Exception ignored) { 197 } 198 } 199 matcher = digitSuffix.matcher(full2); 200 if (matcher.matches()) { 201 try { 202 num2 = Integer.parseInt(matcher.group(2)); 203 name2 = matcher.group(1); 204 } catch (Exception ignored) { 205 } 206 } 207 int compare = name1.compareTo(name2); 208 if (compare != 0 || num1 == num2) return compare; 209 return num1 - num2; 210 } 211 }); 212 213 // Pack. 214 if (!settings.silent) System.out.println(inputDir.inputFile.getName()); 215 TexturePacker packer = new TexturePacker(root, settings); 216 for (Entry file : files) 217 packer.addImage(file.inputFile); 218 packer.pack(inputDir.outputDir, packFileName); 219 } 220 } 221