33constexpr std::array<std::size_t, tile::size_pix> pixel_priority_order = {
110int color_distance_squared(
const Rgba32 &a,
const Rgba32 &b)
112 const int dr =
static_cast<int>(a.
red()) -
static_cast<int>(b.
red());
113 const int dg =
static_cast<int>(a.
green()) -
static_cast<int>(b.
green());
114 const int db =
static_cast<int>(a.
blue()) -
static_cast<int>(b.
blue());
115 return dr * dr + dg * dg + db * db;
131[[nodiscard]] std::vector<std::size_t> find_alternative_colors_sorted(
132 std::size_t current_color_index,
134 const Rgba32 &extrinsic_transparency)
136 struct ColorCandidate {
142 const Rgba32 current_color = (current_color_index == 0) ? extrinsic_transparency : palette.at(current_color_index);
143 std::vector<ColorCandidate> candidates;
146 for (std::size_t candidate_index = 1; candidate_index <
palette::max_size; ++candidate_index) {
147 if (candidate_index == current_color_index) {
151 const Rgba32 candidate_color = palette.
at(candidate_index);
156 const int distance = color_distance_squared(current_color, candidate_color);
161 candidates.push_back({candidate_index, distance});
164 std::ranges::sort(candidates, [](
const ColorCandidate &a,
const ColorCandidate &b) {
165 if (a.distance != b.distance) {
166 return a.distance < b.distance;
168 return a.index < b.index;
171 std::vector<std::size_t> result;
172 result.reserve(candidates.size());
173 for (
const auto &c : candidates) {
174 result.push_back(c.index);
180[[nodiscard]]
IndexPixel make_mangled_pixel(std::size_t original_palette_index, std::size_t alt_color)
182 return IndexPixel{(original_palette_index << 4) | alt_color};
201std::optional<std::pair<PixelTile<IndexPixel>,
TileMangleRecord>> try_mangle_tile(
203 std::size_t tile_index,
205 const Rgba32 &extrinsic_transparency,
212 return !existing_canonical_rgba_tiles.contains(candidate_base) &&
213 !batch_canonical_rgba_tiles.contains(candidate_base);
217 for (std::size_t pixel_index : pixel_priority_order) {
220 const std::vector<std::size_t> alternatives =
221 find_alternative_colors_sorted(original_pixel.
color_index(), palette, extrinsic_transparency);
223 for (
const std::size_t alt_color : alternatives) {
227 candidate_tile.
set(pixel_index, mangled_pixel);
229 if (candidate_is_unique(candidate_tile)) {
233 .
pixel_index = pixel_index, .original_pixel = original_pixel, .mangled_pixel = mangled_pixel}}};
234 return std::make_pair(candidate_tile, record);
242 for (std::size_t p1_idx = 0; p1_idx < pixel_priority_order.size(); ++p1_idx) {
243 const std::size_t p1 = pixel_priority_order[p1_idx];
246 const std::vector<std::size_t> p1_alternatives =
247 find_alternative_colors_sorted(p1_original.
color_index(), palette, extrinsic_transparency);
249 for (std::size_t p2_idx = p1_idx + 1; p2_idx < pixel_priority_order.size(); ++p2_idx) {
250 const std::size_t p2 = pixel_priority_order[p2_idx];
253 const std::vector<std::size_t> p2_alternatives =
254 find_alternative_colors_sorted(p2_original.
color_index(), palette, extrinsic_transparency);
256 for (
const std::size_t p1_alt : p1_alternatives) {
259 for (
const std::size_t p2_alt : p2_alternatives) {
263 candidate_tile.
set(p1, p1_mangled);
264 candidate_tile.
set(p2, p2_mangled);
266 if (candidate_is_unique(candidate_tile)) {
271 .
pixel_index = p1, .original_pixel = p1_original, .mangled_pixel = p1_mangled},
273 .
pixel_index = p2, .original_pixel = p2_original, .mangled_pixel = p2_mangled}}};
274 return std::make_pair(candidate_tile, record);
290 gsl::not_null<const UserDiagnostics *> diag, gsl::not_null<const TilePrinter *> tile_printer)
291 : diag_{diag}, tile_printer_{tile_printer}
296 const std::string &anim_name,
299 const Rgba32 &extrinsic_transparency,
300 const std::vector<
const std::set<
PixelTile<Rgba32>> *> &existing_canonical_rgba_tiles)
const
302 if (palettes.size() != tiles.size()) {
303 panic(
"palettes size " + std::to_string(palettes.size()) +
" != tiles size " + std::to_string(tiles.size()));
305 if (existing_canonical_rgba_tiles.size() != tiles.size()) {
307 "existing_canonical_rgba_tiles size " + std::to_string(existing_canonical_rgba_tiles.size()) +
308 " != tiles size " + std::to_string(tiles.size()));
312 result.
tiles = std::move(tiles);
317 std::set<PixelTile<Rgba32>> batch_canonical_rgba_tiles;
320 std::map<PixelTile<Rgba32>, std::size_t> canonical_first_occurrence;
325 for (std::size_t i = 0; i < result.
tiles.size(); ++i) {
333 const bool is_duplicate_of_previous = canonical_first_occurrence.contains(current_base);
334 const bool is_duplicate_of_existing =
335 existing_canonical_rgba_tiles[i]->contains(current_base) && !is_duplicate_of_previous;
337 if (is_duplicate_of_previous || is_duplicate_of_existing) {
340 const std::optional<std::pair<PixelTile<IndexPixel>,
TileMangleRecord>> mangle_result = try_mangle_tile(
344 extrinsic_transparency,
345 *existing_canonical_rgba_tiles[i],
346 batch_canonical_rgba_tiles);
348 if (!mangle_result.has_value()) {
350 std::vector<std::string> err_msg;
352 "Failed to mangle duplicate key frame tile {} in animation '{}'.",
355 err_msg.emplace_back();
356 err_msg.emplace_back(
"The tile could not be modified to be unique. This may occur if:");
357 err_msg.emplace_back(
" - All possible pixel swaps still produce duplicate tiles");
358 err_msg.emplace_back(
" - The palette does not contain enough distinct opaque colors");
363 current_tile = mangle_result->first;
367 std::vector<std::string> remark_lines;
368 for (
const auto &change : mangle_result->second.pixel_changes) {
371 "Mangled tile {} in animation '{}': pixel ({},{}) changed from index {} to {}.",
385 remark_lines.emplace_back(
"");
386 remark_lines.emplace_back(
"Original tile:");
387 remark_lines.append_range(tile_printer_->
print_tile(original_rgba, extrinsic_transparency));
389 remark_lines.emplace_back(
"Mangled tile:");
390 remark_lines.append_range(tile_printer_->
print_tile(mangled_rgba, extrinsic_transparency));
392 diag_->
remark(
"anim-key-frame-mangle", remark_lines);
398 canonical_first_occurrence.emplace(final_base, i);
399 batch_canonical_rgba_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, palette::max_size > * > &palettes, const Rgba32 &extrinsic_transparency, const std::vector< const std::set< PixelTile< Rgba32 > > * > &existing_canonical_rgba_tiles) const
Mangles duplicate tiles to make them unique.
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.
bool is_transparent(const Rgba32 &extrinsic) const
Checks if this color should be treated as transparent.
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 > canonical_color_tile_from_index_tile(const PixelTile< IndexPixel > &index_tile, const Palette< ColorType, N > &palette, const ColorType &extrinsic)
Converts a PixelTile<IndexPixel> to its canonical color form using a palette (extrinsic transparency)...
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)