package micropolisj.build_tool; import micropolisj.engine.TileSpec; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.nio.charset.Charset; import java.util.*; import javax.imageio.*; import javax.swing.ImageIcon; import javax.xml.stream.*; import static micropolisj.engine.TileSpec.generateTileNames; import static micropolisj.build_tool.TileImage.*; public class MakeTiles { static HashMap tileData = new HashMap(); static HashMap loadedImages = new HashMap(); static final Charset UTF8 = Charset.forName("UTF-8"); static int SKIP_TILES = 0; static int COUNT_TILES = -1; static int TILE_SIZE = STD_SIZE; public static void main(String [] args) throws Exception { if (args.length != 2) { throw new Exception("Wrong number of arguments"); } if (System.getProperty("tile_size") != null) { TILE_SIZE = Integer.parseInt(System.getProperty("tile_size")); } if (System.getProperty("skip_tiles") != null) { SKIP_TILES = Integer.parseInt(System.getProperty("skip_tiles")); } if (System.getProperty("tile_count") != null) { COUNT_TILES = Integer.parseInt(System.getProperty("tile_count")); } File recipeFile = new File(args[0]); File outputDir = new File(args[1]); generateFromRecipe(recipeFile, outputDir); } static class TileMapping { String tileName; TileImage ref; TileImage dest; TileMapping(String tileName, TileImage ref, TileImage dest) { this.tileName = tileName; this.ref = ref; this.dest = dest; } } static void generateFromRecipe(File recipeFile, File outputDir) throws IOException { Properties recipe = new Properties(); recipe.load( new InputStreamReader( new FileInputStream(recipeFile), UTF8 )); // count number of images String [] tileNames = generateTileNames(recipe); int ntiles = COUNT_TILES == -1 ? tileNames.length : COUNT_TILES; // prepare mapping data int nextOffsetY = 0; ArrayList mappings = new ArrayList(); for (int i = 0; i < ntiles; i++) { int tileNumber = SKIP_TILES + i; if (!(tileNumber >= 0 && tileNumber < tileNames.length)) { continue; } String tileName = tileNames[tileNumber]; String rawSpec = recipe.getProperty(tileName); assert rawSpec != null; TileSpec tileSpec = TileSpec.parse(tileNumber, tileName, rawSpec, recipe); TileImage ref = parseFrameSpec(tileSpec); if (ref == null) { // tile is defined, but it has no images continue; } TileImage dest; if (ref.getFrameEndTime(0) > 0) { Animation ani = new Animation(); int t = 0; int n = ref.getFrameEndTime(t); while (n > 0) { TileImageSprite s = new TileImageSprite(); s.offsetY = nextOffsetY; nextOffsetY += TILE_SIZE; Animation.Frame f = new Animation.Frame(s, n-t); ani.addFrame(f); t = n; n = ref.getFrameEndTime(t); } dest = ani; } else { TileImageSprite s = new TileImageSprite(); s.offsetY = nextOffsetY; nextOffsetY += TILE_SIZE; dest = s; } TileMapping m = new TileMapping(tileName, ref, dest); mappings.add(m); } // actually assemble the image BufferedImage buf = new BufferedImage(TILE_SIZE,nextOffsetY,BufferedImage.TYPE_INT_RGB); Graphics2D gr = buf.createGraphics(); for (TileMapping m : mappings) { assert (m.dest instanceof Animation) || (m.dest instanceof TileImageSprite); if (m.dest instanceof Animation) { Animation ani = (Animation) m.dest; for (Animation.Frame f : ani.frames) { TileImageSprite s = (TileImageSprite) f.frame; m.ref.drawTo(gr, s.offsetX, s.offsetY, 0, 0); } } else { TileImageSprite sprite = (TileImageSprite) m.dest; m.ref.drawTo(gr, sprite.offsetX, sprite.offsetY, 0, 0); } } // make parent directories if necessary outputDir.mkdirs(); // output the composed images File outputFile = new File(outputDir, "tiles.png"); System.out.println("Generating tiles array: "+outputFile); ImageIO.write(buf, "png", outputFile); // output an index of all tile names and their offset into // the composed tile array File indexFile = new File(outputDir, "tiles.idx"); System.out.println("Generating tiles index: "+indexFile); writeIndexFile(mappings, indexFile); } static void writeIndexFile(Collection mappings, File indexFile) throws IOException { try { FileOutputStream outStream = new FileOutputStream(indexFile); XMLStreamWriter out = XMLOutputFactory.newInstance().createXMLStreamWriter(outStream, "UTF-8"); out.writeStartDocument(); out.writeStartElement("micropolis-tiles-index"); for (TileMapping m : mappings) { out.writeStartElement("tile"); out.writeAttribute("name", m.tileName); assert (m.dest instanceof Animation) || (m.dest instanceof TileImageSprite); if (m.dest instanceof Animation) { Animation ani = (Animation) m.dest; out.writeStartElement("animation"); for (Animation.Frame f : ani.frames) { TileImageSprite s = (TileImageSprite) f.frame; out.writeStartElement("frame"); out.writeAttribute("offsetY", Integer.toString(s.offsetY / TILE_SIZE)); out.writeEndElement(); } out.writeEndElement(); } else { //assume it is a simple sprite TileImageSprite s = (TileImageSprite ) m.dest; out.writeStartElement("image"); out.writeAttribute("offsetY", Integer.toString(s.offsetY / TILE_SIZE)); out.writeEndElement(); } out.writeEndElement(); } out.writeEndElement(); out.writeEndDocument(); out.close(); outStream.close(); //because XMLStreamWriter does not call it for us } catch (XMLStreamException e) { throw new IOException(e); } } static TileImage parseFrameSpec(TileSpec spec) throws IOException { return parseFrameSpec(spec.getImages()); } static TileImage parseFrameSpec(String rawSpec) throws IOException { String [] parts = rawSpec.split("\\|"); for (int i = 0; i < parts.length; i++) { parts[i] = parts[i].trim(); } return parseFrameSpec(parts); } static TileImage parseFrameSpec(String [] layerStrings) throws IOException { if (layerStrings.length == 1) { return parseLayerSpec(layerStrings[0]); } TileImageLayer result = null; for (String layerStr : layerStrings) { TileImageLayer rv = new TileImageLayer(); rv.below = result; rv.above = parseLayerSpec(layerStr); result = rv; } return result; } static TileImage parseLayerSpec(String layerStr) throws IOException { String [] parts = layerStr.split("@", 2); TileImage img = loadAnimation(parts[0]); if (parts.length >= 2) { TileImageSprite sprite = new TileImageSprite(); sprite.source = img; String offsetInfo = parts[1]; parts = offsetInfo.split(","); if (parts.length >= 1) { sprite.offsetX = Integer.parseInt(parts[0]); } if (parts.length >= 2) { sprite.offsetY = Integer.parseInt(parts[1]); } return sprite; }//endif something given after '@' in image specifier return img; } static File findInkscape() { String exeName = "inkscape"; if (System.getProperty("os.name").startsWith("Windows")) { exeName += ".exe"; } File [] pathsToTry = { new File("/usr/bin"), new File("c:\\Program Files\\Inkscape"), new File("c:\\Program Files (x86)\\Inkscape") }; for (File p : pathsToTry) { File f = new File(p, exeName); if (f.exists()) { return f; } } throw new Error("INKSCAPE not installed (or not found)"); } static File stagingDir = new File("generated"); static File renderSvg(String fileName, File svgFile) throws IOException { File pngFile = new File(stagingDir, fileName+"_"+TILE_SIZE+"x"+TILE_SIZE+".png"); if (pngFile.exists() && pngFile.lastModified() > svgFile.lastModified()) { // looks like the PNG file is already up-to-date return pngFile; } File inkscapeBin = findInkscape(); System.out.println("Generating raster image: "+pngFile); if (pngFile.exists()) { pngFile.delete(); } else { pngFile.getParentFile().mkdirs(); } String [] cmdline = { inkscapeBin.toString(), "--export-dpi="+(TILE_SIZE*90.0/STD_SIZE), "--export-png="+pngFile.toString(), svgFile.toString() }; Process p = Runtime.getRuntime().exec(cmdline); int exit_value; try { exit_value = p.waitFor(); } catch (InterruptedException e) { throw new RuntimeException(e); } if (exit_value != 0) { throw new RuntimeException("Helper exit status: "+exit_value); } if (!pngFile.exists()) { throw new RuntimeException("File not found: "+pngFile); } return pngFile; } static TileImage loadAnimation(String fileName) throws IOException { File f = new File(fileName + ".ani"); if (f.exists()) { return Animation.load(f); } else { return loadImage(fileName); } } static SourceImage loadImage(String fileName) throws IOException { if (!loadedImages.containsKey(fileName)) { loadedImages.put(fileName, loadImageReal(fileName)); } return loadedImages.get(fileName); } static SourceImage loadImageReal(String fileName) throws IOException { File svgFile, pngFile = null; svgFile = new File(fileName+"_"+TILE_SIZE+"x"+TILE_SIZE+".svg"); if (svgFile.exists()) { pngFile = renderSvg(fileName, svgFile); } else { svgFile = new File(fileName+".svg"); if (svgFile.exists()) { pngFile = renderSvg(fileName, svgFile); } } if (pngFile != null && pngFile.exists()) { ImageIcon ii = new ImageIcon(pngFile.toString()); return new SourceImage( ii.getImage(), TILE_SIZE, TILE_SIZE); } pngFile = new File(fileName+"_"+TILE_SIZE+"x"+TILE_SIZE+".png"); if (pngFile.exists()) { ImageIcon ii = new ImageIcon(pngFile.toString()); return new SourceImage( ii.getImage(), TILE_SIZE, TILE_SIZE); } pngFile = new File(fileName+".png"); if (pngFile.exists()) { ImageIcon ii = new ImageIcon(pngFile.toString()); return new SourceImage( ii.getImage(), STD_SIZE, TILE_SIZE); } throw new IOException("File not found: "+fileName+".{svg,png}"); } }