36constexpr std::array<std::size_t, tile::size_pix> pixel_priority_order = {
119int color_distance_squared(
const Rgba32 &a,
const Rgba32 &b)
121 const int dr =
static_cast<int>(a.
red()) -
static_cast<int>(b.
red());
122 const int dg =
static_cast<int>(a.
green()) -
static_cast<int>(b.
green());
123 const int db =
static_cast<int>(a.
blue()) -
static_cast<int>(b.
blue());
124 return dr * dr + dg * dg + db * db;
140[[nodiscard]] std::vector<std::size_t>
143 struct ColorCandidate {
148 const Rgba32 current_color = palette.
at(current_color_index);
149 std::vector<ColorCandidate> candidates;
152 for (std::size_t candidate_index = 1; candidate_index <
pal::max_size; ++candidate_index) {
153 if (candidate_index == current_color_index) {
157 const Rgba32 candidate_color = palette.
at(candidate_index);
158 const int distance = color_distance_squared(current_color, candidate_color);
159 candidates.push_back({candidate_index, distance});
162 std::ranges::sort(candidates, [](
const ColorCandidate &a,
const ColorCandidate &b) {
163 if (a.distance != b.distance) {
164 return a.distance < b.distance;
166 return a.index < b.index;
169 std::vector<std::size_t> result;
170 result.reserve(candidates.size());
171 for (
const auto &c : candidates) {
172 result.push_back(c.index);
184[[nodiscard]]
IndexPixel make_mangled_pixel(std::size_t original_palette_index, std::size_t alt_color)
186 return IndexPixel{(original_palette_index << 4) | alt_color};
211std::optional<std::pair<PixelTile<IndexPixel>,
TileMangleRecord>> try_mangle_tile(
213 std::size_t tile_index,
218 for (std::size_t pixel_index : pixel_priority_order) {
221 const std::vector<std::size_t> alternatives =
222 find_alternative_colors_sorted(original_pixel.
color_index(), palette);
224 for (
const std::size_t alt_color : alternatives) {
228 candidate_tile.
set(pixel_index, mangled_pixel);
232 if (!all_existing_canonical_tiles.contains(candidate_base)) {
236 .
pixel_index = pixel_index, .original_pixel = original_pixel, .mangled_pixel = mangled_pixel}}};
237 return std::make_pair(candidate_tile, record);
247 for (std::size_t p1_idx = 0; p1_idx < pixel_priority_order.size(); ++p1_idx) {
248 const std::size_t p1 = pixel_priority_order[p1_idx];
251 const std::vector<std::size_t> p1_alternatives =
252 find_alternative_colors_sorted(p1_original.
color_index(), palette);
254 for (std::size_t p2_idx = p1_idx + 1; p2_idx < pixel_priority_order.size(); ++p2_idx) {
255 const std::size_t p2 = pixel_priority_order[p2_idx];
258 const std::vector<std::size_t> p2_alternatives =
259 find_alternative_colors_sorted(p2_original.
color_index(), palette);
261 for (
const std::size_t p1_alt : p1_alternatives) {
264 for (
const std::size_t p2_alt : p2_alternatives) {
268 candidate_tile.
set(p1, p1_mangled);
269 candidate_tile.
set(p2, p2_mangled);
273 if (!all_existing_canonical_tiles.contains(candidate_base)) {
278 .
pixel_index = p1, .original_pixel = p1_original, .mangled_pixel = p1_mangled},
280 .
pixel_index = p2, .original_pixel = p2_original, .mangled_pixel = p2_mangled}}};
281 return std::make_pair(candidate_tile, record);
297 gsl::not_null<const UserDiagnostics *> diag, gsl::not_null<const TilePrinter *> tile_printer)
298 : diag_{diag}, tile_printer_{tile_printer}
303 const std::string &anim_name,
306 const Rgba32 &extrinsic_transparency,
309 if (palettes.size() != tiles.size()) {
310 panic(
"palettes size " + std::to_string(palettes.size()) +
" != tiles size " + std::to_string(tiles.size()));
314 result.
tiles = std::move(tiles);
321 std::set<PixelTile<IndexPixel>> all_canonical_tiles = existing_canonical_tiles;
324 std::map<PixelTile<IndexPixel>, std::size_t> canonical_first_occurrence;
331 for (std::size_t i = 0; i < result.
tiles.size(); ++i) {
339 auto it = canonical_first_occurrence.find(current_base);
340 const bool is_duplicate_of_previous = it != canonical_first_occurrence.end();
341 const bool is_duplicate_of_existing =
342 existing_canonical_tiles.contains(current_base) && !is_duplicate_of_previous;
344 if (is_duplicate_of_previous || is_duplicate_of_existing) {
347 const std::optional<std::pair<PixelTile<IndexPixel>,
TileMangleRecord>> mangle_result =
348 try_mangle_tile(current_tile, i, *palettes[i], all_canonical_tiles);
350 if (!mangle_result.has_value()) {
352 std::vector<std::string> err_msg;
354 "Failed to mangle duplicate key frame tile {} in animation '{}'.",
357 err_msg.emplace_back();
358 err_msg.emplace_back(
"The tile could not be modified to be unique. This may occur if:");
359 err_msg.emplace_back(
" - All possible pixel swaps still produce duplicate tiles");
364 current_tile = mangle_result->first;
368 std::vector<std::string> remark_lines;
369 for (
const auto &change : mangle_result->second.pixel_changes) {
372 "Mangled tile {} in animation '{}': pixel ({},{}) changed from index {} to {}.",
386 remark_lines.emplace_back(
"");
387 remark_lines.emplace_back(
"Original tile:");
388 remark_lines.append_range(tile_printer_->
print_tile(original_rgba, extrinsic_transparency));
390 remark_lines.emplace_back(
"Mangled tile:");
391 remark_lines.append_range(tile_printer_->
print_tile(mangled_rgba, extrinsic_transparency));
393 diag_->
remark(
"anim-key-frame-mangle", remark_lines);
399 canonical_first_occurrence.emplace(final_base, i);
400 all_canonical_tiles.insert(final_base);
AnimKeyFrameMangler(gsl::not_null< const UserDiagnostics * > diag, gsl::not_null< const TilePrinter * > tile_printer)
ChainableResult< MangleResult > mangle_duplicates(const std::string &anim_name, std::vector< PixelTile< IndexPixel > > tiles, const std::vector< const Palette< Rgba32, pal::max_size > * > &palettes, const Rgba32 &extrinsic_transparency, const std::set< PixelTile< IndexPixel > > &existing_tiles) const
Mangles duplicate tiles to make them unique.
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.
Represents an indexed color pixel.
std::size_t palette_index() const
Returns the palette index (upper 4 bits).
std::size_t color_index() const
Returns the color index within a palette (lower 4 bits).
A generic palette container for colors that support transparency checking.
ColorType at(std::size_t index) const
Gets the color at a specific index.
An 8x8 tile backed by literal-array-based per-pixel storage of an arbitrary pixel type.
PixelType at(std::size_t i) const
void set(std::size_t i, const PixelType &p)
Represents a 32-bit RGBA color.
std::uint8_t blue() const
std::uint8_t green() const
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > ¶ms) const
Formats a string with styled parameters using fmtlib syntax.
virtual std::vector< std::string > print_tile(const PixelTile< Rgba32 > &tile, const Rgba32 &extrinsic_transparency) const =0
virtual void remark(const std::string &tag, const std::vector< std::string > &lines) const =0
Display a tagged remark message.
const TextFormatter & formatter() const
constexpr std::size_t max_size
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.
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
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).
Result of the mangling operation.
std::set< TileMangleRecord > mangle_records
Record of all modifications made (ordered by tile_index)
std::vector< PixelTile< IndexPixel > > tiles
The tiles after mangling (unique)
A single pixel modification within a tile.
std::size_t pixel_index
Which pixel in the tile (0-63, linear index)
Record of all pixel modifications made to a single tile during mangling.
std::size_t tile_index
Which tile in the key frame (0-based index, unique across records)