Porytiles
Loading...
Searching...
No Matches
layer_image_metatileizer.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4#include <tuple>
5#include <vector>
6
7#include "fmt/format.h"
8
15
16namespace porytiles2 {
17
30template <typename T>
32 public:
52 metatileize(const Image<T> &bottom, const Image<T> &middle, const Image<T> &top) const
53 {
54 // Validate that all images have the same dimensions
55 if (bottom.width() != middle.width() || bottom.height() != middle.height() || bottom.width() != top.width() ||
56 bottom.height() != top.height()) {
57 return FormattableError{fmt::format(
58 "layer images have mismatched dimensions: bottom={}x{}, middle={}x{}, top={}x{}",
59 bottom.width(),
60 bottom.height(),
61 middle.width(),
62 middle.height(),
63 top.width(),
64 top.height())};
65 }
66
67 // Tileize each layer image
68 const auto bottom_tiles_result = tileizer_.tileize(bottom);
69 if (!bottom_tiles_result.has_value()) {
71 FormattableError{"failed to tileize bottom layer"}, bottom_tiles_result};
72 }
73 const auto &bottom_tiles = bottom_tiles_result.value();
74
75 const auto middle_tiles_result = tileizer_.tileize(middle);
76 if (!middle_tiles_result.has_value()) {
78 FormattableError{"failed to tileize middle layer"}, middle_tiles_result};
79 }
80 const auto &middle_tiles = middle_tiles_result.value();
81
82 const auto top_tiles_result = tileizer_.tileize(top);
83 if (!top_tiles_result.has_value()) {
85 FormattableError{"failed to tileize top layer"}, top_tiles_result};
86 }
87 const auto &top_tiles = top_tiles_result.value();
88
89 /*
90 * Validate that dimensions are multiples of metatile size. We already validated that all image dimensions are
91 * identical, so we can just check bottom here as a surrogate for the other two layers. Additionally, we already
92 * checked in the tileization step if the image dimensions were a multiple of 8. Now, we check that the image
93 * dimensions are a multiple of 16 to confirm that it can be correctly metatileized.
94 */
95 if (bottom.width() % metatile::side_length_pix != 0 || bottom.height() % metatile::side_length_pix != 0) {
96 return FormattableError{fmt::format(
97 "image dimensions must be multiples of {}, got {}x{}",
99 bottom.width(),
100 bottom.height())};
101 }
102
103 const std::size_t metatiles_per_row = bottom.width() / metatile::side_length_pix;
104 const std::size_t metatiles_per_col = bottom.height() / metatile::side_length_pix;
105 const std::size_t total_metatiles = metatiles_per_row * metatiles_per_col;
106
107 std::vector<Metatile<T>> metatiles;
108 metatiles.reserve(total_metatiles);
109
110 const std::size_t tiles_per_image_row = bottom.width() / tile::side_length_pix;
111
112 // Process each 16x16 metatile region
113 for (std::size_t metatile_row = 0; metatile_row < metatiles_per_col; ++metatile_row) {
114 for (std::size_t metatile_col = 0; metatile_col < metatiles_per_row; ++metatile_col) {
115 Metatile<T> metatile;
116 populate_metatile_at_position(
117 metatile, bottom_tiles, middle_tiles, top_tiles, metatile_row, metatile_col, tiles_per_image_row);
118 metatiles.push_back(std::move(metatile));
119 }
120 }
121
122 return metatiles;
123 }
124
143 demetatileize(const std::vector<Metatile<T>> &metatiles, std::size_t metatiles_per_row) const
144 {
145 // Validate input parameters
146 if (metatiles_per_row == 0) {
147 panic("metatiles_per_row must be greater than zero");
148 }
149
150 if (metatiles.empty()) {
151 return FormattableError{"input metatiles vector was empty"};
152 }
153
154 // Compute metatiles_per_col using ceiling division
155 const std::size_t metatiles_per_col = (metatiles.size() + metatiles_per_row - 1) / metatiles_per_row;
156
157 // Calculate image dimensions
158 const std::size_t image_width = metatiles_per_row * metatile::side_length_pix;
159 const std::size_t image_height = metatiles_per_col * metatile::side_length_pix;
160
161 // Create the three layer images
162 Image<T> bottom_image{image_width, image_height};
163 Image<T> middle_image{image_width, image_height};
164 Image<T> top_image{image_width, image_height};
165
166 // Process each metatile position in the grid
167 for (std::size_t metatile_row = 0; metatile_row < metatiles_per_col; ++metatile_row) {
168 for (std::size_t metatile_col = 0; metatile_col < metatiles_per_row; ++metatile_col) {
169 const std::size_t metatile_idx = metatile_row * metatiles_per_row + metatile_col;
170
171 // Check if we have a metatile for this position, or if we need to pad with empty pixels
172 if (metatile_idx < metatiles.size()) {
173 const auto &metatile = metatiles[metatile_idx];
174 copy_metatile_to_images(
175 metatile, bottom_image, middle_image, top_image, metatile_row, metatile_col);
176 }
177 else {
178 fill_region_with_transparent(bottom_image, middle_image, top_image, metatile_row, metatile_col);
179 }
180 }
181 }
182
183 return std::make_tuple(std::move(bottom_image), std::move(middle_image), std::move(top_image));
184 }
185
186 private:
187 ImageTileizer<T> tileizer_;
188
189 static void populate_metatile_at_position(
190 Metatile<T> &metatile,
191 const std::vector<PixelTile<T>> &bottom_tiles,
192 const std::vector<PixelTile<T>> &middle_tiles,
193 const std::vector<PixelTile<T>> &top_tiles,
194 std::size_t metatile_row,
195 std::size_t metatile_col,
196 std::size_t tiles_per_image_row)
197 {
198 for (std::size_t tile_idx = 0; tile_idx < metatile::tiles_per_metatile_layer; ++tile_idx) {
199 // Calculate tile position within the metatile
200 const std::size_t tile_row = tile_idx / metatile::tiles_per_side;
201 const std::size_t tile_col = tile_idx % metatile::tiles_per_side;
202
203 // Calculate which tile index we need from the tileized arrays
204 const std::size_t global_tile_row = metatile_row * metatile::tiles_per_side + tile_row;
205 const std::size_t global_tile_col = metatile_col * metatile::tiles_per_side + tile_col;
206 const std::size_t global_tile_idx = global_tile_row * tiles_per_image_row + global_tile_col;
207
208 // Set tiles in the metatile from the tileized arrays
209 metatile.set_bottom(tile_idx, bottom_tiles[global_tile_idx]);
210 metatile.set_middle(tile_idx, middle_tiles[global_tile_idx]);
211 metatile.set_top(tile_idx, top_tiles[global_tile_idx]);
212 }
213 }
214
215 static void copy_metatile_to_images(
216 const Metatile<T> &metatile,
217 Image<T> &bottom_image,
218 Image<T> &middle_image,
219 Image<T> &top_image,
220 std::size_t metatile_row,
221 std::size_t metatile_col)
222 {
223 // Extract tiles from this metatile and place them in the appropriate image positions
224 for (std::size_t tile_idx = 0; tile_idx < metatile::tiles_per_metatile_layer; ++tile_idx) {
225 // Calculate tile position within the metatile
226 const std::size_t tile_row = tile_idx / metatile::tiles_per_side;
227 const std::size_t tile_col = tile_idx % metatile::tiles_per_side;
228
229 // Calculate the starting pixel position for this tile in the image
230 const std::size_t start_pixel_row =
231 metatile_row * metatile::side_length_pix + tile_row * tile::side_length_pix;
232 const std::size_t start_pixel_col =
233 metatile_col * metatile::side_length_pix + tile_col * tile::side_length_pix;
234
235 // Copy pixels from each layer's tile to the corresponding image
236 const auto &bottom_tile = metatile.bottom(tile_idx);
237 const auto &middle_tile = metatile.middle(tile_idx);
238 const auto &top_tile = metatile.top(tile_idx);
239
240 for (std::size_t pixel_row = 0; pixel_row < tile::side_length_pix; ++pixel_row) {
241 for (std::size_t pixel_col = 0; pixel_col < tile::side_length_pix; ++pixel_col) {
242 const std::size_t image_row = start_pixel_row + pixel_row;
243 const std::size_t image_col = start_pixel_col + pixel_col;
244
245 bottom_image.set(image_row, image_col, bottom_tile.at(pixel_row, pixel_col));
246 middle_image.set(image_row, image_col, middle_tile.at(pixel_row, pixel_col));
247 top_image.set(image_row, image_col, top_tile.at(pixel_row, pixel_col));
248 }
249 }
250 }
251 }
252
253 static void fill_region_with_transparent(
254 Image<T> &bottom_image,
255 Image<T> &middle_image,
256 Image<T> &top_image,
257 std::size_t metatile_row,
258 std::size_t metatile_col)
259 {
260 const T transparent_pixel{};
261
262 // Fill the entire 16x16 region for this metatile position with transparent pixels
263 for (std::size_t pixel_row = 0; pixel_row < metatile::side_length_pix; ++pixel_row) {
264 for (std::size_t pixel_col = 0; pixel_col < metatile::side_length_pix; ++pixel_col) {
265 const std::size_t image_row = metatile_row * metatile::side_length_pix + pixel_row;
266 const std::size_t image_col = metatile_col * metatile::side_length_pix + pixel_col;
267
268 bottom_image.set(image_row, image_col, transparent_pixel);
269 middle_image.set(image_row, image_col, transparent_pixel);
270 top_image.set(image_row, image_col, transparent_pixel);
271 }
272 }
273 }
274};
275
276} // namespace porytiles2
A result type that maintains a chainable sequence of errors for debugging and error reporting.
General-purpose error implementation with formatted message support.
Definition error.hpp:117
Service for converting images into collections of 8x8 tiles.
A template for two-dimensional images with arbitrarily typed pixel values.
Definition image.hpp:24
Service for converting layer images into collections of metatiles.
ChainableResult< std::tuple< Image< T >, Image< T >, Image< T > > > demetatileize(const std::vector< Metatile< T > > &metatiles, std::size_t metatiles_per_row) const
Converts a vector of metatiles back into three separate layer images.
ChainableResult< std::vector< Metatile< T > > > metatileize(const Image< T > &bottom, const Image< T > &middle, const Image< T > &top) const
Converts three layer images into a vector of metatiles.
The core tileset entity - a 2x2 grid of PixelTile objects arranged into three layers.
Definition metatile.hpp:95
void set_middle(std::size_t i, PixelTile< PixelType > tile)
Set a PixelTile in the middle layer.
Definition metatile.hpp:250
void set_top(std::size_t i, PixelTile< PixelType > tile)
Set a Tile in the top layer.
Definition metatile.hpp:297
void set_bottom(std::size_t i, PixelTile< PixelType > tile)
Set a PixelTile in the bottom layer.
Definition metatile.hpp:203
An 8x8 tile backed by literal-array-based per-pixel storage of an arbitrary pixel type.
constexpr std::size_t tiles_per_metatile_layer
Definition metatile.hpp:14
constexpr std::size_t side_length_pix
Definition metatile.hpp:16
constexpr std::size_t tiles_per_side
Definition metatile.hpp:13
constexpr std::size_t side_length_pix
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.hpp:53