Skip to content

Commit

Permalink
Improve TextureAtlas parsing, store name/value pairs per region (#6316)
Browse files Browse the repository at this point in the history
* 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.

* Default original size to packed size. Comment field map sizes.

* Javadoc typo.

* Spaces before commas.

* Added bounds/offsets entries to reduce the number of entries needed per region.

* Use equalsIgnoreCase when comparing entry values.

* Check the index first, which is cheaper.

* Skip sorting regions when there are no indexes.

* Added CHANGES entry for #6316.

* Added a pretty print texture packer setting, enabled by default.

* Added a legacy output setting.

* Mentioned the legacyOutput setting in CHANGES.

* Filter used by issue_pack wasn't GWT-compatible.

* Fixed mipmaps not being created when the min filter needs them (fixes AtlasIssueTest).

* Default legacyOutput to true.

* Added optional header entries.

* Set default for Page#format field.

* Ignore values after the 4th.

* Ignore non-int values for unknown region entries.

* Revert change to issue_pack, since the issue was fixed.

And fixed in a more thorough way, too.

* CHANGES entry for AtlasRegion breaking change.

* CHANGES for Region too.

* BSpline#calculate, throw if degree is not 3, javadoc.

#6313

* Clean up. Set the texture on the page rather than using a map.

* Move TextureAtlasData member class to end of TextureAtlas.

Drives my OCD nuts to put member classes at the top.

* 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.
  • Loading branch information
NathanSweet committed Dec 20, 2020
1 parent 3c9a574 commit a4a0240
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 302 deletions.
3 changes: 3 additions & 0 deletions CHANGES
@@ -1,5 +1,6 @@
[1.9.13]
- [BREAKING CHANGE] Fixed keycode representations for ESCAPE, END, INSERT and F1 to F12. These keys are working on Android now, but if you hardcoded or saved the values you might need to migrate.
- [BREAKING CHANGE] TextureAtlas.AtlasRegion and Region splits and pads fields have been removed and moved to name/value pairs, use #findValue("split") and #findValue("pad") instead.
- GWT: Key codes set with Gdx.input.setCatchKey prevent default browser behaviour
- Added Scaling.contain mode: Scales the source to fit the target while keeping the same aspect ratio, but the source is not scaled at all if smaller in both directions.
- API Addition: Added hasContents() to Clipboard interface, to reduce clipboard notifications on iOS 14
Expand All @@ -8,6 +9,8 @@
Following changes might be done depending on platform: Keys.STAR to Keys.NUMPAD_MULTIPLY, Keys.SLASH to Keys.NUMPAD_DIVIDE, Keys.NUM to Keys.NUM_LOCK, Keys.COMMA to Keys.NUMPAD_COMMA, Keys.PERIOD to Keys.NUMPAD_DOT, Keys.COMMA to Keys.NUMPAD_COMMA, Keys.ENTER to Keys.NUMPAD_ENTER, Keys.PLUS to Keys.NUMPAD_ADD, Keys.MINUS to Keys.NUMPAD_SUBTRACT
- Added a QuadFloatTree class.
- Desktop: Cubemap Seamless feature is now enabled by default when supported, can be changed via backend specific methods, see supportsCubeMapSeamless and enableCubeMapSeamless in both LwjglGraphics and Lwjgl3Graphics.
- API Addition: TextureAtlas reads arbitrary name/value pairs for each region. See #6316.
- TexturePacker writes using a new format when legacyOutput is false (default is true). TextureAtlas can read both old and new formats. See #6316.

[1.9.12]
- [BREAKING CHANGE] iOS: Changed how Retina/hdpi handled on iOS. See #3709.
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -402,31 +403,117 @@ private void writePackFile (File outputDir, String scaledPackFileName, Array<Pag
}
}

