Porytiles
Loading...
Searching...
No Matches
tiles_png_workspace.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
11
12namespace {
13
14using namespace porytiles;
15
32bool tiles_color_equivalent(
33 const PixelTile<IndexPixel> &expected,
34 const PixelTile<IndexPixel> &actual,
35 const Palette<Rgba32, pal::max_size> &palette)
36{
37 // Convert both tiles to color space to handle duplicate palette entries, then re-canonicalize
38 constexpr Rgba32 extrinsic{};
39 const auto expected_rgba = color_tile_from_index_tile(expected, palette, extrinsic);
40 const auto actual_rgba = color_tile_from_index_tile(actual, palette, extrinsic);
41 const CanonicalPixelTile canonical_expected{expected_rgba};
42 const CanonicalPixelTile canonical_actual{actual_rgba};
43 const PixelTile<Rgba32> &canonical_expected_pixel = canonical_expected;
44 const PixelTile<Rgba32> &canonical_actual_pixel = canonical_actual;
45 return canonical_expected_pixel == canonical_actual_pixel;
46}
47
61Image<IndexPixel> export_image_range(
62 const TilesPngWorkspace &workspace, std::size_t start_tile, std::size_t end_tile, ExportFlipMode flip_mode)
63{
64 // Standard tiles.png format: 16 tiles per row (128 pixels wide)
65 constexpr std::size_t tiles_per_row = metatile::metatiles_per_row * metatile::tiles_per_side;
66
67 const std::size_t tile_count = end_tile - start_tile;
68
69 // Calculate number of tile rows needed (ceiling division)
70 const std::size_t tiles_per_col = (tile_count + tiles_per_row - 1) / tiles_per_row;
71
72 // Calculate image dimensions
73 const std::size_t image_width = tiles_per_row * tile::side_length_pix;
74 const std::size_t image_height = tiles_per_col * tile::side_length_pix;
75
76 // Create output image
77 Image<IndexPixel> img{image_width, image_height};
78
79 // Copy each tile's pixels into the image
80 for (std::size_t i = 0; i < tile_count; ++i) {
81 const std::size_t tile_idx = start_tile + i;
82
83 // Calculate tile position in the output grid (relative to start_tile)
84 const std::size_t tile_row = i / tiles_per_row;
85 const std::size_t tile_col = i % tiles_per_row;
86
87 // Calculate pixel offsets for this tile
88 const std::size_t pixel_row_offset = tile_row * tile::side_length_pix;
89 const std::size_t pixel_col_offset = tile_col * tile::side_length_pix;
90
91 // Get the tile at this index
92 const auto &canonical_tile = workspace.tile_at(tile_idx);
93
94 // Determine which tile form to use
95 const PixelTile<IndexPixel> &canonical_base = canonical_tile;
96
97 PixelTile<IndexPixel> tile_to_export;
98 if (flip_mode == ExportFlipMode::original) {
99 tile_to_export = canonical_base.flip(canonical_tile.h_flip(), canonical_tile.v_flip());
100 }
101 else {
102 tile_to_export = canonical_base;
103 }
104
105 // Copy all pixels from the tile to the image
106 for (std::size_t pixel_row = 0; pixel_row < tile::side_length_pix; ++pixel_row) {
107 for (std::size_t pixel_col = 0; pixel_col < tile::side_length_pix; ++pixel_col) {
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));
111 }
112 }
113 }
114
115 return img;
116}
117
126std::size_t find_last_non_transparent(const TilesPngWorkspace &workspace, std::size_t scan_start, std::size_t minimum)
127{
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()) {
131 return idx;
132 }
133 }
134 return minimum;
135}
136
137} // namespace
138
139namespace porytiles {
140
141void TilesPngWorkspace::advance_cursor_to_next_transparent()
142{
143 cursor_++;
144 while (cursor_ < capacity_ && !tiles_[cursor_].is_transparent()) {
145 cursor_++;
146 }
147}
148
149TilesPngWorkspace::TilesPngWorkspace(std::size_t capacity) : cursor_{1}, capacity_{capacity}
150{
151 // Initialize tiles_ vector with capacity number of transparent tiles
152 // Default-constructed IndexPixel is IndexPixel(0), which is transparent
153 PixelTile<IndexPixel> transparent_pixel_tile;
154 CanonicalPixelTile transparent_tile{transparent_pixel_tile};
155
156 tiles_.resize(capacity, transparent_tile);
157}
158
160 : cursor_{1}, capacity_{capacity}
161{
162 const PlainTextFormatter formatter{};
163
164 // Precondition: image dimensions are multiples of 8
165 if (img.width() % tile::side_length_pix != 0 || img.height() % tile::side_length_pix != 0) {
166 const auto msg = formatter.format(
167 "Image dimensions must be a multiple of {}, got {}x{}", tile::side_length_pix, img.width(), img.height());
168 panic(msg);
169 }
170
171 // Calculate total tiles in image
172 const std::size_t tiles_per_row = img.width() / tile::side_length_pix;
173 const std::size_t tiles_per_col = img.height() / tile::side_length_pix;
174 const std::size_t total_tiles = tiles_per_row * tiles_per_col;
175
176 // Panic if image contains more tiles than capacity
177 if (total_tiles > capacity) {
178 const auto msg = formatter.format("Image contains {} tiles but capacity is only {}", total_tiles, capacity);
179 panic(msg);
180 }
181
182 // Reserve space for tiles
183 tiles_.reserve(capacity);
184
185 // Extract each 8x8 tile from the image
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) {
188 // Create a PixelTile to hold the extracted tile data
189 PixelTile<IndexPixel> pixel_tile;
190
191 // Calculate pixel offsets for this tile
192 const std::size_t pixel_row_offset = tile_row * tile::side_length_pix;
193 const std::size_t pixel_col_offset = tile_col * tile::side_length_pix;
194
195 // Copy pixels from source image to tile
196 for (std::size_t pixel_row = 0; pixel_row < tile::side_length_pix; ++pixel_row) {
197 for (std::size_t pixel_col = 0; pixel_col < tile::side_length_pix; ++pixel_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));
201 }
202 }
203
204 // Create canonical version of the tile
205 CanonicalPixelTile canonical_tile{pixel_tile};
206 tiles_.push_back(canonical_tile);
207
208 // Add non-transparent tiles to canonical_forms_ map
209 if (!canonical_tile.is_transparent()) {
210 const std::size_t tile_index = tiles_.size() - 1;
211 const PixelTile<IndexPixel> &base_tile = canonical_tile;
212 canonical_forms_[base_tile].push_back(tile_index);
213 }
214 }
215 }
216
217 // Pad tiles_ vector to capacity with transparent tiles
218 PixelTile<IndexPixel> transparent_pixel_tile;
219 CanonicalPixelTile transparent_tile{transparent_pixel_tile};
220 tiles_.resize(capacity, transparent_tile);
221
222 // Set cursor to first transparent tile after tile 0
223 // Start at 0 and let advance_cursor_to_next_transparent increment to 1 and scan forward
224 cursor_ = 0;
225 advance_cursor_to_next_transparent();
226}
227
229 const Image<IndexPixel> &primary_tiles_png, std::size_t primary_tile_count, std::size_t total_capacity)
230{
231 const PlainTextFormatter formatter{};
232
233 // Precondition: primary_tile_count must be less than 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);
237 panic(msg);
238 }
239
240 // Precondition: image dimensions are multiples of 8
241 if (primary_tiles_png.width() % tile::side_length_pix != 0 ||
242 primary_tiles_png.height() % tile::side_length_pix != 0) {
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());
248 panic(msg);
249 }
250
251 // Create empty workspace with total_capacity (all transparent, cursor=1)
252 TilesPngWorkspace workspace{total_capacity};
253
254 // Extract tiles from primary image
255 const std::size_t tiles_per_row = primary_tiles_png.width() / tile::side_length_pix;
256 const std::size_t tiles_per_col = primary_tiles_png.height() / tile::side_length_pix;
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);
259
260 // Load primary tiles into positions 0..tiles_to_load-1
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;
264
265 PixelTile<IndexPixel> pixel_tile;
266 const std::size_t pixel_row_offset = tile_row * tile::side_length_pix;
267 const std::size_t pixel_col_offset = tile_col * tile::side_length_pix;
268
269 for (std::size_t pixel_row = 0; pixel_row < tile::side_length_pix; ++pixel_row) {
270 for (std::size_t pixel_col = 0; pixel_col < tile::side_length_pix; ++pixel_col) {
271 const std::size_t src_row = pixel_row_offset + pixel_row;
272 const std::size_t src_col = pixel_col_offset + pixel_col;
273 /*
274 * Strip true-color encoding (upper nibble = palette index) to get raw color indices. Primary tiles.png
275 * stores pixels as (pal << 4 | color), but index_tile_from_color_tile() produces raw color indices
276 * (0-15). Using color_index() here ensures workspace tiles match what index_tile_from_color_tile()
277 * produces, enabling first_occurrence_of() deduplication.
278 */
279 const IndexPixel true_color_pixel = primary_tiles_png.at(src_row, src_col);
280 pixel_tile.set(pixel_row, pixel_col, IndexPixel{true_color_pixel.color_index()});
281 }
282 }
283
284 CanonicalPixelTile canonical_tile{pixel_tile};
285 workspace.tiles_.at(tile_idx) = canonical_tile;
286
287 // Register non-transparent primary tiles for deduplication
288 if (!canonical_tile.is_transparent()) {
289 const PixelTile<IndexPixel> &base_tile = canonical_tile;
290 workspace.canonical_forms_[base_tile].push_back(tile_idx);
291 }
292 }
293
294 // Position primary_tile_count stays transparent (vanilla secondary tile 0 convention)
295 // Set cursor and anim_start_offset_ to reflect the secondary workspace's logical state
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;
299
300 return workspace;
301}
302
304TilesPngWorkspace::for_standalone_secondary(std::size_t primary_tile_count, std::size_t total_capacity)
305{
306 const PlainTextFormatter formatter{};
307
308 // Precondition: primary_tile_count must be less than total_capacity
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);
312 panic(msg);
313 }
314
315 // Create empty workspace with total_capacity (all transparent, cursor=1)
316 TilesPngWorkspace workspace{total_capacity};
317
318 // No primary tiles to load or register in canonical_forms_.
319 // Position primary_tile_count stays transparent (vanilla secondary tile 0 convention).
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;
323
324 return workspace;
325}
326
328{
329 // Check if we're at capacity
330 if (cursor_ >= capacity_) {
331 panic("TilesPngWorkspace is at capacity");
332 }
333
334 // No point inserting a transparent tile
335 if (tile.is_transparent()) {
336 return 0;
337 }
338
339 // Insert tile at cursor position
340 tiles_[cursor_] = tile;
341 const std::size_t old_cursor = cursor_;
342
343 // Add to canonical_forms_ map
344 const PixelTile<IndexPixel> &base_tile = tile;
345 canonical_forms_[base_tile].push_back(cursor_);
346
347 // Fast-forward cursor to next transparent tile
348 advance_cursor_to_next_transparent();
349
350 return old_cursor;
351}
352
354{
355 const PixelTile<IndexPixel> &base_tile = tile;
356 auto it = canonical_forms_.find(base_tile);
357 if (it != canonical_forms_.end() && !it->second.empty()) {
358 return it->second.front();
359 }
360 return std::nullopt;
361}
362
364 const CanonicalPixelTile<IndexPixel> &tile, const Palette<Rgba32, pal::max_size> &palette) const
365{
366 if (tile.is_transparent()) {
367 return std::nullopt;
368 }
369
370 const PixelTile<IndexPixel> &tile_base = tile;
371
372 for (std::size_t i = 1; i < capacity_; ++i) {
373 if (tiles_[i].is_transparent()) {
374 continue;
375 }
376 const PixelTile<IndexPixel> &workspace_tile_base = tiles_[i];
377 if (tiles_color_equivalent(tile_base, workspace_tile_base, palette)) {
378 return i;
379 }
380 }
381 return std::nullopt;
382}
383
385{
386 if (index >= tiles_.size()) {
387 panic("index " + std::to_string(index) + " >= size " + std::to_string(tiles_.size()));
388 }
389 return tiles_.at(index);
390}
391
393{
394 std::size_t end_tile = capacity_;
396 end_tile = find_last_non_transparent(*this, capacity_, 0) + 1;
397 }
398 return export_image_range(*this, 0, end_tile, flip_mode);
399}
400
402 std::size_t primary_tile_count, ExportFlipMode flip_mode, ExportTrimMode trim_mode) const
403{
404 std::size_t end_tile = capacity_;
406 const std::size_t last_non_transparent = find_last_non_transparent(*this, capacity_, primary_tile_count);
407 // At minimum, export the secondary transparent tile at primary_tile_count
408 end_tile = std::max(last_non_transparent, primary_tile_count) + 1;
409 }
410 return export_image_range(*this, primary_tile_count, end_tile, flip_mode);
411}
412
414{
415 return cursor_ == capacity_;
416}
417
418void TilesPngWorkspace::reserve_anim_slots(std::size_t anim_tile_count, std::size_t start_offset)
419{
420 const PlainTextFormatter formatter{};
421
422 // Precondition: cursor must be at start_offset (no regular tiles inserted past that point)
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",
426 cursor_,
427 start_offset);
428 panic(msg);
429 }
430
431 // Precondition: animation region must fit in the workspace (with room for at least one regular tile)
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 ({}).",
435 start_offset,
436 anim_tile_count,
437 capacity_);
438 panic(msg);
439 }
440
441 // Store the animation region boundaries
442 anim_start_offset_ = start_offset;
443 anim_end_offset_ = start_offset + anim_tile_count;
444
445 // Move cursor past the reserved animation region
446 cursor_ = anim_end_offset_;
447}
448
449void TilesPngWorkspace::place_anim_tile(std::size_t reserved_index, const CanonicalPixelTile<IndexPixel> &tile)
450{
451 const PlainTextFormatter formatter{};
452
453 // Precondition: animation slots must have been reserved
454 if (!has_anim_slots()) {
455 panic("place_animation_tile called but no animation slots were reserved");
456 }
457
458 // reserved_index is 0-based within the reserved region
459 const std::size_t absolute_index = reserved_index + anim_start_offset();
460
461 // Precondition: reserved_index must be within the reserved region
462 if (absolute_index >= anim_end_offset_) {
463 const auto msg = formatter.format(
464 "reserved_index ({}) is out of bounds for animation region (max: {})",
465 reserved_index,
466 anim_end_offset_ - anim_start_offset() - 1);
467 panic(msg);
468 }
469
470 // Place the tile at the absolute index
471 tiles_[absolute_index] = tile;
472
473 // Add to canonical_forms_ map for deduplication support (skip transparent tiles)
474 if (!tile.is_transparent()) {
475 const PixelTile<IndexPixel> &base_tile = tile;
476 canonical_forms_[base_tile].push_back(absolute_index);
477 }
478}
479
480std::optional<std::size_t> TilesPngWorkspace::find_contiguous_transparent_slots(std::size_t count) const
481{
482 // Edge case: count of 0 is trivially satisfied at any position
483 if (count == 0) {
484 return 1; // Return first valid position after tile 0
485 }
486
487 // Scan from index 1 (skip reserved tile 0) looking for contiguous transparent runs
488 std::size_t run_start = 0;
489 std::size_t run_length = 0;
490
491 for (std::size_t i = 1; i < capacity_; ++i) {
492 if (tiles_[i].is_transparent()) {
493 if (run_length == 0) {
494 run_start = i;
495 }
496 run_length++;
497
498 if (run_length >= count) {
499 return run_start;
500 }
501 }
502 else {
503 // Non-transparent tile breaks the run
504 run_length = 0;
505 }
506 }
507
508 // No suitable run found
509 return std::nullopt;
510}
511
513 const std::vector<CanonicalPixelTile<IndexPixel>> &tiles,
514 const std::vector<const Palette<Rgba32, pal::max_size> *> &palettes) const
515{
516 // Edge case: empty sequence is trivially found
517 if (tiles.empty()) {
518 return 1; // Return first valid position after tile 0
519 }
520
521 // Precondition: parallel vectors must have same size
522 if (tiles.size() != palettes.size()) {
523 panic("tiles and palettes vectors must have the same size");
524 }
525
526 // Linear scan through workspace looking for contiguous match
527 // Start at index 1 (skip reserved tile 0)
528 for (std::size_t candidate_start = 1; candidate_start < capacity_; ++candidate_start) {
529 // Check if there's enough room for all tiles from this position
530 if (candidate_start + tiles.size() > capacity_) {
531 break; // No more valid starting positions
532 }
533
534 // Verify all tiles in sequence match using color-equivalence
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;
538 const PixelTile<IndexPixel> &expected_base = tiles[offset];
539 const PixelTile<IndexPixel> &actual_base = tiles_[check_index];
540 const auto &palette = *palettes[offset];
541
542 if (!tiles_color_equivalent(expected_base, actual_base, palette)) {
543 all_match = false;
544 break;
545 }
546 }
547
548 if (all_match) {
549 return candidate_start;
550 }
551 }
552
553 // No contiguous sequence found
554 return std::nullopt;
555}
556
558 std::size_t start_index, const std::vector<CanonicalPixelTile<IndexPixel>> &tiles)
559{
560 const PlainTextFormatter formatter{};
561
562 // Precondition: tiles must fit in workspace
563 if (start_index + tiles.size() > capacity_) {
564 const auto msg = formatter.format(
565 "place_tiles_at: start_index ({}) + tiles.size() ({}) exceeds capacity ({})",
566 start_index,
567 tiles.size(),
568 capacity_);
569 panic(msg);
570 }
571
572 // Precondition: all target positions must be transparent
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()) {
576 const auto msg =
577 formatter.format("place_tiles_at: position {} is not transparent, cannot place tile", target_index);
578 panic(msg);
579 }
580 }
581
582 // Place tiles and update canonical_forms_ map
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];
586
587 tiles_[target_index] = tile;
588
589 // Add to canonical_forms_ map (skip transparent tiles, though they shouldn't be in the input)
590 if (!tile.is_transparent()) {
591 const PixelTile<IndexPixel> &base_tile = tile;
592 canonical_forms_[base_tile].push_back(target_index);
593 }
594 }
595
596 // Advance cursor past any newly-filled positions if needed
597 // The cursor should skip over non-transparent tiles
598 while (cursor_ < capacity_ && !tiles_[cursor_].is_transparent()) {
599 cursor_++;
600 }
601}
602
603} // namespace porytiles
A PixelTile representation that stores the canonical (lexicographically minimal) orientation among al...
A template for two-dimensional images with arbitrarily typed pixel values.
Definition image.hpp:23
std::size_t width() const
Definition image.hpp:114
std::size_t height() const
Definition image.hpp:119
PixelType at(std::size_t i) const
Fetches the pixel value at a given one-dimensional pixel index.
Definition image.hpp:56
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.
Definition palette.hpp:47
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.
Definition rgba32.hpp:23
virtual std::string format(const std::string &format_str, const std::vector< FormatParam > &params) 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 tiles_per_side
Definition metatile.hpp:16
constexpr std::size_t metatiles_per_row
Definition metatile.hpp:22
constexpr std::size_t side_length_pix
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.cpp:43
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).