Skip to content

Commit

Permalink
Implement --input-tileset
Browse files Browse the repository at this point in the history
As discussed in gbdev#575 (comment)
  • Loading branch information
ISSOtm committed Sep 3, 2024
1 parent 1283b0b commit 678cf9d
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 10 deletions.
1 change: 1 addition & 0 deletions contrib/bash_compl/_rgbgfx.bash
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ _rgbgfx_completions() {
[b]="base-tiles:unk"
[c]="colors:unk"
[d]="depth:unk"
[o]="input-tileset:glob-*.2bpp"
[L]="slice:unk"
[N]="nb-tiles:unk"
[n]="nb-palettes:unk"
Expand Down
1 change: 1 addition & 0 deletions contrib/zsh_compl/_rgbgfx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ local args=(
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
'(-i --input-tileset)'{-i,--input-tileset}'+[Use specific tiles]:tileset file:_files -g "*.2bpp"'
'(-L --slice)'{-L,--slice}'+[Only process a portion of the image]:input slice:'
'(-N --nb-tiles)'{-N,--nb-tiles}'+[Limit number of tiles]:tile count:'
'(-n --nb-palettes)'{-n,--nb-palettes}'+[Limit number of palettes]:palette count:'
Expand Down
3 changes: 2 additions & 1 deletion include/gfx/main.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ struct Options {
EMBEDDED,
} palSpecType = NO_SPEC; // -c
std::vector<std::array<std::optional<Rgba>, 4>> palSpec{};
uint8_t bitDepth = 2; // -d
uint8_t bitDepth = 2; // -d
std::string inputTileset{}; // -i
struct {
uint16_t left;
uint16_t top;
Expand Down
40 changes: 39 additions & 1 deletion man/rgbgfx.1
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
.Op Fl b Ar base_ids
.Op Fl c Ar pal_spec
.Op Fl d Ar depth
.Op Fl i Ar input_tiles
.Op Fl L Ar slice
.Op Fl N Ar nb_tiles
.Op Fl n Ar nb_pals
Expand Down Expand Up @@ -164,6 +165,37 @@ for a list of formats and their descriptions.
.It Fl d Ar depth , Fl \-depth Ar depth
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
.It Fl i Ar input_tiles , Fl \-input-tileset Ar input_tiles
Read tiles that will be used to convert this image, and that will always be given the first IDs.
.Ar input_tiles
must contain tile data in
.Dq raw
format, as generated through the
.Fl o
option.
.Pp
If used together with
.Fl o ,
then the image can contain tiles not in
.Ar input_tiles .
Otherwise, the image must be able to be generated using
.Em only
the tiles from
.Ar input_tiles ,
and thus generate different tile data.
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.
.Pp
If more than one color palette is in use, it is also
.Sy strongly
advised to dump it together with the tile data, and to pass it using
.Fl c Cm gbc: Ns Ar input_palette .
This is because
.Nm
may not pack the palettes the same way that it did when generating
.Ar input_tiles .
See
.Sx EXAMPLES
for an example of how to use this.
.It Fl L Ar slice , Fl \-slice Ar slice
Only process a given rectangle of the image.
This is useful for example if the input image is a sheet of some sort, and you want to convert each cel individually.
Expand Down Expand Up @@ -637,7 +669,13 @@ without needing an input image.
.Pp
.Dl $ rgbgfx -c '#fff,#ff0,#f80,#000' -p colors.pal
.Pp
TODO: more examples.
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.
.Pp
.Bd -literal -offset Ds
$ rgbgfx tileset.png -o tileset.2bpp -O -P
$ rgbgfx -i tileset.2bpp -c gbc:tileset.pal level1.png -t level1.tilemap -a level1.attrmap
$ rgbgfx -i tileset.2bpp -c gbc:tileset.pal level2.png -t level2.tilemap -a level2.attrmap
.Ed
.Sh BUGS
Please report bugs and mistakes in this man page on
.Lk https://github.com/gbdev/rgbds/issues GitHub .
Expand Down
5 changes: 5 additions & 0 deletions src/gfx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,11 @@ static char *parseArgv(int argc, char *argv[]) {
options.bitDepth = 2;
}
break;
case 'i':
if (!options.inputTileset.empty())
warning("Overriding input tileset file %s", options.inputTileset.c_str());
options.inputTileset = musl_optarg;
break;
case 'L':
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate");
if (options.inputSlice.left > INT16_MAX) {
Expand Down
82 changes: 74 additions & 8 deletions src/gfx/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ static void outputPalettes(std::vector<Palette> const &palettes) {
if (!options.palettes.empty()) {
File output;
if (!output.open(options.palettes, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", output.c_str(options.palettes), strerror(errno));
fatal("Failed to create \"%s\": %s", output.c_str(options.palettes), strerror(errno));
}

for (Palette const &palette : palettes) {
Expand Down Expand Up @@ -736,6 +736,20 @@ class TileData {
return row;
}

TileData(std::array<uint8_t, 16> &&raw) : _data(raw), _hash(0) {
for (uint8_t y = 0; y < 8; ++y) {
uint16_t bitplanes = _data[y * 2] | _data[y * 2 + 1] << 8;

_hash ^= bitplanes;
if (options.allowMirroring) {
// Count the line itself as mirrorred; vertical mirroring is
// already taken care of because the symmetric line will be XOR'd
// the same way. (...which is a problem, but probably benign.)
_hash ^= flipTable[bitplanes >> 8] << 8 | flipTable[bitplanes & 0xFF];
}
}
}

TileData(Png::TilesVisitor::Tile const &tile, Palette const &palette) : _hash(0) {
size_t writeIndex = 0;
for (uint32_t y = 0; y < 8; ++y) {
Expand Down Expand Up @@ -836,7 +850,7 @@ static void outputTileData(
) {
File output;
if (!output.open(options.output, std::ios_base::out | std::ios_base::binary)) {
fatal("Failed to open \"%s\": %s", output.c_str(options.output), strerror(errno));
fatal("Failed to create \"%s\": %s", output.c_str(options.output), strerror(errno));
}

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

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

if (!options.inputTileset.empty()) {
File inputTileset;
if (!inputTileset.open(options.inputTileset, std::ios::in | std::ios::binary)) {
fatal("Failed to open \"%s\": %s", options.inputTileset.c_str(), strerror(errno));
}

std::array<uint8_t, 16> tile;
size_t const tileSize = options.bitDepth * 8;
for (;;) {
// It's okay to cast between character types.
size_t len = inputTileset->sgetn(reinterpret_cast<char *>(tile.data()), tileSize);
if (len == 0) { // EOF!
break;
} else if (len != tileSize) {
fatal(
"\"%s\" does not contain a multiple of %zu bytes; is it actually tile data?",
options.inputTileset.c_str(),
tileSize
);
} else if (len == 8) {
// Expand the tile data to 2bpp.
for (size_t i = 8; i--;) {
tile[i * 2 + 1] = 0;
tile[i * 2] = tile[i];
}
}

auto [tileID, matchType] = tiles.addTile(std::move(tile));
switch (matchType) {
case TileData::NOPE:
break;
case TileData::HFLIP:
case TileData::VFLIP:
case TileData::VHFLIP:
if (!options.allowMirroring) {
break;
}
[[fallthrough]];
case TileData::EXACT:
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");
}
}
}

for (auto [tile, attr] : zip(png.visitAsTiles(), attrmap)) {
auto [tileID, matchType] = tiles.addTile(tile, palettes[mappings[attr.protoPaletteID]]);
auto [tileID, matchType] = tiles.addTile({tile, palettes[mappings[attr.protoPaletteID]]});

if (matchType == TileData::NOPE && options.output.empty()) {
error("Tile at (%" PRIu32 ", %" PRIu32 ") is not within the input tileset, and `-o` was not given!", tile.x, tile.y);
}

attr.xFlip = matchType == TileData::HFLIP || matchType == TileData::VHFLIP;
attr.yFlip = matchType == TileData::VFLIP || matchType == TileData::VHFLIP;
Expand Down Expand Up @@ -1186,6 +1246,12 @@ continue_visiting_tiles:;
);
}

// I currently cannot figure out useful semantics for this combination of flags.
if (!options.inputTileset.empty()) {
fatal("Input tilesets are not supported without `-u`\nPlease consider explaining your "
"use case to RGBDS' developers!");
}

if (!options.output.empty()) {
options.verbosePrint(Options::VERB_LOG_ACT, "Generating unoptimized tile data...\n");
unoptimized::outputTileData(png, attrmap, palettes, mappings);
Expand Down
4 changes: 4 additions & 0 deletions src/gfx/reverse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ void reverse() {
);
}

if (!options.inputTileset.empty()) {
// TODO: check that the tile data is contained within the tileset
}

// By default, assume tiles are not deduplicated, and add the (allegedly) trimmed tiles
size_t const nbTiles = tiles.size() / tileSize;
options.verbosePrint(Options::VERB_INTERM, "Read %zu tiles.\n", nbTiles);
Expand Down

0 comments on commit 678cf9d

Please sign in to comment.