Skip to content

Commit e47418e

Browse files
committed
Implement --input-tileset
As discussed in gbdev#575 (comment)
1 parent c42e856 commit e47418e

File tree

5 files changed

+124
-10
lines changed

5 files changed

+124
-10
lines changed

include/gfx/main.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ struct Options {
2727
EMBEDDED,
2828
} palSpecType = NO_SPEC; // -c
2929
std::vector<std::array<std::optional<Rgba>, 4>> palSpec{};
30-
uint8_t bitDepth = 2; // -d
30+
uint8_t bitDepth = 2; // -d
31+
std::string inputTileset{}; // -i
3132
struct {
3233
uint16_t left;
3334
uint16_t top;

man/rgbgfx.1

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
.Op Fl b Ar base_ids
1717
.Op Fl c Ar pal_spec
1818
.Op Fl d Ar depth
19+
.Op Fl i Ar input_tiles
1920
.Op Fl L Ar slice
2021
.Op Fl N Ar nb_tiles
2122
.Op Fl n Ar nb_pals
@@ -164,6 +165,37 @@ for a list of formats and their descriptions.
164165
.It Fl d Ar depth , Fl \-depth Ar depth
165166
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
166167
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
168+
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
169+
Read tiles that will be used to convert this image.
170+
.Ar input_tiles
171+
must contain tile data in
172+
.Dq raw
173+
format, as generated through the
174+
.Fl o
175+
option.
176+
.Pp
177+
If used together with
178+
.Fl o ,
179+
then the image can contain tiles not in
180+
.Ar input_tiles .
181+
Otherwise, the image must be able to be generated using
182+
.Em only
183+
the tiles from
184+
.Ar input_tiles ,
185+
and thus generate different tile data.
186+
The former is more useful if you want several images to share a given set of tiles, such as different levels sharing a single tileset; the latter, if you want to control more precisely the numeric IDs of specific tiles.
187+
.Pp
188+
If more than one color palette is in use, it is also
189+
.Sy strongly
190+
advised to dump it together with the tile data, and to pass it using
191+
.Fl c Cm gbc: Ns Ar input_palette .
192+
This is because
193+
.Nm
194+
may not pack the palettes the same way that it did when generating
195+
.Ar input_tiles .
196+
See
197+
.Sx EXAMPLES
198+
for an example of how to use this.
167199
.It Fl L Ar slice , Fl \-slice Ar slice
168200
Only process a given rectangle of the image.
169201
This is useful for example if the input image is a sheet of some sort, and you want to convert each cel individually.
@@ -637,7 +669,13 @@ without needing an input image.
637669
.Pp
638670
.Dl $ rgbgfx -c '#fff,#ff0,#f80,#000' -p colors.pal
639671
.Pp
640-
TODO: more examples.
672+
The following will convert two levels using the same tileset, and error out of any of the level images contain tiles not in the tileset.
673+
.Pp
674+
.Bd -literal -offset Ds
675+
$ rgbgfx tileset.png -o tileset.2bpp -O -P
676+
$ rgbgfx -i tileset.2bpp -c gbc:tileset.pal level1.png -t level1.tilemap -a level1.attrmap
677+
$ rgbgfx -i tileset.2bpp -c gbc:tileset.pal level2.png -t level2.tilemap -a level2.attrmap
678+
.Ed
641679
.Sh BUGS
642680
Please report bugs and mistakes in this man page on
643681
.Lk https://github.com/gbdev/rgbds/issues GitHub .

src/gfx/main.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,11 @@ static char *parseArgv(int argc, char *argv[]) {
425425
options.bitDepth = 2;
426426
}
427427
break;
428+
case 'i':
429+
if (!options.inputTileset.empty())
430+
warning("Overriding input tileset file %s", options.inputTileset.c_str());
431+
options.inputTileset = musl_optarg;
432+
break;
428433
case 'L':
429434
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate");
430435
if (options.inputSlice.left > INT16_MAX) {

src/gfx/process.cpp

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
692692
if (!options.palettes.empty()) {
693693
File output;
694694
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
695-
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
695+
fatal("Failed to create \"%s\": %s", output.c_str(options.palettes), strerror(errno));
696696
}
697697

698698
for (Palette const &palette : palettes) {
@@ -736,6 +736,20 @@ class TileData {
736736
return row;
737737
}
738738

739+
TileData(std::array<uint8_t, 16> &&raw) : _data(raw), _hash(0) {
740+
for (uint8_t y = 0; y < 8; ++y) {
741+
uint16_t bitplanes = _data[y * 2] | _data[y * 2 + 1] << 8;
742+
743+
_hash ^= bitplanes;
744+
if (options.allowMirroring) {
745+
// Count the line itself as mirrorred; vertical mirroring is
746+
// already taken care of because the symmetric line will be XOR'd
747+
// the same way. (...which is a problem, but probably benign.)
748+
_hash ^= flipTable[bitplanes >> 8] << 8 | flipTable[bitplanes & 0xFF];
749+
}
750+
}
751+
}
752+
739753
TileData(Png::TilesVisitor::Tile const &tile, Palette const &palette) : _hash(0) {
740754
size_t writeIndex = 0;
741755
for (uint32_t y = 0; y < 8; ++y) {
@@ -826,7 +840,7 @@ static void outputTileData(
826840
) {
827841
File output;
828842
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
829-
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
843+
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
830844
}
831845

832846
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
@@ -865,7 +879,7 @@ static void outputMaps(
865879
if (!path.empty()) {
866880
file.emplace();
867881
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
868-
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
882+
fatal("Failed to create \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
869883
}
870884
}
871885
};
@@ -913,12 +927,10 @@ struct UniqueTiles {
913927
/*
914928
* Adds a tile to the collection, and returns its ID
915929
*/
916-
std::tuple<uint16_t, TileData::MatchType>
917-
addTile(Png::TilesVisitor::Tile const &tile, Palette const &palette) {
918-
TileData newTile(tile, palette);
930+
std::tuple<uint16_t, TileData::MatchType> addTile(TileData newTile) {
919931
auto [tileData, inserted] = tileset.insert(newTile);
920932

921-
TileData::MatchType matchType = TileData::EXACT;
933+
TileData::MatchType matchType = TileData::NOPE;
922934
if (inserted) {
923935
// Give the new tile the next available unique ID
924936
tileData->tileID = static_cast<uint16_t>(tiles.size());
@@ -953,8 +965,56 @@ static UniqueTiles dedupTiles(
953965
// by caching the full tile data anyway, so we might as well.)
954966
UniqueTiles tiles;
955967

968+
if (!options.inputTileset.empty()) {
969+
File inputTileset;
970+
if (!inputTileset.open(options.inputTileset, std::ios::in | std::ios::binary)) {
971+
fatal("Failed to open \"%s\": %s", options.inputTileset.c_str(), strerror(errno));
972+
}
973+
974+
std::array<uint8_t, 16> tile;
975+
size_t const tileSize = options.bitDepth * 8;
976+
for (;;) {
977+
// It's okay to cast between character types.
978+
size_t len = inputTileset->sgetn(reinterpret_cast<char *>(tile.data()), tileSize);
979+
if (len == 0) { // EOF!
980+
break;
981+
} else if (len != tileSize) {
982+
fatal(
983+
"\"%s\" does not contain a multiple of %zu bytes; is it actually tile data?",
984+
options.inputTileset.c_str(),
985+
tileSize
986+
);
987+
} else if (len == 8) {
988+
// Expand the tile data to 2bpp.
989+
for (size_t i = 8; i--;) {
990+
tile[i * 2 + 1] = 0;
991+
tile[i * 2] = tile[i];
992+
}
993+
}
994+
995+
auto [tileID, matchType] = tiles.addTile(std::move(tile));
996+
switch (matchType) {
997+
case TileData::NOPE:
998+
break;
999+
case TileData::HFLIP:
1000+
case TileData::VFLIP:
1001+
case TileData::VHFLIP:
1002+
if (!options.allowMirroring) {
1003+
break;
1004+
}
1005+
[[fallthrough]];
1006+
case TileData::EXACT:
1007+
error("The input tileset contains tiles that were deduplicated; please check that your deduplication flags (`-u`, `-m`) are consistent with what was used to generate the input tileset");
1008+
}
1009+
}
1010+
}
1011+
9561012
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
957-
auto [tileID, matchType] = tiles.addTile(tile, palettes[mappings[attr.protoPaletteID]]);
1013+
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
1014+
1015+
if (matchType == TileData::NOPE && options.output.empty()) {
1016+
error("Tile at (%" PRIu32 ", %" PRIu32 ") is not within the input tileset, and `-o` was not given!", tile.x, tile.y);
1017+
}
9581018

9591019
attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP;
9601020
attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP;
@@ -1176,6 +1236,12 @@ continue_visiting_tiles:;
11761236
);
11771237
}
11781238

1239+
// I currently cannot figure out useful semantics for this combination of flags.
1240+
if (!options.inputTileset.empty()) {
1241+
fatal("Input tilesets are not supported without `-u`\nPlease consider explaining your "
1242+
"use case to RGBDS' developers!");
1243+
}
1244+
11791245
if (!options.output.empty()) {
11801246
options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n");
11811247
unoptimized::outputTileData(png, attrmap, palettes, mappings);

src/gfx/reverse.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ void reverse() {
136136
);
137137
}
138138

139+
if (!options.inputTileset.empty()) {
140+
// TODO: check that the tile data is contained within the tileset
141+
}
142+
139143
// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
140144
size_t const nbTiles = tiles.size() / tileSize;
141145
options.verbosePrint(Options::VERB_INTERM, "Read %zu tiles.\n", nbTiles);

0 commit comments

Comments
 (0)