From 1e98130c3b39204228cc269b124d8dbab2d5d62b Mon Sep 17 00:00:00 2001 From: hansbonini Date: Tue, 27 May 2025 01:39:05 -0300 Subject: [PATCH 01/10] Add Mesen CGRAM dump as Palette --- tmspec.xml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tmspec.xml b/tmspec.xml index 91d70c5..6c4f6a3 100644 --- a/tmspec.xml +++ b/tmspec.xml @@ -30,6 +30,10 @@ 000000990000009900CC6600000099990099009999CCCCCC666666FF666666FF66FFFF666666FFFF66FF66FFFFFFFFFF Enhanced Graphics Adapter (EGA) + + 000000111111222222333333444444555555666666777777888888999999AAAAAABBBBBBCCCCCCDDDDDDEEEEEEFFFFFF + Grayscale + @@ -206,31 +210,31 @@ FCEUltra Save States (*.fc?) - Genecyst/Kega/Gens Save States (*.gs?) - + + Sega Genesis CRAM Dump (*.bin) + NESticle Save States (*.st?) - ZSNES Save States (*.zs?) - + + Mesen SNES CGRAM Dump (*.dmp) + Tile Layer Pro palette (*.tpl) - Windows Palette (*.pal) - Raw palette file (*.col,*.pal) - + @@ -239,7 +243,7 @@ - + From ee8ba34378b01b314703ed7254216d36bce1e402 Mon Sep 17 00:00:00 2001 From: hansbonini Date: Tue, 27 May 2025 02:05:56 -0300 Subject: [PATCH 02/10] Add Predefined Block Size --- src/main/java/tm/ui/TMUI.java | 154 ++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/src/main/java/tm/ui/TMUI.java b/src/main/java/tm/ui/TMUI.java index f49998f..c31232b 100644 --- a/src/main/java/tm/ui/TMUI.java +++ b/src/main/java/tm/ui/TMUI.java @@ -239,6 +239,11 @@ public class TMUI extends JFrame { private JMenuItem _3200MenuItem = new JMenuItem("3200%"); private JMenu blockSizeMenu = new JMenu("Block Size"); private JCheckBoxMenuItem sizeBlockToCanvasMenuItem = new JCheckBoxMenuItem("Full Canvas"); + private JCheckBoxMenuItem sizeBlockToCanvasMenuItem8x8 = new JCheckBoxMenuItem("8x8 pixels"); + private JCheckBoxMenuItem sizeBlockToCanvasMenuItem8x16 = new JCheckBoxMenuItem("8x16 pixels"); + private JCheckBoxMenuItem sizeBlockToCanvasMenuItem16x16 = new JCheckBoxMenuItem("16x16 pixels"); + private JCheckBoxMenuItem sizeBlockToCanvasMenuItem24x24 = new JCheckBoxMenuItem("24x24 pixels"); + private JCheckBoxMenuItem sizeBlockToCanvasMenuItem32x32 = new JCheckBoxMenuItem("32x32 pixels"); private JMenuItem customBlockSizeMenuItem = new JMenuItem("Custom..."); private JRadioButtonMenuItem rowInterleaveBlocksMenuItem = new JRadioButtonMenuItem("Row-interleave Blocks"); private JMenu modeMenu = new JMenu("Mode"); @@ -1424,9 +1429,47 @@ public void actionPerformed(ActionEvent e) { doSizeBlockToCanvasCommand(); } }); + sizeBlockToCanvasMenuItem8x8.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doSizeBlock8x8ToCanvasCommand(); + } + }); + sizeBlockToCanvasMenuItem8x16.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doSizeBlock8x16ToCanvasCommand(); + } + }); + sizeBlockToCanvasMenuItem16x16.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doSizeBlock16x16ToCanvasCommand(); + } + }); + sizeBlockToCanvasMenuItem24x24.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doSizeBlock24x24ToCanvasCommand(); + } + }); + sizeBlockToCanvasMenuItem32x32.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doSizeBlock32x32ToCanvasCommand(); + } + }); blockSizeMenu.add(sizeBlockToCanvasMenuItem); // blockSizeMenu.addSeparator(); + // Pre Defined Block Size + blockSizeMenu.add(sizeBlockToCanvasMenuItem8x8); + blockSizeMenu.add(sizeBlockToCanvasMenuItem8x16); + blockSizeMenu.add(sizeBlockToCanvasMenuItem16x16); + blockSizeMenu.add(sizeBlockToCanvasMenuItem24x24); + blockSizeMenu.add(sizeBlockToCanvasMenuItem32x32); + // + blockSizeMenu.addSeparator(); // Custom Block Size customBlockSizeMenuItem.setMnemonic(KeyEvent.VK_C); customBlockSizeMenuItem.addActionListener( @@ -2827,6 +2870,117 @@ public void doSizeBlockToCanvasCommand() { if (view != null) { view.setSizeBlockToCanvas(!view.getSizeBlockToCanvas()); sizeBlockToCanvasMenuItem.setSelected(view.getSizeBlockToCanvas()); + sizeBlockToCanvasMenuItem8x8.setSelected(false); + sizeBlockToCanvasMenuItem8x16.setSelected(false); + sizeBlockToCanvasMenuItem16x16.setSelected(false); + sizeBlockToCanvasMenuItem24x24.setSelected(false); + sizeBlockToCanvasMenuItem32x32.setSelected(false); + } + } + + /** + * + * + * + **/ + + public void doSizeBlock8x8ToCanvasCommand() { + TMView view = getSelectedView(); + if (view != null) { + // TMBlockSizeDialog blockSizeDialog = new TMBlockSizeDialog(this, xl); + view.setSizeBlockToCanvas(false); + sizeBlockToCanvasMenuItem.setSelected(false); + sizeBlockToCanvasMenuItem8x8.setSelected(true); + sizeBlockToCanvasMenuItem8x16.setSelected(false); + sizeBlockToCanvasMenuItem16x16.setSelected(false); + sizeBlockToCanvasMenuItem24x24.setSelected(false); + sizeBlockToCanvasMenuItem32x32.setSelected(false); + view.setBlockDimensions(1, 1); + } + } + + /** + * + * + * + **/ + + public void doSizeBlock8x16ToCanvasCommand() { + TMView view = getSelectedView(); + if (view != null) { + // TMBlockSizeDialog blockSizeDialog = new TMBlockSizeDialog(this, xl); + view.setSizeBlockToCanvas(false); + sizeBlockToCanvasMenuItem.setSelected(false); + sizeBlockToCanvasMenuItem8x8.setSelected(false); + sizeBlockToCanvasMenuItem8x16.setSelected(true); + sizeBlockToCanvasMenuItem16x16.setSelected(false); + sizeBlockToCanvasMenuItem24x24.setSelected(false); + sizeBlockToCanvasMenuItem32x32.setSelected(false); + view.setBlockDimensions(1, 2); + } + } + + /** + * + * + * + **/ + + public void doSizeBlock16x16ToCanvasCommand() { + TMView view = getSelectedView(); + if (view != null) { + // TMBlockSizeDialog blockSizeDialog = new TMBlockSizeDialog(this, xl); + view.setSizeBlockToCanvas(false); + sizeBlockToCanvasMenuItem.setSelected(false); + sizeBlockToCanvasMenuItem8x8.setSelected(false); + sizeBlockToCanvasMenuItem8x16.setSelected(false); + sizeBlockToCanvasMenuItem16x16.setSelected(true); + sizeBlockToCanvasMenuItem24x24.setSelected(false); + sizeBlockToCanvasMenuItem32x32.setSelected(false); + view.setBlockDimensions(2, 2); + } + } + + + /** + * + * + * + **/ + + public void doSizeBlock24x24ToCanvasCommand() { + TMView view = getSelectedView(); + if (view != null) { + // TMBlockSizeDialog blockSizeDialog = new TMBlockSizeDialog(this, xl); + view.setSizeBlockToCanvas(false); + sizeBlockToCanvasMenuItem.setSelected(false); + sizeBlockToCanvasMenuItem8x8.setSelected(false); + sizeBlockToCanvasMenuItem8x16.setSelected(false); + sizeBlockToCanvasMenuItem16x16.setSelected(false); + sizeBlockToCanvasMenuItem24x24.setSelected(true); + sizeBlockToCanvasMenuItem32x32.setSelected(false); + view.setBlockDimensions(3, 3); + } + } + + /** + * + * + * + **/ + + public void doSizeBlock32x32ToCanvasCommand() { + TMView view = getSelectedView(); + if (view != null) { + // TMBlockSizeDialog blockSizeDialog = new TMBlockSizeDialog(this, xl); + view.setSizeBlockToCanvas(false); + sizeBlockToCanvasMenuItem.setSelected(false); + sizeBlockToCanvasMenuItem8x8.setSelected(false); + sizeBlockToCanvasMenuItem8x16.setSelected(false); + sizeBlockToCanvasMenuItem16x16.setSelected(false); + sizeBlockToCanvasMenuItem24x24.setSelected(false); + sizeBlockToCanvasMenuItem32x32.setSelected(true); + view.setBlockDimensions(4, 4); } } From 82d1ff9d528a8e5409ab43b187bdbf09e3f2bcf5 Mon Sep 17 00:00:00 2001 From: hansbonini Date: Mon, 28 Jul 2025 05:05:53 -0300 Subject: [PATCH 03/10] Add custom tile size --- src/main/java/tm/canvases/TMEditorCanvas.java | 62 ++++-- .../java/tm/canvases/TMSelectionCanvas.java | 8 +- src/main/java/tm/canvases/TMTileCanvas.java | 180 ++++++++++++++---- .../java/tm/modaldialog/TMTileSizeDialog.java | 141 ++++++++++++++ .../ReversibleTileModifyAction.java | 10 +- .../tm/tilecodecs/CompositeTileCodec.java | 8 +- .../tm/tilecodecs/DirectColorTileCodec.java | 9 +- .../java/tm/tilecodecs/LinearTileCodec.java | 21 +- .../java/tm/tilecodecs/PlanarTileCodec.java | 95 ++++++--- src/main/java/tm/tilecodecs/TileCodec.java | 64 ++++++- .../tm/tilecodecs/_3BPPLinearTileCodec.java | 81 ++++++-- .../tm/tilecodecs/_6BPPLinearTileCodec.java | 162 +++++++++------- src/main/java/tm/ui/TMUI.java | 40 ++++ src/main/java/tm/ui/TMView.java | 4 +- .../resources/languages/language.properties | 4 + .../languages/language_en_US.properties | 4 + tmspec.xml | 18 ++ 17 files changed, 707 insertions(+), 204 deletions(-) create mode 100644 src/main/java/tm/modaldialog/TMTileSizeDialog.java diff --git a/src/main/java/tm/canvases/TMEditorCanvas.java b/src/main/java/tm/canvases/TMEditorCanvas.java index 237e97a..30d19d9 100644 --- a/src/main/java/tm/canvases/TMEditorCanvas.java +++ b/src/main/java/tm/canvases/TMEditorCanvas.java @@ -65,7 +65,6 @@ public class TMEditorCanvas extends TMTileCanvas implements MouseInputListener { private Point moveViewPoint; private Point moveMousePoint; - private int[] tempPixels = new int[8*8]; private Vector modifiedTiles = new Vector(); // tiles modified by operation private Vector modifiedPixels = new Vector(); private Point[][] gridCoords; @@ -646,7 +645,9 @@ private void drawLine(int x1, int y1, int x2, int y2, boolean trace) { protected void setPixelTraceable(int x, int y, int argb) { // mark the tile as modified - tileModified(x/8, y/8); + int tileWidth = (codec != null) ? codec.getTileWidth() : 8; + int tileHeight = (codec != null) ? codec.getTileHeight() : 8; + tileModified(x/tileWidth, y/tileHeight); setPixel(x, y, argb); } @@ -662,8 +663,12 @@ private void tileModified(int col, int row) { if (!modifiedTiles.contains(p)) { modifiedTiles.add(p); // save original pixels + int tileWidth = (codec != null) ? codec.getTileWidth() : 8; + int tileHeight = (codec != null) ? codec.getTileHeight() : 8; + int tileSize = tileWidth * tileHeight; + int[] tempPixels = new int[tileSize]; copyTilePixelsToBuffer(col, row, tempPixels, 0); - IntBuffer ib = IntBuffer.allocate(8*8); + IntBuffer ib = IntBuffer.allocate(tileSize); ib.put(tempPixels); modifiedPixels.add(ib); } @@ -677,12 +682,19 @@ private void tileModified(int col, int row) { **/ public void copyTilePixelsToBuffer(int x, int y, int[] buf, int ofs) { - int pixOfs = (y * 8 * canvasWidth) + (x * 8); - for (int i=0; i<8; i++) { - for (int j=0; j<8; j++) { - buf[ofs++] = pixels[pixOfs++]; + int tileWidth = (codec != null) ? codec.getTileWidth() : 8; + int tileHeight = (codec != null) ? codec.getTileHeight() : 8; + int pixOfs = (y * tileHeight * canvasWidth) + (x * tileWidth); + for (int i=0; ibits * as the encoded tile data source. -* Doesn't work quite right? +* Uses default 8x8 tiles if codec is not set. **/ public TMTileCanvas(byte[] bits, int cols, int rows) { - super(bits, cols*8, rows*8); + super(bits, cols * 8, rows * 8); // default 8x8 tiles this.cols = cols; this.rows = rows; } @@ -98,7 +98,32 @@ public int getMode() { **/ public void setCodec(TileCodec codec) { + // Preserve current tile dimensions when switching codecs + if (this.codec != null) { + codec.setTileDimensions(this.codec.getTileWidth(), this.codec.getTileHeight()); + } this.codec = codec; + updatePixdataSize(); + } + +/** +* +* Updates the pixdata array size based on current tile dimensions. +* +**/ + + private void updatePixdataSize() { + if (codec != null) { + int tileSize = codec.getTileWidth() * codec.getTileHeight(); + if (tileSize > 0 && (pixdata == null || pixdata.length != tileSize)) { + pixdata = new int[tileSize]; + } + } else { + // Default to 8x8 if no codec is set + if (pixdata == null || pixdata.length != 64) { + pixdata = new int[64]; + } + } } /** @@ -158,9 +183,13 @@ public int getPalIndex() { **/ public void setGridSize(int cols, int rows) { - setCanvasSize(cols*8, rows*8); + int tileWidth = (codec != null) ? codec.getTileWidth() : 8; + int tileHeight = (codec != null) ? codec.getTileHeight() : 8; + setCanvasSize(cols * tileWidth, rows * tileHeight); this.cols = cols; this.rows = rows; + // Ensure pixdata array is properly sized + updatePixdataSize(); } /** @@ -175,27 +204,42 @@ public void packTile(int x, int y) { // encode single atomic tile bitsOfs = getTileBitsOffset(x, y); if (bitsOfs >= 0) { + // Ensure pixdata array is properly sized + updatePixdataSize(); + + int tileWidth = codec.getTileWidth(); + int tileHeight = codec.getTileHeight(); // copy pixels - pixOfs = (y * 8 * canvasWidth) + (x * 8); + pixOfs = (y * tileHeight * canvasWidth) + (x * tileWidth); pos = 0; if (codec.getBitsPerPixel() <= 8) { int colorCount = codec.getColorCount(); int colorIndex = palIndex * colorCount; // map RGB values to palette indices - for (int p=0; p<8; p++) { - for (int q=0; q<8; q++) { - pixdata[pos++] = palette.indexOf(colorIndex, pixels[pixOfs++]); + for (int p=0; p 0) && (getTileHeight() > 0) + && (getTileWidth() <= 256) && (getTileHeight() <= 256)); + } + +} diff --git a/src/main/java/tm/reversibleaction/ReversibleTileModifyAction.java b/src/main/java/tm/reversibleaction/ReversibleTileModifyAction.java index 153d4fe..fe6b889 100644 --- a/src/main/java/tm/reversibleaction/ReversibleTileModifyAction.java +++ b/src/main/java/tm/reversibleaction/ReversibleTileModifyAction.java @@ -57,9 +57,12 @@ public ReversibleTileModifyAction( public void undo() { canvas.getView().gotoBookmark(bookmark); + int tileWidth = (canvas.getCodec() != null) ? canvas.getCodec().getTileWidth() : 8; + int tileHeight = (canvas.getCodec() != null) ? canvas.getCodec().getTileHeight() : 8; + int tileSize = tileWidth * tileHeight; for (int i=0; i>= p; } codecs[i].encode(pixels, bits, ofs, stride); diff --git a/src/main/java/tm/tilecodecs/DirectColorTileCodec.java b/src/main/java/tm/tilecodecs/DirectColorTileCodec.java index 5b3cde4..077aaaa 100644 --- a/src/main/java/tm/tilecodecs/DirectColorTileCodec.java +++ b/src/main/java/tm/tilecodecs/DirectColorTileCodec.java @@ -111,12 +111,13 @@ private static int msb(int mask) { **/ public int[] decode(byte[] bits, int ofs, int stride) { + int[] pixels = new int[tileWidth * tileHeight]; int v, r, g, b, a, s; int pos=0; stride *= bytesPerRow; - for (int i=0; i<8; i++) { + for (int i=0; i> bitsPerPixel*m) & pixelMask; + // decode one pixel, but don't exceed tile width + if (pixelsInRow < tileWidth && pos < pixels.length) { + pixels[pos++] = (b >> bitsPerPixel*m) & pixelMask; + pixelsInRow++; + } } } ofs += stride; @@ -148,14 +153,18 @@ public int[] decode(byte[] bits, int ofs, int stride) { public void encode(int[] pixels, byte[] bits, int ofs, int stride) { int pos = 0; stride *= bytesPerRow; - for (int i=0; i<8; i++) { + for (int i=0; i> k) & 0x01) << (7-j); + + for (int i = 0; i < tileHeight; i++) { + // Process pixels in 8-pixel blocks for this row + int pixelsProcessed = 0; + int rowOfs = ofs; + + while (pixelsProcessed < tileWidth) { + // Reset bitplanes for this 8-pixel block + for (int j = 0; j < bitsPerPixel; j++) { + if (rowOfs + bpOffsets[j] < bits.length) { + bits[rowOfs + bpOffsets[j]] = 0; + } + } + + // Encode up to 8 pixels in this block + int pixelsInThisBlock = Math.min(8, tileWidth - pixelsProcessed); + for (int j = 0; j < pixelsInThisBlock; j++) { + int p = (pos < pixels.length) ? pixels[pos++] : 0; + + for (int k = 0; k < bitsPerPixel; k++) { + if (rowOfs + bpOffsets[k] < bits.length) { + bits[rowOfs + bpOffsets[k]] |= ((p >> k) & 0x01) << (7 - j); + } + } } - pos++; + + pixelsProcessed += pixelsInThisBlock; + + // Move to next 8-pixel block in the same row + rowOfs += bytesPerRow; } + + // Move to next row ofs += stride; } } diff --git a/src/main/java/tm/tilecodecs/TileCodec.java b/src/main/java/tm/tilecodecs/TileCodec.java index 0df3931..cfb1bec 100644 --- a/src/main/java/tm/tilecodecs/TileCodec.java +++ b/src/main/java/tm/tilecodecs/TileCodec.java @@ -20,7 +20,7 @@ /** * -* Abstract class for 8x8 ("atomic") tile codecs. +* Abstract class for configurable size tile codecs. * To add a new tile format, simply extend this class and implement decode() and encode(). * **/ @@ -34,9 +34,11 @@ public abstract class TileCodec { private String description; protected int[] pixels; // destination for DEcoded tile data protected int bitsPerPixel; - protected int bytesPerRow; // row = 8 pixels + protected int bytesPerRow; // row = tileWidth pixels protected long colorCount; protected int tileSize; // size of one encoded tile + protected int tileWidth; // width of tile in pixels + protected int tileHeight; // height of tile in pixels /** * @@ -47,13 +49,29 @@ public abstract class TileCodec { **/ public TileCodec(String id, int bitsPerPixel, String description) { + this(id, bitsPerPixel, description, 8, 8); + } + +/** +* +* Constructor with configurable tile dimensions. +* +* @param bitsPerPixel Bits per pixel +* @param tileWidth Width of tile in pixels +* @param tileHeight Height of tile in pixels +* +**/ + + public TileCodec(String id, int bitsPerPixel, String description, int tileWidth, int tileHeight) { this.id = id; this.bitsPerPixel = bitsPerPixel; this.description = description; - bytesPerRow = bitsPerPixel; // because (bitsPerPixel*8)/8 = bitsPerPixel - tileSize = bytesPerRow*8; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + bytesPerRow = (bitsPerPixel * tileWidth + 7) / 8; // round up to nearest byte + tileSize = bytesPerRow * tileHeight; colorCount = 1 << bitsPerPixel; - pixels = new int[8*8]; + pixels = new int[tileWidth * tileHeight]; } /** @@ -89,7 +107,7 @@ public int getBitsPerPixel() { /** * -* Gets the # of bytes per row (8 pixels) for the tile format. +* Gets the # of bytes per row (tileWidth pixels) for the tile format. * **/ @@ -97,6 +115,40 @@ public int getBytesPerRow() { return bytesPerRow; } +/** +* +* Gets the width of the tile in pixels. +* +**/ + + public int getTileWidth() { + return tileWidth; + } + +/** +* +* Gets the height of the tile in pixels. +* +**/ + + public int getTileHeight() { + return tileHeight; + } + +/** +* +* Sets the tile dimensions and recalculates dependent values. +* +**/ + + public void setTileDimensions(int width, int height) { + this.tileWidth = width; + this.tileHeight = height; + bytesPerRow = (bitsPerPixel * tileWidth + 7) / 8; // round up to nearest byte + tileSize = bytesPerRow * tileHeight; + pixels = new int[tileWidth * tileHeight]; + } + /** * * diff --git a/src/main/java/tm/tilecodecs/_3BPPLinearTileCodec.java b/src/main/java/tm/tilecodecs/_3BPPLinearTileCodec.java index bf04f71..58d7bb7 100644 --- a/src/main/java/tm/tilecodecs/_3BPPLinearTileCodec.java +++ b/src/main/java/tm/tilecodecs/_3BPPLinearTileCodec.java @@ -17,22 +17,41 @@ public _3BPPLinearTileCodec() { **/ public int[] decode(byte[] bits, int ofs, int stride) { + int[] pixels = new int[tileWidth * tileHeight]; int pos=0; int b1, b2, b3; stride *= bytesPerRow; - for (int i=0; i<8; i++) { + for (int i=0; i> 5) & 7; - pixels[pos++] = (b1 >> 2) & 7; - pixels[pos++] = ((b1 & 3) << 1) | ((b2 >> 7) & 1); - pixels[pos++] = (b2 >> 4) & 7; - pixels[pos++] = (b2 >> 1) & 7; - pixels[pos++] = ((b2 & 1) << 2) | ((b3 >> 6) & 3); - pixels[pos++] = (b3 >> 3) & 7; - pixels[pos++] = b3 & 7; + + // Original 8-pixel decoding with bounds checking + if (pos + 7 < pixels.length) { + pixels[pos++] = (b1 >> 5) & 7; + pixels[pos++] = (b1 >> 2) & 7; + pixels[pos++] = ((b1 & 3) << 1) | ((b2 >> 7) & 1); + pixels[pos++] = (b2 >> 4) & 7; + pixels[pos++] = (b2 >> 1) & 7; + pixels[pos++] = ((b2 & 1) << 2) | ((b3 >> 6) & 3); + pixels[pos++] = (b3 >> 3) & 7; + pixels[pos++] = b3 & 7; + } else { + // Handle tiles narrower than 8 pixels + for (int j = 0; j < 8 && pos < pixels.length; j++) { + switch (j) { + case 0: pixels[pos++] = (b1 >> 5) & 7; break; + case 1: pixels[pos++] = (b1 >> 2) & 7; break; + case 2: pixels[pos++] = ((b1 & 3) << 1) | ((b2 >> 7) & 1); break; + case 3: pixels[pos++] = (b2 >> 4) & 7; break; + case 4: pixels[pos++] = (b2 >> 1) & 7; break; + case 5: pixels[pos++] = ((b2 & 1) << 2) | ((b3 >> 6) & 3); break; + case 6: pixels[pos++] = (b3 >> 3) & 7; break; + case 7: pixels[pos++] = b3 & 7; break; + } + } + } ofs += stride; } return pixels; @@ -48,18 +67,40 @@ public void encode(int[] pixels, byte[] bits, int ofs, int stride) { int pos = 0; int b1, b2, b3; stride *= bytesPerRow; - for (int i=0; i<8; i++) { + for (int i=0; i> 1; - b2 = (pixels[pos++] & 1) << 7; - b2 |= (pixels[pos++] & 7) << 4; - b2 |= (pixels[pos++] & 7) << 1; - b2 |= (pixels[pos] & 4) >> 2; - b3 = (pixels[pos++] & 3) << 6; - b3 |= (pixels[pos++] & 7) << 3; - b3 |= (pixels[pos++] & 7); + if (pos + 7 < pixels.length) { + // Standard 8-pixel encoding + b1 = (pixels[pos++] & 7) << 5; + b1 |= (pixels[pos++] & 7) << 2; + b1 |= (pixels[pos] & 6) >> 1; + b2 = (pixels[pos++] & 1) << 7; + b2 |= (pixels[pos++] & 7) << 4; + b2 |= (pixels[pos++] & 7) << 1; + b2 |= (pixels[pos] & 4) >> 2; + b3 = (pixels[pos++] & 3) << 6; + b3 |= (pixels[pos++] & 7) << 3; + b3 |= (pixels[pos++] & 7); + } else { + // Handle tiles narrower than 8 pixels + b1 = b2 = b3 = 0; + int[] rowPixels = new int[8]; + for (int j = 0; j < 8; j++) { + rowPixels[j] = (pos < pixels.length) ? pixels[pos++] : 0; + } + + b1 = (rowPixels[0] & 7) << 5; + b1 |= (rowPixels[1] & 7) << 2; + b1 |= (rowPixels[2] & 6) >> 1; + b2 = (rowPixels[2] & 1) << 7; + b2 |= (rowPixels[3] & 7) << 4; + b2 |= (rowPixels[4] & 7) << 1; + b2 |= (rowPixels[5] & 4) >> 2; + b3 = (rowPixels[5] & 3) << 6; + b3 |= (rowPixels[6] & 7) << 3; + b3 |= (rowPixels[7] & 7); + } + bits[ofs++] = (byte)b1; // byte 1: 0001 1122 bits[ofs++] = (byte)b2; // byte 2: 2333 4445 bits[ofs++] = (byte)b3; // byte 3: 5566 6777 diff --git a/src/main/java/tm/tilecodecs/_6BPPLinearTileCodec.java b/src/main/java/tm/tilecodecs/_6BPPLinearTileCodec.java index 81027a9..c7b5e69 100644 --- a/src/main/java/tm/tilecodecs/_6BPPLinearTileCodec.java +++ b/src/main/java/tm/tilecodecs/_6BPPLinearTileCodec.java @@ -17,38 +17,50 @@ public _6BPPLinearTileCodec() { **/ public int[] decode(byte[] bits, int ofs, int stride) { + int[] pixels = new int[tileWidth * tileHeight]; int pos=0; int b1, b2, b3, b4, b5, b6; stride *= bytesPerRow; - for (int i=0; i<8; i++) { - // do one row - /* BPP3 Linear: - * byte 1: 0001 1122 - byte 2: 2333 4445 - byte 3: 5566 6777 - */ - /* BPP6 Linear: - * byte 1: 0000 0011 - byte 2: 1111 2222 - byte 3: 2233 3333 - byte 4: 4444 4455 - byte 5: 5555 6666 - byte 6: 6677 7777 - */ - b6 = bits[ofs++] & 0xFF; // byte 1: 0000 0011 - b5 = bits[ofs++] & 0xFF; // byte 2: 1111 2222 - b4 = bits[ofs++] & 0xFF; // byte 3: 2233 3333 - b3 = bits[ofs++] & 0xFF; // byte 4: 4444 4455 - b2 = bits[ofs++] & 0xFF; // byte 5: 5555 6666 - b1 = bits[ofs++] & 0xFF; // byte 6: 6677 7777 - pixels[pos++] = (b1 >> 2) & 63; - pixels[pos++] = ((b1 & 3) << 4) | ((b2 >> 4) & 15); - pixels[pos++] = ((b2 & 15) << 2) | ((b3 >> 6) & 3); - pixels[pos++] = b3 & 63; - pixels[pos++] = (b4 >> 2) & 63; - pixels[pos++] = ((b4 & 3) << 4) | ((b5 >> 4) & 15); - pixels[pos++] = ((b5 & 15) << 2) | ((b6 >> 6) & 3); - pixels[pos++] = b6 & 63; + for (int i=0; i> 2) & 63; + decodedPixels[1] = ((b1 & 3) << 4) | ((b2 >> 4) & 15); + decodedPixels[2] = ((b2 & 15) << 2) | ((b3 >> 6) & 3); + decodedPixels[3] = b3 & 63; + decodedPixels[4] = (b4 >> 2) & 63; + decodedPixels[5] = ((b4 & 3) << 4) | ((b5 >> 4) & 15); + decodedPixels[6] = ((b5 & 15) << 2) | ((b6 >> 6) & 3); + decodedPixels[7] = b6 & 63; + + // Copy only the pixels we need for this row + int pixelsToTake = Math.min(8, tileWidth - pixelsProcessed); + for (int p = 0; p < pixelsToTake && pos < pixels.length; p++) { + pixels[pos++] = decodedPixels[p]; + } + pixelsProcessed += pixelsToTake; + } else { + // Not enough data, fill with zeros + while (pixelsProcessed < tileWidth && pos < pixels.length) { + pixels[pos++] = 0; + pixelsProcessed++; + } + } + } ofs += stride; } return pixels; @@ -64,48 +76,58 @@ public void encode(int[] pixels, byte[] bits, int ofs, int stride) { int pos = 0; int b1, b2, b3, b4, b5, b6; stride *= bytesPerRow; - for (int i=0; i<8; i++) { - /* BPP3 Linear: - * byte 1: 0001 1122 - byte 2: 2333 4445 - byte 3: 5566 6777 - */ - /* BPP6 Linear: - * byte 1: 0000 0011 - byte 2: 1111 2222 - byte 3: 2233 3333 - byte 4: 4444 4455 - byte 5: 5555 6666 - byte 6: 6677 7777 - */ - - // do one row - b1 = (pixels[pos++] & 63) << 2; - b1 |= (pixels[pos] & 48) >> 4; - - b2 = (pixels[pos++] & 15) << 4; - b2 |= (pixels[pos] & 60) >> 2; - - b3 = (pixels[pos++] & 3) << 6; - b3 |= (pixels[pos++] & 63); - - - b4 = (pixels[pos++] & 63) << 2; - b4 |= (pixels[pos] & 48) >> 4; - - b5 = (pixels[pos++] & 15) << 4; - b5 |= (pixels[pos] & 60) >> 2; - - b6 = (pixels[pos++] & 3) << 6; - b6 |= (pixels[pos++] & 63); + for (int i=0; i> 4; + + b2 = (pixelsToEncode[1] & 15) << 4; + b2 |= (pixelsToEncode[2] & 60) >> 2; + + b3 = (pixelsToEncode[2] & 3) << 6; + b3 |= (pixelsToEncode[3] & 63); + + b4 = (pixelsToEncode[4] & 63) << 2; + b4 |= (pixelsToEncode[5] & 48) >> 4; + + b5 = (pixelsToEncode[5] & 15) << 4; + b5 |= (pixelsToEncode[6] & 60) >> 2; + + b6 = (pixelsToEncode[6] & 3) << 6; + b6 |= (pixelsToEncode[7] & 63); + + // Write the 6 bytes + if (ofs + 5 < bits.length) { + bits[ofs++] = (byte)b6; // byte 1: 0000 0011 + bits[ofs++] = (byte)b5; // byte 2: 1111 2222 + bits[ofs++] = (byte)b4; // byte 3: 2233 3333 + bits[ofs++] = (byte)b3; // byte 4: 4444 4455 + bits[ofs++] = (byte)b2; // byte 5: 5555 6666 + bits[ofs++] = (byte)b1; // byte 6: 6677 7777 + } else { + // Not enough space, skip + break; + } + + pixelsProcessed += pixelsToTake; + } ofs += stride; } } diff --git a/src/main/java/tm/ui/TMUI.java b/src/main/java/tm/ui/TMUI.java index c31232b..7ff097d 100644 --- a/src/main/java/tm/ui/TMUI.java +++ b/src/main/java/tm/ui/TMUI.java @@ -105,6 +105,7 @@ public class TMUI extends JFrame { private TMStretchDialog stretchDialog; private TMCanvasSizeDialog canvasSizeDialog; private TMBlockSizeDialog blockSizeDialog; + private TMTileSizeDialog tileSizeDialog; private TMAddToTreeDialog addBookmarkDialog; private TMAddToTreeDialog addPaletteDialog; private TMOrganizeTreeDialog organizeBookmarksDialog; @@ -245,6 +246,7 @@ public class TMUI extends JFrame { private JCheckBoxMenuItem sizeBlockToCanvasMenuItem24x24 = new JCheckBoxMenuItem("24x24 pixels"); private JCheckBoxMenuItem sizeBlockToCanvasMenuItem32x32 = new JCheckBoxMenuItem("32x32 pixels"); private JMenuItem customBlockSizeMenuItem = new JMenuItem("Custom..."); + private JMenuItem customTileSizeMenuItem = new JMenuItem("Custom Tile Size..."); private JRadioButtonMenuItem rowInterleaveBlocksMenuItem = new JRadioButtonMenuItem("Row-interleave Blocks"); private JMenu modeMenu = new JMenu("Mode"); private JRadioButtonMenuItem _1DimensionalMenuItem = new JRadioButtonMenuItem("1-Dimensional"); @@ -500,6 +502,7 @@ public TMUI() { stretchDialog = new TMStretchDialog(this, xl); canvasSizeDialog = new TMCanvasSizeDialog(this, xl); blockSizeDialog = new TMBlockSizeDialog(this, xl); + tileSizeDialog = new TMTileSizeDialog(this, xl); addBookmarkDialog = new TMAddToTreeDialog(this, "Add_To_Bookmarks_Dialog_Title", xl); addPaletteDialog = new TMAddToTreeDialog(this, "Add_To_Palettes_Dialog_Title", xl); organizeBookmarksDialog = new TMOrganizeTreeDialog(this, "Organize_Bookmarks_Dialog_Title", xl); @@ -1480,6 +1483,15 @@ public void actionPerformed(ActionEvent e) { }); blockSizeMenu.add(customBlockSizeMenuItem); viewMenu.add(blockSizeMenu); + // Custom Tile Size + customTileSizeMenuItem.setText(xlate("Custom_Tile_Size")); + customTileSizeMenuItem.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + doCustomTileSizeCommand(); + } + }); + viewMenu.add(customTileSizeMenuItem); // Row-interleave Blocks rowInterleaveBlocksMenuItem.setMnemonic(KeyEvent.VK_R); rowInterleaveBlocksMenuItem.addActionListener( @@ -3003,6 +3015,34 @@ public void doCustomBlockSizeCommand() { } } + /** + * + * Handles menu command "Custom Tile Size". + * + **/ + public void doCustomTileSizeCommand() { + TMView view = getSelectedView(); + if (view != null) { + TileCodec codec = view.getTileCodec(); + if (codec != null) { + int currentWidth = codec.getTileWidth(); + int currentHeight = codec.getTileHeight(); + int retVal = tileSizeDialog.showDialog(currentWidth, currentHeight); + if (retVal == JOptionPane.OK_OPTION) { + int newWidth = tileSizeDialog.getTileWidth(); + int newHeight = tileSizeDialog.getTileHeight(); + codec.setTileDimensions(newWidth, newHeight); + // Redimensionar o canvas para acomodar os novos tamanhos de tile + TMTileCanvas canvas = view.getEditorCanvas(); + canvas.setGridSize(canvas.getCols(), canvas.getRows()); + // Limpar e redecodificar os pixels com as novas dimensões + canvas.unpackPixels(); + view.repaint(); + } + } + } + } + /** * * diff --git a/src/main/java/tm/ui/TMView.java b/src/main/java/tm/ui/TMView.java index 59ca5c9..59f89b3 100644 --- a/src/main/java/tm/ui/TMView.java +++ b/src/main/java/tm/ui/TMView.java @@ -110,7 +110,9 @@ public void internalFrameClosing(InternalFrameEvent e) { addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { - slider.setSize(slider.getWidth(), editorCanvas.getHeight()); + if (editorCanvas != null) { + slider.setSize(slider.getWidth(), editorCanvas.getHeight()); + } // slider.setSize(slider.getWidth(), // getHeight()-((BasicInternalFrameUI)getUI()).getNorthPane().getHeight()); } diff --git a/src/main/resources/languages/language.properties b/src/main/resources/languages/language.properties index c26e21c..65e5679 100644 --- a/src/main/resources/languages/language.properties +++ b/src/main/resources/languages/language.properties @@ -203,6 +203,10 @@ Offset_Prompt = Offset: Size_Prompt = Size: Columns_Prompt = Columns: Rows_Prompt = Rows: +Tile_Width_Prompt = Tile Width (pixels): +Tile_Height_Prompt = Tile Height (pixels): +Tile_Size_Dialog_Title = Tile Size +Custom_Tile_Size = Custom Tile Size... # Other diff --git a/src/main/resources/languages/language_en_US.properties b/src/main/resources/languages/language_en_US.properties index c26e21c..65e5679 100644 --- a/src/main/resources/languages/language_en_US.properties +++ b/src/main/resources/languages/language_en_US.properties @@ -203,6 +203,10 @@ Offset_Prompt = Offset: Size_Prompt = Size: Columns_Prompt = Columns: Rows_Prompt = Rows: +Tile_Width_Prompt = Tile Width (pixels): +Tile_Height_Prompt = Tile Height (pixels): +Tile_Size_Dialog_Title = Tile Size +Custom_Tile_Size = Custom Tile Size... # Other diff --git a/tmspec.xml b/tmspec.xml index 6c4f6a3..c7b2de8 100644 --- a/tmspec.xml +++ b/tmspec.xml @@ -46,12 +46,18 @@ 2bpp planar + + 2bpp planar reverse + 3bpp planar 4bpp planar + + 4bpp planar reverse + 5bpp planar @@ -69,12 +75,24 @@ 2bpp planar, composite + + 3bpp planar, composite (3x1bpp) + 3bpp planar, composite (2bpp+1bpp) + + 3bpp planar reverse, composite (2bpp+1bpp) + + + 4bpp planar, composite (4x1bpp) + 4bpp planar, composite (2x2bpp) + + 4bpp planar reverse, composite (2x2bpp) + 8bpp planar, composite (4x2bpp) From 68c82471add225a7f15b93299c6d70b75ccef7d0 Mon Sep 17 00:00:00 2001 From: hansbonini Date: Mon, 28 Jul 2025 05:22:04 -0300 Subject: [PATCH 04/10] Add spinners to custom tile size and custom block size --- src/main/java/tm/ui/TMStatusBar.java | 144 +++++++++++++++++++++++++-- src/main/java/tm/ui/TMUI.java | 49 ++++++++- 2 files changed, 186 insertions(+), 7 deletions(-) diff --git a/src/main/java/tm/ui/TMStatusBar.java b/src/main/java/tm/ui/TMStatusBar.java index d9f8894..7e18dbd 100644 --- a/src/main/java/tm/ui/TMStatusBar.java +++ b/src/main/java/tm/ui/TMStatusBar.java @@ -41,6 +41,18 @@ public class TMStatusBar extends JPanel { private JLabel modeLabel = new JLabel(" "); private JLabel tilesLabel = new JLabel(" "); private JLabel messageLabel = new JLabel(" "); + + // Tile size controls + private JLabel tileSizeLabel = new JLabel("Tile Size:"); + private JSpinner tileWidthSpinner; + private JSpinner tileHeightSpinner; + + // Block size controls + private JLabel blockSizeLabel = new JLabel("Block Size:"); + private JSpinner blockWidthSpinner; + private JSpinner blockHeightSpinner; + + private TMUI parentUI; /** * @@ -48,8 +60,10 @@ public class TMStatusBar extends JPanel { * **/ - public TMStatusBar() { + public TMStatusBar(TMUI parentUI) { super(); + this.parentUI = parentUI; + JPanel p1 = new JPanel(); p1.setLayout(new GridLayout(1, 3)); p1.add(messageLabel); @@ -65,11 +79,60 @@ public TMStatusBar() { p3.setLayout(new GridLayout(1, 2)); p3.add(modeLabel); p3.add(tilesLabel); - - setLayout(new GridLayout(1, 4)); - add(p1); - add(p2); - add(p3); + + // Only add tile size controls if parentUI is provided + if (parentUI != null) { + // Initialize tile size spinners + tileWidthSpinner = new JSpinner(new SpinnerNumberModel(8, 1, 256, 1)); + tileHeightSpinner = new JSpinner(new SpinnerNumberModel(8, 1, 256, 1)); + + // Initialize block size spinners + blockWidthSpinner = new JSpinner(new SpinnerNumberModel(16, 1, 256, 1)); + blockHeightSpinner = new JSpinner(new SpinnerNumberModel(16, 1, 256, 1)); + + // Set preferred size for spinners + Dimension spinnerSize = new Dimension(50, 20); + tileWidthSpinner.setPreferredSize(spinnerSize); + tileHeightSpinner.setPreferredSize(spinnerSize); + blockWidthSpinner.setPreferredSize(spinnerSize); + blockHeightSpinner.setPreferredSize(spinnerSize); + + // Add change listeners to spinners + tileWidthSpinner.addChangeListener(e -> updateTileSize()); + tileHeightSpinner.addChangeListener(e -> updateTileSize()); + blockWidthSpinner.addChangeListener(e -> updateBlockSize()); + blockHeightSpinner.addChangeListener(e -> updateBlockSize()); + + // Create tile size panel + JPanel p4 = new JPanel(); + p4.setLayout(new FlowLayout(FlowLayout.LEFT, 2, 0)); + p4.add(tileSizeLabel); + p4.add(new JLabel("W:")); + p4.add(tileWidthSpinner); + p4.add(new JLabel("H:")); + p4.add(tileHeightSpinner); + + // Create block size panel + JPanel p5 = new JPanel(); + p5.setLayout(new FlowLayout(FlowLayout.LEFT, 2, 0)); + p5.add(blockSizeLabel); + p5.add(new JLabel("W:")); + p5.add(blockWidthSpinner); + p5.add(new JLabel("H:")); + p5.add(blockHeightSpinner); + + setLayout(new GridLayout(1, 6)); + add(p1); + add(p2); + add(p3); + add(p4); + add(p5); + } else { + setLayout(new GridLayout(1, 4)); + add(p1); + add(p2); + add(p3); + } // pane.add(new JLabel(" ")); // just some whitespace //offsetLabel.setBorder(new BevelBorder(BevelBorder.LOWERED)); @@ -80,6 +143,70 @@ public TMStatusBar() { //tilesLabel.setBorder(new BevelBorder(BevelBorder.LOWERED)); } +/** +* +* Creates the status bar (legacy constructor for backward compatibility). +* +**/ + + public TMStatusBar() { + this(null); + } + +/** +* +* Updates tile size when spinner values change. +* +**/ + + private void updateTileSize() { + if (parentUI != null) { + int width = (Integer) tileWidthSpinner.getValue(); + int height = (Integer) tileHeightSpinner.getValue(); + parentUI.setTileSize(width, height); + } + } + +/** +* +* Updates block size when spinner values change. +* +**/ + + private void updateBlockSize() { + if (parentUI != null) { + int width = (Integer) blockWidthSpinner.getValue(); + int height = (Integer) blockHeightSpinner.getValue(); + parentUI.setBlockSize(width, height); + } + } + +/** +* +* Updates the tile size spinners to reflect current tile dimensions. +* +**/ + + public void setTileSize(int width, int height) { + if (tileWidthSpinner != null && tileHeightSpinner != null) { + tileWidthSpinner.setValue(width); + tileHeightSpinner.setValue(height); + } + } + +/** +* +* Updates the block size spinners to reflect current block dimensions. +* +**/ + + public void setBlockSize(int width, int height) { + if (blockWidthSpinner != null && blockHeightSpinner != null) { + blockWidthSpinner.setValue(width); + blockHeightSpinner.setValue(height); + } + } + /** * * Sets the text for general message. @@ -197,6 +324,8 @@ else if (ec.isDrawingLine()) { } if (view.getTileCodec() != null) { setCodec(view.getTileCodec().getDescription()); + // Update tile size spinners + setTileSize(view.getTileCodec().getTileWidth(), view.getTileCodec().getTileHeight()); } else { setCodec(""); @@ -204,6 +333,9 @@ else if (ec.isDrawingLine()) { setPalOffset(view.getPalette().getOffset()); setMode(view.getMode()); setTiles(view.getCols(), view.getRows()); + + // Update block size spinners + setBlockSize(view.getBlockWidth(), view.getBlockHeight()); } /** diff --git a/src/main/java/tm/ui/TMUI.java b/src/main/java/tm/ui/TMUI.java index 7ff097d..a0d88ae 100644 --- a/src/main/java/tm/ui/TMUI.java +++ b/src/main/java/tm/ui/TMUI.java @@ -76,7 +76,7 @@ public class TMUI extends JFrame { // UI components private mxScrollableDesktop desktop = new mxScrollableDesktop(); - private TMStatusBar statusBar = new TMStatusBar(); + private TMStatusBar statusBar; private JToolBar toolBar = new JToolBar(JToolBar.HORIZONTAL); private JToolBar toolBarMDI = new JToolBar(JToolBar.HORIZONTAL); private JToolBar toolPalette = new JToolBar(JToolBar.VERTICAL); @@ -514,6 +514,9 @@ public TMUI() { newPaletteDialog.setCodecs(colorcodecs); importInternalPaletteDialog.setCodecs(colorcodecs); + // Initialize status bar with reference to this TMUI instance + statusBar = new TMStatusBar(this); + // Set up the GUI. // main contentpane JPanel pane = new JPanel(); @@ -3038,11 +3041,50 @@ public void doCustomTileSizeCommand() { // Limpar e redecodificar os pixels com as novas dimensões canvas.unpackPixels(); view.repaint(); + // Update status bar display + statusBar.setTileSize(newWidth, newHeight); } } } } + /** + * + * Sets tile size directly (called from status bar spinners). + * + **/ + + public void setTileSize(int width, int height) { + TMView view = getSelectedView(); + if (view != null) { + TileCodec codec = view.getTileCodec(); + if (codec != null) { + codec.setTileDimensions(width, height); + // Redimensionar o canvas para acomodar os novos tamanhos de tile + TMTileCanvas canvas = view.getEditorCanvas(); + canvas.setGridSize(canvas.getCols(), canvas.getRows()); + // Limpar e redecodificar os pixels com as novas dimensões + canvas.unpackPixels(); + view.repaint(); + } + } + } + + /** + * + * Sets block size directly (called from status bar spinners). + * + **/ + + public void setBlockSize(int width, int height) { + TMView view = getSelectedView(); + if (view != null) { + view.setBlockDimensions(width, height); + // Update status bar display + statusBar.setBlockSize(width, height); + } + } + /** * * @@ -4497,6 +4539,11 @@ public void refreshStatusBar() { TMView view = getSelectedView(); if (view != null) { statusBar.viewSelected(view); + // Update tile size spinners + TileCodec codec = view.getTileCodec(); + if (codec != null) { + statusBar.setTileSize(codec.getTileWidth(), codec.getTileHeight()); + } } } From ca0a453e75ae8d6965657c40c6f894609f84a858 Mon Sep 17 00:00:00 2001 From: Hans Bonini Date: Mon, 28 Jul 2025 05:24:28 -0300 Subject: [PATCH 05/10] Add CI/CD --- .github/workflows/maven.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..c6bb036 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,35 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Java CI with Maven + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B package --file pom.xml + + # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive + - name: Update dependency graph + uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 From 24f37083165af404a4e325df3040b7b698d179f3 Mon Sep 17 00:00:00 2001 From: hansbonini Date: Mon, 28 Jul 2025 14:09:36 -0300 Subject: [PATCH 06/10] Add Swizzle Mode --- src/main/java/tm/TMFileResources.java | 5 + src/main/java/tm/canvases/TMEditorCanvas.java | 24 + src/main/java/tm/canvases/TMTileCanvas.java | 81 +++- src/main/java/tm/tilecodecs/SwizzleUtil.java | 415 ++++++++++++++++++ src/main/java/tm/tilecodecs/TileCodec.java | 98 +++++ .../java/tm/treenodes/BookmarkItemNode.java | 14 + src/main/java/tm/ui/CustomSwizzleDialog.java | 160 +++++++ src/main/java/tm/ui/TMStatusBar.java | 44 +- src/main/java/tm/ui/TMUI.java | 124 ++++++ src/main/java/tm/ui/TMView.java | 38 +- .../resources/languages/language.properties | 10 + .../languages/language_en_US.properties | 11 +- 12 files changed, 1004 insertions(+), 20 deletions(-) create mode 100644 src/main/java/tm/tilecodecs/SwizzleUtil.java create mode 100644 src/main/java/tm/ui/CustomSwizzleDialog.java diff --git a/src/main/java/tm/TMFileResources.java b/src/main/java/tm/TMFileResources.java index 8fb80b5..ffdd93d 100644 --- a/src/main/java/tm/TMFileResources.java +++ b/src/main/java/tm/TMFileResources.java @@ -163,6 +163,10 @@ public void addToBookmarksTree(Element e, FolderNode folder) { // String palID = e.getAttribute("palette"); String codecID = e.getAttribute("codec"); TileCodec codec = ui.getTileCodecByID(codecID); + String swizzlePattern = e.getAttribute("swizzlepattern"); + if (swizzlePattern == null || swizzlePattern.isEmpty()) { + swizzlePattern = TileCodec.SWIZZLE_NONE; // Default value for backward compatibility + } String desc = XMLParser.getNodeValue(getChildTag(e, "description", 0)); BookmarkItemNode bookmark = new BookmarkItemNode( offset, @@ -175,6 +179,7 @@ public void addToBookmarksTree(Element e, FolderNode folder) { mode, palIndex, codec, + swizzlePattern, desc); folder.add(bookmark); } diff --git a/src/main/java/tm/canvases/TMEditorCanvas.java b/src/main/java/tm/canvases/TMEditorCanvas.java index 30d19d9..c9aa9fb 100644 --- a/src/main/java/tm/canvases/TMEditorCanvas.java +++ b/src/main/java/tm/canvases/TMEditorCanvas.java @@ -72,6 +72,7 @@ public class TMEditorCanvas extends TMTileCanvas implements MouseInputListener { private int blockWidth=1; private int blockHeight=1; private boolean rowInterleaved=false; + private String swizzlePattern = tm.tilecodecs.TileCodec.SWIZZLE_NONE; private boolean showBlockGrid=false; /** @@ -1586,4 +1587,27 @@ public boolean getRowInterleaveBlocks() { return rowInterleaved; } +/** +* +* Sets the swizzle pattern. +* +**/ + + public void setSwizzlePattern(String swizzlePattern) { + this.swizzlePattern = swizzlePattern; + if (codec != null) { + codec.setSwizzlePattern(swizzlePattern); + } + } + +/** +* +* Gets the swizzle pattern. +* +**/ + + public String getSwizzlePattern() { + return swizzlePattern; + } + } \ No newline at end of file diff --git a/src/main/java/tm/canvases/TMTileCanvas.java b/src/main/java/tm/canvases/TMTileCanvas.java index 8c32372..0b8f2b2 100644 --- a/src/main/java/tm/canvases/TMTileCanvas.java +++ b/src/main/java/tm/canvases/TMTileCanvas.java @@ -20,9 +20,8 @@ import tm.TMPalette; import tm.tilecodecs.TileCodec; -import javax.swing.*; +import tm.tilecodecs.SwizzleUtil; import java.awt.*; -import java.awt.image.*; /** * @@ -303,7 +302,30 @@ public void unpackPixels() { for (int p=0; pMorton order interleaves X,Y bits for cache-friendly access patterns.
" + + "Linear order stores pixels sequentially within each block.
"); + descLabel.setFont(descLabel.getFont().deriveFont(Font.PLAIN, 11.0f)); + mainPanel.add(descLabel, gbc); + + add(mainPanel, BorderLayout.CENTER); + + // Button panel + JPanel buttonPanel = new JPanel(new FlowLayout()); + okButton = new JButton("OK"); + cancelButton = new JButton("Cancel"); + + okButton.addActionListener(e -> { + saveSettings(); + isOK = true; + setVisible(false); + }); + + cancelButton.addActionListener(e -> { + isOK = false; + setVisible(false); + }); + + buttonPanel.add(okButton); + buttonPanel.add(cancelButton); + add(buttonPanel, BorderLayout.SOUTH); + } + + /** + * Sets up the dialog properties. + */ + private void setupDialog() { + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + pack(); + setLocationRelativeTo(getParent()); + setResizable(false); + + // Set default button + getRootPane().setDefaultButton(okButton); + + // ESC key closes dialog + KeyStroke escapeKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false); + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeKeyStroke, "ESCAPE"); + getRootPane().getActionMap().put("ESCAPE", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + isOK = false; + setVisible(false); + } + }); + } + + /** + * Saves the settings to the codec. + */ + private void saveSettings() { + if (codec != null) { + codec.setCustomBlockWidth((Integer) blockWidthSpinner.getValue()); + codec.setCustomBlockHeight((Integer) blockHeightSpinner.getValue()); + codec.setCustomMortonOrder(mortonOrderCheckbox.isSelected()); + } + } + + /** + * Returns whether the OK button was pressed. + */ + public boolean isOK() { + return isOK; + } +} diff --git a/src/main/java/tm/ui/TMStatusBar.java b/src/main/java/tm/ui/TMStatusBar.java index 7e18dbd..f16afb5 100644 --- a/src/main/java/tm/ui/TMStatusBar.java +++ b/src/main/java/tm/ui/TMStatusBar.java @@ -41,6 +41,7 @@ public class TMStatusBar extends JPanel { private JLabel modeLabel = new JLabel(" "); private JLabel tilesLabel = new JLabel(" "); private JLabel messageLabel = new JLabel(" "); + private JLabel swizzleLabel = new JLabel(" "); // Swizzle pattern information // Tile size controls private JLabel tileSizeLabel = new JLabel("Tile Size:"); @@ -120,13 +121,20 @@ public TMStatusBar(TMUI parentUI) { p5.add(blockWidthSpinner); p5.add(new JLabel("H:")); p5.add(blockHeightSpinner); + + // Create swizzle panel + JPanel p6 = new JPanel(); + p6.setLayout(new FlowLayout(FlowLayout.LEFT, 2, 0)); + p6.add(new JLabel("Swizzle:")); + p6.add(swizzleLabel); - setLayout(new GridLayout(1, 6)); + setLayout(new GridLayout(1, 7)); add(p1); add(p2); add(p3); add(p4); add(p5); + add(p6); } else { setLayout(new GridLayout(1, 4)); add(p1); @@ -336,6 +344,9 @@ else if (ec.isDrawingLine()) { // Update block size spinners setBlockSize(view.getBlockWidth(), view.getBlockHeight()); + + // Update swizzle information + setSwizzle(view.getSwizzlePattern(), view.getTileCodec()); } /** @@ -362,4 +373,35 @@ protected static void buildConstraints(GridBagConstraints gbc, int gx, int gy, i public void setCoords(String string) { coordsLabel.setText(string); } + + /** + * Sets the swizzle information text. + */ + public void setSwizzle(String swizzlePattern, tm.tilecodecs.TileCodec codec) { + if (swizzlePattern == null || tm.tilecodecs.TileCodec.SWIZZLE_NONE.equals(swizzlePattern)) { + swizzleLabel.setText("None"); + } else if (tm.tilecodecs.TileCodec.SWIZZLE_CUSTOM.equals(swizzlePattern) && codec != null) { + String mortonText = codec.getCustomMortonOrder() ? "Morton" : "Linear"; + swizzleLabel.setText(String.format("Custom %dx%d %s", + codec.getCustomBlockWidth(), codec.getCustomBlockHeight(), mortonText)); + } else { + // For predefined patterns, just show the pattern name + swizzleLabel.setText(getSwizzleDisplayName(swizzlePattern)); + } + } + + /** + * Gets a user-friendly display name for a swizzle pattern. + */ + private String getSwizzleDisplayName(String pattern) { + switch (pattern) { + case tm.tilecodecs.TileCodec.SWIZZLE_BC: return "BC"; + case tm.tilecodecs.TileCodec.SWIZZLE_PSP: return "PSP"; + case tm.tilecodecs.TileCodec.SWIZZLE_NDS: return "NDS"; + case tm.tilecodecs.TileCodec.SWIZZLE_3DS: return "3DS"; + case tm.tilecodecs.TileCodec.SWIZZLE_WII: return "WII"; + case tm.tilecodecs.TileCodec.SWIZZLE_SWITCH: return "SWITCH"; + default: return pattern != null ? pattern : "None"; + } + } } \ No newline at end of file diff --git a/src/main/java/tm/ui/TMUI.java b/src/main/java/tm/ui/TMUI.java index a0d88ae..6e04f11 100644 --- a/src/main/java/tm/ui/TMUI.java +++ b/src/main/java/tm/ui/TMUI.java @@ -248,6 +248,15 @@ public class TMUI extends JFrame { private JMenuItem customBlockSizeMenuItem = new JMenuItem("Custom..."); private JMenuItem customTileSizeMenuItem = new JMenuItem("Custom Tile Size..."); private JRadioButtonMenuItem rowInterleaveBlocksMenuItem = new JRadioButtonMenuItem("Row-interleave Blocks"); + private JMenu swizzleMenu = new JMenu("Swizzle"); + private JRadioButtonMenuItem swizzleNoneMenuItem = new JRadioButtonMenuItem("None"); + private JRadioButtonMenuItem swizzleBCMenuItem = new JRadioButtonMenuItem("BC"); + private JRadioButtonMenuItem swizzlePSPMenuItem = new JRadioButtonMenuItem("PSP"); + private JRadioButtonMenuItem swizzleNDSMenuItem = new JRadioButtonMenuItem("NDS"); + private JRadioButtonMenuItem swizzle3DSMenuItem = new JRadioButtonMenuItem("3DS"); + private JRadioButtonMenuItem swizzleWiiMenuItem = new JRadioButtonMenuItem("WII"); + private JRadioButtonMenuItem swizzleSWITCHMenuItem = new JRadioButtonMenuItem("SWITCH"); + private JRadioButtonMenuItem swizzleCustomMenuItem = new JRadioButtonMenuItem("Custom"); private JMenu modeMenu = new JMenu("Mode"); private JRadioButtonMenuItem _1DimensionalMenuItem = new JRadioButtonMenuItem("1-Dimensional"); private JRadioButtonMenuItem _2DimensionalMenuItem = new JRadioButtonMenuItem("2-Dimensional"); @@ -299,6 +308,7 @@ public class TMUI extends JFrame { private ButtonGroup paletteButtonGroup = new ButtonGroup(); private ButtonGroup modeButtonGroup = new ButtonGroup(); private ButtonGroup paletteEndiannessButtonGroup = new ButtonGroup(); + private ButtonGroup swizzleButtonGroup = new ButtonGroup(); private Hashtable tileCodecButtonHashtable = new Hashtable(); private Hashtable colorCodecButtonHashtable = new Hashtable(); @@ -408,6 +418,15 @@ public TMUI() { sizeBlockToCanvasMenuItem.setText(xlate("Full_Canvas")); customBlockSizeMenuItem.setText(xlate("Custom_Block_Size")); rowInterleaveBlocksMenuItem.setText(xlate("Row_Interleave_Blocks")); + swizzleMenu.setText(xlate("Swizzle")); + swizzleNoneMenuItem.setText(xlate("Swizzle_None")); + swizzleBCMenuItem.setText(xlate("Swizzle_BC")); + swizzlePSPMenuItem.setText(xlate("Swizzle_PSP")); + swizzleNDSMenuItem.setText(xlate("Swizzle_NDS")); + swizzle3DSMenuItem.setText(xlate("Swizzle_3DS")); + swizzleWiiMenuItem.setText(xlate("Swizzle_WII")); + swizzleSWITCHMenuItem.setText(xlate("Swizzle_SWITCH")); + swizzleCustomMenuItem.setText(xlate("Swizzle_Custom")); blockGridMenuItem.setText(xlate("Block_Grid")); tileGridMenuItem.setText(xlate("Tile_Grid")); pixelGridMenuItem.setText(xlate("Pixel_Grid")); @@ -1504,6 +1523,50 @@ public void actionPerformed(ActionEvent e) { } }); viewMenu.add(rowInterleaveBlocksMenuItem); + // Swizzle submenu + swizzleNoneMenuItem.setMnemonic(KeyEvent.VK_N); + swizzleNoneMenuItem.addActionListener(e -> doSwizzleCommand(TileCodec.SWIZZLE_NONE)); + swizzleButtonGroup.add(swizzleNoneMenuItem); + swizzleMenu.add(swizzleNoneMenuItem); + + swizzleBCMenuItem.setMnemonic(KeyEvent.VK_B); + swizzleBCMenuItem.addActionListener(e -> doSwizzleCommand(TileCodec.SWIZZLE_BC)); + swizzleButtonGroup.add(swizzleBCMenuItem); + swizzleMenu.add(swizzleBCMenuItem); + + swizzlePSPMenuItem.setMnemonic(KeyEvent.VK_P); + swizzlePSPMenuItem.addActionListener(e -> doSwizzleCommand(TileCodec.SWIZZLE_PSP)); + swizzleButtonGroup.add(swizzlePSPMenuItem); + swizzleMenu.add(swizzlePSPMenuItem); + + swizzleNDSMenuItem.setMnemonic(KeyEvent.VK_D); + swizzleNDSMenuItem.addActionListener(e -> doSwizzleCommand(TileCodec.SWIZZLE_NDS)); + swizzleButtonGroup.add(swizzleNDSMenuItem); + swizzleMenu.add(swizzleNDSMenuItem); + + swizzle3DSMenuItem.setMnemonic(KeyEvent.VK_3); + swizzle3DSMenuItem.addActionListener(e -> doSwizzleCommand(TileCodec.SWIZZLE_3DS)); + swizzleButtonGroup.add(swizzle3DSMenuItem); + swizzleMenu.add(swizzle3DSMenuItem); + + swizzleWiiMenuItem.setMnemonic(KeyEvent.VK_W); + swizzleWiiMenuItem.addActionListener(e -> doSwizzleCommand(TileCodec.SWIZZLE_WII)); + swizzleButtonGroup.add(swizzleWiiMenuItem); + swizzleMenu.add(swizzleWiiMenuItem); + + swizzleSWITCHMenuItem.setMnemonic(KeyEvent.VK_H); + swizzleSWITCHMenuItem.addActionListener(e -> doSwizzleCommand(TileCodec.SWIZZLE_SWITCH)); + swizzleButtonGroup.add(swizzleSWITCHMenuItem); + swizzleMenu.add(swizzleSWITCHMenuItem); + + swizzleCustomMenuItem.setMnemonic(KeyEvent.VK_C); + swizzleCustomMenuItem.addActionListener(e -> doCustomSwizzleCommand()); + swizzleButtonGroup.add(swizzleCustomMenuItem); + swizzleMenu.add(swizzleCustomMenuItem); + + // Default to None + swizzleNoneMenuItem.setSelected(true); + viewMenu.add(swizzleMenu); // viewMenu.addSeparator(); // Block Grid @@ -3099,6 +3162,66 @@ public void doRowInterleaveBlocksCommand() { } } + /** + * + * Sets the swizzle pattern for the current view. + * + **/ + + public void doSwizzleCommand(String swizzlePattern) { + TMView view = getSelectedView(); + if (view != null) { + // Set optimal tile dimensions for the selected swizzle pattern + tm.tilecodecs.SwizzleUtil.setOptimalTileDimensions(swizzlePattern, view.getTileCodec()); + + view.setSwizzlePattern(swizzlePattern); + updateSwizzleMenuSelection(swizzlePattern); + refreshStatusBar(); // Update status bar to show swizzle info + + // Refresh the view to apply new tile dimensions + view.getEditorCanvas().repaint(); + } + } + + /** + * + * Opens the custom swizzle configuration dialog. + * + **/ + + public void doCustomSwizzleCommand() { + TMView view = getSelectedView(); + if (view != null) { + CustomSwizzleDialog dialog = new CustomSwizzleDialog(this, view.getTileCodec()); + dialog.setVisible(true); + if (dialog.isOK()) { + view.setSwizzlePattern(TileCodec.SWIZZLE_CUSTOM); + updateSwizzleMenuSelection(TileCodec.SWIZZLE_CUSTOM); + refreshStatusBar(); // Update status bar to show swizzle info + } else { + // If cancelled, revert to previous selection + updateSwizzleMenuSelection(view.getSwizzlePattern()); + } + } + } + + /** + * + * Updates the swizzle menu selection based on the current pattern. + * + **/ + + private void updateSwizzleMenuSelection(String swizzlePattern) { + swizzleNoneMenuItem.setSelected(TileCodec.SWIZZLE_NONE.equals(swizzlePattern)); + swizzleBCMenuItem.setSelected(TileCodec.SWIZZLE_BC.equals(swizzlePattern)); + swizzlePSPMenuItem.setSelected(TileCodec.SWIZZLE_PSP.equals(swizzlePattern)); + swizzleNDSMenuItem.setSelected(TileCodec.SWIZZLE_NDS.equals(swizzlePattern)); + swizzle3DSMenuItem.setSelected(TileCodec.SWIZZLE_3DS.equals(swizzlePattern)); + swizzleWiiMenuItem.setSelected(TileCodec.SWIZZLE_WII.equals(swizzlePattern)); + swizzleSWITCHMenuItem.setSelected(TileCodec.SWIZZLE_SWITCH.equals(swizzlePattern)); + swizzleCustomMenuItem.setSelected(TileCodec.SWIZZLE_CUSTOM.equals(swizzlePattern)); + } + /** * * @@ -4441,6 +4564,7 @@ public void viewSelected(TMView view) { tileGridMenuItem.setSelected(ec.isTileGridVisible()); pixelGridMenuItem.setSelected(ec.isPixelGridVisible()); rowInterleaveBlocksMenuItem.setSelected(ec.getRowInterleaveBlocks()); + updateSwizzleMenuSelection(ec.getSwizzlePattern()); refreshModeSelection(view); refreshTileCodecSelection(view); diff --git a/src/main/java/tm/ui/TMView.java b/src/main/java/tm/ui/TMView.java index 59f89b3..2f44995 100644 --- a/src/main/java/tm/ui/TMView.java +++ b/src/main/java/tm/ui/TMView.java @@ -810,6 +810,7 @@ public void gotoBookmark(BookmarkItemNode bookmark) { editorCanvas.setBlockDimensions(bookmark.getBlockWidth(), bookmark.getBlockHeight()); sizeBlockToCanvas = bookmark.getSizeBlockToCanvas(); editorCanvas.setRowInterleaveBlocks(bookmark.getRowInterleaved()); + editorCanvas.setSwizzlePattern(bookmark.getSwizzlePattern()); editorCanvas.setMode(bookmark.getMode()); setAbsoluteOffset(bookmark.getOffset()); // editorCanvas.setPalette( @@ -839,6 +840,7 @@ public BookmarkItemNode createBookmark(String description) { getMode(), getPalIndex(), getTileCodec(), + getSwizzlePattern(), description); } @@ -955,20 +957,20 @@ public int getBlockHeight() { } /** - * - * Gets whether blocks should be row-interleaved. - * - **/ + * + * Gets whether blocks should be row-interleaved. + * + **/ public boolean getRowInterleaveBlocks() { return editorCanvas.getRowInterleaveBlocks(); } /** - * - * Sets whether blocks should be row-interleaved. - * - **/ + * + * Sets whether blocks should be row-interleaved. + * + **/ public void setRowInterleaveBlocks(boolean rowInterleaved) { editorCanvas.setRowInterleaveBlocks(rowInterleaved); @@ -977,6 +979,26 @@ public void setRowInterleaveBlocks(boolean rowInterleaved) { } /** + * + * Gets the current swizzle pattern. + * + **/ + + public String getSwizzlePattern() { + return editorCanvas.getSwizzlePattern(); + } + + /** + * + * Sets the swizzle pattern for tile rendering. + * + **/ + + public void setSwizzlePattern(String swizzlePattern) { + editorCanvas.setSwizzlePattern(swizzlePattern); + editorCanvas.unpackPixels(); + editorCanvas.repaint(); + } /** * * Sets whether the block size should follow the canvas size. * diff --git a/src/main/resources/languages/language.properties b/src/main/resources/languages/language.properties index 65e5679..01307e8 100644 --- a/src/main/resources/languages/language.properties +++ b/src/main/resources/languages/language.properties @@ -33,6 +33,16 @@ Tile_Grid = Tile Grid Pixel_Grid = Pixel Grid Dark_Mode = Dark Mode +# Swizzle settings +Swizzle = Swizzle +Swizzle_None = None +Swizzle_BC = BC Texture +Swizzle_PSP = PlayStation Portable +Swizzle_NDS = Nintendo DS +Swizzle_3DS = Nintendo 3DS +Swizzle_WII = Nintendo Wii +Swizzle_SWITCH = Nintendo Switch + # Edit Menu Edit = Edit diff --git a/src/main/resources/languages/language_en_US.properties b/src/main/resources/languages/language_en_US.properties index 65e5679..a467365 100644 --- a/src/main/resources/languages/language_en_US.properties +++ b/src/main/resources/languages/language_en_US.properties @@ -33,7 +33,16 @@ Tile_Grid = Tile Grid Pixel_Grid = Pixel Grid Dark_Mode = Dark Mode -# Edit Menu +# Swizzle settings +Swizzle = Swizzle +Swizzle_None = None +Swizzle_BC = BC Texture +Swizzle_PSP = PlayStation Portable +Swizzle_NDS = Nintendo DS +Swizzle_3DS = Nintendo 3DS +Swizzle_WII = Nintendo Wii +Swizzle_SWITCH = Nintendo Switch +Swizzle_Custom = Custom... Edit = Edit Undo = Undo From 6795ae6c07f18ff104cacfa597da74221f509a10 Mon Sep 17 00:00:00 2001 From: hansbonini Date: Mon, 28 Jul 2025 14:21:18 -0300 Subject: [PATCH 07/10] Fix canvas and Block Width --- src/main/java/tm/ui/TMStatusBar.java | 16 +++++++++++++++- src/main/java/tm/ui/TMUI.java | 6 ++++++ src/main/java/tm/ui/TMView.java | 3 +++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/tm/ui/TMStatusBar.java b/src/main/java/tm/ui/TMStatusBar.java index f16afb5..10189e9 100644 --- a/src/main/java/tm/ui/TMStatusBar.java +++ b/src/main/java/tm/ui/TMStatusBar.java @@ -214,6 +214,19 @@ public void setBlockSize(int width, int height) { blockHeightSpinner.setValue(height); } } + + /** + * Updates the block size label to show Full Canvas mode status. + */ + public void updateBlockSizeLabel(boolean isFullCanvas) { + if (isFullCanvas) { + blockSizeLabel.setText("Block Size (Full Canvas):"); + blockSizeLabel.setToolTipText("Block dimensions automatically match canvas size"); + } else { + blockSizeLabel.setText("Block Size:"); + blockSizeLabel.setToolTipText("Block dimensions are independent of canvas size"); + } + } /** * @@ -342,8 +355,9 @@ else if (ec.isDrawingLine()) { setMode(view.getMode()); setTiles(view.getCols(), view.getRows()); - // Update block size spinners + // Update block size spinners and label setBlockSize(view.getBlockWidth(), view.getBlockHeight()); + updateBlockSizeLabel(view.getSizeBlockToCanvas()); // Update swizzle information setSwizzle(view.getSwizzlePattern(), view.getTileCodec()); diff --git a/src/main/java/tm/ui/TMUI.java b/src/main/java/tm/ui/TMUI.java index 6e04f11..4e97b3f 100644 --- a/src/main/java/tm/ui/TMUI.java +++ b/src/main/java/tm/ui/TMUI.java @@ -2953,6 +2953,12 @@ public void doSizeBlockToCanvasCommand() { sizeBlockToCanvasMenuItem16x16.setSelected(false); sizeBlockToCanvasMenuItem24x24.setSelected(false); sizeBlockToCanvasMenuItem32x32.setSelected(false); + + // Update status bar to reflect the change + if (statusBar != null) { + statusBar.updateBlockSizeLabel(view.getSizeBlockToCanvas()); + statusBar.setBlockSize(view.getBlockWidth(), view.getBlockHeight()); + } } } diff --git a/src/main/java/tm/ui/TMView.java b/src/main/java/tm/ui/TMView.java index 2f44995..ec0a652 100644 --- a/src/main/java/tm/ui/TMView.java +++ b/src/main/java/tm/ui/TMView.java @@ -391,6 +391,9 @@ private void updateSlider() { /** * * Sets the size of the tile grid. + * + * Note: When "Full Canvas" block sizing is enabled, changing canvas dimensions + * will also update block dimensions to match the canvas size. * **/ From 311ac53e5622b080973fcc5bef6e9f6f21b0dfcf Mon Sep 17 00:00:00 2001 From: hansbonini Date: Mon, 28 Jul 2025 14:25:42 -0300 Subject: [PATCH 08/10] Standardize Swizzle names on statusbar --- src/main/java/tm/ui/TMStatusBar.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/tm/ui/TMStatusBar.java b/src/main/java/tm/ui/TMStatusBar.java index 10189e9..e2d709e 100644 --- a/src/main/java/tm/ui/TMStatusBar.java +++ b/src/main/java/tm/ui/TMStatusBar.java @@ -396,7 +396,7 @@ public void setSwizzle(String swizzlePattern, tm.tilecodecs.TileCodec codec) { swizzleLabel.setText("None"); } else if (tm.tilecodecs.TileCodec.SWIZZLE_CUSTOM.equals(swizzlePattern) && codec != null) { String mortonText = codec.getCustomMortonOrder() ? "Morton" : "Linear"; - swizzleLabel.setText(String.format("Custom %dx%d %s", + swizzleLabel.setText(String.format("Custom... %dx%d %s", codec.getCustomBlockWidth(), codec.getCustomBlockHeight(), mortonText)); } else { // For predefined patterns, just show the pattern name @@ -406,15 +406,16 @@ public void setSwizzle(String swizzlePattern, tm.tilecodecs.TileCodec codec) { /** * Gets a user-friendly display name for a swizzle pattern. + * Names match those used in the menu system for consistency. */ private String getSwizzleDisplayName(String pattern) { switch (pattern) { - case tm.tilecodecs.TileCodec.SWIZZLE_BC: return "BC"; - case tm.tilecodecs.TileCodec.SWIZZLE_PSP: return "PSP"; - case tm.tilecodecs.TileCodec.SWIZZLE_NDS: return "NDS"; - case tm.tilecodecs.TileCodec.SWIZZLE_3DS: return "3DS"; - case tm.tilecodecs.TileCodec.SWIZZLE_WII: return "WII"; - case tm.tilecodecs.TileCodec.SWIZZLE_SWITCH: return "SWITCH"; + case tm.tilecodecs.TileCodec.SWIZZLE_BC: return "BC Texture"; + case tm.tilecodecs.TileCodec.SWIZZLE_PSP: return "PlayStation Portable"; + case tm.tilecodecs.TileCodec.SWIZZLE_NDS: return "Nintendo DS"; + case tm.tilecodecs.TileCodec.SWIZZLE_3DS: return "Nintendo 3DS"; + case tm.tilecodecs.TileCodec.SWIZZLE_WII: return "Nintendo Wii"; + case tm.tilecodecs.TileCodec.SWIZZLE_SWITCH: return "Nintendo Switch"; default: return pattern != null ? pattern : "None"; } } From 7b79772a4537f0de89e0a76197fc8b1866a27d40 Mon Sep 17 00:00:00 2001 From: hansbonini Date: Mon, 28 Jul 2025 14:31:47 -0300 Subject: [PATCH 09/10] Fix version on pom and add changes to README --- README.md | 11 +++++++++++ pom.xml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 54a9105..7cf146f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,17 @@ It was originally developed SnowBro and later improved by Central MiB and Lab313 ## Changelog +### v0.23 (by hansbonini a.k.a Anime_World) +- Add swizzle modes for texture visualizations as tile +- Fix Block Dimensions setting up canvas dimensions, now they work individually. + +### v0.22 (by hansbonini a.k.a Anime_World) +- Add support for Mesen and Exodus CRAM Dump as Palette +- Add new codecs for visualization +- Add support for custom tile dimensions +- Add easy block size configuration +- Add statusbar configuration for tile and block dimensions + ### v0.21 (by toruzz) - New themes. Old custom system removed - Fractional scale support diff --git a/pom.xml b/pom.xml index a2af0ef..73dfc44 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 tm tilemolester - 1.0-SNAPSHOT + 0.23 jar UTF-8 From 33362477fc9b7aff94d064b6bf85d2dd3bd0b565 Mon Sep 17 00:00:00 2001 From: hansbonini Date: Mon, 28 Jul 2025 14:57:27 -0300 Subject: [PATCH 10/10] Fix block spinners --- src/main/java/tm/ui/TMStatusBar.java | 36 +++++++++++++++++++++------- src/main/java/tm/ui/TMUI.java | 1 + src/main/java/tm/ui/TMView.java | 7 ++---- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/main/java/tm/ui/TMStatusBar.java b/src/main/java/tm/ui/TMStatusBar.java index e2d709e..082115f 100644 --- a/src/main/java/tm/ui/TMStatusBar.java +++ b/src/main/java/tm/ui/TMStatusBar.java @@ -183,6 +183,7 @@ private void updateTileSize() { private void updateBlockSize() { if (parentUI != null) { + // Block dimensions are always independent of canvas size int width = (Integer) blockWidthSpinner.getValue(); int height = (Integer) blockHeightSpinner.getValue(); parentUI.setBlockSize(width, height); @@ -210,22 +211,41 @@ public void setTileSize(int width, int height) { public void setBlockSize(int width, int height) { if (blockWidthSpinner != null && blockHeightSpinner != null) { + // Temporarily remove listeners to avoid triggering updates + ChangeListener[] widthListeners = blockWidthSpinner.getChangeListeners(); + ChangeListener[] heightListeners = blockHeightSpinner.getChangeListeners(); + + for (ChangeListener listener : widthListeners) { + blockWidthSpinner.removeChangeListener(listener); + } + for (ChangeListener listener : heightListeners) { + blockHeightSpinner.removeChangeListener(listener); + } + + // Update values blockWidthSpinner.setValue(width); blockHeightSpinner.setValue(height); + + // Restore listeners + for (ChangeListener listener : widthListeners) { + blockWidthSpinner.addChangeListener(listener); + } + for (ChangeListener listener : heightListeners) { + blockHeightSpinner.addChangeListener(listener); + } } } /** - * Updates the block size label to show Full Canvas mode status. + * Updates the block size label to show that block dimensions are always independent. */ public void updateBlockSizeLabel(boolean isFullCanvas) { - if (isFullCanvas) { - blockSizeLabel.setText("Block Size (Full Canvas):"); - blockSizeLabel.setToolTipText("Block dimensions automatically match canvas size"); - } else { - blockSizeLabel.setText("Block Size:"); - blockSizeLabel.setToolTipText("Block dimensions are independent of canvas size"); - } + // Block dimensions are always independent of canvas size + blockSizeLabel.setText("Block Size:"); + blockSizeLabel.setToolTipText("Block dimensions are independent of canvas size"); + // Always enable spinners since block size is always independent + if (blockWidthSpinner != null) blockWidthSpinner.setEnabled(true); + if (blockHeightSpinner != null) blockHeightSpinner.setEnabled(true); } /** diff --git a/src/main/java/tm/ui/TMUI.java b/src/main/java/tm/ui/TMUI.java index 4e97b3f..fb0d200 100644 --- a/src/main/java/tm/ui/TMUI.java +++ b/src/main/java/tm/ui/TMUI.java @@ -3142,6 +3142,7 @@ public void setTileSize(int width, int height) { /** * * Sets block size directly (called from status bar spinners). + * Block dimensions are always independent of canvas size. * **/ diff --git a/src/main/java/tm/ui/TMView.java b/src/main/java/tm/ui/TMView.java index ec0a652..70755f9 100644 --- a/src/main/java/tm/ui/TMView.java +++ b/src/main/java/tm/ui/TMView.java @@ -392,8 +392,7 @@ private void updateSlider() { * * Sets the size of the tile grid. * - * Note: When "Full Canvas" block sizing is enabled, changing canvas dimensions - * will also update block dimensions to match the canvas size. + * Block dimensions are always independent of canvas size. * **/ @@ -407,9 +406,7 @@ else if (cols > 1024) else if (rows > 1024) rows = 1024; editorCanvas.setGridSize(cols, rows); - if (sizeBlockToCanvas) { - editorCanvas.setBlockDimensions(cols, rows); - } + // Block dimensions are always independent of canvas size updateSlider(); editorCanvas.unpackPixels(); setScale(getScale());