Writer writer = new OutputStreamWriter(new FileOutputStream(packFile, true), "UTF-8");
for (Page page : pages) {
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");
writer.write("repeat: " + getRepeatValue() + "\n");
String tab = "", colon = ":", comma = ",";
if (settings.prettyPrint) {
tab = "\t";
colon = ": ";
comma = ", ";
}

boolean appending = packFile.exists();
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(packFile, true), "UTF-8");
for (int i = 0, n = pages.size; i < n; i++) {
Page page = pages.get(i);

if (settings.legacyOutput)
writePageLegacy(writer, page);
else {
if (i != 0 || appending) writer.write("\n");
writePage(writer, appending, page);
}

page.outputRects.sort();
for (Rect rect : page.outputRects) {
writeRect(writer, page, rect, rect.name);
if (settings.legacyOutput)
writeRectLegacy(writer, page, rect, rect.name);
else
writeRect(writer, page, rect, rect.name);
Array<Alias> aliases = new Array(rect.aliases.toArray());
aliases.sort();
for (Alias alias : aliases) {
Rect aliasRect = new Rect();
aliasRect.set(rect);
alias.apply(aliasRect);
writeRect(writer, page, aliasRect, alias.name);
if (settings.legacyOutput)
writeRectLegacy(writer, page, aliasRect, alias.name);
else
writeRect(writer, page, aliasRect, alias.name);
}
}
}
writer.close();
}

private void writePage (OutputStreamWriter writer, boolean appending, Page page) throws IOException {
String tab = "", colon = ":", comma = ",";
if (settings.prettyPrint) {
tab = "\t";
colon = ": ";
comma = ", ";
}

writer.write(page.imageName + "\n");
writer.write(tab + "size" + colon + page.imageWidth + comma + page.imageHeight + "\n");

if (settings.format != Format.RGBA8888) writer.write(tab + "format" + colon + settings.format + "\n");

if (settings.filterMin != TextureFilter.Nearest || settings.filterMag != TextureFilter.Nearest)
writer.write(tab + "filter" + colon + settings.filterMin + comma + settings.filterMag + "\n");

String repeatValue = getRepeatValue();
if (repeatValue != null) writer.write(tab + "repeat" + colon + repeatValue + "\n");

if (settings.premultiplyAlpha) writer.write(tab + "pma" + colon + "true\n");
}

private void writeRect (Writer writer, Page page, Rect rect, String name) throws IOException {
String tab = "", colon = ":", comma = ",";
if (settings.prettyPrint) {
tab = "\t";
colon = ": ";
comma = ", ";
}

writer.write(Rect.getAtlasName(name, settings.flattenPaths) + "\n");
if (rect.index != -1) writer.write(tab + "index" + colon + rect.index + "\n");

writer.write(tab + "bounds" + colon //
+ (page.x + rect.x) + comma + (page.y + page.height - rect.y - (rect.height - settings.paddingY)) + comma //
+ rect.regionWidth + comma + rect.regionHeight + "\n");

int offsetY = rect.originalHeight - rect.regionHeight - rect.offsetY;
if (rect.offsetX != 0 || offsetY != 0 //
|| rect.originalWidth != rect.regionWidth || rect.originalHeight != rect.regionHeight) {
writer.write(tab + "offsets" + colon //
+ rect.offsetX + comma + offsetY + comma //
+ rect.originalWidth + comma + rect.originalHeight + "\n");
}

if (rect.rotated) writer.write(tab + "rotate" + colon + rect.rotated + "\n");

if (rect.splits != null) {
writer.write(tab + "split" + colon //
+ rect.splits[0] + comma + rect.splits[1] + comma //
+ rect.splits[2] + comma + rect.splits[3] + "\n");
}

if (rect.pads != null) {
if (rect.splits == null) writer.write(tab + "split" + colon + "0" + comma + "0" + comma + "0" + comma + "0\n");
writer.write(
tab + "pad" + colon + rect.pads[0] + comma + rect.pads[1] + comma + rect.pads[2] + comma + rect.pads[3] + "\n");
}
}

private void writePageLegacy (OutputStreamWriter writer, Page page) throws IOException {
writer.write("\n" + page.imageName + "\n");
writer.write("size: " + page.imageWidth + ", " + page.imageHeight + "\n");
writer.write("format: " + settings.format + "\n");
writer.write("filter: " + settings.filterMin + ", " + settings.filterMag + "\n");
String repeatValue = getRepeatValue();
writer.write("repeat: " + (repeatValue == null ? "none" : repeatValue) + "\n");
}

