diff --git a/bin/rescomp.jar b/bin/rescomp.jar index 326c6035..b39618ab 100644 Binary files a/bin/rescomp.jar and b/bin/rescomp.jar differ diff --git a/bin/rescomp.txt b/bin/rescomp.txt index 1b7cdb53..77a0bb17 100644 --- a/bin/rescomp.txt +++ b/bin/rescomp.txt @@ -1,4 +1,4 @@ -ResComp 3.87 (November 2024) +ResComp 3.90 (November 2024) Copyright Stephane Dallongeville https://github.com/Stephane-D/SGDK @@ -111,7 +111,7 @@ If you use a real 'tileset format' image as input (as a font) then you should se to avoid removing duplicated tiles (which may exist). Syntax: -TILESET name file [compression [opt]] +TILESET name file [compression [opt [ordering [export]]]] name name of the output TileSet structure file path of the input file (BMP, PNG image file or TSX Tiled file) @@ -125,6 +125,12 @@ TILESET name file [compression [opt]] 0 / NONE = no optimisation, each tile is unique 1 / ALL = ignore duplicated and flipped tile (default) 2 / DUPLICATE = ignore duplicated tile only + ordering define the tile process order, accepted values: + ROW = process per row (default) + COLUMN = process per column + export enable the tileset PNG export, accept values: + 0 / FALSE = no PNG export (default) + 1 / TRUE = optimized tileset is export to PNG format '-tileset-export.png' Tips: When using a 8bpp indexed image as input you can use the extra bits of palette to provide extra information for the TILEMAP data but you have to @@ -141,7 +147,7 @@ TileMap is used to draw in background plane, it requires a tileset which can be TILEMAP resource isn't meant to be used to store large background level (eat too much space), use MAP resource for that. Syntax (image file input): -TILEMAP name img_file tileset_id [compression [map_opt [map_base]]] +TILEMAP name img_file tileset_id [compression [map_opt [map_base [ordering]]]] name name of the output TileMap structure img_file path of the input image file (BMP or PNG image file) @@ -157,7 +163,10 @@ TILEMAP name img_file tileset_id [compression [map_opt [map_base]]] 1 / ALL = find duplicated and flipped tile (default) 2 / DUPLICATE = find duplicated tile only map_base define the base tilemap value, useful to set a default priority, palette and base tile index offset. - Using a base tile index offset (static tile allocation) allow to use the faster VDP_setTileMapxxx(..) functions. + using a base tile index offset (static tile allocation) allow to use the faster VDP_setTileMapxxx(..) functions. + ordering define the tilemap process order, accepted values: + ROW = process per row (default) + COLUMN = process per column Syntax (TMX tiled file input): TILEMAP name tmx_file layer_id [ts_compression [map_compression [map_base]]] @@ -172,7 +181,10 @@ TILEMAP name tmx_file layer_id [ts_compression [map_compression [map_base]]] 2 / FAST / LZ4W = custom lz4 compression (average compression ratio but fast) map_compression compression type for map (same accepted values than 'ts_compression') map_base define the base tilemap value, useful to set a default priority, palette and base tile index offset. - Using a base tile index offset (static tile allocation) allow to use the faster VDP_setTileMapxxx(..) functions. + using a base tile index offset (static tile allocation) allow to use the faster VDP_setTileMapxxx(..) functions. + ordering define the tilemap process order, accepted values: + ROW = process per row (default) + COLUMN = process per column Tips: When using a 8bpp indexed image as input you can use the extra bits of palette to provide extra information for the TILEMAP data but you have to @@ -207,9 +219,9 @@ MAP name img_file tileset_id [compression [map_base]] 1 / APLIB = aplib library (good compression ratio but slow) 2 / FAST / LZ4W = custom lz4 compression (average compression ratio but fast) map_base define the base tilemap value, useful to set a default priority, palette and base tile index offset. - Using a base tile index offset (static tile allocation) allow to use faster MAP decoding function internally. + using a base tile index offset (static tile allocation) allow to use faster MAP decoding function internally. -MAP name tmx_file layer_id [ts_compression [map_compression [map_base]]] +MAP name tmx_file layer_id [ts_compression [map_compression [map_base [ordering]]]] name name of the output Map structure tmx_file path of the input TMX file (TMX Tiled file with CSV encoded map data) @@ -221,7 +233,10 @@ MAP name tmx_file layer_id [ts_compression [map_compression [map_base]]] 2 / FAST / LZ4W = custom lz4 compression (average compression ratio but fast) map_compression compression type for map (same accepted values than 'ts_compression') map_base define the base tilemap value, useful to set a default priority, palette and base tile index offset. - Using a base tile index offset (static tile allocation) allow to use faster MAP decoding function internally. + using a base tile index offset (static tile allocation) allow to use faster MAP decoding function internally. + ordering define the map process order, accepted values: + ROW = process per row (default) + COLUMN = process per column Tips: When using a 8bpp indexed image as input you can use the extra bits of palette to provide extra information for the TILEMAP data but you have to diff --git a/tools/rescomp/src/sgdk/rescomp/Launcher.java b/tools/rescomp/src/sgdk/rescomp/Launcher.java index bc9e0979..a34881a4 100644 --- a/tools/rescomp/src/sgdk/rescomp/Launcher.java +++ b/tools/rescomp/src/sgdk/rescomp/Launcher.java @@ -30,7 +30,7 @@ else if (dep && depTarget == null) depTarget = param; } - System.out.println("ResComp 3.87 - SGDK Resource Compiler - Copyright 2024 (Stephane Dallongeville)"); + System.out.println("ResComp 3.90 - SGDK Resource Compiler - Copyright 2024 (Stephane Dallongeville)"); if (fileName == null) { diff --git a/tools/rescomp/src/sgdk/rescomp/processor/MapProcessor.java b/tools/rescomp/src/sgdk/rescomp/processor/MapProcessor.java index 1cfe80cf..bf476424 100644 --- a/tools/rescomp/src/sgdk/rescomp/processor/MapProcessor.java +++ b/tools/rescomp/src/sgdk/rescomp/processor/MapProcessor.java @@ -9,6 +9,7 @@ import sgdk.rescomp.resource.Tileset; import sgdk.rescomp.tool.Util; import sgdk.rescomp.type.Basics.Compression; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.rescomp.type.TMX.TMXMap; import sgdk.tool.FileUtil; import sgdk.tool.StringUtil; @@ -37,10 +38,9 @@ public Resource execute(String[] fields) throws Exception System.out.println(" 1 / APLIB = aplib library (good compression ratio but slow)"); System.out.println(" 2 / FAST / LZ4W = custom lz4 compression (average compression ratio but fast)"); System.out.println(" map_base define the base tilemap value, useful to set a default priority, palette and base tile index offset"); - System.out.println( - " Using a base tile index offset (static tile allocation) allow to use faster MAP decoding function internally."); + System.out.println(" using a base tile index offset (static tile allocation) allow to use faster MAP decoding function internally."); System.out.println(); - System.out.println("MAP name \"tmx_file\" \"layer_id\" [ts_compression [map_compression [map_base]]]"); + System.out.println("MAP name \"tmx_file\" \"layer_id\" [ts_compression [map_compression [map_base [ordering]]]]"); System.out.println(" name Map variable name"); System.out.println(" tmx_file path of the input TMX file (TMX Tiled file)"); System.out.println(" layer_id layer name we want to extract map data from."); @@ -51,6 +51,10 @@ public Resource execute(String[] fields) throws Exception System.out.println(" 2 / FAST / LZ4W = custom lz4 compression (average compression ratio but fast)"); System.out.println(" map_compression compression type for map (same accepted values then 'ts_compression')"); System.out.println(" map_base define the base tilemap value, useful to set a default priority, palette and base tile index offset"); + System.out.println(" using a base tile index offset (static tile allocation) allow to use faster MAP decoding function internally."); + System.out.println(" ordering define the map process order, accepted values:"); + System.out.println(" ROW = process per row (default)"); + System.out.println(" COLUMN = process per column"); return null; } @@ -79,14 +83,18 @@ public Resource execute(String[] fields) throws Exception int mapBase = 0; if (fields.length >= 7) mapBase = StringUtil.parseInt(fields[6], 0); + // get tile ordering value + TileOrdering order = TileOrdering.ROW; + if (fields.length >= 8) + order = Util.getTileOrdering(fields[7]); // build TMX map final TMXMap tmxMap = new TMXMap(fileIn, layerName); // then build MAP from TMX Map - return new Map(id, tmxMap.getMapImage(), tmxMap.w * tmxMap.tileSize, tmxMap.h * tmxMap.tileSize, mapBase, 2, tmxMap.getTilesets(id, tileSetCompression, false), - mapCompression, true); + return new Map(id, tmxMap.getMapImage(), tmxMap.w * tmxMap.tileSize, tmxMap.h * tmxMap.tileSize, mapBase, 2, + tmxMap.getTilesets(id, tileSetCompression, false, order), mapCompression, true); } - else + // image file { // get tileset diff --git a/tools/rescomp/src/sgdk/rescomp/processor/TilemapProcessor.java b/tools/rescomp/src/sgdk/rescomp/processor/TilemapProcessor.java index 3b234392..50f0861f 100644 --- a/tools/rescomp/src/sgdk/rescomp/processor/TilemapProcessor.java +++ b/tools/rescomp/src/sgdk/rescomp/processor/TilemapProcessor.java @@ -11,6 +11,7 @@ import sgdk.rescomp.tool.Util; import sgdk.rescomp.type.Basics.Compression; import sgdk.rescomp.type.Basics.TileOptimization; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.rescomp.type.TMX.TMXMap; import sgdk.tool.FileUtil; import sgdk.tool.StringUtil; @@ -29,7 +30,7 @@ public Resource execute(String[] fields) throws Exception if (fields.length < 3) { System.out.println("Wrong TILEMAP definition"); - System.out.println("TILEMAP name \"img_file\" tileset_id [compression [map_opt [map_base]]]"); + System.out.println("TILEMAP name \"img_file\" tileset_id [compression [map_opt [map_base [ordering]]]]"); System.out.println(" name Tilemap variable name"); System.out.println(" file path of the input image file (BMP or PNG image file)"); System.out.println(" tileset_id base tileset resource to use (allow to share tileset along several maps)"); @@ -43,8 +44,11 @@ public Resource execute(String[] fields) throws Exception System.out.println(" 1 / ALL = find duplicate and flipped tile (default)"); System.out.println(" 2 / DUPLICATE = find duplicate tile only"); System.out.println(" map_base define the base tilemap value, useful to set a default priority, palette and base tile index offset"); + System.out.println(" ordering define the tilemap process order, accepted values:"); + System.out.println(" ROW = process per row (default)"); + System.out.println(" COLUMN = process per column"); System.out.println(); - System.out.println("TILEMAP name \"tmx_file\" \"layer_id\" [ts_compression [map_compression [map_base]]]"); + System.out.println("TILEMAP name \"tmx_file\" \"layer_id\" [ts_compression [map_compression [map_base [ordering]]]]"); System.out.println(" name Tilemap variable name"); System.out.println(" tmx_file path of the input TMX file (TMX Tiled file)"); System.out.println(" layer_id layer name we want to extract map data from."); @@ -55,6 +59,9 @@ public Resource execute(String[] fields) throws Exception System.out.println(" 2 / FAST / LZ4W = custom lz4 compression (average compression ratio but fast)"); System.out.println(" map_compression compression type for map (same accepted values then 'ts_compression')"); System.out.println(" map_base define the base tilemap value, useful to set a default priority, palette and base tile index offset."); + System.out.println(" ordering define the tilemap process order, accepted values:"); + System.out.println(" ROW = process per row (default)"); + System.out.println(" COLUMN = process per column"); return null; } @@ -83,17 +90,21 @@ public Resource execute(String[] fields) throws Exception int mapBase = 0; if (fields.length >= 7) mapBase = StringUtil.parseInt(fields[6], 0); + // get tile ordering value + TileOrdering order = TileOrdering.ROW; + if (fields.length >= 8) + order = Util.getTileOrdering(fields[7]); // build TMX map final TMXMap tmxMap = new TMXMap(fileIn, layerName); // get tilesets for this TMX map - final List tilesets = tmxMap.getTilesets(id, tileSetCompression, false); + final List tilesets = tmxMap.getTilesets(id, tileSetCompression, false, order); // then build TileMap from TMX Map return Tilemap.getTilemap(id, new Tileset(tilesets), mapBase, tmxMap.getMapImage(), (tmxMap.w * tmxMap.tileSize) / 8, - (tmxMap.h * tmxMap.tileSize) / 8, TileOptimization.ALL, mapCompression); + (tmxMap.h * tmxMap.tileSize) / 8, TileOptimization.ALL, mapCompression, order); } - else + // image file { // get packed value @@ -108,6 +119,10 @@ public Resource execute(String[] fields) throws Exception int mapBase = 0; if (fields.length >= 7) mapBase = StringUtil.parseInt(fields[6], 0); + // get tile ordering value + TileOrdering order = TileOrdering.ROW; + if (fields.length >= 8) + order = Util.getTileOrdering(fields[7]); // get tileset final Tileset tileset = (Tileset) Compiler.getResourceById(fields[3]); @@ -115,7 +130,7 @@ public Resource execute(String[] fields) throws Exception if (tileset == null) throw new InvalidParameterException("TILEMAP resource definition error: Tileset '" + fields[3] + "' not found !"); - return Tilemap.getTilemap(id, tileset, mapBase, fileIn, tileOpt, compression); + return Tilemap.getTilemap(id, tileset, mapBase, fileIn, tileOpt, compression, order); } } } diff --git a/tools/rescomp/src/sgdk/rescomp/processor/TilesetProcessor.java b/tools/rescomp/src/sgdk/rescomp/processor/TilesetProcessor.java index b7f0b98c..2b2c8a2a 100644 --- a/tools/rescomp/src/sgdk/rescomp/processor/TilesetProcessor.java +++ b/tools/rescomp/src/sgdk/rescomp/processor/TilesetProcessor.java @@ -7,8 +7,10 @@ import sgdk.rescomp.tool.Util; import sgdk.rescomp.type.Basics.Compression; import sgdk.rescomp.type.Basics.TileOptimization; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.rescomp.type.TSX; import sgdk.tool.FileUtil; +import sgdk.tool.StringUtil; public class TilesetProcessor implements Processor { @@ -24,7 +26,7 @@ public Resource execute(String[] fields) throws Exception if (fields.length < 3) { System.out.println("Wrong TILESET definition"); - System.out.println("TILESET name \"file\" [compression [opt]]"); + System.out.println("TILESET name \"file\" [compression [opt [ordering [export]]]]"); System.out.println(" name Tileset variable name"); System.out.println(" file path of the input file (BMP, PNG image file or TSX Tiled file)"); System.out.println(" compression compression type, accepted values:"); @@ -36,6 +38,12 @@ public Resource execute(String[] fields) throws Exception System.out.println(" 0 / NONE = no optimisation, each tile is unique (default for TSX file)"); System.out.println(" 1 / ALL = ignore duplicated and flipped tile (default for image file)"); System.out.println(" 2 / DUPLICATE = ignore duplicated tile only"); + System.out.println(" ordering define the tile process order, accepted values:"); + System.out.println(" ROW = process per row (default)"); + System.out.println(" COLUMN = process per column"); + System.out.println(" export enable the tileset PNG export, accept values:"); + System.out.println(" 0 / FALSE = no PNG export (default)"); + System.out.println(" 1 / TRUE = optimized tileset is export to PNG format '-tileset-export.png'"); return null; } @@ -57,10 +65,18 @@ public Resource execute(String[] fields) throws Exception // force ALL for TSX format if (!tsx && (fields.length >= 5)) opt = Util.getTileOpt(fields[4]); + // get tile ordering value + TileOrdering order = TileOrdering.ROW; + if (fields.length >= 6) + order = Util.getTileOrdering(fields[5]); + // PNG export + boolean export = false; + if (fields.length >= 7) + export = StringUtil.parseBoolean(fields[6], false); // add resource file (used for deps generation) Compiler.addResourceFile(fileIn); - return Tileset.getTileset(id, tsx ? TSX.getTSXTilesetPath(fileIn) : fileIn, compression, opt, tsx, false); + return Tileset.getTileset(id, tsx ? TSX.getTSXTilesetPath(fileIn) : fileIn, compression, opt, tsx, false, order, export); } } diff --git a/tools/rescomp/src/sgdk/rescomp/resource/Image.java b/tools/rescomp/src/sgdk/rescomp/resource/Image.java index d35abe27..09ccb32b 100644 --- a/tools/rescomp/src/sgdk/rescomp/resource/Image.java +++ b/tools/rescomp/src/sgdk/rescomp/resource/Image.java @@ -1,15 +1,14 @@ package sgdk.rescomp.resource; import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import sgdk.rescomp.Resource; import sgdk.rescomp.tool.Util; import sgdk.rescomp.type.Basics.Compression; import sgdk.rescomp.type.Basics.TileOptimization; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.tool.ImageUtil; import sgdk.tool.ImageUtil.BasicImageInfo; @@ -54,9 +53,9 @@ public Image(String id, String imgFile, Compression compression, TileOptimizatio final int ht = h / 8; // build TILESET with wanted compression - tileset = (Tileset) addInternalResource(new Tileset(id + "_tileset", image, w, h, 0, 0, wt, ht, tileOpt, compression, false, false)); + tileset = (Tileset) addInternalResource(new Tileset(id + "_tileset", image, w, h, 0, 0, wt, ht, tileOpt, compression, false, false, TileOrdering.ROW)); // build TILEMAP with wanted compression - tilemap = (Tilemap) addInternalResource(Tilemap.getTilemap(id + "_tilemap", tileset, mapBase, image, wt, ht, tileOpt, compression)); + tilemap = (Tilemap) addInternalResource(Tilemap.getTilemap(id + "_tilemap", tileset, mapBase, image, wt, ht, tileOpt, compression, TileOrdering.ROW)); // build PALETTE palette = (Palette) addInternalResource(new Palette(id + "_palette", imgFile, 64, true)); diff --git a/tools/rescomp/src/sgdk/rescomp/resource/Map.java b/tools/rescomp/src/sgdk/rescomp/resource/Map.java index 76ec7db5..e2b6bf54 100644 --- a/tools/rescomp/src/sgdk/rescomp/resource/Map.java +++ b/tools/rescomp/src/sgdk/rescomp/resource/Map.java @@ -10,6 +10,7 @@ import sgdk.rescomp.type.Basics.Compression; import sgdk.rescomp.type.Basics.TileEquality; import sgdk.rescomp.type.Basics.TileOptimization; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.rescomp.type.MapBlock; import sgdk.rescomp.type.Metatile; import sgdk.rescomp.type.Tile; @@ -116,7 +117,6 @@ public Map(String id, byte[] image8bpp, int imageWidth, int imageHeight, int map // set to -1 to mark that it's not yet set (we shouldn't never meet an offset of 65535 realistically) Arrays.fill(mapBlockRowOffsets, (short) -1); - // important to always use the same loop order when building Tileset and Tilemap object for (int j = 0; j < hb; j++) { int mbrii = 0; diff --git a/tools/rescomp/src/sgdk/rescomp/resource/Tilemap.java b/tools/rescomp/src/sgdk/rescomp/resource/Tilemap.java index a05bf24f..18bbcda3 100644 --- a/tools/rescomp/src/sgdk/rescomp/resource/Tilemap.java +++ b/tools/rescomp/src/sgdk/rescomp/resource/Tilemap.java @@ -10,6 +10,7 @@ import sgdk.rescomp.type.Basics.Compression; import sgdk.rescomp.type.Basics.TileEquality; import sgdk.rescomp.type.Basics.TileOptimization; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.rescomp.type.Tile; import sgdk.tool.ArrayUtil; import sgdk.tool.ImageUtil; @@ -18,7 +19,7 @@ public class Tilemap extends Resource { public static Tilemap getTilemap(String id, Tileset tileset, int mapBase, byte[] image8bpp, int imageWidth, int imageHeight, int startTileX, int startTileY, - int widthTile, int heightTile, TileOptimization opt, Compression compression) + int widthTile, int heightTile, TileOptimization opt, Compression compression, TileOrdering order) { final int w = widthTile; final int h = heightTile; @@ -32,7 +33,6 @@ public static Tilemap getTilemap(String id, Tileset tileset, int mapBase, byte[] final short[] data = new short[w * h]; int offset = 0; - // important to always use the same loop order when building Tileset and Tilemap object for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) @@ -48,7 +48,17 @@ public static Tilemap getTilemap(String id, Tileset tileset, int mapBase, byte[] // if no optimization, just use current offset as index if (opt == TileOptimization.NONE) - index = offset + mapBaseTileInd; + { + // important to respect tile ordering when computing index + if (order == TileOrdering.ROW) + { + index = ((j * w) + i) + mapBaseTileInd; + } + else + { + index = ((i * h) + j) + mapBaseTileInd; + } + } else { // use system tiles for plain tiles if possible @@ -78,12 +88,12 @@ public static Tilemap getTilemap(String id, Tileset tileset, int mapBase, byte[] } public static Tilemap getTilemap(String id, Tileset tileset, int mapBase, byte[] image8bpp, int widthTile, int heightTile, TileOptimization opt, - Compression compression) + Compression compression, TileOrdering order) { - return getTilemap(id, tileset, mapBase, image8bpp, widthTile * 8, heightTile * 8, 0, 0, widthTile, heightTile, opt, compression); + return getTilemap(id, tileset, mapBase, image8bpp, widthTile * 8, heightTile * 8, 0, 0, widthTile, heightTile, opt, compression, order); } - public static Tilemap getTilemap(String id, Tileset tileset, int mapBase, String imgFile, TileOptimization tileOpt, Compression compression) + public static Tilemap getTilemap(String id, Tileset tileset, int mapBase, String imgFile, TileOptimization tileOpt, Compression compression, TileOrdering order) throws Exception { // get 8bpp pixels and also check image dimension is aligned to tile @@ -102,7 +112,7 @@ public static Tilemap getTilemap(String id, Tileset tileset, int mapBase, String // b0-b3 = pixel data; b4-b5 = palette index; b7 = priority bit // build TILEMAP with wanted compression - return Tilemap.getTilemap(id, tileset, mapBase, image, w / 8, h / 8, tileOpt, compression); + return Tilemap.getTilemap(id, tileset, mapBase, image, w / 8, h / 8, tileOpt, compression, order); } public final int w; diff --git a/tools/rescomp/src/sgdk/rescomp/resource/Tileset.java b/tools/rescomp/src/sgdk/rescomp/resource/Tileset.java index 62d24da2..ce70bde1 100644 --- a/tools/rescomp/src/sgdk/rescomp/resource/Tileset.java +++ b/tools/rescomp/src/sgdk/rescomp/resource/Tileset.java @@ -1,6 +1,9 @@ package sgdk.rescomp.resource; import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; @@ -13,14 +16,17 @@ import sgdk.rescomp.type.Basics.Compression; import sgdk.rescomp.type.Basics.TileEquality; import sgdk.rescomp.type.Basics.TileOptimization; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.rescomp.type.Tile; +import sgdk.tool.ArrayUtil; +import sgdk.tool.FileUtil; import sgdk.tool.ImageUtil; import sgdk.tool.ImageUtil.BasicImageInfo; public class Tileset extends Resource { - public static Tileset getTileset(String id, String imgFile, Compression compression, TileOptimization tileOpt, boolean addBlank, boolean temp) - throws Exception + public static Tileset getTileset(String id, String imgFile, Compression compression, TileOptimization tileOpt, boolean addBlank, boolean temp, + TileOrdering order, boolean export) throws Exception { // get 8bpp pixels and also check image dimension is aligned to tile final byte[] image = ImageUtil.getImageAs8bpp(imgFile, true, true); @@ -36,7 +42,31 @@ public static Tileset getTileset(String id, String imgFile, Compression compress // we determine 'h' from data length and 'w' as we can crop image vertically to remove palette data final int h = image.length / w; - return new Tileset(id, image, w, h, 0, 0, w / 8, h / 8, tileOpt, compression, addBlank, temp); + final Tileset result = new Tileset(id, image, w, h, 0, 0, w / 8, h / 8, tileOpt, compression, addBlank, temp, order); + + // export tileset to PNG ? + if (export) + { + // get the tileset image (8bpp format) + final byte[] tilesetImage = result.getTilesetImage(); + + // get the palette + int[] palette = (imgInfo.bpp > 8) ? ImageUtil.getRGBA8888PaletteFromTiles(imgFile) : ImageUtil.getRGBA8888PaletteFromIndColImage(imgFile); + // need to convert back to ABGR format + palette = ImageUtil.ARGBtoABGR(palette); + // create the IndexColorModel + final IndexColorModel cm = new IndexColorModel(8, 16, palette, 0, false, 0, DataBuffer.TYPE_BYTE); + + // width is fixed to 16*8 (128) pixels, easy to get the height + final int imgH = tilesetImage.length / (16 * 8); + // create the BufferedImage + final BufferedImage exportImage = ImageUtil.createIndexedImage(16 * 8, imgH, cm, tilesetImage); + + // save it + ImageUtil.save(exportImage, "png", FileUtil.setExtension(imgFile, "-tileset-export.png")); + } + + return result; } // tiles @@ -76,7 +106,7 @@ public Tileset(List tilesets) offset += 8; } - // build BIN (tiles data) - no stored as this is a temporary tileset + // build BIN (tiles data) - not stored as this is a temporary tileset bin = new Bin(id + "_data", data, Compression.NONE); // compute hash code @@ -110,15 +140,15 @@ public Tileset(String id, boolean blankTile) isDuplicate = false; final int[] data; - + if (blankTile) { // just add a blank tile add(new Tile(new int[8], 8, 0, false, 0)); - + // build the binary bloc data = new int[tiles.size() * 8]; - + int offset = 0; for (Tile t : tiles) { @@ -126,7 +156,8 @@ public Tileset(String id, boolean blankTile) offset += 8; } } - else data = new int[0]; + else + data = new int[0]; // build BIN (tiles data) resource (temporary tileset so don't add as internal resource) bin = new Bin(id + "_data", data, Compression.NONE); @@ -136,7 +167,7 @@ public Tileset(String id, boolean blankTile) } public Tileset(String id, byte[] image8bpp, int imageWidth, int imageHeight, int startTileX, int startTileY, int widthTile, int heightTile, - TileOptimization opt, Compression compression, boolean addBlank, boolean temp) + TileOptimization opt, Compression compression, boolean addBlank, boolean temp, TileOrdering order) { super(id); @@ -146,22 +177,45 @@ public Tileset(String id, byte[] image8bpp, int imageWidth, int imageHeight, int tileIndexesMap = new HashMap<>(); tileByHashcodeMap = new HashMap<>(); - // important to always use the same loop order when building Tileset and Tilemap/Map object - for (int j = 0; j < heightTile; j++) + // important to always use the **same loop order** when building Tileset and Tilemap/Map object + if (order == TileOrdering.ROW) + { + for (int j = 0; j < heightTile; j++) + { + for (int i = 0; i < widthTile; i++) + { + // get tile + final Tile tile = Tile.getTile(image8bpp, imageWidth, imageHeight, (i + startTileX) * 8, (j + startTileY) * 8, 8); + // find if tile already exist + final int index = getTileIndex(tile, opt); + + // blank tile + hasBlank |= tile.isBlank(); + + // not found --> add it + if (index == -1) + add(tile); + } + } + } + else { for (int i = 0; i < widthTile; i++) { - // get tile - final Tile tile = Tile.getTile(image8bpp, imageWidth, imageHeight, (i + startTileX) * 8, (j + startTileY) * 8, 8); - // find if tile already exist - final int index = getTileIndex(tile, opt); + for (int j = 0; j < heightTile; j++) + { + // get tile + final Tile tile = Tile.getTile(image8bpp, imageWidth, imageHeight, (i + startTileX) * 8, (j + startTileY) * 8, 8); + // find if tile already exist + final int index = getTileIndex(tile, opt); - // blank tile - hasBlank |= tile.isBlank(); + // blank tile + hasBlank |= tile.isBlank(); - // not found --> add it - if (index == -1) - add(tile); + // not found --> add it + if (index == -1) + add(tile); + } } } @@ -344,6 +398,32 @@ public int getTileIndex(Tile tile, TileOptimization opt) return -1; } + public byte[] getTilesetImage() + { + final int w = 16; + final int h = (tiles.size() + 15) / w; + final int imgW = w * 8; + final int imgH = h * 8; + final byte[] tilesetImage = new byte[imgW * imgH]; + + int ind = 0; + for (int y = 0; y < imgH; y += 8) + { + for (int x = 0; x < imgW; x += 8) + { + final Tile tile = tiles.get(ind); + final byte[] imageTile = ImageUtil.convertTo8bpp(ArrayUtil.intToByte(tile.data), 4); + // then copy tile + Tile.copyTile(tilesetImage, imgW, imageTile, x, y, 8); + // next + if (++ind >= tiles.size()) + return tilesetImage; + } + } + + return tilesetImage; + } + @Override public int internalHashCode() { diff --git a/tools/rescomp/src/sgdk/rescomp/tool/Util.java b/tools/rescomp/src/sgdk/rescomp/tool/Util.java index cdf8606f..9f8f4bbb 100644 --- a/tools/rescomp/src/sgdk/rescomp/tool/Util.java +++ b/tools/rescomp/src/sgdk/rescomp/tool/Util.java @@ -14,6 +14,7 @@ import sgdk.rescomp.type.Basics.PackedData; import sgdk.rescomp.type.Basics.SoundDriver; import sgdk.rescomp.type.Basics.TileOptimization; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.rescomp.type.SpriteCell.OptimizationLevel; import sgdk.rescomp.type.SpriteCell.OptimizationType; import sgdk.tool.FileUtil; @@ -153,6 +154,18 @@ public static OptimizationLevel getSpriteOptLevel(String text) throw new IllegalArgumentException("Unrecognized sprite optimization level: '" + text + "'"); } + + public static TileOrdering getTileOrdering(String text) + { + final String upText = text.toUpperCase(); + + if (StringUtil.equals(upText, "COLUMN")) + return TileOrdering.COLUMN; + if (StringUtil.equals(upText, "ROW")) + return TileOrdering.ROW; + + throw new IllegalArgumentException("Unrecognized tile ordering: '" + text + "'"); + } public static Color getColor(String string) { diff --git a/tools/rescomp/src/sgdk/rescomp/type/Basics.java b/tools/rescomp/src/sgdk/rescomp/type/Basics.java index bae00587..bfe1aa1d 100644 --- a/tools/rescomp/src/sgdk/rescomp/type/Basics.java +++ b/tools/rescomp/src/sgdk/rescomp/type/Basics.java @@ -19,6 +19,11 @@ public static enum TileOptimization NONE, ALL, DUPLICATE_ONLY } + public static enum TileOrdering + { + ROW, COLUMN + }; + public static enum TileEquality { NONE, EQUAL, VFLIP(false, true), HFLIP(true, false), HVFLIP(true, true); diff --git a/tools/rescomp/src/sgdk/rescomp/type/TMX.java b/tools/rescomp/src/sgdk/rescomp/type/TMX.java index a595da15..7a95072f 100644 --- a/tools/rescomp/src/sgdk/rescomp/type/TMX.java +++ b/tools/rescomp/src/sgdk/rescomp/type/TMX.java @@ -20,6 +20,7 @@ import sgdk.rescomp.resource.Tileset; import sgdk.rescomp.tool.Util; import sgdk.rescomp.type.Basics.Compression; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.rescomp.type.SFieldDef.SGDKObjectType; import sgdk.rescomp.type.TFieldDef.TiledObjectType; import sgdk.rescomp.type.TSX.TSXTileset; @@ -441,9 +442,9 @@ private TSXTileset getTSXTilesetFor(int tileInd) return null; } - public List getTilesets(String baseId, Compression compression, boolean temp) throws Exception + public List getTilesets(String baseId, Compression compression, boolean temp, TileOrdering order) throws Exception { - return TSX.getTilesets(usedTilesets, baseId, compression, temp); + return TSX.getTilesets(usedTilesets, baseId, compression, temp, order); } // public Tilemap getTilemap(int mapBase) throws Exception diff --git a/tools/rescomp/src/sgdk/rescomp/type/TSX.java b/tools/rescomp/src/sgdk/rescomp/type/TSX.java index a3d9766c..4031f1fa 100644 --- a/tools/rescomp/src/sgdk/rescomp/type/TSX.java +++ b/tools/rescomp/src/sgdk/rescomp/type/TSX.java @@ -11,6 +11,7 @@ import sgdk.rescomp.tool.Util; import sgdk.rescomp.type.Basics.Compression; import sgdk.rescomp.type.Basics.TileOptimization; +import sgdk.rescomp.type.Basics.TileOrdering; import sgdk.tool.FileUtil; import sgdk.tool.ImageUtil; import sgdk.tool.StringUtil; @@ -123,10 +124,10 @@ public String getTilesetPath() } // return optimized tilset - public Tileset getTileset(String id, Compression compression, boolean temp) throws Exception + public Tileset getTileset(String id, Compression compression, boolean temp, TileOrdering order) throws Exception { // always add a blank tile if not present for TSX tileset (Tiled does not count it) - return Tileset.getTileset(id, getTilesetPath(), compression, TileOptimization.ALL, true, temp); + return Tileset.getTileset(id, getTilesetPath(), compression, TileOptimization.ALL, true, temp, order, false); } public byte[] getTilesetImage8bpp(boolean cropPalette) throws Exception @@ -174,7 +175,7 @@ public int compareTo(TSXTileset t) } } - public static List getTilesets(List tsxTilesets, String baseId, Compression compression, boolean temp) throws Exception + public static List getTilesets(List tsxTilesets, String baseId, Compression compression, boolean temp, TileOrdering order) throws Exception { final List tilesets = new ArrayList<>(); @@ -188,7 +189,7 @@ public static List getTilesets(List tsxTilesets, String bas else { for (TSXTileset tsxTileset : tsxTilesets) - tilesets.add(tsxTileset.getTileset(baseId + "_tileset" + ind++, compression, temp)); + tilesets.add(tsxTileset.getTileset(baseId + "_tileset" + ind++, compression, temp, order)); } return tilesets;