30bool tiles_color_equivalent(
35 constexpr Rgba32 extrinsic{};
57 const std::size_t tile_count = end_tile - start_tile;
60 const std::size_t tiles_per_col = (tile_count + tiles_per_row - 1) / tiles_per_row;
70 for (std::size_t i = 0; i < tile_count; ++i) {
71 const std::size_t tile_idx = start_tile + i;
74 const std::size_t tile_row = i / tiles_per_row;
75 const std::size_t tile_col = i % tiles_per_row;
82 const auto &canonical_tile = workspace.
tile_at(tile_idx);
88 if (flip_mode == ExportFlipMode::original) {
89 tile_to_export = canonical_base.
flip(canonical_tile.h_flip(), canonical_tile.v_flip());
92 tile_to_export = canonical_base;
98 const std::size_t dest_row = pixel_row_offset + pixel_row;
99 const std::size_t dest_col = pixel_col_offset + pixel_col;
100 img.
set(dest_row, dest_col, tile_to_export.
at(pixel_row, pixel_col));
114std::size_t find_last_non_transparent(
const TilesPngWorkspace &workspace, std::size_t scan_start, std::size_t minimum)
116 for (std::size_t i = scan_start; i > minimum; --i) {
117 const std::size_t idx = i - 1;
118 if (!workspace.
tile_at(idx).is_transparent()) {
129void TilesPngWorkspace::advance_cursor_to_next_transparent()
132 while (cursor_ < capacity_ && !tiles_[cursor_].is_transparent()) {
144 tiles_.resize(
capacity, transparent_tile);
148 : cursor_{1}, capacity_{capacity}
154 const auto msg = formatter.
format(
162 const std::size_t total_tiles = tiles_per_row * tiles_per_col;
166 const auto msg = formatter.format(
"Image contains {} tiles but capacity is only {}", total_tiles,
capacity);
174 for (std::size_t tile_row = 0; tile_row < tiles_per_col; ++tile_row) {
175 for (std::size_t tile_col = 0; tile_col < tiles_per_row; ++tile_col) {
186 const std::size_t src_row = pixel_row_offset + pixel_row;
187 const std::size_t src_col = pixel_col_offset + pixel_col;
188 pixel_tile.
set(pixel_row, pixel_col, img.
at(src_row, src_col));
194 tiles_.push_back(canonical_tile);
197 if (!canonical_tile.is_transparent()) {
198 const std::size_t tile_index = tiles_.size() - 1;
200 canonical_forms_[base_tile].push_back(tile_index);
208 tiles_.resize(
capacity, transparent_tile);
213 advance_cursor_to_next_transparent();
217 const Image<IndexPixel> &primary_tiles_png, std::size_t primary_tile_count, std::size_t total_capacity)
222 if (primary_tile_count >= total_capacity) {
223 const auto msg = formatter.
format(
224 "primary_tile_count ({}) must be less than total_capacity ({})", primary_tile_count, total_capacity);
231 const auto msg = formatter.format(
232 "Primary tiles.png dimensions must be a multiple of {}, got {}x{}",
234 primary_tiles_png.
width(),
235 primary_tiles_png.
height());
245 const std::size_t tiles_in_image = tiles_per_row * tiles_per_col;
246 const std::size_t tiles_to_load = std::min(tiles_in_image, primary_tile_count);
249 for (std::size_t tile_idx = 0; tile_idx < tiles_to_load; ++tile_idx) {
250 const std::size_t tile_row = tile_idx / tiles_per_row;
251 const std::size_t tile_col = tile_idx % tiles_per_row;
259 const std::size_t src_row = pixel_row_offset + pixel_row;
260 const std::size_t src_col = pixel_col_offset + pixel_col;
265 const IndexPixel true_color_pixel = primary_tiles_png.
at(src_row, src_col);
271 workspace.tiles_.at(tile_idx) = canonical_tile;
274 if (!canonical_tile.is_transparent()) {
276 workspace.canonical_forms_[base_tile].push_back(tile_idx);
282 workspace.cursor_ = primary_tile_count + 1;
283 workspace.anim_start_offset_ = primary_tile_count + 1;
284 workspace.anim_end_offset_ = primary_tile_count + 1;
295 if (primary_tile_count >= total_capacity) {
296 const auto msg = formatter.
format(
297 "primary_tile_count ({}) must be less than total_capacity ({})", primary_tile_count, total_capacity);
306 workspace.cursor_ = primary_tile_count + 1;
307 workspace.anim_start_offset_ = primary_tile_count + 1;
308 workspace.anim_end_offset_ = primary_tile_count + 1;
316 if (cursor_ >= capacity_) {
317 panic(
"TilesPngWorkspace is at capacity");
326 tiles_[cursor_] = tile;
327 const std::size_t old_cursor = cursor_;
331 canonical_forms_[base_tile].push_back(cursor_);
334 advance_cursor_to_next_transparent();
342 auto it = canonical_forms_.find(base_tile);
343 if (it != canonical_forms_.end() && !it->second.empty()) {
344 return it->second.front();
358 for (std::size_t i = 1; i < capacity_; ++i) {
359 if (tiles_[i].is_transparent()) {
363 if (tiles_color_equivalent(tile_base, workspace_tile_base, palette)) {
372 if (index >= tiles_.size()) {
373 panic(
"index " + std::to_string(index) +
" >= size " + std::to_string(tiles_.size()));
375 return tiles_.at(index);
380 std::size_t end_tile = capacity_;
382 end_tile = find_last_non_transparent(*
this, capacity_, 0) + 1;
384 return export_image_range(*
this, 0, end_tile, flip_mode);
390 std::size_t end_tile = capacity_;
392 const std::size_t last_non_transparent = find_last_non_transparent(*
this, capacity_, primary_tile_count);
394 end_tile = std::max(last_non_transparent, primary_tile_count) + 1;
396 return export_image_range(*
this, primary_tile_count, end_tile, flip_mode);
401 return cursor_ == capacity_;
409 if (cursor_ != start_offset) {
410 const auto msg = formatter.
format(
411 "reserve_anim_slots: cursor ({}) must be at start_offset ({}) before reserving animation slots",
418 if (start_offset + anim_tile_count >= capacity_) {
419 const auto msg = formatter.format(
420 "start_offset ({}) + anim_tile_count ({}) must be less than capacity ({}).",
428 anim_start_offset_ = start_offset;
429 anim_end_offset_ = start_offset + anim_tile_count;
432 cursor_ = anim_end_offset_;
441 panic(
"place_animation_tile called but no animation slots were reserved");
448 if (absolute_index >= anim_end_offset_) {
449 const auto msg = formatter.format(
450 "reserved_index ({}) is out of bounds for animation region (max: {})",
457 tiles_[absolute_index] = tile;
462 canonical_forms_[base_tile].push_back(absolute_index);
474 std::size_t run_start = 0;
475 std::size_t run_length = 0;
477 for (std::size_t i = 1; i < capacity_; ++i) {
478 if (tiles_[i].is_transparent()) {
479 if (run_length == 0) {
484 if (run_length >= count) {
508 if (tiles.size() != palettes.size()) {
509 panic(
"tiles and palettes vectors must have the same size");
514 for (std::size_t candidate_start = 1; candidate_start < capacity_; ++candidate_start) {
516 if (candidate_start + tiles.size() > capacity_) {
521 bool all_match =
true;
522 for (std::size_t offset = 0; offset < tiles.size(); ++offset) {
523 const std::size_t check_index = candidate_start + offset;
526 const auto &palette = *palettes[offset];
528 if (!tiles_color_equivalent(expected_base, actual_base, palette)) {
535 return candidate_start;
549 if (start_index + tiles.size() > capacity_) {
550 const auto msg = formatter.
format(
551 "place_tiles_at: start_index ({}) + tiles.size() ({}) exceeds capacity ({})",
559 for (std::size_t offset = 0; offset < tiles.size(); ++offset) {
560 const std::size_t target_index = start_index + offset;
561 if (!tiles_[target_index].is_transparent()) {
563 formatter.format(
"place_tiles_at: position {} is not transparent, cannot place tile", target_index);
569 for (std::size_t offset = 0; offset < tiles.size(); ++offset) {
570 const std::size_t target_index = start_index + offset;
571 const auto &tile = tiles[offset];
573 tiles_[target_index] = tile;
576 if (!tile.is_transparent()) {
578 canonical_forms_[base_tile].push_back(target_index);
584 while (cursor_ < capacity_ && !tiles_[cursor_].is_transparent()) {
A PixelTile representation that stores the canonical (lexicographically minimal) orientation among al...
A template for two-dimensional images with arbitrarily typed pixel values.
std::size_t width() const
std::size_t height() const
PixelType at(std::size_t i) const
Fetches the pixel value at a given one-dimensional pixel index.
Represents an indexed color pixel.
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.
An 8x8 tile backed by literal-array-based per-pixel storage of an arbitrary pixel type.
bool is_transparent() const
Checks if this entire PixelTile is transparent (intrinsic transparency only).
PixelTile flip(bool h_flip, bool v_flip) const
Creates a flipped copy of this PixelTile.
PixelType at(std::size_t i) const
void set(std::size_t i, const PixelType &p)
TextFormatter implementation that strips all styling from text.
Represents a 32-bit RGBA color.
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 workspace for managing canonical IndexPixel tiles destined for tiles.png output.
bool at_capacity() const
Checks if the workspace has reached capacity and can no longer accept new tile insertions.
std::optional< std::size_t > find_contiguous_transparent_slots(std::size_t count) const
Finds the first contiguous run of transparent tiles that can accommodate the requested count.
Image< IndexPixel > export_secondary_image(std::size_t primary_tile_count, ExportFlipMode flip_mode=ExportFlipMode::canonical, ExportTrimMode trim_mode=ExportTrimMode::trim_trailing_transparent) const
Exports only the secondary portion of the workspace (tiles from primary_tile_count onward).
TilesPngWorkspace(std::size_t capacity)
Constructs a TilesPngWorkspace with a specified capacity, initializing all slots with transparent til...
std::size_t capacity() const
Returns the maximum number of tiles this workspace can hold.
CanonicalPixelTile< IndexPixel > tile_at(std::size_t index) const
Retrieves the canonical tile at the specified index in the workspace.
std::size_t anim_start_offset() const
Returns the starting absolute index for animation tiles.
static TilesPngWorkspace for_secondary(const Image< IndexPixel > &primary_tiles_png, std::size_t primary_tile_count, std::size_t total_capacity)
Creates a workspace pre-loaded with primary tiles for secondary tileset compilation.
std::optional< std::size_t > first_occurrence_of_by_color(const CanonicalPixelTile< IndexPixel > &tile, const Palette< Rgba32, palette::max_size > &palette) const
Finds the first occurrence of a tile using color-equivalence comparison.
void place_tiles_at(std::size_t start_index, const std::vector< CanonicalPixelTile< IndexPixel > > &tiles)
Places tiles at specific positions for patch mode animation placement.
bool has_anim_slots() const
Returns whether animation slots have been reserved.
std::optional< std::size_t > first_occurrence_of(const CanonicalPixelTile< IndexPixel > &tile) const
Finds the first occurrence index of a given canonical tile in the workspace.
std::optional< std::size_t > find_existing_contiguous_tiles_by_color(const std::vector< CanonicalPixelTile< IndexPixel > > &tiles, const std::vector< const Palette< Rgba32, palette::max_size > * > &palettes) const
Checks if a sequence of tiles already exists contiguously using color-equivalence comparison.
static TilesPngWorkspace for_standalone_secondary(std::size_t primary_tile_count, std::size_t total_capacity)
Creates a workspace for standalone secondary compilation with no paired primary.
void place_anim_tile(std::size_t reserved_index, const CanonicalPixelTile< IndexPixel > &tile)
Places an animation keyframe tile at a specific reserved index.
void reserve_anim_slots(std::size_t anim_tile_count, std::size_t start_offset=1)
Reserves contiguous slots for animation keyframe tiles starting at start_offset.
std::size_t insert_tile(const CanonicalPixelTile< IndexPixel > &tile)
Attempts to insert a non-transparent tile into the workspace at the current cursor position.
Image< IndexPixel > export_image(ExportFlipMode flip_mode=ExportFlipMode::canonical, ExportTrimMode trim_mode=ExportTrimMode::trim_trailing_transparent) const
Exports the workspace tiles to an Image<IndexPixel> in tiles.png format.
constexpr std::size_t side_length_pix
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
ExportTrimMode
Defines how trailing transparent tiles should be handled during image export.
@ trim_trailing_transparent
Trim trailing transparent tiles from the exported image.
ExportFlipMode
Defines whether flip transformations should be applied during image export.
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)...