private void writeRectLegacy (Writer writer, Page page, Rect rect, String name) throws IOException {
writer.write(Rect.getAtlasName(name, settings.flattenPaths) + "\n");
writer.write(" rotate: " + rect.rotated + "\n");
writer
Expand All @@ -446,11 +533,11 @@ private void writeRect (Writer writer, Page page, Rect rect, String name) throws
writer.write(" index: " + rect.index + "\n");
}

private String getRepeatValue () {
private @Null String getRepeatValue () {
if (settings.wrapX == TextureWrap.Repeat && settings.wrapY == TextureWrap.Repeat) return "xy";
if (settings.wrapX == TextureWrap.Repeat && settings.wrapY == TextureWrap.ClampToEdge) return "x";
if (settings.wrapX == TextureWrap.ClampToEdge && settings.wrapY == TextureWrap.Repeat) return "y";
return "none";
return null;
}

private int getBufferedImageType (Format format) {
Expand Down Expand Up @@ -843,6 +930,8 @@ static public class Settings {
public String[] scaleSuffix = {""};
public Resampling[] scaleResampling = {Resampling.bicubic};
public String atlasExtension = ".atlas";
public boolean prettyPrint = true;
public boolean legacyOutput = true;

public Settings () {
}
Expand Down Expand Up @@ -894,6 +983,8 @@ public void set (Settings settings) {
scaleSuffix = Arrays.copyOf(settings.scaleSuffix, settings.scaleSuffix.length);
scaleResampling = Arrays.copyOf(settings.scaleResampling, settings.scaleResampling.length);
atlasExtension = settings.atlasExtension;
prettyPrint = settings.prettyPrint;
legacyOutput = settings.legacyOutput;
}

public String getScaledPackFileName (String packFileName, int scaleIndex) {
Expand Down
Expand Up @@ -100,7 +100,7 @@ public void splitAtlas (TextureAtlasData atlas, String outputDir) {
String extension = null;

// check if the region is a ninepatch or a normal image and delegate accordingly
if (region.splits == null) {
if (region.findValue("split") == null) {
splitImage = extractImage(img, region, outputDirFile, 0);
if (region.width != region.originalWidth || region.height != region.originalHeight) {
BufferedImage originalImg = new BufferedImage(region.originalWidth, region.originalHeight, img.getType());
Expand Down Expand Up @@ -181,17 +181,19 @@ private BufferedImage extractNinePatch (BufferedImage page, Region region, File
g2.setColor(Color.BLACK);

// Draw the four lines to save the ninepatch's padding and splits
int startX = region.splits[0] + NINEPATCH_PADDING;
int endX = region.width - region.splits[1] + NINEPATCH_PADDING - 1;
int startY = region.splits[2] + NINEPATCH_PADDING;
int endY = region.height - region.splits[3] + NINEPATCH_PADDING - 1;
int[] splits = region.findValue("split");
int startX = splits[0] + NINEPATCH_PADDING;
int endX = region.width - splits[1] + NINEPATCH_PADDING - 1;
int startY = splits[2] + NINEPATCH_PADDING;
int endY = region.height - splits[3] + NINEPATCH_PADDING - 1;
if (endX >= startX) g2.drawLine(startX, 0, endX, 0);
if (endY >= startY) g2.drawLine(0, startY, 0, endY);
if (region.pads != null) {
int padStartX = region.pads[0] + NINEPATCH_PADDING;
int padEndX = region.width - region.pads[1] + NINEPATCH_PADDING - 1;
int padStartY = region.pads[2] + NINEPATCH_PADDING;
int padEndY = region.height - region.pads[3] + NINEPATCH_PADDING - 1;
int[] pads = region.findValue("pad");
if (pads != null) {
int padStartX = pads[0] + NINEPATCH_PADDING;
int padEndX = region.width - pads[1] + NINEPATCH_PADDING - 1;
int padStartY = pads[2] + NINEPATCH_PADDING;
int padEndY = region.height - pads[3] + NINEPATCH_PADDING - 1;
g2.drawLine(padStartX, splitImage.getHeight() - 1, padEndX, splitImage.getHeight() - 1);
g2.drawLine(splitImage.getWidth() - 1, padStartY, splitImage.getWidth() - 1, padEndY);
}
Expand Down
4 changes: 2 additions & 2 deletions gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java
Expand Up @@ -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;
Expand Down

0 comments on commit a4a0240

Please sign in to comment.