48 panic(
"anim '" + anim.
name() +
"' has no frames");
51 const auto &representative_frame = anim.
frames().begin()->second;
52 const auto &representative_pal = representative_frame.palette();
56 std::vector<std::string> err_msg{};
58 "Representative frame '{}' internal palette size '{}': must be '{}'.",
59 FormatParam{representative_frame.frame_name(), Style::bold},
60 FormatParam{representative_pal.size(), Style::bold},
62 err_msg.emplace_back(
"");
63 err_msg.append_range(pal_printer.
print_rgba_pal(representative_pal));
68 std::vector<std::size_t> extrinsic_transparency_slots;
70 const Rgba32 &color = representative_pal.at(slot);
72 extrinsic_transparency_slots.push_back(slot);
76 if (!extrinsic_transparency_slots.empty()) {
77 std::string slot_list;
78 for (
const auto &slot : extrinsic_transparency_slots) {
79 if (!slot_list.empty()) {
82 slot_list += std::to_string(slot);
84 std::vector<std::string> err_msg{};
86 "Representative frame '{}' palette contains extrinsic transparency color '{}' in non-zero slot(s): {}",
87 FormatParam{representative_frame.frame_name(), Style::bold},
88 FormatParam{extrinsic_transparency.value().to_jasc_str(), Style::bold},
90 err_msg.emplace_back(
"");
91 err_msg.emplace_back(
"The extrinsic transparency color should only appear in slot 0.");
92 err_msg.emplace_back(
"Either correct the PNG palette or change the extrinsic transparency setting.");
93 err_msg.emplace_back(
"");
101 for (
const auto &[frame_name, frame] : anim.frames()) {
102 if (&frame == &representative_frame) {
105 const auto &frame_pal = frame.palette();
106 bool palettes_match = (frame_pal.size() == representative_pal.size());
107 if (palettes_match) {
108 for (std::size_t slot = 0; slot < representative_pal.size(); ++slot) {
109 if (frame_pal.at(slot) != representative_pal.at(slot)) {
110 palettes_match =
false;
115 if (!palettes_match) {
116 std::vector<std::string> err_msg{};
118 "Animation '{}' frame '{}' has an internal palette that does not match the representative frame '{}' "
123 err_msg.emplace_back(
"");
126 err_msg.append_range(pal_printer.
print_rgba_pal(representative_pal));
127 err_msg.emplace_back(
"");
138 for (std::size_t pal_idx = 0; pal_idx < tileset_pals.size(); ++pal_idx) {
141 const Rgba32 &png_pal_color = representative_pal.at(slot);
145 panic(
"png_pal slot " + std::to_string(slot) +
" is extrinsically transparent");
148 const Rgba32 &tileset_color = tileset_pals[pal_idx].at(slot);
149 if (png_pal_color != tileset_color) {
157 std::vector<std::string> remark_lines;
159 "Animation '{}' representative frame '{}' internal palette matched Porymap palette '{}':",
161 FormatParam{representative_frame.frame_name(), Style::bold},
163 remark_lines.emplace_back(
"");
164 remark_lines.append_range(pal_printer.
print_rgba_pal(tileset_pals[pal_idx]));
165 diag.
remark(
"animation-palette-resolution-strategy", remark_lines);
170 std::vector<std::string> err_msg{};
172 "Failed to find matching palette for internal palette of representative frame '{}'.",
173 FormatParam{representative_frame.frame_name(), Style::bold}));
174 err_msg.emplace_back(
"");
175 err_msg.append_range(pal_printer.
print_rgba_pal(representative_pal));
182 case AnimPalResolutionStrategy::palette_00:
184 case AnimPalResolutionStrategy::palette_01:
186 case AnimPalResolutionStrategy::palette_02:
188 case AnimPalResolutionStrategy::palette_03:
190 case AnimPalResolutionStrategy::palette_04:
192 case AnimPalResolutionStrategy::palette_05:
194 case AnimPalResolutionStrategy::palette_06:
196 case AnimPalResolutionStrategy::palette_07:
198 case AnimPalResolutionStrategy::palette_08:
200 case AnimPalResolutionStrategy::palette_09:
202 case AnimPalResolutionStrategy::palette_10:
204 case AnimPalResolutionStrategy::palette_11:
206 case AnimPalResolutionStrategy::palette_12:
208 case AnimPalResolutionStrategy::palette_13:
210 case AnimPalResolutionStrategy::palette_14:
212 case AnimPalResolutionStrategy::palette_15:
242 const std::string &anim_name,
243 std::size_t subtile_index,
244 std::size_t tile_index,
245 std::span<const TilemapEntry> metatiles_bin,
255 std::optional<std::size_t> &internal_png_pal_cache)
258 const auto explicit_pal = extract_pal_index(strategy.
value());
259 if (explicit_pal.has_value()) {
261 "animation-palette-resolution-strategy",
263 "Animation '{}' subtile {} using explicit palette '{}'.",
268 return *explicit_pal;
271 switch (strategy.
value()) {
272 case AnimPalResolutionStrategy::scan_local_metatiles: {
273 std::set<std::size_t> found_for_subtile{};
275 for (
const auto &entry : metatiles_bin) {
276 if (entry.tile_index() == tile_index) {
277 found_for_subtile.insert(entry.pal_index());
281 if (found_for_subtile.empty()) {
282 std::vector<std::string> err_msg{};
284 "Animation '{}' subtile {} at tile index '{}' is not referenced in local metatiles.",
288 err_msg.emplace_back(
289 "Consider using a different palette resolution strategy (e.g. 'palette-00', "
290 "'internal-png-palette', etc.).");
295 if (found_for_subtile.size() > 1) {
302 std::string pal_list;
303 for (
const auto &pal_idx : found_for_subtile) {
304 if (!pal_list.empty()) {
310 switch (multi_pal_strategy.
value()) {
311 case AnimMultiPalSubtileResolutionStrategy::error: {
312 std::vector<std::string> err_msg;
314 "Animation '{}' subtile {} at tile index '{}' is referenced with multiple palettes: {}.",
319 err_msg.emplace_back(
320 "Picking one palette arbitrarily would produce incorrect RGBA output in the layer PNGs.");
323 err_msg.emplace_back(
"");
324 for (
const auto &pal_idx : found_for_subtile) {
328 "Tile under palette '{}':",
FormatParam{pal_filename(pal_idx), Style::bold}));
329 err_msg.append_range(tile_printer.
print_tile(rgba_tile, extrinsic_transparency.
value()));
332 err_msg.emplace_back(
"");
333 err_msg.emplace_back(
334 "Consider using an explicit palette resolution strategy (e.g. 'palette-00') to resolve the "
339 case AnimMultiPalSubtileResolutionStrategy::warning: {
340 const std::size_t chosen_pal = *found_for_subtile.begin();
342 std::vector<std::string> warn_msg;
344 "Animation '{}' subtile {} at tile index '{}' is referenced with multiple palettes: {}.",
350 "Using palette '{}'. Set 'frame_linking: manual' to handle palette assignment via overrides.",
351 FormatParam{pal_filename(chosen_pal), Style::bold}));
354 warn_msg.emplace_back(
"");
355 for (
const auto &pal_idx : found_for_subtile) {
359 "Tile under palette '{}':",
FormatParam{pal_filename(pal_idx), Style::bold}));
360 warn_msg.append_range(tile_printer.
print_tile(rgba_tile, extrinsic_transparency.
value()));
364 diag.
warning(
"animation-multi-pal-subtile", warn_msg);
368 case AnimMultiPalSubtileResolutionStrategy::split:
369 return FormattableError{
"The 'split' mode for multi-pal subtile resolution is not yet implemented."};
373 return *found_for_subtile.begin();
376 case AnimPalResolutionStrategy::internal_png_pal: {
377 if (internal_png_pal_cache.has_value()) {
378 return *internal_png_pal_cache;
380 std::vector<std::string> err_msg{};
382 "Palette resolution strategy '{}' failed.",
383 FormatParam{to_string(AnimPalResolutionStrategy::internal_png_pal), Style::bold}));
387 internal_png_pal_strategy(anim, pals, extrinsic_transparency, diag, pal_printer),
390 internal_png_pal_cache = match;
394 case AnimPalResolutionStrategy::scan_all_tilesets:
395 panic(
"scan_all_tilesets not yet implemented");
398 panic(
"unhandled AnimPalResolutionStrategy value");
403 const std::string &anim_name,
404 std::size_t tile_offset,
405 std::size_t tile_count,
406 std::span<const TilemapEntry> metatiles_bin,
417 if (per_subtile_strategies.size() != tile_count) {
419 "per_subtile_strategies size " + std::to_string(per_subtile_strategies.size()) +
" != tile_count " +
420 std::to_string(tile_count));
423 std::vector<std::size_t> per_tile_pals(tile_count);
424 std::optional<std::size_t> internal_png_pal_cache;
426 for (std::size_t i = 0; i < tile_count; ++i) {
427 const std::size_t tile_index = tile_offset + i;
430 resolve_subtile_palette(
435 per_subtile_strategies[i],
440 extrinsic_transparency,
444 internal_png_pal_cache),
445 std::vector<std::size_t>,
447 "Failed to resolve palette for animation '{}' subtile {}.",
450 per_tile_pals[i] = pal_idx;
454 if (tile_count > 1) {
455 const std::size_t first_pal = per_tile_pals.at(0);
456 const bool uses_multiple_palettes =
457 !std::ranges::all_of(per_tile_pals, [&](std::size_t idx) {
return idx == first_pal; });
458 if (uses_multiple_palettes) {
459 std::set<std::size_t> unique_pals{per_tile_pals.begin(), per_tile_pals.end()};
460 std::string pal_list;
461 for (
const auto &pal_idx : unique_pals) {
462 if (!pal_list.empty()) {
468 "animation-palette-resolution-strategy",
470 "Animation '{}' uses multiple palettes across subtiles: {}.",
476 return per_tile_pals;
494[[nodiscard]]
bool has_duplicate_key_frame_tiles(
499 std::set<PixelTile<IndexPixel>> seen;
500 for (
const auto &tile : key_frame_tiles) {
503 if (inter_anim_canonical_tiles.contains(base)) {
506 if (external_canonical_tiles.contains(base)) {
509 if (!seen.insert(base).second) {
516struct DuplicateInfo {
517 std::vector<std::size_t> inter_anim_indices;
518 std::vector<std::size_t> cross_range_indices;
519 std::vector<std::pair<std::size_t, std::size_t>> intra_anim_pairs;
538[[nodiscard]] DuplicateInfo categorize_duplicate_key_frame_tiles(
546 std::map<PixelTile<IndexPixel>, std::size_t> seen;
548 for (std::size_t i = 0; i < key_frame_tiles.size(); ++i) {
553 if (inter_anim_canonical_tiles.contains(base)) {
554 info.inter_anim_indices.push_back(i);
556 else if (external_canonical_tiles.contains(base)) {
557 info.cross_range_indices.push_back(i);
560 auto [it, inserted] = seen.emplace(base, i);
562 info.intra_anim_pairs.emplace_back(it->second, i);
576void backport_mangles_to_tiles_png(
587 for (
const auto &record : records) {
588 const std::size_t global_tile_idx = base_tile_offset + record.tile_index;
589 const std::size_t tile_row = global_tile_idx / tiles_per_row;
590 const std::size_t tile_col = global_tile_idx % tiles_per_row;
592 for (
const auto &change : record.pixel_changes) {
597 tiles_img.
set(img_row, img_col, change.mangled_pixel);
609 const std::string &tileset_name,
619 config_, global_anim_multi_pal_subtile_resolution_strategy, tileset_name,
Animation<Rgba32>);
624 const auto &pals = porymap_component.
pals();
625 const auto &metatiles_bin = porymap_component.
metatiles_bin();
626 const auto &tiles_png = porymap_component.
tiles_png();
629 result.params(anim.
params());
636 if (tile_count == 0) {
638 std::vector<std::string>{
639 "Animation '{}' has a tile count of '{}'.",
640 "The animation's generated C code may not have been parsed correctly, or the animation defines no "
642 std::vector<std::vector<FormatParam>>{
645 if (tile_offset == 0) {
647 std::vector<std::string>{
648 "Animation '{}' has a tile offset of '{}'.",
649 "Tile 0 in tiles.png is reserved and cannot be an animation tile."},
650 std::vector<std::vector<FormatParam>>{
660 std::vector<ConfigValue<AnimPalResolutionStrategy>> per_subtile_strategies;
661 per_subtile_strategies.reserve(tile_count);
663 const auto &configs_map = per_anim_overrides.value();
664 const auto anim_cfg_it = configs_map.find(anim.
name());
665 const PerAnimOverride *anim_cfg_ptr = (anim_cfg_it != configs_map.end()) ? &anim_cfg_it->second :
nullptr;
667 if (anim_cfg_ptr !=
nullptr) {
673 : global_anim_pal_resolution_strategy;
678 std::vector<std::string>{
679 "Animation '{}' config 'per_tile_palette_resolution_strategies' has '{}' entries, but "
680 "animation has '{}' subtiles.",
681 "The per_tile_palette_resolution_strategies list must have exactly one entry per subtile."},
682 std::vector<std::vector<FormatParam>>{
688 for (std::size_t i = 0; i < tile_count; ++i) {
690 per_subtile_strategies.push_back(
694 per_subtile_strategies.push_back(effective_default);
700 for (std::size_t i = 0; i < tile_count; ++i) {
701 per_subtile_strategies.push_back(effective_default);
707 for (std::size_t i = 0; i < tile_count; ++i) {
708 per_subtile_strategies.push_back(global_anim_pal_resolution_strategy);
718 : global_anim_multi_pal_subtile_resolution_strategy;
723 : global_frame_linking;
726 std::vector<std::string> err_msg{};
729 err_msg.emplace_back(
"Use 'automatic' or 'manual' frame linking until hybrid support.");
739 std::vector<AnimOverrideEntry> overrides;
741 for (std::size_t mt_idx = 0; mt_idx < num_metatiles; ++mt_idx) {
744 if (entry.tile_index() >= tile_offset && entry.tile_index() < tile_offset + tile_count) {
751 entry.tile_index() - tile_offset,
760 result_params.
overrides(std::move(overrides));
761 result.params(std::move(result_params));
766 find_pals_for_anim_tiles(
771 per_subtile_strategies,
772 effective_multi_pal_strategy,
776 extrinsic_transparency,
782 "Failed to find palette for animation '{}'.",
FormatParam{anim.name(), Style::bold}));
785 std::vector<PixelTile<Rgba32>> rgba_tiles;
786 rgba_tiles.reserve(frame.tiles().size());
787 for (std::size_t i = 0; i < frame.tiles().size(); ++i) {
789 frame.tiles()[i], pals.at(manual_pal_indices[i]), extrinsic_transparency.
value()));
792 result.put_frame(frame.frame_name(), std::move(rgba_frame));
801 find_pals_for_anim_tiles(
806 per_subtile_strategies,
807 effective_multi_pal_strategy,
811 extrinsic_transparency,
819 std::vector<const Palette<Rgba32, pal::max_size> *> pal_ptrs;
820 pal_ptrs.reserve(pal_indices.size());
821 for (std::size_t idx : pal_indices) {
822 pal_ptrs.push_back(&pals.at(idx));
826 std::vector<PixelTile<IndexPixel>> key_frame_index_tiles =
836 const std::size_t total_tiles =
838 std::set<PixelTile<IndexPixel>> existing_canonical_tiles;
839 for (std::size_t i = 0; i < total_tiles; ++i) {
840 if (i >= tile_offset && i < tile_offset + tile_count) {
845 existing_canonical_tiles.insert(base);
848 existing_canonical_tiles.insert(inter_anim_canonical_tiles.begin(), inter_anim_canonical_tiles.end());
856 : global_anim_key_frame_resolution_strategy;
864 if (has_duplicate_key_frame_tiles(key_frame_index_tiles, inter_anim_canonical_tiles, existing_canonical_tiles)) {
865 switch (effective_key_frame_strategy.
value()) {
867 const auto dup_info = categorize_duplicate_key_frame_tiles(
868 key_frame_index_tiles, inter_anim_canonical_tiles, existing_canonical_tiles);
869 std::vector<std::string> err_msg{};
871 "Animation '{}' has duplicate key frame tiles (flip-equivalent tiles are considered duplicates):",
873 for (
const auto &idx : dup_info.inter_anim_indices) {
875 " - Tile {} is flip-equivalent to another animation's key frame tile.",
878 for (
const auto &idx : dup_info.cross_range_indices) {
880 " - Tile {} is flip-equivalent to a non-animation tile in tiles.png.",
883 for (
const auto &[i, j] : dup_info.intra_anim_pairs) {
885 " - Tile {} and tile {} are flip-equivalent.",
889 err_msg.emplace_back(
"");
890 err_msg.emplace_back(
"Consider using 'mangle' strategy to auto-resolve.");
896 panic(
"warning not yet implemented");
903 mangler.mangle_duplicates(
905 std::move(key_frame_index_tiles),
907 extrinsic_transparency.
value(),
908 existing_canonical_tiles),
911 "Failed to mangle duplicate key frame tiles for animation '{}'.",
913 key_frame_index_tiles = std::move(mangle_result.tiles);
916 if (!mangle_result.mangle_records.empty()) {
917 backport_mangles_to_tiles_png(porymap_component, tile_offset, mangle_result.mangle_records);
923 panic(
"unhandled AnimKeyFrameResolutionStrategy value");
928 std::vector<PixelTile<Rgba32>> key_frame_rgba_tiles;
929 key_frame_rgba_tiles.reserve(key_frame_index_tiles.size());
930 for (std::size_t i = 0; i < key_frame_index_tiles.size(); ++i) {
932 key_frame_index_tiles[i], pals.at(pal_indices[i]), extrinsic_transparency.
value()));
936 AnimFrame key_frame{
"key", std::move(key_frame_rgba_tiles)};
937 result.key_frame(std::move(key_frame));
940 if (frame.tiles().size() != tile_count) {
942 "frame '" + frame.frame_name() +
"' tile count " + std::to_string(frame.tiles().size()) +
943 " != animation tile_count " + std::to_string(tile_count));
946 std::vector<PixelTile<Rgba32>> rgba_tiles;
947 rgba_tiles.reserve(frame.tiles().size());
949 for (std::size_t i = 0; i < frame.tiles().size(); ++i) {
950 rgba_tiles.push_back(
955 result.put_frame(frame.frame_name(), std::move(rgba_frame));
#define PT_TRY_ASSIGN_CHAIN_ERR(var, expr, return_type,...)
Unwraps a ChainableResult, chaining a new error message on failure.
ChainableResult< Animation< Rgba32 > > decompile_animation(const std::string &tileset_name, const Animation< IndexPixel > &anim, const std::set< PixelTile< IndexPixel > > &inter_anim_canonical_tiles, PorymapTilesetComponent &porymap_component) const
Decompiles an IndexPixel animation to Rgba32 format.
Represents a single frame of an animation, containing tiles and a frame name.
const std::string & frame_name() const
Service that mangles duplicate key frame tiles to make them unique.
Configuration parameters for a single tileset animation.
const std::vector< AnimOverrideEntry > & overrides() const
Returns the manual override entries for this animation.
std::size_t tile_offset() const
Returns the VRAM tile offset for this animation.
std::size_t tile_count() const
Returns the number of tiles per animation frame.
A complete tileset animation with name, configuration, and frame data.
const AnimParams & params() const
bool has_frames() const
Checks if this animation has any frames.
const std::string & name() const
std::vector< AnimFrame< PixelType > > frames_values() const
const std::map< std::string, AnimFrame< PixelType > > & frames() const
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 container that wraps a configuration value with its name and source information.
ConfigValue< U > derive(const ConfigPODField< U > &override) const
Creates a child ConfigValue from a ConfigPODField, inheriting this value's source provenance.
const T & value() const &
A template for two-dimensional images with arbitrarily typed pixel values.
void set(std::size_t i, PixelType pixel)
Sets the pixel value at a given one-dimensional pixel index.
std::size_t width() const
std::size_t height() const
A collection of printer functions for the Palette and related types.
virtual std::vector< std::string > print_rgba_pal(const Palette< Rgba32, pal::max_size > &pal) const =0
virtual std::vector< std::string > print_rgba_pal_with_highlights(const Palette< Rgba32 > &pal, const std::vector< std::size_t > &slots) const =0
A generic palette container for colors that support transparency checking.
An 8x8 tile backed by literal-array-based per-pixel storage of an arbitrary pixel type.
const std::array< Palette< Rgba32, pal::max_size >, pal::num_pals > & pals() const
const std::vector< TilemapEntry > & metatiles_bin() const
const Image< IndexPixel > & tiles_png() const
Represents a 32-bit RGBA color.
bool is_transparent(const Rgba32 &extrinsic) const
Checks if this color should be treated as transparent.
bool is_extrinsically_transparent(const Rgba32 &extrinsic) const
Checks if this color matches the extrinsic transparency color.
static const Style bold
Bold text formatting.
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > ¶ms) const
Formats a string with styled parameters using fmtlib syntax.
A collection of printer functions for various tile types.
virtual std::vector< std::string > print_tile(const PixelTile< Rgba32 > &tile, const Rgba32 &extrinsic_transparency) const =0
Abstract class for structured error reporting and diagnostic output.
virtual void remark(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged remark message.
const TextFormatter & formatter() const
virtual void warning(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged warning message.
virtual void remark_note(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged note message associated with a remark.
constexpr std::size_t num_pals
constexpr std::size_t max_size
constexpr std::size_t side_length_pix
constexpr std::pair< std::size_t, std::size_t > index_to_row_col(std::size_t index)
Converts a linear index to row and column coordinates.
PixelTile< PixelType > extract_single_tile(const Image< PixelType > &img, std::size_t tile_idx, std::size_t tiles_per_row=metatile::metatiles_per_row *metatile::tiles_per_side)
Extracts a single tile from an image at a given tile index.
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
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.
AnimPalResolutionStrategy
Strategy for determining which palette to use when decompiling animation tiles.
@ canonical
Export tiles in canonical form without applying flip transformations.
std::string pal_filename(std::size_t pal_index)
Constructs a palette filename from a palette index.
std::vector< std::string > format_config_note(const TextFormatter &format, const ConfigValue< T > &config)
Format a ConfigValue into diagnostic note lines.
std::vector< std::string > format_config_note_with_separator(const TextFormatter &format, const ConfigValue< T > &config)
Format a ConfigValue into diagnostic note lines with a separator.
@ warning
Emit a warning and continue decompilation.
@ mangle
Mangle duplicate tiles to make them unique, then backport changes to tiles.png.
@ error
Emit a formatted error and fail decompilation.
@ manual
Use manual overrides in anim.json.
@ hybrid
Automatic key.png linking plus manual override pass (not yet implemented).
PixelTile< ColorType > color_tile_from_index_tile(const PixelTile< IndexPixel > &index_tile, const Palette< ColorType, N > &palette)
Converts a PixelTile<IndexPixel> to a PixelTile<ColorType> using a palette (intrinsic transparency).
A manual override that maps a specific metatile entry to an animation subtile.
std::size_t pal_index
The palette index to use for this tile.
bool has_value() const
Checks whether this field has a value set.
Per-animation configuration override for animation decompilation.
std::vector< ConfigPODField< AnimPalResolutionStrategy > > per_tile_pal_resolution_strategies
ConfigPODField< AnimMultiPalSubtileResolutionStrategy > multi_pal_subtile_resolution_strategy
ConfigPODField< AnimKeyFrameResolutionStrategy > key_frame_resolution_strategy
ConfigPODField< FrameLinking > linking
ConfigPODField< AnimPalResolutionStrategy > pal_resolution_strategy
#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...