From c13809c61d1be6b525a30d434b1487ff6efaa982 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Wed, 16 Dec 2020 15:36:15 -0800 Subject: [PATCH 01/26] Changed TextureAtlas parsing to store unrecognized values as name/value pairs. Moved split and pad to name/value pairs. Changed TexturePacker to omit entries that are default values and write a pma entry for each page. --- .../tools/texturepacker/TexturePacker.java | 43 +- .../tools/texturepacker/TextureUnpacker.java | 22 +- .../gdx/graphics/g2d/PixmapPacker.java | 4 +- .../gdx/graphics/g2d/TextureAtlas.java | 395 ++++++++++-------- .../badlogic/gdx/scenes/scene2d/ui/Skin.java | 8 +- .../gdx/tests/PixmapPackerIOTest.java | 26 +- 6 files changed, 278 insertions(+), 220 deletions(-) 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..d87d371684f 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; @@ -405,10 +406,17 @@ private void writePackFile (File outputDir, String scaledPackFileName, Array= 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..ea52364d9ce 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,170 +35,157 @@ 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; - } + static final String[] entry = new String[5]; 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); + ObjectMap> pageFields = new ObjectMap(15, 0.99f); + 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]); + } + }); + pageFields.put("repeat", new Field() { + public void parse (Page page) { + String direction = entry[1]; + if (direction.equals("x")) + page.uWrap = Repeat; + else if (direction.equals("y")) + page.vWrap = Repeat; + else if (direction.equals("xy")) { + page.uWrap = Repeat; + page.vWrap = Repeat; + } + } + }); + pageFields.put("pma", new Field() { + public void parse (Page page) { + page.pma = entry[1].equals("true"); + } + }); + + ObjectMap> regionFields = new ObjectMap(127, 0.99f); + regionFields.put("rotate", new Field() { + public void parse (Region region) { + String value = entry[1]; + if (value.equalsIgnoreCase("true")) + region.degrees = 90; + else if (!value.equalsIgnoreCase("false")) // + region.degrees = Integer.valueOf(value); + region.rotate = region.degrees == 90; + } + }); + regionFields.put("xy", new Field() { + public void parse (Region region) { + region.left = Integer.parseInt(entry[1]); + region.top = Integer.parseInt(entry[2]); + } + }); + regionFields.put("size", new Field() { + public void parse (Region region) { + region.width = Integer.parseInt(entry[1]); + region.height = Integer.parseInt(entry[2]); + } + }); + regionFields.put("orig", new Field() { + public void parse (Region region) { + region.originalWidth = Integer.parseInt(entry[1]); + region.originalHeight = Integer.parseInt(entry[2]); + } + }); + regionFields.put("offset", new Field() { + public void parse (Region region) { + region.offsetX = Integer.parseInt(entry[1]); + region.offsetY = Integer.parseInt(entry[2]); + } + }); + regionFields.put("index", new Field() { + public void parse (Region region) { + region.index = Integer.parseInt(entry[1]); + } + }); + + BufferedReader reader = new BufferedReader(new InputStreamReader(packFile.read()), 512); try { - Page pageImage = null; + String line = reader.readLine(); + Page page = 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); + if (line.trim().length() == 0) { + page = null; + line = reader.readLine(); + } else if (page == null) { + page = new Page(); + page.textureFile = imagesDir.child(line); + while (true) { + int count = readEntry(line = reader.readLine()); + if (count == 0) break; + Field field = pageFields.get(entry[0]); + if (field == null) throw new GdxRuntimeException("Invalid line: " + line); + field.parse(page); } - 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); + pages.add(page); } 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.page = page; 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); + if (flip) region.flip = true; + Array names = null, values = null; + while (true) { + int count = readEntry(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(); + values = new Array(); + } + names.add(entry[0]); + int[] entryValues = new int[count]; + for (int i = 0; i < count; i++) + entryValues[i] = Integer.parseInt(entry[i + 1]); + values.add(entryValues); } } - - 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; - + if (names != null) { + region.names = names.toArray(String.class); + region.values = values.toArray(int[].class); + } regions.add(region); } } } catch (Exception ex) { - throw new GdxRuntimeException("Error reading pack file: " + packFile, ex); + throw new GdxRuntimeException("Error reading texture atlas file: " + packFile, ex); } finally { StreamUtils.closeQuietly(reader); } @@ -208,6 +200,60 @@ public Array getPages () { public Array getRegions () { return regions; } + + static private int readEntry (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(); + int i = 1, lastMatch = colon + 1; + for (; i < 4; i++) { + int comma = line.indexOf(',', lastMatch); + if (comma == -1) break; + entry[i] = line.substring(lastMatch, comma).trim(); + lastMatch = comma + 1; + } + entry[i] = line.substring(lastMatch).trim(); + return i; + } + + static public class Page { + public FileHandle textureFile; + public Texture texture; + public float width, height; + public boolean useMipMaps; + public boolean pma; + public Format format; + public TextureFilter minFilter = TextureFilter.Nearest, magFilter = TextureFilter.Nearest; + public TextureWrap uWrap = ClampToEdge, vWrap = ClampToEdge; + } + + static public class Region { + public Page page; + public int index = -1; + public String name; + public float offsetX, offsetY; + public int originalWidth, originalHeight; + public boolean rotate; + public int degrees; + public int left, top; + public int width, height; + public boolean flip; + public String[] names; + public 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; + } + } + + static private interface Field { + public void parse (T object); + } } /** Creates an empty atlas to which regions can be added. */ @@ -275,8 +321,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); } @@ -307,8 +353,8 @@ 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. + /** 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) { for (int i = 0, n = regions.size; i < n; i++) @@ -330,7 +376,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++) { @@ -360,8 +407,8 @@ public Sprite createSprite (String name) { 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. + /** 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. * @see #createSprite(String) */ public Sprite createSprite (String name, int index) { @@ -374,9 +421,9 @@ public Sprite createSprite (String name, int index) { 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); @@ -408,10 +455,11 @@ public 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,8 +471,8 @@ 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(); @@ -441,29 +489,6 @@ public int compare (Region region1, Region region2) { } }; - 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(); - } - - /** 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; - } - /** Describes the region of a packed image and provides information about the original image before it was packed. */ static public class AtlasRegion extends TextureRegion { /** The number at the end of the original image file name, or -1 if none.
@@ -474,11 +499,12 @@ static public class AtlasRegion extends TextureRegion { public int index; /** 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 +531,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 beyond 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 beyond 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 +557,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 +570,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 flip 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 +590,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/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 () { From bab610de1c26640d1ae5e8c07b749c2fefc2cd77 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 17 Dec 2020 14:51:09 -0800 Subject: [PATCH 02/26] Default original size to packed size. Comment field map sizes. --- .../tools/texturepacker/TexturePacker.java | 3 ++- .../gdx/graphics/g2d/TextureAtlas.java | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) 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 d87d371684f..1cbb6fc3b4e 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 @@ -451,7 +451,8 @@ private void writeRect (Writer writer, Page page, Rect rect, String name) throws writer.write("\tpad: " + rect.pads[0] + ", " + rect.pads[1] + ", " + rect.pads[2] + ", " + rect.pads[3] + "\n"); } - writer.write("\torig: " + rect.originalWidth + ", " + rect.originalHeight + "\n"); + if (rect.originalWidth != rect.regionWidth || rect.originalHeight != rect.regionHeight) + writer.write("\torig: " + rect.originalWidth + ", " + rect.originalHeight + "\n"); int offsetY = rect.originalHeight - rect.regionHeight - rect.offsetY; if (rect.offsetX != 0 || offsetY != 0) writer.write("\toffset: " + rect.offsetX + ", " + offsetY + "\n"); diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index ea52364d9ce..05948acaa7a 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -55,7 +55,7 @@ public static class TextureAtlasData { final Array regions = new Array(); public TextureAtlasData (FileHandle packFile, FileHandle imagesDir, boolean flip) { - ObjectMap> pageFields = new ObjectMap(15, 0.99f); + 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]); @@ -92,7 +92,7 @@ public void parse (Page page) { } }); - ObjectMap> regionFields = new ObjectMap(127, 0.99f); + ObjectMap> regionFields = new ObjectMap(127, 0.99f); // Size needed to avoid collisions. regionFields.put("rotate", new Field() { public void parse (Region region) { String value = entry[1]; @@ -149,8 +149,7 @@ public void parse (Region region) { int count = readEntry(line = reader.readLine()); if (count == 0) break; Field field = pageFields.get(entry[0]); - if (field == null) throw new GdxRuntimeException("Invalid line: " + line); - field.parse(page); + if (field != null) field.parse(page); // Silently ignore unknown page fields. } pages.add(page); } else { @@ -177,6 +176,10 @@ public void parse (Region region) { values.add(entryValues); } } + if (region.originalWidth == 0 && region.originalHeight == 0) { + region.originalWidth = region.width; + region.originalHeight = region.height; + } if (names != null) { region.names = names.toArray(String.class); region.values = values.toArray(int[].class); @@ -230,17 +233,17 @@ static public class Page { static public class Region { public Page page; - public int index = -1; 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 boolean flip; - public String[] names; - public int[][] values; + public @Null String[] names; + public @Null int[][] values; public @Null int[] findValue (String name) { if (names != null) { From 1de40280dd9307f28b7b6aebb56744e6bdc9e18c Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 17 Dec 2020 14:53:16 -0800 Subject: [PATCH 03/26] Javadoc typo. --- gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index 05948acaa7a..da48a6a7f31 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -573,7 +573,7 @@ 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 + /** 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); From 377a7aa299cd90f9c39a7bca6c27af54888ae552 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 17 Dec 2020 14:56:15 -0800 Subject: [PATCH 04/26] Spaces before commas. --- .../com/badlogic/gdx/tools/texturepacker/TexturePacker.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 1cbb6fc3b4e..09f54938e88 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 @@ -406,12 +406,12 @@ private void writePackFile (File outputDir, String scaledPackFileName, Array Date: Thu, 17 Dec 2020 15:20:28 -0800 Subject: [PATCH 05/26] Added bounds/offsets entries to reduce the number of entries needed per region. --- .../tools/texturepacker/TexturePacker.java | 28 +++++++++++-------- .../gdx/graphics/g2d/TextureAtlas.java | 16 +++++++++++ 2 files changed, 32 insertions(+), 12 deletions(-) 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 09f54938e88..bda151dc834 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 @@ -436,28 +436,32 @@ private void writePackFile (File outputDir, String scaledPackFileName, Array() { + 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() { public void parse (Region region) { region.originalWidth = Integer.parseInt(entry[1]); @@ -127,6 +135,14 @@ public void parse (Region region) { 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("index", new Field() { public void parse (Region region) { region.index = Integer.parseInt(entry[1]); From a2947414fb4c08190d7f171ecc5d9ce9509f09c2 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 17 Dec 2020 15:28:31 -0800 Subject: [PATCH 06/26] Use equalsIgnoreCase when comparing entry values. --- gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index 21b2dbb6ef5..e0bea619391 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -76,11 +76,11 @@ public void parse (Page page) { pageFields.put("repeat", new Field() { public void parse (Page page) { String direction = entry[1]; - if (direction.equals("x")) + if (direction.equalsIgnoreCase("x")) page.uWrap = Repeat; - else if (direction.equals("y")) + else if (direction.equalsIgnoreCase("y")) page.vWrap = Repeat; - else if (direction.equals("xy")) { + else if (direction.equalsIgnoreCase("xy")) { page.uWrap = Repeat; page.vWrap = Repeat; } @@ -88,7 +88,7 @@ else if (direction.equals("xy")) { }); pageFields.put("pma", new Field() { public void parse (Page page) { - page.pma = entry[1].equals("true"); + page.pma = entry[1].equalsIgnoreCase("true"); } }); From 2bc0a3fa352105d2f0db18fb0094a7fb47741311 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 17 Dec 2020 16:34:38 -0800 Subject: [PATCH 07/26] Check the index first, which is cheaper. --- gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index e0bea619391..56f52f96cb2 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -433,8 +433,8 @@ public Sprite createSprite (String name) { public 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; From 5cfe62284151b5229a1b3d5d1e74f8af0b96260e Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 17 Dec 2020 16:36:32 -0800 Subject: [PATCH 08/26] Skip sorting regions when there are no indexes. --- gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index 56f52f96cb2..9d2676eabdd 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -92,6 +92,7 @@ public void parse (Page page) { } }); + final boolean[] hasIndexes = {false}; ObjectMap> regionFields = new ObjectMap(127, 0.99f); // Size needed to avoid collisions. regionFields.put("rotate", new Field() { public void parse (Region region) { @@ -146,6 +147,7 @@ public void parse (Region region) { regionFields.put("index", new Field() { public void parse (Region region) { region.index = Integer.parseInt(entry[1]); + if (region.index != -1) hasIndexes[0] = true; } }); @@ -209,7 +211,7 @@ public void parse (Region region) { StreamUtils.closeQuietly(reader); } - regions.sort(indexComparator); + if (hasIndexes[0]) regions.sort(indexComparator); } public Array getPages () { From 5cfa7ccf1a1a1215860d7261a4c850f85a0c5095 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 17 Dec 2020 19:09:40 -0800 Subject: [PATCH 09/26] Added CHANGES entry for #6316. --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 0d2db80564b..c60b856a1b6 100755 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,5 @@ [1.9.13] +- [BREAKING CHANGE] TexturePacker writes using a new format (TextureAtlas can still read old format). See #6316. - 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 @@ -7,6 +8,7 @@ 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. [1.9.12] - [BREAKING CHANGE] iOS: Changed how Retina/hdpi handled on iOS. See #3709. From b852c832287add1bfacbe8e0009d179aff604811 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 17 Dec 2020 21:51:14 -0800 Subject: [PATCH 10/26] Added a pretty print texture packer setting, enabled by default. --- .../tools/texturepacker/TexturePacker.java | 62 ++++++++++++------- 1 file changed, 41 insertions(+), 21 deletions(-) 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 bda151dc834..55b398fa804 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 @@ -403,20 +403,30 @@ private void writePackFile (File outputDir, String scaledPackFileName, Array Date: Thu, 17 Dec 2020 22:08:03 -0800 Subject: [PATCH 11/26] Added a legacy output setting. --- .../tools/texturepacker/TexturePacker.java | 83 +++++++++++++++---- 1 file changed, 68 insertions(+), 15 deletions(-) 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 55b398fa804..6b11d69c9dc 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 @@ -410,40 +410,62 @@ 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) { @@ -482,6 +504,35 @@ private void writeRect (Writer writer, Page page, Rect rect, String name) throws } } + 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 + .write(" xy: " + (page.x + rect.x) + ", " + (page.y + page.height - rect.y - (rect.height - settings.paddingY)) + "\n"); + + writer.write(" size: " + rect.regionWidth + ", " + rect.regionHeight + "\n"); + if (rect.splits != null) { + writer.write(" split: " // + + rect.splits[0] + ", " + rect.splits[1] + ", " + rect.splits[2] + ", " + rect.splits[3] + "\n"); + } + if (rect.pads != null) { + if (rect.splits == null) writer.write(" split: 0, 0, 0, 0\n"); + writer.write(" pad: " + rect.pads[0] + ", " + rect.pads[1] + ", " + rect.pads[2] + ", " + rect.pads[3] + "\n"); + } + writer.write(" orig: " + rect.originalWidth + ", " + rect.originalHeight + "\n"); + writer.write(" offset: " + rect.offsetX + ", " + (rect.originalHeight - rect.regionHeight - rect.offsetY) + "\n"); + writer.write(" index: " + rect.index + "\n"); + } + 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"; @@ -880,6 +931,7 @@ static public class Settings { public Resampling[] scaleResampling = {Resampling.bicubic}; public String atlasExtension = ".atlas"; public boolean prettyPrint = true; + public boolean legacyOutput; public Settings () { } @@ -932,6 +984,7 @@ public void set (Settings settings) { scaleResampling = Arrays.copyOf(settings.scaleResampling, settings.scaleResampling.length); atlasExtension = settings.atlasExtension; prettyPrint = settings.prettyPrint; + legacyOutput = settings.legacyOutput; } public String getScaledPackFileName (String packFileName, int scaleIndex) { From e7f9d1345cb29caf6b6b7b4815b0e453848fab7f Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Thu, 17 Dec 2020 22:15:44 -0800 Subject: [PATCH 12/26] Mentioned the legacyOutput setting in CHANGES. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index ee39977965f..60a8c312c9f 100755 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,5 @@ [1.9.13] -- [BREAKING CHANGE] TexturePacker writes using a new format (TextureAtlas can still read old format). See #6316. +- [BREAKING CHANGE] TexturePacker writes using a new format (TextureAtlas can still read the old format). Use the legacyOutput setting if the old format is needed. See #6316. - [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. - 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. From e2d25c93e4b4f66c2ae0adfb09680a4c8ebefc0e Mon Sep 17 00:00:00 2001 From: Tommy Ettinger Date: Fri, 18 Dec 2020 16:31:58 -0800 Subject: [PATCH 13/26] Filter used by issue_pack wasn't GWT-compatible. --- tests/gdx-tests-android/assets/data/issue_pack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gdx-tests-android/assets/data/issue_pack b/tests/gdx-tests-android/assets/data/issue_pack index 6fbf470dc08..511540c53c4 100644 --- a/tests/gdx-tests-android/assets/data/issue_pack +++ b/tests/gdx-tests-android/assets/data/issue_pack @@ -1,7 +1,7 @@ resource1.jpg format: RGBA8888 -filter: MipMapLinearNearest,Nearest +filter: Linear,Nearest repeat: none map rotate: false From 32fee82b3f17a1057e6fea4e1ab72663f2f5070b Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 18 Dec 2020 14:06:54 -0800 Subject: [PATCH 14/26] Fixed mipmaps not being created when the min filter needs them (fixes AtlasIssueTest). --- gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index 9d2676eabdd..9da73c6e004 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -71,6 +71,7 @@ public void parse (Page page) { 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() { From cd2f2d6e66bec28ced43cbd6328cab044cc508be Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 18 Dec 2020 14:07:17 -0800 Subject: [PATCH 15/26] Default legacyOutput to true. --- CHANGES | 2 +- .../src/com/badlogic/gdx/tools/texturepacker/TexturePacker.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 60a8c312c9f..84589e30b74 100755 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,4 @@ [1.9.13] -- [BREAKING CHANGE] TexturePacker writes using a new format (TextureAtlas can still read the old format). Use the legacyOutput setting if the old format is needed. See #6316. - [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. - 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. @@ -10,6 +9,7 @@ - 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 6b11d69c9dc..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 @@ -931,7 +931,7 @@ static public class Settings { public Resampling[] scaleResampling = {Resampling.bicubic}; public String atlasExtension = ".atlas"; public boolean prettyPrint = true; - public boolean legacyOutput; + public boolean legacyOutput = true; public Settings () { } From eb570191a20db7615b7fc6bd7b4ee13ad351d070 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 18 Dec 2020 16:22:56 -0800 Subject: [PATCH 16/26] Added optional header entries. --- .../com/badlogic/gdx/graphics/g2d/TextureAtlas.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index 9da73c6e004..5e68a35e0c9 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -155,6 +155,16 @@ public void parse (Region region) { BufferedReader reader = new BufferedReader(new InputStreamReader(packFile.read()), 512); 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(line) == 0) break; // Silently ignore all header fields. + line = reader.readLine(); + } + // Page and region entries. Page page = null; while (true) { if (line == null) break; @@ -165,8 +175,7 @@ public void parse (Region region) { page = new Page(); page.textureFile = imagesDir.child(line); while (true) { - int count = readEntry(line = reader.readLine()); - if (count == 0) break; + if (readEntry(line = reader.readLine()) == 0) break; Field field = pageFields.get(entry[0]); if (field != null) field.parse(page); // Silently ignore unknown page fields. } From abd8eb3a4fa842525e9b6fb03c703bb07aba9ed9 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 18 Dec 2020 16:23:15 -0800 Subject: [PATCH 17/26] Set default for Page#format field. --- gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index 5e68a35e0c9..9748811f8fa 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -253,10 +253,10 @@ static public class Page { public Texture texture; public float width, height; public boolean useMipMaps; - public boolean pma; - public Format format; + public Format format = Format.RGBA8888; public TextureFilter minFilter = TextureFilter.Nearest, magFilter = TextureFilter.Nearest; public TextureWrap uWrap = ClampToEdge, vWrap = ClampToEdge; + public boolean pma; } static public class Region { From 17ac61ea586d4d0381bbb92a8d1f6832be551d34 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 18 Dec 2020 16:30:37 -0800 Subject: [PATCH 18/26] Ignore values after the 4th. --- .../com/badlogic/gdx/graphics/g2d/TextureAtlas.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index 9748811f8fa..83d22c8ef3b 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -237,15 +237,16 @@ static private int readEntry (String line) throws IOException { int colon = line.indexOf(':'); if (colon == -1) return 0; entry[0] = line.substring(0, colon).trim(); - int i = 1, lastMatch = colon + 1; - for (; i < 4; i++) { + for (int i = 1, lastMatch = colon + 1;; i++) { int comma = line.indexOf(',', lastMatch); - if (comma == -1) break; + 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; } - entry[i] = line.substring(lastMatch).trim(); - return i; } static public class Page { From 0519e76476b4a18b4c82cceac4e0a76dc58233fd Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 18 Dec 2020 16:31:04 -0800 Subject: [PATCH 19/26] Ignore non-int values for unknown region entries. --- gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index 83d22c8ef3b..e19c8c3d816 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -199,8 +199,12 @@ public void parse (Region region) { } names.add(entry[0]); int[] entryValues = new int[count]; - for (int i = 0; i < count; i++) - entryValues[i] = Integer.parseInt(entry[i + 1]); + for (int i = 0; i < count; i++) { + try { + entryValues[i] = Integer.parseInt(entry[i + 1]); + } catch (NumberFormatException ignored) { + } + } values.add(entryValues); } } From 1c9b09c362d406c5738b6970ea78673df1dbed08 Mon Sep 17 00:00:00 2001 From: Tommy Ettinger Date: Fri, 18 Dec 2020 17:16:34 -0800 Subject: [PATCH 20/26] Revert change to issue_pack, since the issue was fixed. And fixed in a more thorough way, too. --- tests/gdx-tests-android/assets/data/issue_pack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gdx-tests-android/assets/data/issue_pack b/tests/gdx-tests-android/assets/data/issue_pack index 511540c53c4..6fbf470dc08 100644 --- a/tests/gdx-tests-android/assets/data/issue_pack +++ b/tests/gdx-tests-android/assets/data/issue_pack @@ -1,7 +1,7 @@ resource1.jpg format: RGBA8888 -filter: Linear,Nearest +filter: MipMapLinearNearest,Nearest repeat: none map rotate: false From 37fafc13562427ba99c39531661c4d1883678289 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 18 Dec 2020 18:27:39 -0800 Subject: [PATCH 21/26] CHANGES entry for AtlasRegion breaking change. --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 84589e30b74..f01543deaf3 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#splits and #pads 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 From 9c548594a9769b2e9bb9889c4fd3c2a1b5a9ffd4 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Fri, 18 Dec 2020 18:31:07 -0800 Subject: [PATCH 22/26] CHANGES for Region too. --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f01543deaf3..7dafe4c845c 100755 --- a/CHANGES +++ b/CHANGES @@ -1,6 +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#splits and #pads have been removed and moved to name/value pairs, use #findValue("split") and #findValue("pad") instead. +- [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 From ccd9f423ecd36e06ed33e913fe6fa70ecfef6795 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sat, 19 Dec 2020 15:13:39 -0800 Subject: [PATCH 23/26] BSpline#calculate, throw if degree is not 3, javadoc. #6313 --- gdx/src/com/badlogic/gdx/math/BSpline.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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; From 6f1dd1acb2032a202be63459f8664a82a51e0f9a Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 20 Dec 2020 10:51:41 -0800 Subject: [PATCH 24/26] Clean up. Set the texture on the page rather than using a map. --- .../gdx/graphics/g2d/TextureAtlas.java | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index e19c8c3d816..e994aaef6bf 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -48,9 +48,19 @@ public class TextureAtlas implements Disposable { private final ObjectSet textures = new ObjectSet(4); private final Array regions = new Array(); - public static class TextureAtlasData { + static public class TextureAtlasData { static final String[] entry = new String[5]; + 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; + } + }; + final Array pages = new Array(); final Array regions = new Array(); @@ -202,7 +212,7 @@ public void parse (Region region) { for (int i = 0; i < count; i++) { try { entryValues[i] = Integer.parseInt(entry[i + 1]); - } catch (NumberFormatException ignored) { + } catch (NumberFormatException ignored) { // Silently ignore non-integer values. } } values.add(entryValues); @@ -253,6 +263,10 @@ static private int readEntry (String line) throws IOException { } } + static private interface Field { + public void parse (T object); + } + static public class Page { public FileHandle textureFile; public Texture texture; @@ -286,10 +300,6 @@ static public class Region { return null; } } - - static private interface Field { - public void parse (T object); - } } /** Creates an empty atlas to which regions can be added. */ @@ -324,11 +334,8 @@ public TextureAtlas (FileHandle packFile, FileHandle imagesDir, boolean flip) { /** @param data May be null. */ public TextureAtlas (TextureAtlasData data) { - if (data != null) load(data); - } + if (data == null) return; - private void load (TextureAtlasData data) { - ObjectMap pageToTexture = new ObjectMap(); for (Page page : data.pages) { Texture texture = null; if (page.texture == null) { @@ -340,15 +347,15 @@ private void load (TextureAtlasData data) { texture.setFilter(page.minFilter, page.magFilter); texture.setWrap(page.uWrap, page.vWrap); } + page.texture = texture; textures.add(texture); - pageToTexture.put(page, texture); } 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 ? height : width, + region.rotate ? width : height); atlasRegion.index = region.index; atlasRegion.name = region.name; atlasRegion.offsetX = region.offsetX; @@ -515,16 +522,6 @@ public void 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; - } - }; - /** Describes the region of a packed image and provides information about the original image before it was packed. */ static public class AtlasRegion extends TextureRegion { /** The number at the end of the original image file name, or -1 if none.
From ca465ccc54d3b594112b02032b3942ebdd8bd5f8 Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 20 Dec 2020 10:52:59 -0800 Subject: [PATCH 25/26] Move TextureAtlasData member class to end of TextureAtlas. Drives my OCD nuts to put member classes at the top. --- .../gdx/graphics/g2d/TextureAtlas.java | 440 +++++++++--------- 1 file changed, 220 insertions(+), 220 deletions(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index e994aaef6bf..e15c69a2ac1 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -48,6 +48,226 @@ public class TextureAtlas implements Disposable { private final ObjectSet textures = new ObjectSet(4); private final Array regions = new Array(); + /** Creates an empty atlas to which regions can be added. */ + public TextureAtlas () { + } + + /** Loads the specified pack file using {@link FileType#Internal}, using the parent directory of the pack file to find the page + * images. */ + public TextureAtlas (String internalPackFile) { + this(Gdx.files.internal(internalPackFile)); + } + + /** Loads the specified pack file, using the parent directory of the pack file to find the page images. */ + public TextureAtlas (FileHandle packFile) { + this(packFile, packFile.parent()); + } + + /** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner. + * @see #TextureAtlas(FileHandle) */ + public TextureAtlas (FileHandle packFile, boolean flip) { + this(packFile, packFile.parent(), flip); + } + + public TextureAtlas (FileHandle packFile, FileHandle imagesDir) { + this(packFile, imagesDir, false); + } + + /** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner. */ + 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) return; + + 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); + } + page.texture = texture; + textures.add(texture); + } + + for (Region region : data.regions) { + int width = region.width; + int height = region.height; + AtlasRegion atlasRegion = new AtlasRegion(region.page.texture, region.left, region.top, region.rotate ? height : width, + region.rotate ? width : height); + atlasRegion.index = region.index; + atlasRegion.name = region.name; + atlasRegion.offsetX = region.offsetX; + atlasRegion.offsetY = region.offsetY; + atlasRegion.originalHeight = region.originalHeight; + atlasRegion.originalWidth = region.originalWidth; + atlasRegion.rotate = region.rotate; + atlasRegion.degrees = region.degrees; + atlasRegion.names = region.names; + atlasRegion.values = region.values; + if (region.flip) atlasRegion.flip(false, true); + regions.add(atlasRegion); + } + } + + /** Adds a region to the atlas. The specified texture will be disposed when the atlas is disposed. */ + public AtlasRegion addRegion (String name, Texture texture, int x, int y, int width, int height) { + textures.add(texture); + AtlasRegion region = new AtlasRegion(texture, x, y, width, height); + region.name = name; + region.index = -1; + regions.add(region); + return region; + } + + /** Adds a region to the atlas. The texture for the specified region will be disposed when the atlas is disposed. */ + 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; + } + + /** Returns all regions in the atlas. */ + 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) { + 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) { + 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; + return region; + } + return null; + } + + /** 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. */ + public Array findRegions (String name) { + Array matched = new Array(AtlasRegion.class); + for (int i = 0, n = regions.size; i < n; i++) { + AtlasRegion region = regions.get(i); + if (region.name.equals(name)) matched.add(new AtlasRegion(region)); + } + return matched; + } + + /** Returns all regions in the atlas as sprites. This method creates a new sprite for each region, so the result should be + * stored rather than calling this method multiple times. + * @see #createSprite(String) */ + public Array createSprites () { + Array sprites = new Array(true, regions.size, Sprite.class); + for (int i = 0, n = regions.size; i < n; i++) + sprites.add(newSprite(regions.get(i))); + return sprites; + } + + /** 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) { + 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. + * @see #createSprite(String) */ + public Sprite createSprite (String name, int index) { + for (int i = 0, n = regions.size; i < n; i++) { + AtlasRegion region = regions.get(i); + 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. + * @see #createSprite(String) */ + public Array createSprites (String name) { + Array matched = new Array(Sprite.class); + for (int i = 0, n = regions.size; i < n; i++) { + AtlasRegion region = regions.get(i); + if (region.name.equals(name)) matched.add(newSprite(region)); + } + return matched; + } + + private Sprite newSprite (AtlasRegion region) { + if (region.packedWidth == region.originalWidth && region.packedHeight == region.originalHeight) { + if (region.rotate) { + Sprite sprite = new Sprite(region); + sprite.setBounds(0, 0, region.getRegionHeight(), region.getRegionWidth()); + sprite.rotate90(true); + return sprite; + } + return new Sprite(region); + } + return new AtlasSprite(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) { + for (int i = 0, n = regions.size; i < n; i++) { + AtlasRegion region = regions.get(i); + if (region.name.equals(name)) { + 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]); + int[] pads = region.findValue("pad"); + if (pads != null) patch.setPadding(pads[0], pads[1], pads[2], pads[3]); + return patch; + } + } + return null; + } + + /** @return the textures of the pages, unordered */ + 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. */ + public void dispose () { + for (Texture texture : textures) + texture.dispose(); + textures.clear(0); + } + static public class TextureAtlasData { static final String[] entry = new String[5]; @@ -302,226 +522,6 @@ static public class Region { } } - /** Creates an empty atlas to which regions can be added. */ - public TextureAtlas () { - } - - /** Loads the specified pack file using {@link FileType#Internal}, using the parent directory of the pack file to find the page - * images. */ - public TextureAtlas (String internalPackFile) { - this(Gdx.files.internal(internalPackFile)); - } - - /** Loads the specified pack file, using the parent directory of the pack file to find the page images. */ - public TextureAtlas (FileHandle packFile) { - this(packFile, packFile.parent()); - } - - /** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner. - * @see #TextureAtlas(FileHandle) */ - public TextureAtlas (FileHandle packFile, boolean flip) { - this(packFile, packFile.parent(), flip); - } - - public TextureAtlas (FileHandle packFile, FileHandle imagesDir) { - this(packFile, imagesDir, false); - } - - /** @param flip If true, all regions loaded will be flipped for use with a perspective where 0,0 is the upper left corner. */ - 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) return; - - 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); - } - page.texture = texture; - textures.add(texture); - } - - for (Region region : data.regions) { - int width = region.width; - int height = region.height; - AtlasRegion atlasRegion = new AtlasRegion(region.page.texture, region.left, region.top, region.rotate ? height : width, - region.rotate ? width : height); - atlasRegion.index = region.index; - atlasRegion.name = region.name; - atlasRegion.offsetX = region.offsetX; - atlasRegion.offsetY = region.offsetY; - atlasRegion.originalHeight = region.originalHeight; - atlasRegion.originalWidth = region.originalWidth; - atlasRegion.rotate = region.rotate; - atlasRegion.degrees = region.degrees; - atlasRegion.names = region.names; - atlasRegion.values = region.values; - if (region.flip) atlasRegion.flip(false, true); - regions.add(atlasRegion); - } - } - - /** Adds a region to the atlas. The specified texture will be disposed when the atlas is disposed. */ - public AtlasRegion addRegion (String name, Texture texture, int x, int y, int width, int height) { - textures.add(texture); - AtlasRegion region = new AtlasRegion(texture, x, y, width, height); - region.name = name; - region.index = -1; - regions.add(region); - return region; - } - - /** Adds a region to the atlas. The texture for the specified region will be disposed when the atlas is disposed. */ - 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; - } - - /** Returns all regions in the atlas. */ - 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) { - 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) { - 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; - return region; - } - return null; - } - - /** 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. */ - public Array findRegions (String name) { - Array matched = new Array(AtlasRegion.class); - for (int i = 0, n = regions.size; i < n; i++) { - AtlasRegion region = regions.get(i); - if (region.name.equals(name)) matched.add(new AtlasRegion(region)); - } - return matched; - } - - /** Returns all regions in the atlas as sprites. This method creates a new sprite for each region, so the result should be - * stored rather than calling this method multiple times. - * @see #createSprite(String) */ - public Array createSprites () { - Array sprites = new Array(true, regions.size, Sprite.class); - for (int i = 0, n = regions.size; i < n; i++) - sprites.add(newSprite(regions.get(i))); - return sprites; - } - - /** 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) { - 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. - * @see #createSprite(String) */ - public Sprite createSprite (String name, int index) { - for (int i = 0, n = regions.size; i < n; i++) { - AtlasRegion region = regions.get(i); - 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. - * @see #createSprite(String) */ - public Array createSprites (String name) { - Array matched = new Array(Sprite.class); - for (int i = 0, n = regions.size; i < n; i++) { - AtlasRegion region = regions.get(i); - if (region.name.equals(name)) matched.add(newSprite(region)); - } - return matched; - } - - private Sprite newSprite (AtlasRegion region) { - if (region.packedWidth == region.originalWidth && region.packedHeight == region.originalHeight) { - if (region.rotate) { - Sprite sprite = new Sprite(region); - sprite.setBounds(0, 0, region.getRegionHeight(), region.getRegionWidth()); - sprite.rotate90(true); - return sprite; - } - return new Sprite(region); - } - return new AtlasSprite(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) { - for (int i = 0, n = regions.size; i < n; i++) { - AtlasRegion region = regions.get(i); - if (region.name.equals(name)) { - 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]); - int[] pads = region.findValue("pad"); - if (pads != null) patch.setPadding(pads[0], pads[1], pads[2], pads[3]); - return patch; - } - } - return null; - } - - /** @return the textures of the pages, unordered */ - 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. */ - public void dispose () { - for (Texture texture : textures) - texture.dispose(); - textures.clear(0); - } - /** Describes the region of a packed image and provides information about the original image before it was packed. */ static public class AtlasRegion extends TextureRegion { /** The number at the end of the original image file name, or -1 if none.
From 96886aff6cb63e92fcee740d1d8553114ce8ebcc Mon Sep 17 00:00:00 2001 From: NathanSweet Date: Sun, 20 Dec 2020 11:34:10 -0800 Subject: [PATCH 26/26] Bunch of clean up. * Reuse region names/values arrays. * Initialize index to -1 by default. * Comments to note deprecated fields. * Use @Null. * 1024 BufferedReader size. * Don't need to store index comparator on a field. * Changed equalsIgnoreCase to equals. Most of the format is case sensitive so seemed odd to ignore case only in some places. * Use ensureCapacity for textures and regions collections. * Clean up wonky texture initialization. * Don't need a static field for reading the entry, plus it wasn't thread safe. --- .../gdx/graphics/g2d/TextureAtlas.java | 154 ++++++++---------- 1 file changed, 68 insertions(+), 86 deletions(-) diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java index e15c69a2ac1..a68a1b60898 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/TextureAtlas.java @@ -78,30 +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) return; - + 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); - } - page.texture = texture; - textures.add(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(region.page.texture, 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; @@ -122,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; } @@ -132,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; } @@ -143,18 +131,16 @@ public Array getRegions () { } /** 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) { + * 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; @@ -188,9 +174,9 @@ 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; @@ -198,9 +184,8 @@ public Sprite createSprite (String name) { /** 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. * @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.index != index) continue; @@ -238,9 +223,8 @@ 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)) { @@ -269,22 +253,12 @@ public void dispose () { } static public class TextureAtlasData { - static final String[] entry = new String[5]; - - 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; - } - }; - 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) { @@ -307,41 +281,25 @@ public void parse (Page page) { pageFields.put("repeat", new Field() { public void parse (Page page) { String direction = entry[1]; - if (direction.equalsIgnoreCase("x")) - page.uWrap = Repeat; - else if (direction.equalsIgnoreCase("y")) - page.vWrap = Repeat; - else if (direction.equalsIgnoreCase("xy")) { - page.uWrap = Repeat; - page.vWrap = Repeat; - } + 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].equalsIgnoreCase("true"); + page.pma = entry[1].equals("true"); } }); final boolean[] hasIndexes = {false}; ObjectMap> regionFields = new ObjectMap(127, 0.99f); // Size needed to avoid collisions. - regionFields.put("rotate", new Field() { - public void parse (Region region) { - String value = entry[1]; - if (value.equalsIgnoreCase("true")) - region.degrees = 90; - else if (!value.equalsIgnoreCase("false")) // - region.degrees = Integer.valueOf(value); - region.rotate = region.degrees == 90; - } - }); - regionFields.put("xy", new Field() { + 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() { + 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]); @@ -355,13 +313,13 @@ public void parse (Region region) { region.height = Integer.parseInt(entry[4]); } }); - regionFields.put("orig", new Field() { + 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() { + 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]); @@ -375,6 +333,16 @@ public void parse (Region region) { 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]); @@ -382,7 +350,7 @@ public void parse (Region region) { } }); - BufferedReader reader = new BufferedReader(new InputStreamReader(packFile.read()), 512); + BufferedReader reader = new BufferedReader(new InputStreamReader(packFile.read()), 1024); try { String line = reader.readLine(); // Ignore empty lines before first entry. @@ -391,11 +359,12 @@ public void parse (Region region) { // Header entries. while (true) { if (line == null || line.trim().length() == 0) break; - if (readEntry(line) == 0) break; // Silently ignore all header fields. + 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) { @@ -405,7 +374,7 @@ public void parse (Region region) { page = new Page(); page.textureFile = imagesDir.child(line); while (true) { - if (readEntry(line = reader.readLine()) == 0) break; + 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. } @@ -415,17 +384,16 @@ public void parse (Region region) { region.page = page; region.name = line; if (flip) region.flip = true; - Array names = null, values = null; while (true) { - int count = readEntry(line = reader.readLine()); + 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(); - values = new Array(); + names = new Array(8); + values = new Array(8); } names.add(entry[0]); int[] entryValues = new int[count]; @@ -442,9 +410,11 @@ public void parse (Region region) { region.originalWidth = region.width; region.originalHeight = region.height; } - if (names != null) { + 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); } @@ -455,7 +425,17 @@ public void parse (Region region) { StreamUtils.closeQuietly(reader); } - if (hasIndexes[0]) regions.sort(indexComparator); + 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 () { @@ -466,7 +446,7 @@ public Array getRegions () { return regions; } - static private int readEntry (String line) throws IOException { + 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; @@ -488,8 +468,10 @@ static private interface Field { } static public class Page { - public FileHandle textureFile; - public Texture texture; + /** 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; @@ -529,7 +511,7 @@ 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 @@ -564,10 +546,10 @@ static public class AtlasRegion extends TextureRegion { * tightly packing polygons). */ public int degrees; - /** Names for name/value pairs beyond the fields provided on this class, each entry corresponding to {@link #values}. */ + /** Names for name/value pairs other than the fields provided on this class, each entry corresponding to {@link #values}. */ public @Null String[] names; - /** Values for name/value pairs beyond the fields provided on this class, each entry corresponding to {@link #names}. */ + /** 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) {