diff --git a/README.md b/README.md index ba0e0e1..0fd532a 100644 --- a/README.md +++ b/README.md @@ -708,3 +708,14 @@ The tool deletes `tooling/build` at the end of a run, as it always has. A work directory you chose yourself is kept unless you pass `--clean`, and `--no-clean` keeps the default one too. Cleanup refuses to run when the work directory contains the current directory or the output directory. + +### Failures stop the run + +A problem part-way through never produces a run that looks successful. A render +directory that is missing or cannot be read, a file in it that is not a readable +image, an input that leaves nothing to pack, and a page or coordinates file that +cannot be written all end the run with a nonzero exit status. The coordinates +JSON is written only after every page is on disk, so a failed run cannot leave +an index that points at pages which do not exist. A build that drives the +tooling can stop there instead of packaging partial or stale assets. The render +step fails the same way when an icon cannot be drawn. diff --git a/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasGenerator.java b/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasGenerator.java index 4063e25..3ba5413 100644 --- a/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasGenerator.java +++ b/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasGenerator.java @@ -51,16 +51,16 @@ protected BufferedImage resizeToLayoutSize(BufferedImage source) { protected abstract JsonObject entryToJson(AtlasEntry entry); /** - * Loads, packs and writes the atlas. Fails with an {@link UncheckedIOException} rather than leaving a - * half-written set of files behind. + * Loads, packs and writes the atlas. Nothing to pack is an {@link IllegalStateException} and a file that + * cannot be written an {@link UncheckedIOException}: a run that reports success without producing the atlas, + * or leaves a half-written set of files behind, would let a build package stale or partial assets. */ public void generate() { List entries = loadTextures(); System.out.println("Loaded " + entries.size() + " textures."); if (entries.isEmpty()) { - System.err.println("No textures to pack!"); - return; + throw new IllegalStateException("No textures to pack into " + atlasOutputPath); } int capacity = layout.capacity(); diff --git a/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/OverlayGenerator.java b/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/OverlayGenerator.java index 9f56352..7d95952 100644 --- a/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/OverlayGenerator.java +++ b/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/OverlayGenerator.java @@ -103,7 +103,7 @@ public static void main(String[] args) { System.out.println("\n" + "=".repeat(50)); System.out.println("Overlay generation complete!"); System.out.println("=".repeat(50)); - } catch (IOException | UncheckedIOException | IllegalArgumentException e) { + } catch (IOException | UncheckedIOException | IllegalArgumentException | IllegalStateException e) { System.err.println("Failed to generate overlays: " + e.getMessage()); e.printStackTrace(); System.exit(1); diff --git a/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/SpritesheetGenerator.java b/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/SpritesheetGenerator.java index 91145c4..4399cb7 100644 --- a/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/SpritesheetGenerator.java +++ b/tooling/src/main/java/net/aerh/imagegenerator/tooling/spritesheet/SpritesheetGenerator.java @@ -8,15 +8,16 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; -import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Set; +import java.util.stream.Stream; /** * Generates the item texture atlas from a directory of individual PNG renders. @@ -60,7 +61,7 @@ public static void main(String[] args) { new SpritesheetGenerator(inputDir, paths, layout).generate(); System.out.println("Texture atlas generation complete!"); - } catch (IllegalArgumentException | UncheckedIOException e) { + } catch (IllegalArgumentException | IllegalStateException | UncheckedIOException e) { System.err.println("Error: " + e.getMessage()); System.exit(1); } @@ -95,52 +96,38 @@ private void loadTrimMaterials() { } } + /** + * Reads every PNG in the input directory in name order. A directory that cannot be listed and a file that + * is not a readable image both end the run: skipping them would pack an atlas with holes and report success, + * which is worse for a build than a clear failure. + */ @Override protected List loadTextures() { loadTrimMaterials(); - List textures = new ArrayList<>(); - File folder = inputDir.toFile(); - File[] files = folder.listFiles(f -> f.isFile() && f.getName().toLowerCase().endsWith(".png")); - - if (files == null) { - System.err.println("No textures found in: " + folder.getAbsolutePath()); - return textures; - } - - System.out.println("Loading textures from: " + folder.getAbsolutePath()); - Arrays.sort(files, Comparator.comparing(File::getName)); + List files = listPngs(); + System.out.println("Loading textures from: " + inputDir.toAbsolutePath()); + List textures = new ArrayList<>(); int skipped = 0; int trimFiltered = 0; - for (File file : files) { - String name = file.getName().replace(".png", ""); + for (Path file : files) { + String name = file.getFileName().toString().replace(".png", ""); if (isArmorTrimVariant(name)) { trimFiltered++; continue; } - BufferedImage image = loadImage(file); - if (image == null) { - System.err.println("Failed to load: " + file.getName()); + BufferedImage processed = resizeToLayoutSize(readImage(file)); + + if (ImageUtil.isFullyTransparent(processed)) { + skipped++; continue; } - try { - BufferedImage processed = resizeToLayoutSize(image); - - if (ImageUtil.isFullyTransparent(processed)) { - skipped++; - continue; - } - - textures.add(new AtlasEntry(name, processed)); - } catch (Exception e) { - System.err.println("Failed to process: " + file.getName()); - e.printStackTrace(); - } + textures.add(new AtlasEntry(name, processed)); } if (skipped > 0) { @@ -154,11 +141,30 @@ protected List loadTextures() { return textures; } - private BufferedImage loadImage(File file) { + private List listPngs() { + try (Stream listing = Files.list(inputDir)) { + return listing + .filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().toLowerCase().endsWith(".png")) + .sorted(Comparator.comparing(file -> file.getFileName().toString())) + .toList(); + } catch (IOException e) { + throw new UncheckedIOException("Cannot read texture input directory " + inputDir.toAbsolutePath(), e); + } + } + + /** + * @throws UncheckedIOException when the file cannot be read, or is not in a format ImageIO understands + */ + private static BufferedImage readImage(Path file) { try { - return ImageIO.read(file); + BufferedImage image = ImageIO.read(file.toFile()); + if (image == null) { + throw new IOException("not a readable image"); + } + return image; } catch (IOException e) { - return null; + throw new UncheckedIOException("Failed to load texture " + file + ": " + e.getMessage(), e); } } diff --git a/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasGeneratorPagingTest.java b/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasGeneratorPagingTest.java index 346132c..f125ac3 100644 --- a/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasGeneratorPagingTest.java +++ b/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasGeneratorPagingTest.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; class AtlasGeneratorPagingTest { @@ -31,7 +32,7 @@ class AtlasGeneratorPagingTest { @Test void defaultLayoutPacksSixteenTilesPerRowWithoutGutters() throws IOException { List entries = solidEntries(17, 256); - new TestAtlas(AtlasLayout.DEFAULT, entries).generate(); + atlas(AtlasLayout.DEFAULT, entries).generate(); BufferedImage page = ImageIO.read(directory.resolve("atlas.png").toFile()); assertEquals(4096, page.getWidth()); @@ -52,7 +53,7 @@ void defaultLayoutPacksSixteenTilesPerRowWithoutGutters() throws IOException { @Test void pagesKeepPixelsAndTransparentGutters() throws IOException { List entries = List.of(entry("red", RED, 2), entry("green", GREEN, 2), entry("blue", BLUE, 2)); - new TestAtlas(new AtlasLayout(2, 8, 4, 1), entries).generate(); + atlas(new AtlasLayout(2, 8, 4, 1), entries).generate(); BufferedImage first = ImageIO.read(directory.resolve("atlas.png").toFile()); BufferedImage second = ImageIO.read(directory.resolve("atlas-1.png").toFile()); @@ -74,7 +75,7 @@ void pagesKeepPixelsAndTransparentGutters() throws IOException { @Test void singlePageKeepsTheOriginalFilename() throws IOException { - new TestAtlas(new AtlasLayout(2, 4, 4, 0), List.of(entry("red", RED, 2))).generate(); + atlas(new AtlasLayout(2, 4, 4, 0), List.of(entry("red", RED, 2))).generate(); assertTrue(Files.isRegularFile(directory.resolve("atlas.png"))); assertFalse(Files.exists(directory.resolve("atlas-0.png"))); @@ -89,7 +90,7 @@ void singlePageKeepsTheOriginalFilename() throws IOException { @Test void exactlyCapacityEntriesFillOnePage() throws IOException { - new TestAtlas(new AtlasLayout(2, 4, 4, 0), solidEntries(4, 2)).generate(); + atlas(new AtlasLayout(2, 4, 4, 0), solidEntries(4, 2)).generate(); assertTrue(Files.exists(directory.resolve("atlas.png"))); assertFalse(Files.exists(directory.resolve("atlas-1.png"))); @@ -100,7 +101,7 @@ void exactlyCapacityEntriesFillOnePage() throws IOException { @Test void oneEntryOverCapacityStartsASecondPage() throws IOException { - new TestAtlas(new AtlasLayout(2, 4, 4, 0), solidEntries(5, 2)).generate(); + atlas(new AtlasLayout(2, 4, 4, 0), solidEntries(5, 2)).generate(); assertTrue(Files.exists(directory.resolve("atlas-1.png"))); assertFalse(Files.exists(directory.resolve("atlas-2.png"))); @@ -116,7 +117,7 @@ void paddedLayoutOffsetsBothAxesAndLeavesGuttersBetweenRows() throws IOException List entries = List.of( entry("a", RED, 2), entry("b", GREEN, 2), entry("c", BLUE, 2), entry("d", RED, 2) ); - new TestAtlas(new AtlasLayout(2, 8, 8, 1), entries).generate(); + atlas(new AtlasLayout(2, 8, 8, 1), entries).generate(); BufferedImage page = ImageIO.read(directory.resolve("atlas.png").toFile()); assertEquals(8, page.getWidth()); @@ -137,7 +138,7 @@ void paddedLayoutOffsetsBothAxesAndLeavesGuttersBetweenRows() throws IOException @Test void lastPageIsOnlyAsTallAsItsRows() throws IOException { - new TestAtlas(new AtlasLayout(2, 4, 8, 0), solidEntries(10, 2)).generate(); + atlas(new AtlasLayout(2, 4, 8, 0), solidEntries(10, 2)).generate(); BufferedImage first = ImageIO.read(directory.resolve("atlas.png").toFile()); BufferedImage second = ImageIO.read(directory.resolve("atlas-1.png").toFile()); @@ -151,7 +152,7 @@ void stalePagesFromAnEarlierRunAreRemoved() throws IOException { Files.write(directory.resolve("atlas-1.png"), new byte[]{1, 2, 3}); Files.write(directory.resolve("atlas-2.png"), new byte[]{1, 2, 3}); - new TestAtlas(new AtlasLayout(2, 4, 4, 0), List.of(entry("red", RED, 2))).generate(); + atlas(new AtlasLayout(2, 4, 4, 0), List.of(entry("red", RED, 2))).generate(); assertFalse(Files.exists(directory.resolve("atlas-1.png"))); assertFalse(Files.exists(directory.resolve("atlas-2.png"))); @@ -163,7 +164,7 @@ void stalePagesBeyondAGapAreRemovedAndUnrelatedFilesAreKept() throws IOException Files.write(directory.resolve("other-1.png"), new byte[]{1, 2, 3}); Files.write(directory.resolve("atlas-x.png"), new byte[]{1, 2, 3}); - new TestAtlas(new AtlasLayout(2, 4, 4, 0), List.of(entry("red", RED, 2))).generate(); + atlas(new AtlasLayout(2, 4, 4, 0), List.of(entry("red", RED, 2))).generate(); assertFalse(Files.exists(directory.resolve("atlas-3.png"))); assertTrue(Files.exists(directory.resolve("other-1.png")), "a different atlas's pages are left alone"); @@ -175,7 +176,7 @@ void aShrinkingRunKeepsThePagesItWroteAndRemovesTheRest() throws IOException { Files.write(directory.resolve("atlas-1.png"), new byte[]{1, 2, 3}); Files.write(directory.resolve("atlas-2.png"), new byte[]{1, 2, 3}); - new TestAtlas(new AtlasLayout(2, 4, 4, 0), solidEntries(5, 2)).generate(); + atlas(new AtlasLayout(2, 4, 4, 0), solidEntries(5, 2)).generate(); assertEquals(4, ImageIO.read(directory.resolve("atlas-1.png").toFile()).getWidth()); assertFalse(Files.exists(directory.resolve("atlas-2.png"))); @@ -183,7 +184,7 @@ void aShrinkingRunKeepsThePagesItWroteAndRemovesTheRest() throws IOException { @Test void coordinatesCarryPageAndAtlasButNotPageDimensions() throws IOException { - new TestAtlas(new AtlasLayout(2, 4, 4, 0), solidEntries(5, 2)).generate(); + atlas(new AtlasLayout(2, 4, 4, 0), solidEntries(5, 2)).generate(); JsonArray json = readCoordinates(); assertEquals(5, json.size()); @@ -194,7 +195,7 @@ void coordinatesCarryPageAndAtlasButNotPageDimensions() throws IOException { @Test void pageOutputPathNumbersPagesBeforeTheExtension() { - TestAtlas atlas = new TestAtlas(AtlasLayout.DEFAULT, List.of()); + TestAtlas atlas = atlas(AtlasLayout.DEFAULT, List.of()); assertEquals(directory.resolve("atlas.png"), atlas.pageOutputPath(0)); assertEquals(directory.resolve("atlas-2.png"), atlas.pageOutputPath(2)); } @@ -202,15 +203,18 @@ void pageOutputPathNumbersPagesBeforeTheExtension() { @Test void pageOutputPathIgnoresDotsInDirectoryNames() { Path output = directory.resolve("v26.3").resolve("atlas"); - TestAtlas atlas = new TestAtlas(AtlasLayout.DEFAULT, List.of(), output); + TestAtlas atlas = atlas(AtlasLayout.DEFAULT, List.of(), output); assertEquals(output, atlas.pageOutputPath(0)); assertEquals(directory.resolve("v26.3").resolve("atlas-1"), atlas.pageOutputPath(1)); } @Test - void generatingNothingWritesNoFiles() { - new TestAtlas(new AtlasLayout(2, 4, 4, 0), List.of()).generate(); + void generatingNothingIsAnErrorAndWritesNoFiles() { + TestAtlas atlas = atlas(new AtlasLayout(2, 4, 4, 0), List.of()); + IllegalStateException error = assertThrows(IllegalStateException.class, atlas::generate); + + assertTrue(error.getMessage().contains("atlas.png"), error.getMessage()); assertFalse(Files.exists(directory.resolve("atlas.png"))); assertFalse(Files.exists(directory.resolve("coordinates.json"))); } @@ -253,32 +257,11 @@ private static void assertTransparent(BufferedImage image, int x0, int y0, int w } } - private class TestAtlas extends AtlasGenerator { - - private final List entries; - - TestAtlas(AtlasLayout layout, List entries) { - this(layout, entries, directory.resolve("atlas.png")); - } - - TestAtlas(AtlasLayout layout, List entries, Path atlasOutputPath) { - super(layout, atlasOutputPath, directory.resolve("coordinates.json")); - this.entries = entries; - } - - @Override - protected List loadTextures() { - return entries; - } + private TestAtlas atlas(AtlasLayout layout, List entries) { + return atlas(layout, entries, directory.resolve("atlas.png")); + } - @Override - protected JsonObject entryToJson(AtlasEntry entry) { - JsonObject json = new JsonObject(); - json.addProperty("name", entry.name); - json.addProperty("x", entry.x); - json.addProperty("y", entry.y); - json.addProperty("size", layout.tileSize()); - return json; - } + private TestAtlas atlas(AtlasLayout layout, List entries, Path atlasOutputPath) { + return new TestAtlas(layout, entries, atlasOutputPath, directory.resolve("coordinates.json")); } } diff --git a/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasWriteFailureTest.java b/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasWriteFailureTest.java new file mode 100644 index 0000000..a79a038 --- /dev/null +++ b/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/AtlasWriteFailureTest.java @@ -0,0 +1,71 @@ +package net.aerh.imagegenerator.tooling.spritesheet; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * A page or coordinates file that cannot be written has to fail the run before the coordinates are published, + * so a build never packages a JSON index that points at pages which were not written. + */ +class AtlasWriteFailureTest { + + private static final AtlasLayout ONE_TILE_PER_PAGE = new AtlasLayout(2, 2, 2, 0); + + @TempDir + Path directory; + + @Test + void anAtlasParentThatIsAFileFailsBeforeAnythingIsWritten() throws IOException { + Path blocker = directory.resolve("blocker"); + Files.writeString(blocker, "not a directory"); + TestAtlas atlas = new TestAtlas(ONE_TILE_PER_PAGE, List.of(blank("item")), + blocker.resolve("atlas.png"), directory.resolve("coords.json")); + + UncheckedIOException error = assertThrows(UncheckedIOException.class, atlas::generate); + + assertTrue(error.getMessage().contains("atlas.png"), error.getMessage()); + assertFalse(Files.exists(directory.resolve("coords.json"))); + } + + @Test + void aLaterPageThatCannotBeWrittenStopsBeforeTheCoordinatesArePublished() throws IOException { + Path secondPage = directory.resolve("atlas-1.png"); + Files.createDirectory(secondPage); + Files.writeString(secondPage.resolve("occupant"), "keeps the directory from being deleted"); + TestAtlas atlas = new TestAtlas(ONE_TILE_PER_PAGE, List.of(blank("first"), blank("second")), + directory.resolve("atlas.png"), directory.resolve("coords.json")); + + UncheckedIOException error = assertThrows(UncheckedIOException.class, atlas::generate); + + assertTrue(error.getMessage().contains("atlas-1.png"), error.getMessage()); + assertTrue(Files.isRegularFile(directory.resolve("atlas.png")), "the first page was written before the failure"); + assertFalse(Files.exists(directory.resolve("coords.json"))); + } + + @Test + void aCoordinatesParentThatIsAFileFailsTheRun() throws IOException { + Path blocker = directory.resolve("blocker"); + Files.writeString(blocker, "not a directory"); + TestAtlas atlas = new TestAtlas(ONE_TILE_PER_PAGE, List.of(blank("item")), + directory.resolve("atlas.png"), blocker.resolve("coords.json")); + + UncheckedIOException error = assertThrows(UncheckedIOException.class, atlas::generate); + + assertTrue(error.getMessage().contains("coords.json"), error.getMessage()); + } + + private static AtlasGenerator.AtlasEntry blank(String name) { + return new AtlasGenerator.AtlasEntry(name, new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB)); + } +} diff --git a/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/SpritesheetGeneratorTest.java b/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/SpritesheetGeneratorTest.java index 4198b2c..951885e 100644 --- a/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/SpritesheetGeneratorTest.java +++ b/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/SpritesheetGeneratorTest.java @@ -108,7 +108,7 @@ void aPaletteLayoutWithoutItsKeyFailsTheRunInsteadOfPackingTrims() throws IOExce () -> new SpritesheetGenerator(input, paths(), LAYOUT).generate()); assertTrue(error.getMessage().contains("trim_base.png"), error.getMessage()); - assertFalse(Files.exists(paths().spritesheets()), "nothing should be written"); + assertNothingWritten(); } @Test @@ -121,17 +121,60 @@ void inputDefaultsToTheRenderedItemsDirectory() throws IOException { } @Test - void missingInputDirectoryWritesNothing() { - new SpritesheetGenerator(root.resolve("missing"), paths(), LAYOUT).generate(); + void aMissingInputDirectoryIsAnError() { + SpritesheetGenerator generator = new SpritesheetGenerator(root.resolve("missing"), paths(), LAYOUT); - assertFalse(Files.exists(paths().spritesheets())); - assertFalse(Files.exists(paths().json())); + UncheckedIOException error = assertThrows(UncheckedIOException.class, generator::generate); + + assertTrue(error.getMessage().contains("missing"), error.getMessage()); + assertNothingWritten(); + } + + @Test + void anInputDirectoryWithoutPngsIsAnError() throws IOException { + Path input = root.resolve("input"); + Files.createDirectories(input); + Files.writeString(input.resolve("notes.txt"), "not an image"); + SpritesheetGenerator generator = new SpritesheetGenerator(input, paths(), LAYOUT); + + IllegalStateException error = assertThrows(IllegalStateException.class, generator::generate); + + assertTrue(error.getMessage().contains("No textures to pack"), error.getMessage()); + assertNothingWritten(); + } + + @Test + void aFileThatIsNotAnImageIsAnError() throws IOException { + Path input = root.resolve("input"); + writePng(input.resolve("a.png"), RED); + Files.writeString(input.resolve("broken.png"), "not a PNG"); + SpritesheetGenerator generator = new SpritesheetGenerator(input, paths(), LAYOUT); + + UncheckedIOException error = assertThrows(UncheckedIOException.class, generator::generate); + + assertTrue(error.getMessage().contains("broken.png"), error.getMessage()); + assertNothingWritten(); + } + + @Test + void inputMadeOnlyOfTransparentImagesIsAnError() throws IOException { + Path input = root.resolve("input"); + writePng(input.resolve("empty.png"), 0); + SpritesheetGenerator generator = new SpritesheetGenerator(input, paths(), LAYOUT); + + assertThrows(IllegalStateException.class, generator::generate); + assertNothingWritten(); } private ToolingPaths paths() { return new ToolingPaths(root.resolve("work"), root.resolve("out")); } + private void assertNothingWritten() { + assertFalse(Files.exists(paths().spritesheets()), "no atlas page should be written"); + assertFalse(Files.exists(paths().json()), "no coordinates file should be written"); + } + private JsonArray readCoordinates() throws IOException { Path json = paths().json().resolve("atlas_coordinates.json"); assertTrue(Files.isRegularFile(json), "coordinates file should exist"); diff --git a/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/TestAtlas.java b/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/TestAtlas.java new file mode 100644 index 0000000..1da9fd7 --- /dev/null +++ b/tooling/src/test/java/net/aerh/imagegenerator/tooling/spritesheet/TestAtlas.java @@ -0,0 +1,35 @@ +package net.aerh.imagegenerator.tooling.spritesheet; + +import com.google.gson.JsonObject; + +import java.nio.file.Path; +import java.util.List; + +/** + * An atlas generator fed a fixed list of entries, so tests can exercise packing and writing without a directory + * of PNGs to load first. + */ +class TestAtlas extends AtlasGenerator { + + private final List entries; + + TestAtlas(AtlasLayout layout, List entries, Path atlasOutputPath, Path jsonOutputPath) { + super(layout, atlasOutputPath, jsonOutputPath); + this.entries = entries; + } + + @Override + protected List loadTextures() { + return entries; + } + + @Override + protected JsonObject entryToJson(AtlasEntry entry) { + JsonObject json = new JsonObject(); + json.addProperty("name", entry.name); + json.addProperty("x", entry.x); + json.addProperty("y", entry.y); + json.addProperty("size", layout.tileSize()); + return json; + } +}