implements PLN-0082
This commit is contained in:
parent
ad2bfeb66f
commit
0be02622c4
@ -1,5 +1,5 @@
|
|||||||
{"type":"meta","next_id":{"DSC":39,"AGD":42,"DEC":38,"PLN":87,"LSN":53,"CLSN":1}}
|
{"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-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-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"}]}
|
{"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"}]}
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
id: PLN-0082
|
id: PLN-0082
|
||||||
ticket: studio-packer-rgba8888-asset-pipeline
|
ticket: studio-packer-rgba8888-asset-pipeline
|
||||||
title: RGBA8888 Palette Model and ARGB Conversion
|
title: RGBA8888 Palette Model and ARGB Conversion
|
||||||
status: review
|
status: done
|
||||||
created: 2026-05-23
|
created: 2026-05-23
|
||||||
completed:
|
completed: 2026-05-23
|
||||||
ref_decisions:
|
ref_decisions:
|
||||||
- DEC-0037
|
- DEC-0037
|
||||||
tags:
|
tags:
|
||||||
@ -124,12 +124,12 @@ diagnostic wording to remove `indices 1..15`.
|
|||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
||||||
- [ ] `PackerPaletteV1` exposes `rgba8888` and no RGB565 field.
|
- [x] `PackerPaletteV1` exposes `rgba8888` and no RGB565 field.
|
||||||
- [ ] `PackerGlyphBankWalker` stores canonical RGBA values.
|
- [x] `PackerGlyphBankWalker` stores canonical RGBA values.
|
||||||
- [ ] Magenta is not special.
|
- [x] Magenta is not special.
|
||||||
- [ ] Alpha zero and partial alpha are valid palette data.
|
- [x] Alpha zero and partial alpha are valid palette data.
|
||||||
- [ ] All 16 palette indices are usable.
|
- [x] All 16 palette indices are usable.
|
||||||
- [ ] Unit tests cover conversion and palette indexing semantics.
|
- [x] Unit tests cover conversion and palette indexing semantics.
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package p.packer.models;
|
package p.packer.models;
|
||||||
|
|
||||||
public record PackerPaletteV1(
|
public record PackerPaletteV1(
|
||||||
int[] originalArgb8888,
|
int[] rgba8888) {
|
||||||
short[] convertedRgb565) {
|
|
||||||
}
|
}
|
||||||
@ -383,13 +383,10 @@ public class PackerAssetWalker {
|
|||||||
if (!indexNode.isInt() || !paletteNode.isObject()) {
|
if (!indexNode.isInt() || !paletteNode.isObject()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final int originalCount = paletteNode.path("originalArgb8888").isArray()
|
final int colorCount = paletteNode.path("rgba8888").isArray()
|
||||||
? paletteNode.path("originalArgb8888").size()
|
? paletteNode.path("rgba8888").size()
|
||||||
: 0;
|
: 0;
|
||||||
final int convertedCount = paletteNode.path("convertedRgb565").isArray()
|
palettes.add(new DeclaredPalette(indexNode.intValue(), colorCount));
|
||||||
? paletteNode.path("convertedRgb565").size()
|
|
||||||
: 0;
|
|
||||||
palettes.add(new DeclaredPalette(indexNode.intValue(), Math.min(originalCount, convertedCount)));
|
|
||||||
}
|
}
|
||||||
palettes.sort(java.util.Comparator.comparingInt(DeclaredPalette::index));
|
palettes.sort(java.util.Comparator.comparingInt(DeclaredPalette::index));
|
||||||
return List.copyOf(palettes);
|
return List.copyOf(palettes);
|
||||||
|
|||||||
@ -11,8 +11,7 @@ import java.nio.file.Path;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphBankRequirements> {
|
public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphBankRequirements> {
|
||||||
private static final int MAX_COLORS_PER_PALETTE = 15;
|
private static final int MAX_COLORS_PER_PALETTE = 16;
|
||||||
private static final int COLOR_KEY_RGB = 0x00FF00FF;
|
|
||||||
private static final Set<String> SUPPORTED_MIME_TYPES = Set.of(
|
private static final Set<String> SUPPORTED_MIME_TYPES = Set.of(
|
||||||
"image/png"
|
"image/png"
|
||||||
);
|
);
|
||||||
@ -69,58 +68,37 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphB
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<Integer, Integer> paletteIndexByRgb = new LinkedHashMap<>();
|
final Map<Integer, Integer> paletteIndexByRgba = new LinkedHashMap<>();
|
||||||
final byte[] paletteIndices = new byte[image.getWidth() * image.getHeight()];
|
final byte[] paletteIndices = new byte[image.getWidth() * image.getHeight()];
|
||||||
boolean partialAlphaFound = false;
|
|
||||||
boolean exceededPaletteLimit = false;
|
boolean exceededPaletteLimit = false;
|
||||||
|
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
for (var y = 0; y < image.getHeight(); y++) {
|
for (var y = 0; y < image.getHeight(); y++) {
|
||||||
for (var x = 0; x < image.getWidth(); x++) {
|
for (var x = 0; x < image.getWidth(); x++) {
|
||||||
final int argb = image.getRGB(x, y);
|
final int argb = image.getRGB(x, y);
|
||||||
final int alpha = (argb >>> 24) & 0xFF;
|
final int rgba = argbToRgba(argb);
|
||||||
final int rgb = argb & 0x00FFFFFF;
|
|
||||||
|
|
||||||
if (alpha == 0 || rgb == COLOR_KEY_RGB) {
|
Integer paletteIndex = paletteIndexByRgba.get(rgba);
|
||||||
paletteIndices[offset++] = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alpha < 0xFF) {
|
|
||||||
partialAlphaFound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer paletteIndex = paletteIndexByRgb.get(rgb);
|
|
||||||
if (paletteIndex == null) {
|
if (paletteIndex == null) {
|
||||||
if (paletteIndexByRgb.size() >= MAX_COLORS_PER_PALETTE) {
|
if (paletteIndexByRgba.size() >= MAX_COLORS_PER_PALETTE) {
|
||||||
exceededPaletteLimit = true;
|
exceededPaletteLimit = true;
|
||||||
paletteIndices[offset++] = 0;
|
paletteIndices[offset++] = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
paletteIndex = paletteIndexByRgb.size() + 1;
|
paletteIndex = paletteIndexByRgba.size();
|
||||||
paletteIndexByRgb.put(rgb, paletteIndex);
|
paletteIndexByRgba.put(rgba, paletteIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
paletteIndices[offset++] = paletteIndex.byteValue();
|
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) {
|
if (exceededPaletteLimit) {
|
||||||
diagnostics.add(new PackerDiagnostic(
|
diagnostics.add(new PackerDiagnostic(
|
||||||
PackerDiagnosticSeverity.ERROR,
|
PackerDiagnosticSeverity.ERROR,
|
||||||
PackerDiagnosticCategory.STRUCTURAL,
|
PackerDiagnosticCategory.STRUCTURAL,
|
||||||
"Tile image exceeds color limit for " + fileProbe.path().getFileName()
|
"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(),
|
fileProbe.path(),
|
||||||
true
|
true
|
||||||
));
|
));
|
||||||
@ -131,7 +109,7 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphB
|
|||||||
}
|
}
|
||||||
|
|
||||||
final var tile = new PackerTileIndexedV1(image.getWidth(), image.getHeight(), paletteIndices);
|
final var tile = new PackerTileIndexedV1(image.getWidth(), image.getHeight(), paletteIndices);
|
||||||
final var palette = buildPalette(paletteIndexByRgb);
|
final var palette = buildPalette(paletteIndexByRgba);
|
||||||
// Disabled for now: derived tile/palette JSON artifacts are not emitted during normal walks.
|
// Disabled for now: derived tile/palette JSON artifacts are not emitted during normal walks.
|
||||||
// If we want filesystem-side debug artifacts again, uncomment the call below.
|
// If we want filesystem-side debug artifacts again, uncomment the call below.
|
||||||
// serializeProbeArtifacts(fileProbe, assetRoot, tile, palette, diagnostics);
|
// serializeProbeArtifacts(fileProbe, assetRoot, tile, palette, diagnostics);
|
||||||
@ -150,7 +128,7 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphB
|
|||||||
final Object width = tile.get("width");
|
final Object width = tile.get("width");
|
||||||
final Object height = tile.get("height");
|
final Object height = tile.get("height");
|
||||||
final Object indices = tile.get("paletteIndices");
|
final Object indices = tile.get("paletteIndices");
|
||||||
final Object colors = palette.get("originalArgb8888");
|
final Object colors = palette.get("rgba8888");
|
||||||
return width instanceof Number
|
return width instanceof Number
|
||||||
&& height instanceof Number
|
&& height instanceof Number
|
||||||
&& indices instanceof List<?>
|
&& indices instanceof List<?>
|
||||||
@ -224,39 +202,27 @@ public class PackerGlyphBankWalker extends PackerAbstractBankWalker<PackerGlyphB
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> toPalettePayload(final PackerPaletteV1 palette) {
|
private Map<String, Object> toPalettePayload(final PackerPaletteV1 palette) {
|
||||||
final List<Integer> convertedRgb565 = new ArrayList<>(palette.convertedRgb565().length);
|
|
||||||
for (final short value : palette.convertedRgb565()) {
|
|
||||||
convertedRgb565.add(Short.toUnsignedInt(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Map.of(
|
return Map.of(
|
||||||
"originalArgb8888", palette.originalArgb8888(),
|
"rgba8888", palette.rgba8888()
|
||||||
"convertedRgb565", convertedRgb565
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PackerPaletteV1 buildPalette(final Map<Integer, Integer> paletteIndexByRgb) {
|
private PackerPaletteV1 buildPalette(final Map<Integer, Integer> paletteIndexByRgba) {
|
||||||
final int[] originalArgb8888 = new int[paletteIndexByRgb.size()];
|
final int[] rgba8888 = new int[paletteIndexByRgba.size()];
|
||||||
final short[] convertedRgb565 = new short[paletteIndexByRgb.size()];
|
for (final var entry : paletteIndexByRgba.entrySet()) {
|
||||||
for (final var entry : paletteIndexByRgb.entrySet()) {
|
final int rgba = entry.getKey();
|
||||||
final int rgb = entry.getKey();
|
final int index = entry.getValue();
|
||||||
final int index = entry.getValue() - 1;
|
rgba8888[index] = rgba;
|
||||||
final int argb = 0xFF000000 | rgb;
|
|
||||||
originalArgb8888[index] = argb;
|
|
||||||
convertedRgb565[index] = convertToRgb565(argb);
|
|
||||||
}
|
}
|
||||||
return new PackerPaletteV1(originalArgb8888, convertedRgb565);
|
return new PackerPaletteV1(rgba8888);
|
||||||
}
|
}
|
||||||
|
|
||||||
private short convertToRgb565(final int argb) {
|
private int argbToRgba(final int argb) {
|
||||||
final int red = (argb >> 16) & 0xFF;
|
final int alpha = (argb >>> 24) & 0xFF;
|
||||||
final int green = (argb >> 8) & 0xFF;
|
final int red = (argb >>> 16) & 0xFF;
|
||||||
|
final int green = (argb >>> 8) & 0xFF;
|
||||||
final int blue = argb & 0xFF;
|
final int blue = argb & 0xFF;
|
||||||
|
return (red << 24) | (green << 16) | (blue << 8) | alpha;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private java.awt.image.BufferedImage readImage(
|
private java.awt.image.BufferedImage readImage(
|
||||||
|
|||||||
@ -1161,22 +1161,19 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
Object paletteMetadata,
|
Object paletteMetadata,
|
||||||
String artifactFile,
|
String artifactFile,
|
||||||
Map<Integer, Integer> declaredPaletteIndexByRgb) {
|
Map<Integer, Integer> declaredPaletteIndexByRgb) {
|
||||||
final int[] originalArgb8888 = originalPaletteColors(paletteMetadata);
|
final int[] rgba8888 = paletteColors(paletteMetadata);
|
||||||
if (originalArgb8888.length == 0) {
|
if (rgba8888.length == 0) {
|
||||||
throw new IllegalStateException("Selected tile artifact is missing palette metadata required for palette remapping: " + artifactFile);
|
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);
|
final byte[] remapped = Arrays.copyOf(tile.paletteIndices(), tile.paletteIndices().length);
|
||||||
for (int index = 0; index < remapped.length; index += 1) {
|
for (int index = 0; index < remapped.length; index += 1) {
|
||||||
final int paletteIndex = Byte.toUnsignedInt(remapped[index]);
|
final int paletteIndex = Byte.toUnsignedInt(remapped[index]);
|
||||||
if (paletteIndex == 0) {
|
if (paletteIndex >= rgba8888.length) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (paletteIndex > originalArgb8888.length) {
|
|
||||||
throw new IllegalStateException("Tile palette index exceeds discovered palette size for artifact: " + artifactFile);
|
throw new IllegalStateException("Tile palette index exceeds discovered palette size for artifact: " + artifactFile);
|
||||||
}
|
}
|
||||||
final int originalArgb = originalArgb8888[paletteIndex - 1];
|
final int rgba = rgba8888[paletteIndex];
|
||||||
final Integer materialIndex = declaredPaletteIndexByRgb.get(rgb24(originalArgb));
|
final Integer materialIndex = declaredPaletteIndexByRgb.get(rgba);
|
||||||
if (materialIndex == null || materialIndex < 1 || materialIndex > 15) {
|
if (materialIndex == null || materialIndex < 0 || materialIndex > 15) {
|
||||||
throw new IllegalStateException("Unable to resolve canonical palette index for artifact color in " + artifactFile);
|
throw new IllegalStateException("Unable to resolve canonical palette index for artifact color in " + artifactFile);
|
||||||
}
|
}
|
||||||
remapped[index] = (byte) (materialIndex & 0x0F);
|
remapped[index] = (byte) (materialIndex & 0x0F);
|
||||||
@ -1189,29 +1186,29 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
if (!(palettesNode instanceof ArrayNode palettesArray) || palettesArray.isEmpty()) {
|
if (!(palettesNode instanceof ArrayNode palettesArray) || palettesArray.isEmpty()) {
|
||||||
throw new IllegalStateException("Glyph packing requires at least one declared pipeline palette.");
|
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()) {
|
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<Integer, Integer> mapping = new LinkedHashMap<>();
|
final LinkedHashMap<Integer, Integer> mapping = new LinkedHashMap<>();
|
||||||
for (int index = 0; index < paletteNode.size(); index += 1) {
|
for (int index = 0; index < paletteNode.size(); index += 1) {
|
||||||
final JsonNode colorNode = paletteNode.get(index);
|
final JsonNode colorNode = paletteNode.get(index);
|
||||||
if (!colorNode.canConvertToInt()) {
|
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);
|
return Map.copyOf(mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[] originalPaletteColors(Object paletteMetadata) {
|
private int[] paletteColors(Object paletteMetadata) {
|
||||||
if (paletteMetadata instanceof PackerPaletteV1 palette) {
|
if (paletteMetadata instanceof PackerPaletteV1 palette) {
|
||||||
return palette.originalArgb8888();
|
return palette.rgba8888();
|
||||||
}
|
}
|
||||||
if (!(paletteMetadata instanceof Map<?, ?> map)) {
|
if (!(paletteMetadata instanceof Map<?, ?> map)) {
|
||||||
return new int[0];
|
return new int[0];
|
||||||
}
|
}
|
||||||
final Object colors = map.get("originalArgb8888");
|
final Object colors = map.get("rgba8888");
|
||||||
if (!(colors instanceof List<?> list)) {
|
if (!(colors instanceof List<?> list)) {
|
||||||
return new int[0];
|
return new int[0];
|
||||||
}
|
}
|
||||||
@ -1226,10 +1223,6 @@ public final class FileSystemPackerWorkspaceService implements PackerWorkspaceSe
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int rgb24(int argb) {
|
|
||||||
return argb & 0x00FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PackedAsset packSceneBank(PackerRuntimeAsset runtimeAsset, PackerAssetDeclaration declaration) {
|
private PackedAsset packSceneBank(PackerRuntimeAsset runtimeAsset, PackerAssetDeclaration declaration) {
|
||||||
final ScenePackRequest request = runtimeAsset.walkProjection().buildCandidateFiles().stream()
|
final ScenePackRequest request = runtimeAsset.walkProjection().buildCandidateFiles().stream()
|
||||||
.map(file -> file.metadata().get("scenePackRequest"))
|
.map(file -> file.metadata().get("scenePackRequest"))
|
||||||
|
|||||||
@ -109,17 +109,12 @@ public final class PackerReadMessageMapper {
|
|||||||
"paletteIndices", List.copyOf(paletteIndices));
|
"paletteIndices", List.copyOf(paletteIndices));
|
||||||
}
|
}
|
||||||
if (value instanceof PackerPaletteV1 palette) {
|
if (value instanceof PackerPaletteV1 palette) {
|
||||||
final List<Integer> originalArgb8888 = new java.util.ArrayList<>(palette.originalArgb8888().length);
|
final List<Integer> rgba8888 = new java.util.ArrayList<>(palette.rgba8888().length);
|
||||||
for (int argb : palette.originalArgb8888()) {
|
for (int rgba : palette.rgba8888()) {
|
||||||
originalArgb8888.add(argb);
|
rgba8888.add(rgba);
|
||||||
}
|
|
||||||
final List<Integer> convertedRgb565 = new java.util.ArrayList<>(palette.convertedRgb565().length);
|
|
||||||
for (short rgb565 : palette.convertedRgb565()) {
|
|
||||||
convertedRgb565.add(Short.toUnsignedInt(rgb565));
|
|
||||||
}
|
}
|
||||||
return Map.of(
|
return Map.of(
|
||||||
"originalArgb8888", List.copyOf(originalArgb8888),
|
"rgba8888", List.copyOf(rgba8888));
|
||||||
"convertedRgb565", List.copyOf(convertedRgb565));
|
|
||||||
}
|
}
|
||||||
if (value instanceof Map<?, ?> nestedMap) {
|
if (value instanceof Map<?, ?> nestedMap) {
|
||||||
final java.util.LinkedHashMap<String, Object> normalized = new java.util.LinkedHashMap<>();
|
final java.util.LinkedHashMap<String, Object> normalized = new java.util.LinkedHashMap<>();
|
||||||
|
|||||||
@ -5,8 +5,10 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import p.packer.models.PackerAssetCacheEntry;
|
import p.packer.models.PackerAssetCacheEntry;
|
||||||
import p.packer.models.PackerFileCacheEntry;
|
import p.packer.models.PackerFileCacheEntry;
|
||||||
import p.packer.models.PackerTileIndexedV1;
|
import p.packer.models.PackerDiagnostic;
|
||||||
import p.packer.models.PackerGlyphBankRequirements;
|
import p.packer.models.PackerGlyphBankRequirements;
|
||||||
|
import p.packer.models.PackerPaletteV1;
|
||||||
|
import p.packer.models.PackerTileIndexedV1;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
@ -42,7 +44,7 @@ final class PackerGlyphBankWalkerTest {
|
|||||||
null,
|
null,
|
||||||
Map.of(
|
Map.of(
|
||||||
"tile", Map.of("width", 16, "height", 16),
|
"tile", Map.of("width", 16, "height", 16),
|
||||||
"palette", Map.of("originalArgb8888", List.of(0xFFFFFFFF))),
|
"palette", Map.of("rgba8888", List.of(0xFFFFFFFF))),
|
||||||
List.of())));
|
List.of())));
|
||||||
|
|
||||||
final var result = walker.walk(assetRoot, new PackerGlyphBankRequirements(16), Optional.of(priorCache));
|
final var result = walker.walk(assetRoot, new PackerGlyphBankRequirements(16), Optional.of(priorCache));
|
||||||
@ -52,6 +54,54 @@ final class PackerGlyphBankWalkerTest {
|
|||||||
assertTrue(tile.paletteIndices().length > 0);
|
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 {
|
private void writeTile(Path path) throws Exception {
|
||||||
final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||||
for (int y = 0; y < 16; y++) {
|
for (int y = 0; y < 16; y++) {
|
||||||
@ -61,4 +111,18 @@ final class PackerGlyphBankWalkerTest {
|
|||||||
}
|
}
|
||||||
ImageIO.write(image, "png", path.toFile());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user