Skip to content

Commit 678cf9d

Browse files
committed
Implement --input-tileset
As discussed in gbdev#575 (comment)
1 parent 1283b0b commit 678cf9d

File tree

7 files changed

+126
-10
lines changed

7 files changed

+126
-10
lines changed

contrib/bash_compl/_rgbgfx.bash

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ _rgbgfx_completions() {
2121
[b]="base-tiles:unk"
2222
[c]="colors:unk"
2323
[d]="depth:unk"
24+
[o]="input-tileset:glob-*.2bpp"
2425
[L]="slice:unk"
2526
[N]="nb-tiles:unk"
2627
[n]="nb-palettes:unk"

contrib/zsh_compl/_rgbgfx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ local args=(
3030
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
3131
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
3232
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
33+
'(-i --input-tileset)'{-i,--input-tileset}'+[Use specific tiles]:tileset file:_files -g "*.2bpp"'
3334
'(-L --slice)'{-L,--slice}'+[Only process a portion of the image]:input slice:'
3435
'(-N --nb-tiles)'{-N,--nb-tiles}'+[Limit number of tiles]:tile count:'
3536
'(-n --nb-palettes)'{-n,--nb-palettes}'+[Limit number of palettes]:palette count:'

include/gfx/main.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ struct Options {
2828
EMBEDDED,
2929
} palSpecType = NO_SPEC; // -c
3030
std::vector<std::array<std::optional<Rgba>, 4>> palSpec{};
31-
uint8_t bitDepth = 2; // -d
31+
uint8_t bitDepth = 2; // -d
32+
std::string inputTileset{}; // -i
3233
struct {
3334
uint16_t left;
3435
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, and that will always be given the first IDs.
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
@@ -427,6 +427,11 @@ static char *parseArgv(int argc, char *argv[]) {
427427
options.bitDepth = 2;
428428
}
429429
break;
430+
case 'i':
431+
if (!options.inputTileset.empty())
432+
warning("Overriding input tileset file %s", options.inputTileset.c_str());
433+
options.inputTileset = musl_optarg;
434+
break;
430435
case 'L':
431436
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate");
432437
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) {
@@ -836,7 +850,7 @@ static void outputTileData(
836850
) {
837851
File output;
838852
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
839-
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
853+
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
840854
}
841855

842856
uint16_t widthTiles = options.inputSlice.width ? options.inputSlice.width : png.getWidth() / 8;
@@ -875,7 +889,7 @@ static void outputMaps(
875889
if (!path.empty()) {
876890
file.emplace();
877891
if (!file->open(path, std::ios_base::out | std::ios_base::binary)) {
878-
fatal("Failed to open \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
892+
fatal("Failed to create \"%s\": %s", file->c_str(options.tilemap), strerror(errno));
879893
}
880894
}
881895
};
@@ -923,12 +937,10 @@ struct UniqueTiles {
923937
/*
924938
* Adds a tile to the collection, and returns its ID
925939
*/
926-
std::tuple<uint16_t, TileData::MatchType>
927-
addTile(Png::TilesVisitor::Tile const &tile, Palette const &palette) {
928-
TileData newTile(tile, palette);
940+
std::tuple<uint16_t, TileData::MatchType> addTile(TileData newTile) {
929941
auto [tileData, inserted] = tileset.insert(newTile);
930942

931-
TileData::MatchType matchType = TileData::EXACT;
943+
TileData::MatchType matchType = TileData::NOPE;
932944
if (inserted) {
933945
// Give the new tile the next available unique ID
934946
tileData->tileID = static_cast<uint16_t>(tiles.size());
@@ -963,8 +975,56 @@ static UniqueTiles dedupTiles(
963975
// by caching the full tile data anyway, so we might as well.)
964976
UniqueTiles tiles;
965977

978+
if (!options.inputTileset.empty()) {
979+
File inputTileset;
980+
if (!inputTileset.open(options.inputTileset, std::ios::in | std::ios::binary)) {
981+
fatal("Failed to open \"%s\": %s", options.inputTileset.c_str(), strerror(errno));
982+
}
983+
984+
std::array<uint8_t, 16> tile;
985+
size_t const tileSize = options.bitDepth * 8;
986+
for (;;) {
987+
// It's okay to cast between character types.
988+
size_t len = inputTileset->sgetn(reinterpret_cast<char *>(tile.data()), tileSize);
989+
if (len == 0) { // EOF!
990+
break;
991+
} else if (len != tileSize) {
992+
fatal(
993+
"\"%s\" does not contain a multiple of %zu bytes; is it actually tile data?",
994+
options.inputTileset.c_str(),
995+
tileSize
996+
);
997+
} else if (len == 8) {
998+
// Expand the tile data to 2bpp.
999+
for (size_t i = 8; i--;) {
1000+
tile[i * 2 + 1] = 0;
1001+
tile[i * 2] = tile[i];
1002+
}
1003+
}
1004+
1005+
auto [tileID, matchType] = tiles.addTile(std::move(tile));
1006+
switch (matchType) {
1007+
case TileData::NOPE:
1008+
break;
1009+
case TileData::HFLIP:
1010+
case TileData::VFLIP:
1011+
case TileData::VHFLIP:
1012+
if (!options.allowMirroring) {
1013+
break;
1014+
}
1015+
[[fallthrough]];
1016+
case TileData::EXACT:
1017+
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");
1018+
}
1019+
}
1020+
}
1021+
9661022
for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
967-
auto [tileID, matchType] = tiles.addTile(tile, palettes[mappings[attr.protoPaletteID]]);
1023+
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});
1024+
1025+
if (matchType == TileData::NOPE && options.output.empty()) {
1026+
error("Tile at (%" PRIu32 ", %" PRIu32 ") is not within the input tileset, and `-o` was not given!", tile.x, tile.y);
1027+
}
9681028

9691029
attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP;
9701030
attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP;
@@ -1186,6 +1246,12 @@ continue_visiting_tiles:;
11861246
);
11871247
}
11881248

1249+
// I currently cannot figure out useful semantics for this combination of flags.
1250+
if (!options.inputTileset.empty()) {
1251+
fatal("Input tilesets are not supported without `-u`\nPlease consider explaining your "
1252+
"use case to RGBDS' developers!");
1253+
}
1254+
11891255
if (!options.output.empty()) {
11901256
options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n");
11911257
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)