Porytiles
Loading...
Searching...
No Matches
primary_tileset_compiler.cpp
Go to the documentation of this file.
2
3#include <array>
4#include <memory>
5#include <ranges>
6#include <vector>
7
27
28#include <unordered_set>
29
30namespace porytiles2 {
31
33{
34 // Initialize all the compilation services
35 LayerImageMetatileizer<Rgba32> metatileizer{};
36 TileValidator validator{format_, diag_, tile_printer_};
37 LayerModeConverter layer_converter{format_, diag_, tile_printer_};
38
39 // Grab configuration values we'll need
40 PT_UNWRAP_SCOPED_CONFIG(config_, extrinsic_transparency, tileset.name(), std::unique_ptr<Tileset>);
41
42 // Convert layer images into vector<RgbaMetatile>
44 metatiles,
45 metatileizer.metatileize(
46 tileset.porytiles_component().bottom(),
47 tileset.porytiles_component().middle(),
48 tileset.porytiles_component().top()),
49 "failed to metatileize input layer images for " + tileset.name(),
50 std::unique_ptr<Tileset>);
51
52 /*
53 * TODO: our first step in any compilation operation should validate the LayerMode. First, we need to check the
54 * num_tiles_per_metatile configuration setting. If it's set to 8, the user is requesting dual-layer compilation. If
55 * it's 12, triple. Any other value will have been caught earlier by config validation. If it's 8, then we need to
56 * check the input metatiles and throw an error for all metatiles that have non-transparent content on all three
57 * layers. While doing this, we can also compute the inferred layer type and save it into a vector for later.
58 *
59 * If it's 12, then we're good, just move on. No need to validate or do anything special for the inferred layer type
60 * vector. Just set it to LayerType::normal and move on.
61 *
62 * Since we'll be overwriting the output tilemap entries and attributes as part of the compilation operation, no
63 * need to validate them via detect_layer_mode at this point. (Let's really think through this. Would we want to
64 * warn the user somewhere if the Porymap component metatiles are corrupt? Obviously in the decompilation operations
65 * this is an error condition.)
66 */
67
68 // TODO: remove, here for testing
70 tileset.porymap_component().detect_layer_mode(), "layer mode detection failed", std::unique_ptr<Tileset>);
71
72 // TODO: remove these, just here to test config/diagnostic stuff
73 PT_UNWRAP_SCOPED_CONFIG(config_, num_tiles_primary, tileset.name(), std::unique_ptr<Tileset>);
74 diag_->note(
75 "config-info",
76 std::vector{
77 format_->format(
78 "{} {}",
79 FormatParam{num_tiles_primary.name() + ":", Style::bold},
80 FormatParam{num_tiles_primary, Style::bold}),
81 format_->format("({})", num_tiles_primary.source()),
82 std::string{"foo"},
83 std::string{"bar"}});
84 diag_->warn("test-warning", std::vector{std::string{"foo"}, std::string{"bar"}, std::string{"baz"}});
85 diag_->warn_note("test-warning", std::vector{std::string{"foo"}, std::string{"bar"}, std::string{"baz"}});
86 diag_->err("test-error", std::vector{std::string{"foo"}, std::string{"bar"}, std::string{"baz"}});
87
88 // Leaf step to throw error if there are too many metatiles.
89 PT_UNWRAP_SCOPED_CONFIG(config_, num_metatiles_primary, tileset.name(), std::unique_ptr<Tileset>);
90 if (metatiles.size() > num_metatiles_primary.value()) {
91 return FormattableError{
92 "too many input metatiles: found '{}' > '{}' (num_metatiles_primary)",
93 FormatParam{metatiles.size(), Style::bold},
94 FormatParam{num_metatiles_primary, Style::bold}};
95 }
96
97 // Decompose vector<RgbaMetatile> into vector<PixelTile<Rgba32>>
98 std::vector<PixelTile<Rgba32>> tiles{};
99 tiles.reserve(metatiles.size() * metatile::tiles_per_metatile);
100 for (const auto &metatile : metatiles) {
101 const auto decomposed = metatile.decompose();
102 for (const auto &pixel_tile : decomposed) {
103 tiles.push_back(pixel_tile);
104 }
105 }
106
107 // Leaf step to throw errors if:
108 // - any tiles contain an invalid alpha value
109 // - any tiles have more than 15+1 colors
110 // - generate precision loss warnings if some colors collapse to the same 5-bit color
111 PT_TRY_CALL_CHAIN_ERR(validator.validate_alpha_channels(tiles), "tile validation error", std::unique_ptr<Tileset>);
113 validator.validate_unique_color_count(tiles, extrinsic_transparency.value()),
114 "tile validation error",
115 std::unique_ptr<Tileset>);
117 validator.generate_precision_loss_warnings(tiles), "tile validation error", std::unique_ptr<Tileset>);
118
119 // Create color index map from vector<RgbaTile>
120 // TODO: impl
121
122 // Throw error if ColorIndexMap has too many unique colors
123
124 // Create PackSets for the bin packing step
125 // const auto &color_index_map = color_index_map_builder.build_map(norm_tiles, rgba_magenta);
126 // ColorSetBuilder color_set_builder{text_formatter_};
127 // PackSetGenerator assignable_tile_generator{&color_set_builder};
128 // std::vector<PackSet> assignable_tiles = assignable_tile_generator.generate(norm_tiles, color_index_map);
129
130 // TODO: set up these components correctly, for now we just use some dummy values
131 auto new_porytiles_component = std::make_unique<PorytilesTilesetComponent>(tileset.porytiles_component());
132
133 // TODO: The resulting PorymapTilesetComponent may be incomplete. E.g., the user may have specified PLA
134 // files; they will be present on disk. We don't want to clobber them when saving the newly compiled
135 // component. So we'll need to pull them from the original component and inject them into this one before
136 // returning. We should probably add PLA file handling to the Tileset repository aggregate root. That way. all
137 // this is handled automatically via the save/load abstraction mechanisms. PLA files are a first-class domain
138 // concept, so they should be handled like any other file type (e.g. pal files, override files, etc). If we do that,
139 // then here, instead of making a new PorymapComponent, we can invoke the copy ctor. And then we should add explicit
140 // "reset" functions for the tilemap entries, tiles.png, pals, etc to clear the old values.
141 auto new_porymap_component = std::make_unique<PorymapTilesetComponent>();
142
143 Image<IndexPixel> tiles_png{128, 128};
144 Palette pal{rgba_red};
145 pal.set(extrinsic_transparency.value(), 0);
146
147 new_porymap_component->tiles_png(tiles_png);
148 for (unsigned int i = 0; i < pal::num_pals; i++) {
149 new_porymap_component->set_pal(pal, i);
150 }
151
152 /*
153 * TODO: here, we need to check if dual-layer output is enabled. If so, check the layer type and skip the empty
154 * layer.
155 */
156 new_porymap_component->push_back_tilemap_entry(TilemapEntry{1, 1, false, false});
157 new_porymap_component->push_back_tilemap_entry(TilemapEntry{1, 1, true, true});
158
159 // TODO: write attributes for real, for now just write back what we read
160 for (const auto &attr : tileset.porymap_component().metatile_attributes_bin()) {
161 new_porymap_component->push_back_attribute(attr);
162 }
163
164 auto new_tileset =
165 std::make_unique<Tileset>(tileset.name(), std::move(new_porytiles_component), std::move(new_porymap_component));
166
167 return new_tileset;
168}
169
172{
173 // Initialize all the compilation services
174 LayerImageMetatileizer<Rgba32> metatileizer{};
175 TileValidator validator{format_, diag_, tile_printer_};
176 LayerModeConverter layer_mode_converter{format_, diag_, tile_printer_};
177 MetatileDecompiler metatile_decompiler{format_, diag_, tile_printer_};
178
179 // Showcase how config validation can work
180 // ConfigValue<std::size_t> test{0, "foo", "bar", std::vector<std::string>{"baz", "bat", "cat", "mat"}};
181 // PT_TRY_CALL_CHAIN_ERR(size_t_val_greater_than_zero(test), "config validation failed", std::unique_ptr<Tileset>);
182
183 // Grab configuration values we'll need
184 PT_UNWRAP_SCOPED_CONFIG(config_, extrinsic_transparency, tileset.name(), std::unique_ptr<Tileset>);
185 PT_UNWRAP_SCOPED_CONFIG(config_, num_pals_total, tileset.name(), std::unique_ptr<Tileset>);
186 PT_UNWRAP_SCOPED_CONFIG(config_, num_pals_primary, tileset.name(), std::unique_ptr<Tileset>);
187 PT_UNWRAP_SCOPED_CONFIG(config_, num_metatiles_primary, tileset.name(), std::unique_ptr<Tileset>);
188
189 // Read Porytiles layer images and decompose into tile vectors
191 porytiles_metatiles,
192 metatileizer.metatileize(
193 tileset.porytiles_component().bottom(),
194 tileset.porytiles_component().middle(),
195 tileset.porytiles_component().top()),
196 "failed to metatileize input layer images for " + tileset.name(),
197 std::unique_ptr<Tileset>);
198 if (porytiles_metatiles.size() > num_metatiles_primary) {
199 // TODO: better error message
200 return FormattableError{"too many input metatiles in Porytiles component"};
201 }
202 std::vector<PixelTile<Rgba32>> porytiles_pixel_rgba = metatile::decompose(porytiles_metatiles);
203 std::vector<CanonicalPixelTile<Rgba32>> porytiles_canonical_pixel_rgba =
204 transform<CanonicalPixelTile<Rgba32>>(porytiles_pixel_rgba);
205
206 // Leaf step to throw errors if:
207 // - any tiles contain an invalid alpha value
208 // - any tiles have more than 15+1 colors
209 // - generate precision loss warnings if some colors collapse to the same 5-bit color
211 validator.validate_alpha_channels(porytiles_pixel_rgba), "tile validation error", std::unique_ptr<Tileset>);
213 validator.validate_unique_color_count(porytiles_pixel_rgba, extrinsic_transparency.value()),
214 "tile validation error",
215 std::unique_ptr<Tileset>);
217 validator.generate_precision_loss_warnings(porytiles_pixel_rgba),
218 "tile validation error",
219 std::unique_ptr<Tileset>);
220
221 // Decompile Porymap tilemap entries and decompose into tile vector
223 tilemap_entries,
224 layer_mode_converter.triple_layerize(tileset.porymap_component()),
225 "failed to triple-layerize Porymap component for tileset " + tileset.name(),
226 std::unique_ptr<Tileset>);
228 porymap_metatiles,
229 metatile_decompiler.decompile_metatiles(
230 tilemap_entries, tileset.porymap_component().tiles_png(), tileset.porymap_component().pals()),
231 "failed to decompile Porymap component for tileset " + tileset.name(),
232 std::unique_ptr<Tileset>);
233 /*
234 * We don't need to check porymap_metatiles size here. We're going to overwrite it anyway. We only need to check the
235 * size of the final tilemap entry vector. Patch builds don't need to preserve tilemap entries since those cannot be
236 * referenced by other tilesets.
237 */
238 std::vector<PixelTile<Rgba32>> porymap_pixel_rgba = metatile::decompose(porymap_metatiles);
239 std::vector<CanonicalPixelTile<Rgba32>> porymap_canonical_pixel_rgba =
240 transform<CanonicalPixelTile<Rgba32>>(porymap_pixel_rgba);
241
242 /*
243 * Create ColorIndexMap from porytiles_tiles. We don't actually need a ColorIndexMap for a pals:fixed patch build.
244 * However, we build one so that we can throw if the user specified too many unique colors in the input. We're
245 * guaranteed to fail again at a later step if this is triggered. But we'll use this opportunity to emit an error
246 * early and then continue.
247 */
248 ColorIndexMap color_index_map{porytiles_pixel_rgba, extrinsic_transparency.value()};
249 std::size_t color_count = color_index_map.size();
250 std::size_t color_count_limit = num_pals_primary.value() * (pal::max_size - 1);
251 if (color_count > color_count_limit) {
252 diag_->err(
253 "color-limit-exceeded",
254 format_->format(
255 "too many unique colors ({}) in Porytiles component for tileset '{}'",
256 FormatParam{color_count, Style::bold},
257 FormatParam{tileset.name(), Style::bold}));
258
259 std::vector<std::string> note_text;
260 note_text.push_back(format_->format(
261 "unique color count limit is '{}' due to configuration", FormatParam{color_count_limit, Style::bold}));
262 note_text.push_back(format_->format(
263 "{} = {}",
264 FormatParam{num_pals_primary.name(), Style::bold},
265 FormatParam{num_pals_primary.value(), Style::bold}));
266 note_text.push_back(format_->format("Source: {}", num_pals_primary.source()));
267 // Add source details if available
268 if (!num_pals_primary.source_details().empty()) {
269 note_text.emplace_back("");
270 std::ranges::copy(num_pals_primary.source_details(), std::back_inserter(note_text));
271 }
272 note_text.emplace_back("");
273 note_text.push_back(format_->format(
274 "Color limit definition: {} * {}: {} * {}: {}",
275 FormatParam{num_pals_primary.name(), Style::bold},
276 FormatParam{"nontransparent_colors_per_pal", Style::bold},
277 FormatParam{num_pals_primary.value(), Style::bold},
278 FormatParam{(pal::max_size - 1), Style::bold},
279 FormatParam{color_count_limit, Style::bold}));
280
281 diag_->note("color-limit-exceeded", note_text);
282 }
283
284 // Create canonical ShapeTile vectors from porytiles input
285 // We don't actually need this for tiles:fixed pals:fixed builds.
286 // But if we were going to do pal assignment, we'd need std::vector<CanonicalShapeTile<ColorIndex>>.
287 // If pals weren't fixed, here we'd want to do bin packing to get new colors into the pals with the Porymap pals
288 // used as overrides in the packing process.
289 //
290 // std::vector<CanonicalShapeTile<ColorIndex>> porytiles_canonical_color_index_shapes =
291 // transform(porytiles_pixel_rgba, [&color_index_map, &extrinsic_transparency](const PixelTile<Rgba32> &tile) {
292 // return CanonicalShapeTile{from_pixel_tile(tile, color_index_map, extrinsic_transparency.value())};
293 // });
294 // std::vector<CanonicalShapeTile<Rgba32>> porytiles_canonical_rgba_shapes = transform(
295 // porytiles_canonical_color_index_shapes, [&color_index_map](const CanonicalShapeTile<ColorIndex> &tile) {
296 // return CanonicalShapeTile{shape_tile_to_pixel_colors(tile, color_index_map)};
297 // });
298
299 // TODO: Copy in the Porymap pals then normalize transparency
300 std::vector<unsigned int> pal_indexes;
301 std::vector<Palette<Rgba32>> porymap_pals{};
302 porymap_pals.reserve(num_pals_primary.value());
303 for (unsigned int i = 0; i < num_pals_primary.value(); i++) {
304 porymap_pals.push_back(tileset.porymap_component().pals()[i]);
305 }
306
307 panic("TODO: finish implementation");
308}
309
310} // namespace porytiles2
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, msg, return_type)
Unwraps a ChainableResult, chaining a new error message on failure.
#define PT_TRY_CALL_CHAIN_ERR(expr, msg, return_type)
Unwraps a void ChainableResult, chaining a new error message on failure.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A bidirectional mapping between pixel color values and sequential integer indices.
std::size_t size() const
Returns the number of unique colors in the mapping.
A text parameter with associated styling for formatted output.
General-purpose error implementation with formatted message support.
Definition error.hpp:117
A template for two-dimensional images with arbitrarily typed pixel values.
Definition image.hpp:24
Service for converting layer images into collections of metatiles.
A palette container for colors that support transparency checking.
Definition palette.hpp:34
const std::array< Palette< Rgba32 >, pal::num_pals > & pals() const
ChainableResult< tileset::LayerMode > detect_layer_mode() const
const Image< IndexPixel > & tiles_png() const
const std::vector< MetatileAttribute > & metatile_attributes_bin() const
ChainableResult< std::unique_ptr< Tileset > > compile_patch_tiles_fixed_pals_fixed(const Tileset &tileset)
ChainableResult< std::unique_ptr< Tileset > > compile(const Tileset &tileset)
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 collection of tile validation functions for compilation operations.
Represents a tilemap entry referencing a tile with palette and flip attributes.
A complete tileset containing both Porytiles and Porymap components.
Definition tileset.hpp:14
const PorytilesTilesetComponent & porytiles_component() const
Definition tileset.hpp:37
const PorymapTilesetComponent & porymap_component() const
Definition tileset.hpp:47
const std::string & name() const
Definition tileset.hpp:32
void err(const std::string &tag, const std::string &msg) const
Display a tagged error message.
void warn(const std::string &tag, const std::string &msg) const
Display a tagged warning message.
void note(const std::string &tag, const std::string &msg) const
Display a tagged informational note message.
void warn_note(const std::string &tag, const std::string &msg) const
Display a note message with a warning tag.
constexpr std::size_t tiles_per_metatile
Definition metatile.hpp:15
constexpr std::size_t max_size
Definition palette.hpp:14
constexpr std::size_t num_pals
Definition palette.hpp:16
constexpr Rgba32 rgba_red
Definition rgba32.hpp:117
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.hpp:53
@ bold
Bold text formatting.
#define PT_UNWRAP_SCOPED_CONFIG(ptr, config, tileset, return_type)
Unwraps a scoped config value from a DomainConfig, AppConfig, or InfraConfig object,...