Porytiles
Loading...
Searching...
No Matches
primary_tileset_decompiler.cpp
Go to the documentation of this file.
2
3#include <array>
4#include <format>
5#include <iostream>
6#include <memory>
7#include <ranges>
8#include <set>
9#include <vector>
10
26
27namespace porytiles {
29{
30 // Unwrap config values
31 PT_UNWRAP_TILESET_CONFIG_PTR(config_, extrinsic_transparency, tileset.name(), std::unique_ptr<Tileset>);
32 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_pals_in_primary, tileset.name(), std::unique_ptr<Tileset>);
33 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_pals_total, tileset.name(), std::unique_ptr<Tileset>);
34 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_metatiles_in_primary, tileset.name(), std::unique_ptr<Tileset>);
35 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_tiles_in_primary, tileset.name(), std::unique_ptr<Tileset>);
36 PT_UNWRAP_TILESET_CONFIG_PTR(config_, num_tiles_per_metatile, tileset.name(), std::unique_ptr<Tileset>);
37
38 LayerModeConverter layer_mode_converter{format_, diag_, tile_printer_, extrinsic_transparency};
39 MetatileDecompiler metatile_decompiler{format_, diag_, tile_printer_, extrinsic_transparency};
40
41 // Decompile Porymap tilemap entries
43 tilemap_entries,
44 layer_mode_converter.triple_layerize(tileset.porymap_component()),
45 std::unique_ptr<Tileset>,
46 std::format("Failed to triple-layerize Porymap component for tileset '{}'.", tileset.name()));
47
48 /*
49 * Create the new Porymap component early so we can pass it for potential backporting during animation
50 * decompilation. If mangle strategy is used, duplicate key frame tiles will be modified and backported to
51 * tiles.png.
52 *
53 * IMPORTANT: This must happen BEFORE metatile decompilation so that mangled tiles are used when decompiling
54 * metatiles.
55 */
56 auto new_porymap_component = std::make_unique<PorymapTilesetComponent>(tileset.porymap_component());
57
58 auto new_porytiles_component = std::make_unique<PorytilesTilesetComponent>();
59 std::size_t i = 0;
60 for (const auto &pal : tileset.porytiles_component().pals()) {
61 // Copy over Porytiles pals
62 if (pal.has_value()) {
63 new_porytiles_component->set_pal(i, pal.value());
64 }
65 i++;
66 }
67
68 // Copy metatile attributes from Porymap component to Porytiles component
69 const auto &porymap_attributes = tileset.porymap_component().metatile_attributes_bin();
70 for (std::size_t metatile_id = 0; metatile_id < porymap_attributes.size(); metatile_id++) {
71 new_porytiles_component->insert_attribute(metatile_id, porymap_attributes[metatile_id]);
72 }
73
74 /*
75 * Decompile animations from Porymap component to Porytiles component.
76 *
77 * IMPORTANT: This must happen BEFORE metatile decompilation so that any mangled key frame tiles are present in
78 * new_porymap_component->tiles_png() when metatiles are decompiled
79 */
80 // Triple-layerize the metatiles for the AnimDecompiler (it uses triple-layer addressing for override extraction)
81 new_porymap_component->metatiles_bin(tilemap_entries);
82
83 if (const auto &porymap_animations = tileset.porymap_component().anims(); !porymap_animations.empty()) {
84 // Accumulate canonical forms of previously-processed animations' key frame tiles for inter-animation detection
85 std::set<PixelTile<IndexPixel>> inter_anim_canonical_tiles;
86
87 AnimDecompiler anim_decompiler{config_, diag_, tile_printer_, pal_printer_};
88
89 for (const auto &index_pixel_anim : porymap_animations | std::views::values) {
90 // Run animation decompilation
92 rgba_anim,
93 anim_decompiler.decompile_animation(
94 tileset.name(), index_pixel_anim, inter_anim_canonical_tiles, *new_porymap_component),
95 std::unique_ptr<Tileset>,
96 diag_->formatter().format(
97 "Failed to decompile animation '{}'.", FormatParam{index_pixel_anim.name(), Style::bold}));
98
99 /*
100 * After successful decompilation, extract canonical tiles from this animation's (potentially mangled) key
101 * frame range in tiles.png and add them to the accumulator for inter-animation duplicate detection.
102 */
103 const std::size_t anim_tile_offset = index_pixel_anim.params().tile_offset();
104 if (anim_tile_offset == 0) {
105 // Unreachable.
106 panic("anim '" + index_pixel_anim.name() + "' offset is 0");
107 }
108 const std::size_t anim_tile_count = index_pixel_anim.params().tile_count();
109 const auto anim_tiles =
110 extract_tiles_from_image(new_porymap_component->tiles_png(), anim_tile_offset, anim_tile_count);
111 for (const auto &t : anim_tiles) {
113 const PixelTile<IndexPixel> &base = canonical;
114 inter_anim_canonical_tiles.insert(base);
115 }
116
117 new_porytiles_component->add_anim(std::move(rgba_anim));
118 }
119
120 // Warn about per anim overrides that reference non-existent animations (after all animations processed)
121 PT_UNWRAP_TILESET_CONFIG_PTR(config_, per_anim_overrides, tileset.name(), std::unique_ptr<Tileset>);
122 for (const auto &key : per_anim_overrides.value() | std::views::keys) {
123 if (!porymap_animations.contains(key)) {
124 diag_->warning(
125 "unused-animation-config",
126 {diag_->formatter().format(
127 "Animation config for '{}' does not match any animation in tileset '{}'.",
129 FormatParam{tileset.name(), Style::bold})});
130 }
131 }
132 }
133
134 // Restore original metatiles_bin (line 81 set triple-layer format for AnimDecompiler, but output must match input)
135 new_porymap_component->metatiles_bin(tileset.porymap_component().metatiles_bin());
136
137 // Decompile metatiles AFTER animation processing so that any mangled key frame tiles are used
139 metatiles,
140 metatile_decompiler.decompile_metatiles(
141 tilemap_entries, new_porymap_component->tiles_png(), tileset.porymap_component().pals()),
142 std::unique_ptr<Tileset>,
143 std::format("Failed to decompile Porymap component for tileset '{}'.", tileset.name()));
144
145 // Convert metatiles into three layer images
146 LayerImageMetatileizer<Rgba32> metatileizer{};
147 constexpr std::size_t metatiles_per_row = 8; // Standard width for layer images (128 pixels)
148
150 layer_images,
151 metatileizer.demetatileize(metatiles, metatiles_per_row),
152 std::unique_ptr<Tileset>,
153 format_->format(
154 "Failed to demetatileize metatiles for tileset '{}'.", FormatParam{tileset.name(), Style::bold}));
155
156 auto &[bottom_image, middle_image, top_image] = layer_images;
157
158 // Set each porytiles_component layer to the new image
159 new_porytiles_component->bottom(bottom_image);
160 new_porytiles_component->middle(middle_image);
161 new_porytiles_component->top(top_image);
162
163 auto new_tileset =
164 std::make_unique<Tileset>(tileset.name(), std::move(new_porytiles_component), std::move(new_porymap_component));
165
166 return new_tileset;
167}
168
169} // namespace porytiles
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
Decompiles indexed animation tiles to RGBA format.
A PixelTile representation that stores the canonical (lexicographically minimal) orientation among al...
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A text parameter with associated styling for formatted output.
Service for converting layer images into collections of metatiles.
An 8x8 tile backed by literal-array-based per-pixel storage of an arbitrary pixel type.
ChainableResult< std::unique_ptr< Tileset > > decompile(const Tileset &tileset) const
static const Style bold
Bold text formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) const
Formats a string with styled parameters using fmtlib syntax.
A complete tileset containing both Porytiles and Porymap components.
Definition tileset.hpp:14
const TextFormatter & formatter() const
virtual void warning(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged warning message.
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.cpp:43
std::vector< PixelTile< PixelType > > extract_tiles_from_image(const Image< PixelType > &img, std::size_t tile_offset, std::size_t tile_count, std::size_t tiles_per_row=16)
Extracts a subset of 8x8 tiles from a tileset image at a specific offset.
@ canonical
Export tiles in canonical form without applying flip transformations.
@ tileset
Configuration scoped to a specific tileset.
#define PT_UNWRAP_TILESET_CONFIG_PTR(ptr, config, tileset_name, return_type)
Unwraps a tileset-scoped config value via pointer access, returning early if the value is not availab...