32bool tiles_color_equivalent(
38 constexpr Rgba32 extrinsic{};
45 return canonical_expected_pixel == canonical_actual_pixel;
67 const std::size_t tile_count = end_tile - start_tile;
70 const std::size_t tiles_per_col = (tile_count + tiles_per_row - 1) / tiles_per_row;
80 for (std::size_t i = 0; i < tile_count; ++i) {
81 const std::size_t tile_idx = start_tile + i;
84 const std::size_t tile_row = i / tiles_per_row;
85 const std::size_t tile_col = i % tiles_per_row;
92 const auto &canonical_tile = workspace.
tile_at(tile_idx);
98 if (flip_mode == ExportFlipMode::original) {
99 tile_to_export = canonical_base.
flip(canonical_tile.h_flip(), canonical_tile.v_flip());
102 tile_to_export = canonical_base;
108 const std::size_t dest_row = pixel_row_offset + pixel_row;
109 const std::size_t dest_col = pixel_col_offset + pixel_col;
110 img.
set(dest_row, dest_col, tile_to_export.
at(pixel_row, pixel_col));
126std::size_t find_last_non_transparent(
const TilesPngWorkspace &workspace, std::size_t scan_start, std::size_t minimum)
128 for (std::size_t i = scan_start; i > minimum; --i) {
129 const std::size_t idx = i - 1;
130 if (!workspace.
tile_at(idx).is_transparent()) {
141void TilesPngWorkspace::advance_cursor_to_next_transparent()
144 while (cursor_ < capacity_ && !tiles_[cursor_].is_transparent()) {
156 tiles_.resize(
capacity, transparent_tile);
160 : cursor_{1}, capacity_{capacity}
166 const auto msg = formatter.
format(
174 const std::size_t total_tiles = tiles_per_row * tiles_per_col;
178 const auto msg = formatter.format(
"Image contains {} tiles but capacity is only {}", total_tiles,
capacity);
186 for (std::size_t tile_row = 0; tile_row < tiles_per_col; ++tile_row) {
187 for (std::size_t tile_col = 0; tile_col < tiles_per_row; ++tile_col) {
198 const std::size_t src_row = pixel_row_offset + pixel_row;
199 const std::size_t src_col = pixel_col_offset + pixel_col;
200 pixel_tile.
set(pixel_row, pixel_col, img.
at(src_row, src_col));
206 tiles_.push_back(canonical_tile);
209 if (!canonical_tile.is_transparent()) {
210 const std::size_t tile_index = tiles_.size() - 1;
212 canonical_forms_[base_tile].push_back(tile_index);
220 tiles_.resize(
capacity, transparent_tile);
225 advance_cursor_to_next_transparent();
229 const Image<IndexPixel> &primary_tiles_png, std::size_t primary_tile_count, std::size_t total_capacity)
234 if (primary_tile_count >= total_capacity) {
235 const auto msg = formatter.
format(
236 "primary_tile_count ({}) must be less than total_capacity ({})", primary_tile_count, total_capacity);
243 const auto msg = formatter.format(
244 "Primary tiles.png dimensions must be a multiple of {}, got {}x{}",
246 primary_tiles_png.
width(),
247 primary_tiles_png.
height());
257 const std::size_t tiles_in_image = tiles_per_row * tiles_per_col;
258 const std::size_t tiles_to_load = std::min(tiles_in_image, primary_tile_count);
261 for (std::size_t tile_idx = 0; tile_idx < tiles_to_load; ++tile_idx) {
262 const std::size_t tile_row = tile_idx / tiles_per_row;
263 const std::size_t tile_col = tile_idx % tiles_per_row;
271 const std::size_t src_row = pixel_row_offset + pixel_row;
272 const std::size_t src_col = pixel_col_offset + pixel_col;
279 const IndexPixel true_color_pixel = primary_tiles_png.
at(src_row, src_col);
285 workspace.tiles_.at(tile_idx) = canonical_tile;
288 if (!canonical_tile.is_transparent()) {
290 workspace.canonical_forms_[base_tile].push_back(tile_idx);
296 workspace.cursor_ = primary_tile_count + 1;
297 workspace.anim_start_offset_ = primary_tile_count + 1;
298 workspace.anim_end_offset_ = primary_tile_count + 1;
309 if (primary_tile_count >= total_capacity) {
310 const auto msg = formatter.
format(
311 "primary_tile_count ({}) must be less than total_capacity ({})", primary_tile_count, total_capacity);
320 workspace.cursor_ = primary_tile_count + 1;
321 workspace.anim_start_offset_ = primary_tile_count + 1;
322 workspace.anim_end_offset_ = primary_tile_count + 1;
330 if (cursor_ >= capacity_) {
331 panic(
"TilesPngWorkspace is at capacity");
340 tiles_[cursor_] = tile;
341 const std::size_t old_cursor = cursor_;
345 canonical_forms_[base_tile].push_back(cursor_);
348 advance_cursor_to_next_transparent();
356 auto it = canonical_forms_.find(base_tile);
357 if (it != canonical_forms_.end() && !it->second.empty()) {
358 return it->second.front();
372 for (std::size_t i = 1; i < capacity_; ++i) {
373 if (tiles_[i].is_transparent()) {
377 if (tiles_color_equivalent(tile_base, workspace_tile_base, palette)) {
386 if (index >= tiles_.size()) {
387 panic(
"index " + std::to_string(index) +
" >= size " + std::to_string(tiles_.size()));
389 return tiles_.at(index);
394 std::size_t end_tile = capacity_;
396 end_tile = find_last_non_transparent(*
this, capacity_, 0) + 1;
398 return export_image_range(*
this, 0, end_tile, flip_mode);
404 std::size_t end_tile = capacity_;
406 const std::size_t last_non_transparent = find_last_non_transparent(*
this, capacity_, primary_tile_count);
408 end_tile = std::max(last_non_transparent, primary_tile_count) + 1;
410 return export_image_range(*
this, primary_tile_count, end_tile, flip_mode);
415 return cursor_ == capacity_;
423 if (cursor_ != start_offset) {
424 const auto msg = formatter.
format(
425 "reserve_anim_slots: cursor ({}) must be at start_offset ({}) before reserving animation slots",
432 if (start_offset + anim_tile_count >= capacity_) {
433 const auto msg = formatter.format(
434 "start_offset ({}) + anim_tile_count ({}) must be less than capacity ({}).",
442 anim_start_offset_ = start_offset;
443 anim_end_offset_ = start_offset + anim_tile_count;
446 cursor_ = anim_end_offset_;
455 panic(
"place_animation_tile called but no animation slots were reserved");
462 if (absolute_index >= anim_end_offset_) {
463 const auto msg = formatter.format(
464 "reserved_index ({}) is out of bounds for animation region (max: {})",
471 tiles_[absolute_index] = tile;
476 canonical_forms_[base_tile].push_back(absolute_index);
488 std::size_t run_start = 0;
489 std::size_t run_length = 0;
491 for (std::size_t i = 1; i < capacity_; ++i) {
492 if (tiles_[i].is_transparent()) {
493 if (run_length == 0) {
498 if (run_length >= count) {
522 if (tiles.size() != palettes.size()) {
523 panic(
"tiles and palettes vectors must have the same size");
528 for (std::size_t candidate_start = 1; candidate_start < capacity_; ++candidate_start) {
530 if (candidate_start + tiles.size() > capacity_) {
535 bool all_match =
true;
536 for (std::size_t offset = 0; offset < tiles.size(); ++offset) {
537 const std::size_t check_index = candidate_start + offset;
540 const auto &palette = *palettes[offset];
542 if (!tiles_color_equivalent(expected_base, actual_base, palette)) {
549 return candidate_start;
563 if (start_index + tiles.size() > capacity_) {
564 const auto msg = formatter.
format(
565 "place_tiles_at: start_index ({}) + tiles.size() ({}) exceeds capacity ({})",
573 for (std::size_t offset = 0; offset < tiles.size(); ++offset) {
574 const std::size_t target_index = start_index + offset;
575 if (!tiles_[target_index].is_transparent()) {
577 formatter.format(
"place_tiles_at: position {} is not transparent, cannot place tile", target_index);
583 for (std::size_t offset = 0; offset < tiles.size(); ++offset) {
584 const std::size_t target_index = start_index + offset;
585 const auto &tile = tiles[offset];
587 tiles_[target_index] = tile;
590 if (!tile.is_transparent()) {
592 canonical_forms_[base_tile].push_back(target_index);
598 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, pal::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.
std::optional< std::size_t > find_existing_contiguous_tiles_by_color(const std::vector< CanonicalPixelTile< IndexPixel > > &tiles, const std::vector< const Palette< Rgba32, pal::max_size > * > &palettes) const
Checks if a sequence of tiles already exists contiguously using color-equivalence comparison.
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.
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 > 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).