diff --git a/CHANGES b/CHANGES index ea50859296e..7dafe4c845c 100755 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,6 @@ [1.9.13] - [BREAKING CHANGE] Fixed keycode representations for ESCAPE, END, INSERT and F1 to F12. These keys are working on Android now, but if you hardcoded or saved the values you might need to migrate. +- [BREAKING CHANGE] TextureAtlas.AtlasRegion and Region splits and pads fields have been removed and moved to name/value pairs, use #findValue("split") and #findValue("pad") instead. - GWT: Key codes set with Gdx.input.setCatchKey prevent default browser behaviour - Added Scaling.contain mode: Scales the source to fit the target while keeping the same aspect ratio, but the source is not scaled at all if smaller in both directions. - API Addition: Added hasContents() to Clipboard interface, to reduce clipboard notifications on iOS 14 @@ -8,6 +9,8 @@ Following changes might be done depending on platform: Keys.STAR to Keys.NUMPAD_MULTIPLY, Keys.SLASH to Keys.NUMPAD_DIVIDE, Keys.NUM to Keys.NUM_LOCK, Keys.COMMA to Keys.NUMPAD_COMMA, Keys.PERIOD to Keys.NUMPAD_DOT, Keys.COMMA to Keys.NUMPAD_COMMA, Keys.ENTER to Keys.NUMPAD_ENTER, Keys.PLUS to Keys.NUMPAD_ADD, Keys.MINUS to Keys.NUMPAD_SUBTRACT - Added a QuadFloatTree class. - Desktop: Cubemap Seamless feature is now enabled by default when supported, can be changed via backend specific methods, see supportsCubeMapSeamless and enableCubeMapSeamless in both LwjglGraphics and Lwjgl3Graphics. +- API Addition: TextureAtlas reads arbitrary name/value pairs for each region. See #6316. +- TexturePacker writes using a new format when legacyOutput is false (default is true). TextureAtlas can read both old and new formats. See #6316. [1.9.12] - [BREAKING CHANGE] iOS: Changed how Retina/hdpi handled on iOS. See #3709. diff --git a/extensions/gdx-tools/src/com/badlogic/gdx/tools/texturepacker/TexturePacker.java b/extensions/gdx-tools/src/com/badlogic/gdx/tools/texturepacker/TexturePacker.java index 9f81a2d4514..32f56e023b2 100644 --- a/extensions/gdx-tools/src/com/badlogic/gdx/tools/texturepacker/TexturePacker.java +++ b/extensions/gdx-tools/src/com/badlogic/gdx/tools/texturepacker/TexturePacker.java @@ -44,6 +44,7 @@ import com.badlogic.gdx.utils.FloatArray; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.Json; +import com.badlogic.gdx.utils.Null; import java.awt.Color; import java.awt.Graphics2D; @@ -402,31 +403,117 @@ private void writePackFile (File outputDir, String scaledPackFileName, Array aliases = new Array(rect.aliases.toArray()); aliases.sort(); for (Alias alias : aliases) { Rect aliasRect = new Rect(); aliasRect.set(rect); alias.apply(aliasRect); - writeRect(writer, page, aliasRect, alias.name); + if (settings.legacyOutput) + writeRectLegacy(writer, page, aliasRect, alias.name); + else + writeRect(writer, page, aliasRect, alias.name); } } } writer.close(); } + private void writePage (OutputStreamWriter writer, boolean appending, Page page) throws IOException { + String tab = "", colon = ":", comma = ","; + if (settings.prettyPrint) { + tab = "\t"; + colon = ": "; + comma = ", "; + } + + writer.write(page.imageName + "\n"); + writer.write(tab + "size" + colon + page.imageWidth + comma + page.imageHeight + "\n"); + + if (settings.format != Format.RGBA8888) writer.write(tab + "format" + colon + settings.format + "\n"); + + if (settings.filterMin != TextureFilter.Nearest || settings.filterMag != TextureFilter.Nearest) + writer.write(tab + "filter" + colon + settings.filterMin + comma + settings.filterMag + "\n"); + + String repeatValue = getRepeatValue(); + if (repeatValue != null) writer.write(tab + "repeat" + colon + repeatValue + "\n"); + + if (settings.premultiplyAlpha) writer.write(tab + "pma" + colon + "true\n"); + } + private void writeRect (Writer writer, Page page, Rect rect, String name) throws IOException { + String tab = "", colon = ":", comma = ","; + if (settings.prettyPrint) { + tab = "\t"; + colon = ": "; + comma = ", "; + } + + writer.write(Rect.getAtlasName(name, settings.flattenPaths) + "\n"); + if (rect.index != -1) writer.write(tab + "index" + colon + rect.index + "\n"); + + writer.write(tab + "bounds" + colon // + + (page.x + rect.x) + comma + (page.y + page.height - rect.y - (rect.height - settings.paddingY)) + comma // + + rect.regionWidth + comma + rect.regionHeight + "\n"); + + int offsetY = rect.originalHeight - rect.regionHeight - rect.offsetY; + if (rect.offsetX != 0 || offsetY != 0 // + || rect.originalWidth != rect.regionWidth || rect.originalHeight != rect.regionHeight) { + writer.write(tab + "offsets" + colon // + + rect.offsetX + comma + offsetY + comma // + + rect.originalWidth + comma + rect.originalHeight + "\n"); + } + + if (rect.rotated) writer.write(tab + "rotate" + colon + rect.rotated + "\n"); + + if (rect.splits != null) { + writer.write(tab + "split" + colon // + + rect.splits[0] + comma + rect.splits[1] + comma // + + rect.splits[2] + comma + rect.splits[3] + "\n"); + } + + if (rect.pads != null) { + if (rect.splits == null) writer.write(tab + "split" + colon + "0" + comma + "0" + comma + "0" + comma + "0\n"); + writer.write( + tab + "pad" + colon + rect.pads[0] + comma + rect.pads[1] + comma + rect.pads[2] + comma + rect.pads[3] + "\n"); + } + } + + private void writePageLegacy (OutputStreamWriter writer, Page page) throws IOException { + writer.write("\n" + page.imageName + "\n"); + writer.write("size: " + page.imageWidth + ", " + page.imageHeight + "\n"); + writer.write("format: " + settings.format + "\n"); + writer.write("filter: " + settings.filterMin + ", " + settings.filterMag + "\n"); + String repeatValue = getRepeatValue(); + writer.write("repeat: " + (repeatValue == null ? "none" : repeatValue) + "\n"); + } + + private void writeRectLegacy (Writer writer, Page page, Rect rect, String name) throws IOException { writer.write(Rect.getAtlasName(name, settings.flattenPaths) + "\n"); writer.write(" rotate: " + rect.rotated + "\n"); writer @@ -446,11 +533,11 @@ private void writeRect (Writer writer, Page page, Rect rect, String name) throws writer.write(" index: " + rect.index + "\n"); } - private String getRepeatValue () { + private @Null String getRepeatValue () { if (settings.wrapX == TextureWrap.Repeat && settings.wrapY == TextureWrap.Repeat) return "xy"; if (settings.wrapX == TextureWrap.Repeat && settings.wrapY == TextureWrap.ClampToEdge) return "x"; if (settings.wrapX == TextureWrap.ClampToEdge && settings.wrapY == TextureWrap.Repeat) return "y"; - return "none"; + return null; } private int getBufferedImageType (Format format) { @@ -843,6 +930,8 @@ static public class Settings { public String[] scaleSuffix = {""}; public Resampling[] scaleResampling = {Resampling.bicubic}; public String atlasExtension = ".atlas"; + public boolean prettyPrint = true; + public boolean legacyOutput = true; public Settings () { } @@ -894,6 +983,8 @@ public void set (Settings settings) { scaleSuffix = Arrays.copyOf(settings.scaleSuffix, settings.scaleSuffix.length); scaleResampling = Arrays.copyOf(settings.scaleResampling, settings.scaleResampling.length); atlasExtension = settings.atlasExtension; + prettyPrint = settings.prettyPrint; + legacyOutput = settings.legacyOutput; } public String getScaledPackFileName (String packFileName, int scaleIndex) { diff --git a/extensions/gdx-tools/src/com/badlogic/gdx/tools/texturepacker/TextureUnpacker.java b/extensions/gdx-tools/src/com/badlogic/gdx/tools/texturepacker/TextureUnpacker.java index 6a809cada6b..a132d7c83cb 100644 --- a/extensions/gdx-tools/src/com/badlogic/gdx/tools/texturepacker/TextureUnpacker.java +++ b/extensions/gdx-tools/src/com/badlogic/gdx/tools/texturepacker/TextureUnpacker.java @@ -100,7 +100,7 @@ public void splitAtlas (TextureAtlasData atlas, String outputDir) { String extension = null; // check if the region is a ninepatch or a normal image and delegate accordingly - if (region.splits == null) { + if (region.findValue("split") == null) { splitImage = extractImage(img, region, outputDirFile, 0); if (region.width != region.originalWidth || region.height != region.originalHeight) { BufferedImage originalImg = new BufferedImage(region.originalWidth, region.originalHeight, img.getType()); @@ -181,17 +181,19 @@ private BufferedImage extractNinePatch (BufferedImage page, Region region, File g2.setColor(Color.BLACK); // Draw the four lines to save the ninepatch's padding and splits - int startX = region.splits[0] + NINEPATCH_PADDING; - int endX = region.width - region.splits[1] + NINEPATCH_PADDING - 1; - int startY = region.splits[2] + NINEPATCH_PADDING; - int endY = region.height - region.splits[3] + NINEPATCH_PADDING - 1; + int[] splits = region.findValue("split"); + int startX = splits[0] + NINEPATCH_PADDING; + int endX = region.width - splits[1] + NINEPATCH_PADDING - 1; + int startY = splits[2] + NINEPATCH_PADDING; + int endY = region.height - splits[3] + NINEPATCH_PADDING - 1; if (endX >= startX) g2.drawLine(startX, 0, endX, 0); if (endY >= startY) g2.drawLine(0, startY, 0, endY); - if (region.pads != null) { - int padStartX = region.pads[0] + NINEPATCH_PADDING; - int padEndX = region.width - region.pads[1] + NINEPATCH_PADDING - 1; - int padStartY = region.pads[2] + NINEPATCH_PADDING; - int padEndY = region.height - region.pads[3] + NINEPATCH_PADDING - 1; + int[] pads = region.findValue("pad"); + if (pads != null) { + int padStartX = pads[0] + NINEPATCH_PADDING; + int padEndX = region.width - pads[1] + NINEPATCH_PADDING - 1; + int padStartY = pads[2] + NINEPATCH_PADDING; + int padEndY = region.height - pads[3] + NINEPATCH_PADDING - 1; g2.drawLine(padStartX, splitImage.getHeight() - 1, padEndX, splitImage.getHeight() - 1); g2.drawLine(splitImage.getWidth() - 1, padStartY, splitImage.getWidth() - 1, padEndY); } diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java b/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java index d1a6d1d3543..5416c0c9538 100755 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java @@ -365,8 +365,8 @@ public synchronized void updateTextureAtlas (TextureAtlas atlas, TextureFilter m TextureAtlas.AtlasRegion region = new TextureAtlas.AtlasRegion(page.texture, (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); if (rect.splits != null) { - region.splits = rect.splits; - region.pads = rect.pads; + region.names = new String[] {"split", "pad"}; + region.values = new int[][] {rect.splits, rect.pads}; } int imageIndex = -1; diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index 28ac89eb549..a68a1b60898 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -18,6 +18,11 @@ import static com.badlogic.gdx.graphics.Texture.TextureWrap.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Comparator; + import com.badlogic.gdx.Files.FileType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; @@ -30,186 +35,19 @@ import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.GdxRuntimeException; +import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.ObjectSet; -import com.badlogic.gdx.utils.Sort; import com.badlogic.gdx.utils.StreamUtils; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Set; - /** Loads images from texture atlases created by TexturePacker.
*
* A TextureAtlas must be disposed to free up the resources consumed by the backing textures. * @author Nathan Sweet */ public class TextureAtlas implements Disposable { - static final String[] tuple = new String[4]; - private final ObjectSet textures = new ObjectSet(4); private final Array regions = new Array(); - public static class TextureAtlasData { - public static class Page { - public final FileHandle textureFile; - public Texture texture; - public final float width, height; - public final boolean useMipMaps; - public final Format format; - public final TextureFilter minFilter; - public final TextureFilter magFilter; - public final TextureWrap uWrap; - public final TextureWrap vWrap; - - public Page (FileHandle handle, float width, float height, boolean useMipMaps, Format format, TextureFilter minFilter, - TextureFilter magFilter, TextureWrap uWrap, TextureWrap vWrap) { - this.width = width; - this.height = height; - this.textureFile = handle; - this.useMipMaps = useMipMaps; - this.format = format; - this.minFilter = minFilter; - this.magFilter = magFilter; - this.uWrap = uWrap; - this.vWrap = vWrap; - } - } - - public static class Region { - public Page page; - public int index; - public String name; - public float offsetX; - public float offsetY; - public int originalWidth; - public int originalHeight; - public boolean rotate; - public int degrees; - public int left; - public int top; - public int width; - public int height; - public boolean flip; - public int[] splits; - public int[] pads; - } - - final Array pages = new Array(); - final Array regions = new Array(); - - public TextureAtlasData (FileHandle packFile, FileHandle imagesDir, boolean flip) { - BufferedReader reader = new BufferedReader(new InputStreamReader(packFile.read()), 64); - try { - Page pageImage = null; - while (true) { - String line = reader.readLine(); - if (line == null) break; - if (line.trim().length() == 0) - pageImage = null; - else if (pageImage == null) { - FileHandle file = imagesDir.child(line); - - float width = 0, height = 0; - if (readTuple(reader) == 2) { // size is only optional for an atlas packed with an old TexturePacker. - width = Integer.parseInt(tuple[0]); - height = Integer.parseInt(tuple[1]); - readTuple(reader); - } - Format format = Format.valueOf(tuple[0]); - - readTuple(reader); - TextureFilter min = TextureFilter.valueOf(tuple[0]); - TextureFilter max = TextureFilter.valueOf(tuple[1]); - - String direction = readValue(reader); - TextureWrap repeatX = ClampToEdge; - TextureWrap repeatY = ClampToEdge; - if (direction.equals("x")) - repeatX = Repeat; - else if (direction.equals("y")) - repeatY = Repeat; - else if (direction.equals("xy")) { - repeatX = Repeat; - repeatY = Repeat; - } - - pageImage = new Page(file, width, height, min.isMipMap(), format, min, max, repeatX, repeatY); - pages.add(pageImage); - } else { - String rotateValue = readValue(reader); - int degrees; - if (rotateValue.equalsIgnoreCase("true")) - degrees = 90; - else if (rotateValue.equalsIgnoreCase("false")) - degrees = 0; - else - degrees = Integer.valueOf(rotateValue); - - readTuple(reader); - int left = Integer.parseInt(tuple[0]); - int top = Integer.parseInt(tuple[1]); - - readTuple(reader); - int width = Integer.parseInt(tuple[0]); - int height = Integer.parseInt(tuple[1]); - - Region region = new Region(); - region.page = pageImage; - region.left = left; - region.top = top; - region.width = width; - region.height = height; - region.name = line; - region.rotate = degrees == 90; - region.degrees = degrees; - - if (readTuple(reader) == 4) { // split is optional - region.splits = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]), - Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])}; - - if (readTuple(reader) == 4) { // pad is optional, but only present with splits - region.pads = new int[] {Integer.parseInt(tuple[0]), Integer.parseInt(tuple[1]), - Integer.parseInt(tuple[2]), Integer.parseInt(tuple[3])}; - - readTuple(reader); - } - } - - region.originalWidth = Integer.parseInt(tuple[0]); - region.originalHeight = Integer.parseInt(tuple[1]); - - readTuple(reader); - region.offsetX = Integer.parseInt(tuple[0]); - region.offsetY = Integer.parseInt(tuple[1]); - - region.index = Integer.parseInt(readValue(reader)); - - if (flip) region.flip = true; - - regions.add(region); - } - } - } catch (Exception ex) { - throw new GdxRuntimeException("Error reading pack file: " + packFile, ex); - } finally { - StreamUtils.closeQuietly(reader); - } - - regions.sort(indexComparator); - } - - public Array getPages () { - return pages; - } - - public Array getRegions () { - return regions; - } - } - /** Creates an empty atlas to which regions can be added. */ public TextureAtlas () { } @@ -240,33 +78,20 @@ public TextureAtlas (FileHandle packFile, FileHandle imagesDir, boolean flip) { this(new TextureAtlasData(packFile, imagesDir, flip)); } - /** @param data May be null. */ - public TextureAtlas (TextureAtlasData data) { - if (data != null) load(data); - } - - private void load (TextureAtlasData data) { - ObjectMap pageToTexture = new ObjectMap(); + public TextureAtlas (@Null TextureAtlasData data) { + textures.ensureCapacity(data.pages.size); for (Page page : data.pages) { - Texture texture = null; - if (page.texture == null) { - texture = new Texture(page.textureFile, page.format, page.useMipMaps); - texture.setFilter(page.minFilter, page.magFilter); - texture.setWrap(page.uWrap, page.vWrap); - } else { - texture = page.texture; - texture.setFilter(page.minFilter, page.magFilter); - texture.setWrap(page.uWrap, page.vWrap); - } - textures.add(texture); - pageToTexture.put(page, texture); + if (page.texture == null) page.texture = new Texture(page.textureFile, page.format, page.useMipMaps); + page.texture.setFilter(page.minFilter, page.magFilter); + page.texture.setWrap(page.uWrap, page.vWrap); + textures.add(page.texture); } + regions.ensureCapacity(data.regions.size); for (Region region : data.regions) { - int width = region.width; - int height = region.height; - AtlasRegion atlasRegion = new AtlasRegion(pageToTexture.get(region.page), region.left, region.top, - region.rotate ? height : width, region.rotate ? width : height); + AtlasRegion atlasRegion = new AtlasRegion(region.page.texture, region.left, region.top, // + region.rotate ? region.height : region.width, // + region.rotate ? region.width : region.height); atlasRegion.index = region.index; atlasRegion.name = region.name; atlasRegion.offsetX = region.offsetX; @@ -275,8 +100,8 @@ private void load (TextureAtlasData data) { atlasRegion.originalWidth = region.originalWidth; atlasRegion.rotate = region.rotate; atlasRegion.degrees = region.degrees; - atlasRegion.splits = region.splits; - atlasRegion.pads = region.pads; + atlasRegion.names = region.names; + atlasRegion.values = region.values; if (region.flip) atlasRegion.flip(false, true); regions.add(atlasRegion); } @@ -287,7 +112,6 @@ public AtlasRegion addRegion (String name, Texture texture, int x, int y, int wi textures.add(texture); AtlasRegion region = new AtlasRegion(texture, x, y, width, height); region.name = name; - region.index = -1; regions.add(region); return region; } @@ -297,7 +121,6 @@ public AtlasRegion addRegion (String name, TextureRegion textureRegion) { textures.add(textureRegion.texture); AtlasRegion region = new AtlasRegion(textureRegion); region.name = name; - region.index = -1; regions.add(region); return region; } @@ -307,19 +130,17 @@ public Array getRegions () { return regions; } - /** Returns the first region found with the specified name. This method uses string comparison to find the region, so the result - * should be cached rather than calling this method multiple times. - * @return The region, or null. */ - public AtlasRegion findRegion (String name) { + /** Returns the first region found with the specified name. This method uses string comparison to find the region, so the + * result should be cached rather than calling this method multiple times. */ + public @Null AtlasRegion findRegion (String name) { for (int i = 0, n = regions.size; i < n; i++) if (regions.get(i).name.equals(name)) return regions.get(i); return null; } /** Returns the first region found with the specified name and index. This method uses string comparison to find the region, so - * the result should be cached rather than calling this method multiple times. - * @return The region, or null. */ - public AtlasRegion findRegion (String name, int index) { + * the result should be cached rather than calling this method multiple times. */ + public @Null AtlasRegion findRegion (String name, int index) { for (int i = 0, n = regions.size; i < n; i++) { AtlasRegion region = regions.get(i); if (!region.name.equals(name)) continue; @@ -330,7 +151,8 @@ public AtlasRegion findRegion (String name, int index) { } /** Returns all regions with the specified name, ordered by smallest to largest {@link AtlasRegion#index index}. This method - * uses string comparison to find the regions, so the result should be cached rather than calling this method multiple times. */ + * uses string comparison to find the regions, so the result should be cached rather than calling this method multiple + * times. */ public Array findRegions (String name) { Array matched = new Array(AtlasRegion.class); for (int i = 0, n = regions.size; i < n; i++) { @@ -352,31 +174,30 @@ public Array createSprites () { /** Returns the first region found with the specified name as a sprite. If whitespace was stripped from the region when it was * packed, the sprite is automatically positioned as if whitespace had not been stripped. This method uses string comparison to - * find the region and constructs a new sprite, so the result should be cached rather than calling this method multiple times. - * @return The sprite, or null. */ - public Sprite createSprite (String name) { + * find the region and constructs a new sprite, so the result should be cached rather than calling this method multiple + * times. */ + public @Null Sprite createSprite (String name) { for (int i = 0, n = regions.size; i < n; i++) if (regions.get(i).name.equals(name)) return newSprite(regions.get(i)); return null; } - /** Returns the first region found with the specified name and index as a sprite. This method uses string comparison to find the - * region and constructs a new sprite, so the result should be cached rather than calling this method multiple times. - * @return The sprite, or null. + /** Returns the first region found with the specified name and index as a sprite. This method uses string comparison to find + * the region and constructs a new sprite, so the result should be cached rather than calling this method multiple times. * @see #createSprite(String) */ - public Sprite createSprite (String name, int index) { + public @Null Sprite createSprite (String name, int index) { for (int i = 0, n = regions.size; i < n; i++) { AtlasRegion region = regions.get(i); - if (!region.name.equals(name)) continue; if (region.index != index) continue; + if (!region.name.equals(name)) continue; return newSprite(regions.get(i)); } return null; } - /** Returns all regions with the specified name as sprites, ordered by smallest to largest {@link AtlasRegion#index index}. This - * method uses string comparison to find the regions and constructs new sprites, so the result should be cached rather than - * calling this method multiple times. + /** Returns all regions with the specified name as sprites, ordered by smallest to largest {@link AtlasRegion#index index}. + * This method uses string comparison to find the regions and constructs new sprites, so the result should be cached rather + * than calling this method multiple times. * @see #createSprite(String) */ public Array createSprites (String name) { Array matched = new Array(Sprite.class); @@ -402,16 +223,16 @@ private Sprite newSprite (AtlasRegion region) { /** Returns the first region found with the specified name as a {@link NinePatch}. The region must have been packed with * ninepatch splits. This method uses string comparison to find the region and constructs a new ninepatch, so the result should - * be cached rather than calling this method multiple times. - * @return The ninepatch, or null. */ - public NinePatch createPatch (String name) { + * be cached rather than calling this method multiple times. */ + public @Null NinePatch createPatch (String name) { for (int i = 0, n = regions.size; i < n; i++) { AtlasRegion region = regions.get(i); if (region.name.equals(name)) { - int[] splits = region.splits; + int[] splits = region.findValue("split"); if (splits == null) throw new IllegalArgumentException("Region does not have ninepatch splits: " + name); NinePatch patch = new NinePatch(region, splits[0], splits[1], splits[2], splits[3]); - if (region.pads != null) patch.setPadding(region.pads[0], region.pads[1], region.pads[2], region.pads[3]); + int[] pads = region.findValue("pad"); + if (pads != null) patch.setPadding(pads[0], pads[1], pads[2], pads[3]); return patch; } } @@ -423,45 +244,264 @@ public ObjectSet getTextures () { return textures; } - /** Releases all resources associated with this TextureAtlas instance. This releases all the textures backing all TextureRegions - * and Sprites, which should no longer be used after calling dispose. */ + /** Releases all resources associated with this TextureAtlas instance. This releases all the textures backing all + * TextureRegions and Sprites, which should no longer be used after calling dispose. */ public void dispose () { for (Texture texture : textures) texture.dispose(); textures.clear(0); } - static final Comparator indexComparator = new Comparator() { - public int compare (Region region1, Region region2) { - int i1 = region1.index; - if (i1 == -1) i1 = Integer.MAX_VALUE; - int i2 = region2.index; - if (i2 == -1) i2 = Integer.MAX_VALUE; - return i1 - i2; - } - }; - - static String readValue (BufferedReader reader) throws IOException { - String line = reader.readLine(); - int colon = line.indexOf(':'); - if (colon == -1) throw new GdxRuntimeException("Invalid line: " + line); - return line.substring(colon + 1).trim(); - } + static public class TextureAtlasData { + final Array pages = new Array(); + final Array regions = new Array(); + + public TextureAtlasData (FileHandle packFile, FileHandle imagesDir, boolean flip) { + final String[] entry = new String[5]; + + ObjectMap> pageFields = new ObjectMap(15, 0.99f); // Size needed to avoid collisions. + pageFields.put("size", new Field() { + public void parse (Page page) { + page.width = Integer.parseInt(entry[1]); + page.height = Integer.parseInt(entry[2]); + } + }); + pageFields.put("format", new Field() { + public void parse (Page page) { + page.format = Format.valueOf(entry[1]); + } + }); + pageFields.put("filter", new Field() { + public void parse (Page page) { + page.minFilter = TextureFilter.valueOf(entry[1]); + page.magFilter = TextureFilter.valueOf(entry[2]); + page.useMipMaps = page.minFilter.isMipMap(); + } + }); + pageFields.put("repeat", new Field() { + public void parse (Page page) { + String direction = entry[1]; + if (direction.indexOf('x') != -1) page.uWrap = Repeat; + if (direction.indexOf('y') != -1) page.vWrap = Repeat; + } + }); + pageFields.put("pma", new Field() { + public void parse (Page page) { + page.pma = entry[1].equals("true"); + } + }); + + final boolean[] hasIndexes = {false}; + ObjectMap> regionFields = new ObjectMap(127, 0.99f); // Size needed to avoid collisions. + regionFields.put("xy", new Field() { // Deprecated, use bounds. + public void parse (Region region) { + region.left = Integer.parseInt(entry[1]); + region.top = Integer.parseInt(entry[2]); + } + }); + regionFields.put("size", new Field() { // Deprecated, use bounds. + public void parse (Region region) { + region.width = Integer.parseInt(entry[1]); + region.height = Integer.parseInt(entry[2]); + } + }); + regionFields.put("bounds", new Field() { + public void parse (Region region) { + region.left = Integer.parseInt(entry[1]); + region.top = Integer.parseInt(entry[2]); + region.width = Integer.parseInt(entry[3]); + region.height = Integer.parseInt(entry[4]); + } + }); + regionFields.put("orig", new Field() { // Deprecated, use offsets. + public void parse (Region region) { + region.originalWidth = Integer.parseInt(entry[1]); + region.originalHeight = Integer.parseInt(entry[2]); + } + }); + regionFields.put("offset", new Field() { // Deprecated, use offsets. + public void parse (Region region) { + region.offsetX = Integer.parseInt(entry[1]); + region.offsetY = Integer.parseInt(entry[2]); + } + }); + regionFields.put("offsets", new Field() { + public void parse (Region region) { + region.offsetX = Integer.parseInt(entry[1]); + region.offsetY = Integer.parseInt(entry[2]); + region.originalWidth = Integer.parseInt(entry[3]); + region.originalHeight = Integer.parseInt(entry[4]); + } + }); + regionFields.put("rotate", new Field() { + public void parse (Region region) { + String value = entry[1]; + if (value.equals("true")) + region.degrees = 90; + else if (!value.equals("false")) // + region.degrees = Integer.valueOf(value); + region.rotate = region.degrees == 90; + } + }); + regionFields.put("index", new Field() { + public void parse (Region region) { + region.index = Integer.parseInt(entry[1]); + if (region.index != -1) hasIndexes[0] = true; + } + }); + + BufferedReader reader = new BufferedReader(new InputStreamReader(packFile.read()), 1024); + try { + String line = reader.readLine(); + // Ignore empty lines before first entry. + while (line != null && line.trim().length() == 0) + line = reader.readLine(); + // Header entries. + while (true) { + if (line == null || line.trim().length() == 0) break; + if (readEntry(entry, line) == 0) break; // Silently ignore all header fields. + line = reader.readLine(); + } + // Page and region entries. + Page page = null; + Array names = null, values = null; + while (true) { + if (line == null) break; + if (line.trim().length() == 0) { + page = null; + line = reader.readLine(); + } else if (page == null) { + page = new Page(); + page.textureFile = imagesDir.child(line); + while (true) { + if (readEntry(entry, line = reader.readLine()) == 0) break; + Field field = pageFields.get(entry[0]); + if (field != null) field.parse(page); // Silently ignore unknown page fields. + } + pages.add(page); + } else { + Region region = new Region(); + region.page = page; + region.name = line; + if (flip) region.flip = true; + while (true) { + int count = readEntry(entry, line = reader.readLine()); + if (count == 0) break; + Field field = regionFields.get(entry[0]); + if (field != null) + field.parse(region); + else { + if (names == null) { + names = new Array(8); + values = new Array(8); + } + names.add(entry[0]); + int[] entryValues = new int[count]; + for (int i = 0; i < count; i++) { + try { + entryValues[i] = Integer.parseInt(entry[i + 1]); + } catch (NumberFormatException ignored) { // Silently ignore non-integer values. + } + } + values.add(entryValues); + } + } + if (region.originalWidth == 0 && region.originalHeight == 0) { + region.originalWidth = region.width; + region.originalHeight = region.height; + } + if (names != null && names.size > 0) { + region.names = names.toArray(String.class); + region.values = values.toArray(int[].class); + names.clear(); + values.clear(); + } + regions.add(region); + } + } + } catch (Exception ex) { + throw new GdxRuntimeException("Error reading texture atlas file: " + packFile, ex); + } finally { + StreamUtils.closeQuietly(reader); + } + + if (hasIndexes[0]) { + regions.sort(new Comparator() { + public int compare (Region region1, Region region2) { + int i1 = region1.index; + if (i1 == -1) i1 = Integer.MAX_VALUE; + int i2 = region2.index; + if (i2 == -1) i2 = Integer.MAX_VALUE; + return i1 - i2; + } + }); + } + } + + public Array getPages () { + return pages; + } + + public Array getRegions () { + return regions; + } + + static private int readEntry (String[] entry, String line) throws IOException { + if (line == null || line.length() == 0) return 0; + int colon = line.indexOf(':'); + if (colon == -1) return 0; + entry[0] = line.substring(0, colon).trim(); + for (int i = 1, lastMatch = colon + 1;; i++) { + int comma = line.indexOf(',', lastMatch); + if (comma == -1) { + entry[i] = line.substring(lastMatch).trim(); + return i; + } + entry[i] = line.substring(lastMatch, comma).trim(); + lastMatch = comma + 1; + if (i == 4) return 4; + } + } + + static private interface Field { + public void parse (T object); + } + + static public class Page { + /** May be null if this page isn't associated with a file. In that case, {@link #texture} must be set. */ + public @Null FileHandle textureFile; + /** May be null if the texture is not yet loaded. */ + public @Null Texture texture; + public float width, height; + public boolean useMipMaps; + public Format format = Format.RGBA8888; + public TextureFilter minFilter = TextureFilter.Nearest, magFilter = TextureFilter.Nearest; + public TextureWrap uWrap = ClampToEdge, vWrap = ClampToEdge; + public boolean pma; + } - /** Returns the number of tuple values read (1, 2 or 4). */ - static int readTuple (BufferedReader reader) throws IOException { - String line = reader.readLine(); - int colon = line.indexOf(':'); - if (colon == -1) throw new GdxRuntimeException("Invalid line: " + line); - int i = 0, lastMatch = colon + 1; - for (i = 0; i < 3; i++) { - int comma = line.indexOf(',', lastMatch); - if (comma == -1) break; - tuple[i] = line.substring(lastMatch, comma).trim(); - lastMatch = comma + 1; - } - tuple[i] = line.substring(lastMatch).trim(); - return i + 1; + static public class Region { + public Page page; + public String name; + public boolean flip; + public int index = -1; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public boolean rotate; + public int degrees; + public int left, top; + public int width, height; + public @Null String[] names; + public @Null int[][] values; + + public @Null int[] findValue (String name) { + if (names != null) { + for (int i = 0, n = names.length; i < n; i++) + if (name.equals(names[i])) return values[i]; + } + return null; + } + } } /** Describes the region of a packed image and provides information about the original image before it was packed. */ @@ -471,14 +511,15 @@ static public class AtlasRegion extends TextureRegion { * When sprites are packed, if the original file name ends with a number, it is stored as the index and is not considered as * part of the sprite's name. This is useful for keeping animation frames in order. * @see TextureAtlas#findRegions(String) */ - public int index; + public int index = -1; /** The name of the original image file, without the file's extension.
- * If the name ends with an underscore followed by only numbers, that part is excluded: - * underscores denote special instructions to the texture packer. */ + * If the name ends with an underscore followed by only numbers, that part is excluded: underscores denote special + * instructions to the texture packer. */ public String name; - /** The offset from the left of the original image to the left of the packed image, after whitespace was removed for packing. */ + /** The offset from the left of the original image to the left of the packed image, after whitespace was removed for + * packing. */ public float offsetX; /** The offset from the bottom of the original image to the bottom of the packed image, after whitespace was removed for @@ -505,11 +546,11 @@ static public class AtlasRegion extends TextureRegion { * tightly packing polygons). */ public int degrees; - /** The ninepatch splits, or null if not a ninepatch. Has 4 elements: left, right, top, bottom. */ - public int[] splits; + /** Names for name/value pairs other than the fields provided on this class, each entry corresponding to {@link #values}. */ + public @Null String[] names; - /** The ninepatch pads, or null if not a ninepatch or the has no padding. Has 4 elements: left, right, top, bottom. */ - public int[] pads; + /** Values for name/value pairs other than the fields provided on this class, each entry corresponding to {@link #names}. */ + public @Null int[][] values; public AtlasRegion (Texture texture, int x, int y, int width, int height) { super(texture, x, y, width, height); @@ -531,8 +572,8 @@ public AtlasRegion (AtlasRegion region) { originalHeight = region.originalHeight; rotate = region.rotate; degrees = region.degrees; - splits = region.splits; - pads = region.pads; + names = region.names; + values = region.values; } public AtlasRegion (TextureRegion region) { @@ -544,7 +585,8 @@ public AtlasRegion (TextureRegion region) { } @Override - /** Flips the region, adjusting the offset so the image appears to be flip as if no whitespace has been removed for packing. */ + /** Flips the region, adjusting the offset so the image appears to be flipped as if no whitespace has been removed for + * packing. */ public void flip (boolean x, boolean y) { super.flip(x, y); if (x) offsetX = originalWidth - offsetX - getRotatedPackedWidth(); @@ -563,6 +605,14 @@ public float getRotatedPackedHeight () { return rotate ? packedWidth : packedHeight; } + public @Null int[] findValue (String name) { + if (names != null) { + for (int i = 0, n = names.length; i < n; i++) + if (name.equals(names[i])) return values[i]; + } + return null; + } + public String toString () { return name; } diff --git a/gdx/src/com/badlogic/gdx/math/BSpline.java b/gdx/src/com/badlogic/gdx/math/BSpline.java index ed10a41ef1c..3a92ce0e091 100644 --- a/gdx/src/com/badlogic/gdx/math/BSpline.java +++ b/gdx/src/com/badlogic/gdx/math/BSpline.java @@ -135,7 +135,7 @@ public static > T derivative (final T out, final float t, fi * @param i The span (0<=i> T calculate (final T out, final int i, final case 3: return cubic(out, i, u, points, continuous, tmp); } - return out; + throw new IllegalArgumentException(); } /** Calculates the n-degree b-spline derivative for the given span (i) at the given position (u). @@ -153,7 +153,7 @@ public static > T calculate (final T out, final int i, final * @param i The span (0<=i> T derivative (final T out, final int i, fina case 3: return cubic_derivative(out, i, u, points, continuous, tmp); } - return out; + throw new IllegalArgumentException(); } public T[] controlPoints; diff --git a/gdx/src/com/badlogic/gdx/scenes/scene2d/ui/Skin.java b/gdx/src/com/badlogic/gdx/scenes/scene2d/ui/Skin.java index afa4e13a582..ee06cb70b52 100644 --- a/gdx/src/com/badlogic/gdx/scenes/scene2d/ui/Skin.java +++ b/gdx/src/com/badlogic/gdx/scenes/scene2d/ui/Skin.java @@ -239,7 +239,7 @@ public TiledDrawable getTiledDrawable (String name) { } /** Returns a registered ninepatch. If no ninepatch is found but a region exists with the name, a ninepatch is created from the - * region and stored in the skin. If the region is an {@link AtlasRegion} then the {@link AtlasRegion#splits} are used, + * region and stored in the skin. If the region is an {@link AtlasRegion} then its split {@link AtlasRegion#values} are used, * otherwise the ninepatch will have the region as the center patch. */ public NinePatch getPatch (String name) { NinePatch patch = optional(name, NinePatch.class); @@ -248,10 +248,10 @@ public NinePatch getPatch (String name) { try { TextureRegion region = getRegion(name); if (region instanceof AtlasRegion) { - int[] splits = ((AtlasRegion)region).splits; + int[] splits = ((AtlasRegion)region).findValue("split"); if (splits != null) { patch = new NinePatch(region, splits[0], splits[1], splits[2], splits[3]); - int[] pads = ((AtlasRegion)region).pads; + int[] pads = ((AtlasRegion)region).findValue("pad"); if (pads != null) patch.setPadding(pads[0], pads[1], pads[2], pads[3]); } } @@ -298,7 +298,7 @@ public Drawable getDrawable (String name) { TextureRegion textureRegion = getRegion(name); if (textureRegion instanceof AtlasRegion) { AtlasRegion region = (AtlasRegion)textureRegion; - if (region.splits != null) + if (region.findValue("split") != null) drawable = new NinePatchDrawable(getPatch(name)); else if (region.rotate || region.packedWidth != region.originalWidth || region.packedHeight != region.originalHeight) drawable = new SpriteDrawable(getSprite(name)); diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerIOTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerIOTest.java index 81323e9650d..78cd6ae79ee 100755 --- a/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerIOTest.java +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerIOTest.java @@ -146,20 +146,28 @@ private void compare (TextureAtlas original, TextureAtlas.AtlasRegion loaded) { if (originalRegion.originalWidth != loaded.originalWidth) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); if (originalRegion.originalHeight != loaded.originalHeight) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); if (originalRegion.rotate != loaded.rotate) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); - if (originalRegion.splits != null && loaded.splits != null) { - for (int i = 0; i < originalRegion.splits.length; i++) { - if (originalRegion.splits[i] != loaded.splits[i]) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); + + int[] originalSplits = originalRegion.findValue("split"); + int[] loadedSplits = loaded.findValue("split"); + if (originalSplits != null && loadedSplits != null) { + for (int i = 0; i < originalSplits.length; i++) { + if (originalSplits[i] != loadedSplits[i]) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); } } else { - if (originalRegion.splits != loaded.splits) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); + if (originalSplits != loadedSplits) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); } - if (originalRegion.pads != null && loaded.pads != null) { - for (int i = 0; i < originalRegion.pads.length; i++) { - if (originalRegion.pads[i] != loaded.pads[i]) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); + + int[] originalPads = originalRegion.findValue("pad"); + int[] loadedPads = loaded.findValue("pad"); + if (originalPads != null && loadedPads != null) { + for (int i = 0; i < originalPads.length; i++) { + if (originalPads[i] != loadedPads[i]) + throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); } } else { - if (originalRegion.pads != loaded.pads) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); - } } + if (originalPads != loadedPads) throw new GdxRuntimeException("Original AtlasRegion differs from loaded"); + } + } @Override public void render () {