Porytiles
Loading...
Searching...
No Matches
tiles_png_workspace.cpp
Go to the documentation of this file.
2
3#include "fmt/format.h"
4
7
8namespace {
9
10using namespace porytiles2;
11
20Image<IndexPixel> export_image_helper(const TilesPngWorkspace &workspace, bool apply_flips)
21{
22 // Standard tiles.png format: 16 tiles per row (128 pixels wide)
23 const std::size_t tiles_per_row = 16;
24
25 // Calculate number of tile rows needed (ceiling division)
26 const std::size_t tiles_per_col = (workspace.capacity() + tiles_per_row - 1) / tiles_per_row;
27
28 // Calculate image dimensions
29 const std::size_t image_width = tiles_per_row * tile::side_length_pix;
30 const std::size_t image_height = tiles_per_col * tile::side_length_pix;
31
32 // Create output image
33 Image<IndexPixel> img{image_width, image_height};
34
35 // Copy each tile's pixels into the image
36 for (std::size_t tile_idx = 0; tile_idx < workspace.capacity(); ++tile_idx) {
37 // Calculate tile position in the grid
38 const std::size_t tile_row = tile_idx / tiles_per_row;
39 const std::size_t tile_col = tile_idx % tiles_per_row;
40
41 // Calculate pixel offsets for this tile
42 const std::size_t pixel_row_offset = tile_row * tile::side_length_pix;
43 const std::size_t pixel_col_offset = tile_col * tile::side_length_pix;
44
45 // Get the tile at this index
46 const auto &canonical_tile = workspace.tile_at(tile_idx);
47
48 // Determine which tile form to use
49 // Get base class reference to avoid implicit slicing
50 const PixelTile<IndexPixel> &canonical_base = canonical_tile;
51
52 PixelTile<IndexPixel> tile_to_export;
53 if (apply_flips) {
54 // Apply flip transformations to restore original form
55 tile_to_export = canonical_base.flip(canonical_tile.h_flip(), canonical_tile.v_flip());
56 }
57 else {
58 // Use canonical form as-is (explicit base class assignment)
59 tile_to_export = canonical_base;
60 }
61
62 // Copy all pixels from the tile to the image
63 for (std::size_t pixel_row = 0; pixel_row < tile::side_length_pix; ++pixel_row) {
64 for (std::size_t pixel_col = 0; pixel_col < tile::side_length_pix; ++pixel_col) {
65 const std::size_t dest_row = pixel_row_offset + pixel_row;
66 const std::size_t dest_col = pixel_col_offset + pixel_col;
67 img.set(dest_row, dest_col, tile_to_export.at(pixel_row, pixel_col));
68 }
69 }
70 }
71
72 return img;
73}
74
75} // namespace
76
77namespace porytiles2 {
78
79TilesPngWorkspace::TilesPngWorkspace(std::size_t capacity) : cursor_{1}, capacity_{capacity}
80{
81 // Initialize tiles_ vector with capacity number of transparent tiles
82 // Default-constructed IndexPixel is IndexPixel(0), which is transparent
83 PixelTile<IndexPixel> transparent_pixel_tile;
84 CanonicalPixelTile transparent_tile{transparent_pixel_tile};
85
86 tiles_.resize(capacity, transparent_tile);
87}
88
90 : cursor_{1}, capacity_{capacity}
91{
92 /*
93 * TODO: what should we do if tile 0 is not transparent? This is the pokeemerald convention but it's possible we
94 * could run into a tileset in the wild where this is the case. E.g. unit test SecondConstructorShouldLoadValidImage
95 * showcases this. We'd want to warn the user somehow? Maybe we can throw this warning further up the stack in the
96 * application?
97 */
98
99 const PlainTextFormatter formatter{};
100
101 // Precondition: image dimensions are multiples of 8
102 if (img.width() % tile::side_length_pix != 0 || img.height() % tile::side_length_pix != 0) {
103 const auto msg = formatter.format(
104 "Image dimensions must be a multiple of {}, got {}x{}", tile::side_length_pix, img.width(), img.height());
105 panic(msg);
106 }
107
108 // Calculate total tiles in image
109 const std::size_t tiles_per_row = img.width() / tile::side_length_pix;
110 const std::size_t tiles_per_col = img.height() / tile::side_length_pix;
111 const std::size_t total_tiles = tiles_per_row * tiles_per_col;
112
113 // Panic if image contains more tiles than capacity
114 if (total_tiles > capacity) {
115 const auto msg = formatter.format("Image contains {} tiles but capacity is only {}", total_tiles, capacity);
116 panic(msg);
117 }
118
119 // Reserve space for tiles
120 tiles_.reserve(capacity);
121
122 // Extract each 8x8 tile from the image
123 for (std::size_t tile_row = 0; tile_row < tiles_per_col; ++tile_row) {
124 for (std::size_t tile_col = 0; tile_col < tiles_per_row; ++tile_col) {
125 // Create a PixelTile to hold the extracted tile data
126 PixelTile<IndexPixel> pixel_tile;
127
128 // Calculate pixel offsets for this tile
129 const std::size_t pixel_row_offset = tile_row * tile::side_length_pix;
130 const std::size_t pixel_col_offset = tile_col * tile::side_length_pix;
131
132 // Copy pixels from source image to tile
133 for (std::size_t pixel_row = 0; pixel_row < tile::side_length_pix; ++pixel_row) {
134 for (std::size_t pixel_col = 0; pixel_col < tile::side_length_pix; ++pixel_col) {
135 const std::size_t src_row = pixel_row_offset + pixel_row;
136 const std::size_t src_col = pixel_col_offset + pixel_col;
137 pixel_tile.set(pixel_row, pixel_col, img.at(src_row, src_col));
138 }
139 }
140
141 // Create canonical version of the tile
142 CanonicalPixelTile canonical_tile{pixel_tile};
143 tiles_.push_back(canonical_tile);
144
145 // Add non-transparent tiles to canonical_forms_ map
146 if (!canonical_tile.is_transparent()) {
147 const std::size_t tile_index = tiles_.size() - 1;
148 const PixelTile<IndexPixel> &base_tile = canonical_tile;
149 canonical_forms_[base_tile].push_back(tile_index);
150 }
151 }
152 }
153
154 // Pad tiles_ vector to capacity with transparent tiles
155 PixelTile<IndexPixel> transparent_pixel_tile;
156 CanonicalPixelTile transparent_tile{transparent_pixel_tile};
157 tiles_.resize(capacity, transparent_tile);
158
159 // Set cursor to first transparent tile after tile 0
160 cursor_ = 1;
161 while (cursor_ < capacity && !tiles_[cursor_].is_transparent()) {
162 cursor_++;
163 }
164}
165
167{
168 // Check if we're at capacity
169 if (cursor_ >= capacity_) {
170 return false;
171 }
172
173 // No point inserting a transparent tile
174 if (tile.is_transparent()) {
175 return false;
176 }
177
178 // Insert tile at cursor position
179 tiles_[cursor_] = tile;
180
181 // Add to canonical_forms_ map
182 const PixelTile<IndexPixel> &base_tile = tile;
183 canonical_forms_[base_tile].push_back(cursor_);
184
185 // Fast-forward cursor to next transparent tile
186 cursor_++;
187 while (cursor_ < capacity_ && !tiles_[cursor_].is_transparent()) {
188 cursor_++;
189 }
190
191 return true;
192}
193
195{
196 const PixelTile<IndexPixel> &base_tile = tile;
197 auto it = canonical_forms_.find(base_tile);
198 if (it != canonical_forms_.end() && !it->second.empty()) {
199 return it->second.front();
200 }
201 return std::nullopt;
202}
203
205{
206 if (index >= tiles_.size()) {
207 panic("index " + std::to_string(index) + " >= size " + std::to_string(tiles_.size()));
208 }
209 return tiles_.at(index);
210}
211
213{
214 return export_image_helper(*this, false);
215}
216
218{
219 return export_image_helper(*this, true);
220}
221
223{
224 return cursor_ == capacity_;
225}
226
227} // namespace porytiles2
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:24
std::size_t width() const
Gets the width of this image in pixels.
Definition image.hpp:108
PixelType at(std::size_t i) const
Fetches the pixel value at a given one-dimensional pixel index.
Definition image.hpp:45
std::size_t height() const
Gets the height of this image in pixels.
Definition image.hpp:118
An 8x8 tile backed by literal-array-based per-pixel storage of an arbitrary pixel type.
PixelTile flip(bool h_flip, bool v_flip) const
Creates a flipped copy of this PixelTile.
void set(std::size_t i, const PixelType &p)
PixelType at(std::size_t i) const
bool is_transparent() const
Checks if this entire PixelTile is transparent (intrinsic transparency only).
TextFormatter implementation that strips all styling from text.
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 insert_tile(const CanonicalPixelTile< IndexPixel > &tile)
Attempts to insert a non-transparent tile into the workspace at the current cursor position.
bool at_capacity() const
Checks if the workspace has reached capacity and can no longer accept new tile insertions.
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.
Image< IndexPixel > export_canonical_image() const
Exports the workspace tiles to an Image<IndexPixel> in canonical form (tiles.png format).
CanonicalPixelTile< IndexPixel > tile_at(std::size_t index) const
Retrieves the canonical tile at the specified index in the workspace.
TilesPngWorkspace(std::size_t capacity)
Constructs a TilesPngWorkspace with a specified capacity, initializing all slots with transparent til...
Image< IndexPixel > export_original_image() const
Exports the workspace tiles to an Image<IndexPixel> in original (pre-canonicalization) form.
std::size_t capacity() const
Returns the maximum number of tiles this workspace can hold.
constexpr std::size_t side_length_pix
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.hpp:53