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 Aug 10, 2024
1 parent c42e856 commit ad05bd7
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 9 deletions.
3 changes: 2 additions & 1 deletion include/gfx/main.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,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
3 changes: 3 additions & 0 deletions 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,8 @@ 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
TODO
.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
5 changes: 5 additions & 0 deletions src/gfx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,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 @@ -826,7 +840,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 @@ -865,7 +879,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 @@ -913,12 +927,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 @@ -953,8 +965,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 @@ -1176,6 +1236,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 ad05bd7

Please sign in to comment.