From 0be02622c4f427ac782db9a52b04e6e812357b9b Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Sat, 23 May 2026 22:20:24 +0100 Subject: [PATCH] implements PLN-0082 --- discussion/index.ndjson | 2 +- ...a8888-palette-model-and-argb-conversion.md | 16 ++-- .../java/p/packer/models/PackerPaletteV1.java | 5 +- .../repositories/PackerAssetWalker.java | 9 +-- .../repositories/PackerGlyphBankWalker.java | 80 ++++++------------- .../FileSystemPackerWorkspaceService.java | 33 +++----- .../services/PackerReadMessageMapper.java | 13 +-- .../PackerGlyphBankWalkerTest.java | 68 +++++++++++++++- 8 files changed, 120 insertions(+), 106 deletions(-) diff --git a/discussion/index.ndjson b/discussion/index.ndjson index a0306ab6..5d67a3e2 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -1,5 +1,5 @@ {"type":"meta","next_id":{"DSC":39,"AGD":42,"DEC":38,"PLN":87,"LSN":53,"CLSN":1}} -{"type":"discussion","id":"DSC-0038","status":"open","ticket":"studio-packer-rgba8888-asset-pipeline","title":"Studio and Packer RGBA8888 Asset Pipeline Alignment","created_at":"2026-05-23","updated_at":"2026-05-23","tags":["studio","packer","assets","glyph-bank","palette","rgba8888","runtime-alignment"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-studio-packer-rgba8888-asset-pipeline.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23"}],"decisions":[{"id":"DEC-0037","file":"DEC-0037-studio-packer-rgba8888-asset-pipeline.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23","ref_agenda":"AGD-0041"}],"plans":[{"id":"PLN-0081","file":"PLN-0081-rgba8888-packer-and-studio-spec-propagation.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0082","file":"PLN-0082-rgba8888-palette-model-and-argb-conversion.md","status":"review","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0083","file":"PLN-0083-rgba8888-glyph-payload-emission-and-sizes.md","status":"review","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0084","file":"PLN-0084-rgba8888-packer-projections-and-workspace-apis.md","status":"review","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0085","file":"PLN-0085-rgba8888-tests-fixtures-and-conformance.md","status":"review","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0086","file":"PLN-0086-rgba8888-final-integration-and-residue-removal.md","status":"review","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0038","status":"open","ticket":"studio-packer-rgba8888-asset-pipeline","title":"Studio and Packer RGBA8888 Asset Pipeline Alignment","created_at":"2026-05-23","updated_at":"2026-05-23","tags":["studio","packer","assets","glyph-bank","palette","rgba8888","runtime-alignment"],"agendas":[{"id":"AGD-0041","file":"AGD-0041-studio-packer-rgba8888-asset-pipeline.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23"}],"decisions":[{"id":"DEC-0037","file":"DEC-0037-studio-packer-rgba8888-asset-pipeline.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23","ref_agenda":"AGD-0041"}],"plans":[{"id":"PLN-0081","file":"PLN-0081-rgba8888-packer-and-studio-spec-propagation.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0082","file":"PLN-0082-rgba8888-palette-model-and-argb-conversion.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0083","file":"PLN-0083-rgba8888-glyph-payload-emission-and-sizes.md","status":"review","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0084","file":"PLN-0084-rgba8888-packer-projections-and-workspace-apis.md","status":"review","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0085","file":"PLN-0085-rgba8888-tests-fixtures-and-conformance.md","status":"review","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]},{"id":"PLN-0086","file":"PLN-0086-rgba8888-final-integration-and-residue-removal.md","status":"review","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0037"]}],"lessons":[]} {"type":"discussion","id":"DSC-0037","status":"done","ticket":"pbs-autocomplete-parameter-names","title":"PBS autocomplete parameter names for stdlib and method calls","created_at":"2026-05-08","updated_at":"2026-05-14","tags":["compiler-pbs","studio","lsp","autocomplete","signature-help","stdlib"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0052","file":"discussion/lessons/DSC-0037-pbs-autocomplete-parameter-names/LSN-0052-canonical-callable-parameter-names-through-pbs-editor-assistance.md","status":"done","created_at":"2026-05-14","updated_at":"2026-05-14"}]} {"type":"discussion","id":"DSC-0036","status":"open","ticket":"pbs-symbol-documentation-and-hover-markdown","title":"Modelo de documentacao de simbolos em PBS e consumo markdown no hover","created_at":"2026-05-08","updated_at":"2026-05-08","tags":["compiler","compiler-pbs","studio","lsp","vscode","editor","hover","documentation","markdown"],"agendas":[{"id":"AGD-0039","file":"AGD-0039-pbs-symbol-documentation-and-hover-markdown.md","status":"open","created_at":"2026-05-08","updated_at":"2026-05-08"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0035","status":"done","ticket":"pbs-lsp-editor-assistance-wave-1","title":"Wave 1 de assistencia editorial via LSP para PBS no VS Code","created_at":"2026-05-08","updated_at":"2026-05-08","tags":["studio","lsp","vscode","compiler","compiler-pbs","editor","completion","hover","signature-help"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0051","file":"discussion/lessons/DSC-0035-pbs-lsp-editor-assistance-wave-1/LSN-0051-compiler-backed-editor-assistance-for-pbs.md","status":"done","created_at":"2026-05-08","updated_at":"2026-05-08"}]} diff --git a/discussion/workflow/plans/PLN-0082-rgba8888-palette-model-and-argb-conversion.md b/discussion/workflow/plans/PLN-0082-rgba8888-palette-model-and-argb-conversion.md index 2a89ff06..ba2219cb 100644 --- a/discussion/workflow/plans/PLN-0082-rgba8888-palette-model-and-argb-conversion.md +++ b/discussion/workflow/plans/PLN-0082-rgba8888-palette-model-and-argb-conversion.md @@ -2,9 +2,9 @@ id: PLN-0082 ticket: studio-packer-rgba8888-asset-pipeline title: RGBA8888 Palette Model and ARGB Conversion -status: review +status: done created: 2026-05-23 -completed: +completed: 2026-05-23 ref_decisions: - DEC-0037 tags: @@ -124,12 +124,12 @@ diagnostic wording to remove `indices 1..15`. ## Acceptance Criteria -- [ ] `PackerPaletteV1` exposes `rgba8888` and no RGB565 field. -- [ ] `PackerGlyphBankWalker` stores canonical RGBA values. -- [ ] Magenta is not special. -- [ ] Alpha zero and partial alpha are valid palette data. -- [ ] All 16 palette indices are usable. -- [ ] Unit tests cover conversion and palette indexing semantics. +- [x] `PackerPaletteV1` exposes `rgba8888` and no RGB565 field. +- [x] `PackerGlyphBankWalker` stores canonical RGBA values. +- [x] Magenta is not special. +- [x] Alpha zero and partial alpha are valid palette data. +- [x] All 16 palette indices are usable. +- [x] Unit tests cover conversion and palette indexing semantics. ## Dependencies diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerPaletteV1.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerPaletteV1.java index c67b8b6d..1af7ef1d 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerPaletteV1.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/models/PackerPaletteV1.java @@ -1,6 +1,5 @@ package p.packer.models; public record PackerPaletteV1( - int[] originalArgb8888, - short[] convertedRgb565) { -} \ No newline at end of file + int[] rgba8888) { +} diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java index 89b63d94..95c2a95d 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerAssetWalker.java @@ -383,13 +383,10 @@ public class PackerAssetWalker { if (!indexNode.isInt() || !paletteNode.isObject()) { continue; } - final int originalCount = paletteNode.path("originalArgb8888").isArray() - ? paletteNode.path("originalArgb8888").size() + final int colorCount = paletteNode.path("rgba8888").isArray() + ? paletteNode.path("rgba8888").size() : 0; - final int convertedCount = paletteNode.path("convertedRgb565").isArray() - ? paletteNode.path("convertedRgb565").size() - : 0; - palettes.add(new DeclaredPalette(indexNode.intValue(), Math.min(originalCount, convertedCount))); + palettes.add(new DeclaredPalette(indexNode.intValue(), colorCount)); } palettes.sort(java.util.Comparator.comparingInt(DeclaredPalette::index)); return List.copyOf(palettes); diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerGlyphBankWalker.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerGlyphBankWalker.java index 0923b9a3..306845ef 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerGlyphBankWalker.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/repositories/PackerGlyphBankWalker.java @@ -11,8 +11,7 @@ import java.nio.file.Path; import java.util.*; public class PackerGlyphBankWalker extends PackerAbstractBankWalker { - private static final int MAX_COLORS_PER_PALETTE = 15; - private static final int COLOR_KEY_RGB = 0x00FF00FF; + private static final int MAX_COLORS_PER_PALETTE = 16; private static final Set SUPPORTED_MIME_TYPES = Set.of( "image/png" ); @@ -69,58 +68,37 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker paletteIndexByRgb = new LinkedHashMap<>(); + final Map paletteIndexByRgba = new LinkedHashMap<>(); final byte[] paletteIndices = new byte[image.getWidth() * image.getHeight()]; - boolean partialAlphaFound = false; boolean exceededPaletteLimit = false; int offset = 0; for (var y = 0; y < image.getHeight(); y++) { for (var x = 0; x < image.getWidth(); x++) { final int argb = image.getRGB(x, y); - final int alpha = (argb >>> 24) & 0xFF; - final int rgb = argb & 0x00FFFFFF; + final int rgba = argbToRgba(argb); - if (alpha == 0 || rgb == COLOR_KEY_RGB) { - paletteIndices[offset++] = 0; - continue; - } - - if (alpha < 0xFF) { - partialAlphaFound = true; - } - - Integer paletteIndex = paletteIndexByRgb.get(rgb); + Integer paletteIndex = paletteIndexByRgba.get(rgba); if (paletteIndex == null) { - if (paletteIndexByRgb.size() >= MAX_COLORS_PER_PALETTE) { + if (paletteIndexByRgba.size() >= MAX_COLORS_PER_PALETTE) { exceededPaletteLimit = true; paletteIndices[offset++] = 0; continue; } - paletteIndex = paletteIndexByRgb.size() + 1; - paletteIndexByRgb.put(rgb, paletteIndex); + paletteIndex = paletteIndexByRgba.size(); + paletteIndexByRgba.put(rgba, paletteIndex); } paletteIndices[offset++] = paletteIndex.byteValue(); } } - if (partialAlphaFound) { - diagnostics.add(new PackerDiagnostic( - PackerDiagnosticSeverity.WARNING, - PackerDiagnosticCategory.HYGIENE, - "Tile contains partial alpha; flattening to RGB and ignoring alpha channel", - fileProbe.path(), - false - )); - } - if (exceededPaletteLimit) { diagnostics.add(new PackerDiagnostic( PackerDiagnosticSeverity.ERROR, PackerDiagnosticCategory.STRUCTURAL, "Tile image exceeds color limit for " + fileProbe.path().getFileName() - + ": expected at most " + MAX_COLORS_PER_PALETTE + " colors for indices 1..15", + + ": expected at most " + MAX_COLORS_PER_PALETTE + " RGBA8888 colors", fileProbe.path(), true )); @@ -131,7 +109,7 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker @@ -224,39 +202,27 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker toPalettePayload(final PackerPaletteV1 palette) { - final List convertedRgb565 = new ArrayList<>(palette.convertedRgb565().length); - for (final short value : palette.convertedRgb565()) { - convertedRgb565.add(Short.toUnsignedInt(value)); - } - return Map.of( - "originalArgb8888", palette.originalArgb8888(), - "convertedRgb565", convertedRgb565 + "rgba8888", palette.rgba8888() ); } - private PackerPaletteV1 buildPalette(final Map paletteIndexByRgb) { - final int[] originalArgb8888 = new int[paletteIndexByRgb.size()]; - final short[] convertedRgb565 = new short[paletteIndexByRgb.size()]; - for (final var entry : paletteIndexByRgb.entrySet()) { - final int rgb = entry.getKey(); - final int index = entry.getValue() - 1; - final int argb = 0xFF000000 | rgb; - originalArgb8888[index] = argb; - convertedRgb565[index] = convertToRgb565(argb); + private PackerPaletteV1 buildPalette(final Map paletteIndexByRgba) { + final int[] rgba8888 = new int[paletteIndexByRgba.size()]; + for (final var entry : paletteIndexByRgba.entrySet()) { + final int rgba = entry.getKey(); + final int index = entry.getValue(); + rgba8888[index] = rgba; } - return new PackerPaletteV1(originalArgb8888, convertedRgb565); + return new PackerPaletteV1(rgba8888); } - private short convertToRgb565(final int argb) { - final int red = (argb >> 16) & 0xFF; - final int green = (argb >> 8) & 0xFF; + private int argbToRgba(final int argb) { + final int alpha = (argb >>> 24) & 0xFF; + final int red = (argb >>> 16) & 0xFF; + final int green = (argb >>> 8) & 0xFF; final int blue = argb & 0xFF; - - final int r5 = (red >> 3) & 0x1F; - final int g6 = (green >> 2) & 0x3F; - final int b5 = (blue >> 3) & 0x1F; - return (short) ((r5 << 11) | (g6 << 5) | b5); + return (red << 24) | (green << 16) | (blue << 8) | alpha; } private java.awt.image.BufferedImage readImage( diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java index 2e23235d..b065946d 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/FileSystemPackerWorkspaceService.java @@ -1161,22 +1161,19 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe Object paletteMetadata, String artifactFile, Map declaredPaletteIndexByRgb) { - final int[] originalArgb8888 = originalPaletteColors(paletteMetadata); - if (originalArgb8888.length == 0) { + final int[] rgba8888 = paletteColors(paletteMetadata); + if (rgba8888.length == 0) { throw new IllegalStateException("Selected tile artifact is missing palette metadata required for palette remapping: " + artifactFile); } final byte[] remapped = Arrays.copyOf(tile.paletteIndices(), tile.paletteIndices().length); for (int index = 0; index < remapped.length; index += 1) { final int paletteIndex = Byte.toUnsignedInt(remapped[index]); - if (paletteIndex == 0) { - continue; - } - if (paletteIndex > originalArgb8888.length) { + if (paletteIndex >= rgba8888.length) { throw new IllegalStateException("Tile palette index exceeds discovered palette size for artifact: " + artifactFile); } - final int originalArgb = originalArgb8888[paletteIndex - 1]; - final Integer materialIndex = declaredPaletteIndexByRgb.get(rgb24(originalArgb)); - if (materialIndex == null || materialIndex < 1 || materialIndex > 15) { + final int rgba = rgba8888[paletteIndex]; + final Integer materialIndex = declaredPaletteIndexByRgb.get(rgba); + if (materialIndex == null || materialIndex < 0 || materialIndex > 15) { throw new IllegalStateException("Unable to resolve canonical palette index for artifact color in " + artifactFile); } remapped[index] = (byte) (materialIndex & 0x0F); @@ -1189,29 +1186,29 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe if (!(palettesNode instanceof ArrayNode palettesArray) || palettesArray.isEmpty()) { throw new IllegalStateException("Glyph packing requires at least one declared pipeline palette."); } - final JsonNode paletteNode = palettesArray.get(0).path("palette").path("originalArgb8888"); + final JsonNode paletteNode = palettesArray.get(0).path("palette").path("rgba8888"); if (!paletteNode.isArray() || paletteNode.isEmpty()) { - throw new IllegalStateException("Glyph packing requires originalArgb8888 entries in the declared pipeline palette."); + throw new IllegalStateException("Glyph packing requires rgba8888 entries in the declared pipeline palette."); } final LinkedHashMap mapping = new LinkedHashMap<>(); for (int index = 0; index < paletteNode.size(); index += 1) { final JsonNode colorNode = paletteNode.get(index); if (!colorNode.canConvertToInt()) { - throw new IllegalStateException("Declared pipeline palette contains a non-integer originalArgb8888 entry."); + throw new IllegalStateException("Declared pipeline palette contains a non-integer rgba8888 entry."); } - mapping.put(rgb24(colorNode.intValue()), index + 1); + mapping.put(colorNode.intValue(), index); } return Map.copyOf(mapping); } - private int[] originalPaletteColors(Object paletteMetadata) { + private int[] paletteColors(Object paletteMetadata) { if (paletteMetadata instanceof PackerPaletteV1 palette) { - return palette.originalArgb8888(); + return palette.rgba8888(); } if (!(paletteMetadata instanceof Map map)) { return new int[0]; } - final Object colors = map.get("originalArgb8888"); + final Object colors = map.get("rgba8888"); if (!(colors instanceof List list)) { return new int[0]; } @@ -1226,10 +1223,6 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe return values; } - private int rgb24(int argb) { - return argb & 0x00FFFFFF; - } - private PackedAsset packSceneBank(PackerRuntimeAsset runtimeAsset, PackerAssetDeclaration declaration) { final ScenePackRequest request = runtimeAsset.walkProjection().buildCandidateFiles().stream() .map(file -> file.metadata().get("scenePackRequest")) diff --git a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java index 1b1693a1..3d533dec 100644 --- a/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java +++ b/prometeu-packer/prometeu-packer-v1/src/main/java/p/packer/services/PackerReadMessageMapper.java @@ -109,17 +109,12 @@ public final class PackerReadMessageMapper { "paletteIndices", List.copyOf(paletteIndices)); } if (value instanceof PackerPaletteV1 palette) { - final List originalArgb8888 = new java.util.ArrayList<>(palette.originalArgb8888().length); - for (int argb : palette.originalArgb8888()) { - originalArgb8888.add(argb); - } - final List convertedRgb565 = new java.util.ArrayList<>(palette.convertedRgb565().length); - for (short rgb565 : palette.convertedRgb565()) { - convertedRgb565.add(Short.toUnsignedInt(rgb565)); + final List rgba8888 = new java.util.ArrayList<>(palette.rgba8888().length); + for (int rgba : palette.rgba8888()) { + rgba8888.add(rgba); } return Map.of( - "originalArgb8888", List.copyOf(originalArgb8888), - "convertedRgb565", List.copyOf(convertedRgb565)); + "rgba8888", List.copyOf(rgba8888)); } if (value instanceof Map nestedMap) { final java.util.LinkedHashMap normalized = new java.util.LinkedHashMap<>(); diff --git a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerGlyphBankWalkerTest.java b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerGlyphBankWalkerTest.java index ce2538cc..398a5098 100644 --- a/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerGlyphBankWalkerTest.java +++ b/prometeu-packer/prometeu-packer-v1/src/test/java/p/packer/repositories/PackerGlyphBankWalkerTest.java @@ -5,8 +5,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import p.packer.models.PackerAssetCacheEntry; import p.packer.models.PackerFileCacheEntry; -import p.packer.models.PackerTileIndexedV1; +import p.packer.models.PackerDiagnostic; import p.packer.models.PackerGlyphBankRequirements; +import p.packer.models.PackerPaletteV1; +import p.packer.models.PackerTileIndexedV1; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -42,7 +44,7 @@ final class PackerGlyphBankWalkerTest { null, Map.of( "tile", Map.of("width", 16, "height", 16), - "palette", Map.of("originalArgb8888", List.of(0xFFFFFFFF))), + "palette", Map.of("rgba8888", List.of(0xFFFFFFFF))), List.of()))); final var result = walker.walk(assetRoot, new PackerGlyphBankRequirements(16), Optional.of(priorCache)); @@ -52,6 +54,54 @@ final class PackerGlyphBankWalkerTest { assertTrue(tile.paletteIndices().length > 0); } + @Test + void preservesRgba8888PaletteDataAndUsesIndexZeroAsOrdinaryColor() throws Exception { + final Path assetRoot = Files.createDirectories(tempDir.resolve("rgba")); + final Path filePath = assetRoot.resolve("tile.png"); + final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + image.setRGB(0, 0, 0x80FF0000); + image.setRGB(1, 0, 0x00000000); + image.setRGB(2, 0, 0xFFFF00FF); + for (int y = 0; y < 16; y++) { + for (int x = 3; x < 16; x++) { + image.setRGB(x, y, 0xFF00FF00); + } + } + ImageIO.write(image, "png", filePath.toFile()); + + final PackerGlyphBankWalker walker = new PackerGlyphBankWalker(MAPPER); + final var result = walker.walk(assetRoot, new PackerGlyphBankRequirements(16)); + + assertTrue(result.diagnostics().stream().noneMatch(diagnostic -> diagnostic.message().contains("partial alpha"))); + final var metadata = result.probeResults().getFirst().metadata(); + final PackerTileIndexedV1 tile = (PackerTileIndexedV1) metadata.get("tile"); + final PackerPaletteV1 palette = (PackerPaletteV1) metadata.get("palette"); + + assertEquals(0, Byte.toUnsignedInt(tile.paletteIndices()[0])); + assertEquals(1, Byte.toUnsignedInt(tile.paletteIndices()[1])); + assertEquals(2, Byte.toUnsignedInt(tile.paletteIndices()[2])); + assertArrayEquals(new int[]{0xFF000080, 0x00000000, 0xFF00FFFF, 0x00FF00FF}, palette.rgba8888()); + } + + @Test + void acceptsSixteenRgbaColorsAndRejectsSeventeenth() throws Exception { + final Path validRoot = Files.createDirectories(tempDir.resolve("sixteen")); + writeColorCountTile(validRoot.resolve("tile.png"), 16); + + final PackerGlyphBankWalker walker = new PackerGlyphBankWalker(MAPPER); + final var valid = walker.walk(validRoot, new PackerGlyphBankRequirements(16)); + + assertTrue(valid.diagnostics().stream().noneMatch(PackerDiagnostic::blocking)); + + final Path invalidRoot = Files.createDirectories(tempDir.resolve("seventeen")); + writeColorCountTile(invalidRoot.resolve("tile.png"), 17); + final var invalid = walker.walk(invalidRoot, new PackerGlyphBankRequirements(16)); + + assertTrue(invalid.probeResults().stream() + .flatMap(probeResult -> probeResult.diagnostics().stream()) + .anyMatch(PackerDiagnostic::blocking)); + } + private void writeTile(Path path) throws Exception { final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); for (int y = 0; y < 16; y++) { @@ -61,4 +111,18 @@ final class PackerGlyphBankWalkerTest { } ImageIO.write(image, "png", path.toFile()); } + + private void writeColorCountTile(Path path, int colorCount) throws Exception { + final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + final int index = Math.min(colorCount - 1, y * 16 + x); + final int red = (index * 17) & 0xFF; + final int green = (index * 31) & 0xFF; + final int blue = (index * 47) & 0xFF; + image.setRGB(x, y, 0xFF000000 | (red << 16) | (green << 8) | blue); + } + } + ImageIO.write(image, "png", path.toFile()); + } }