Porytiles
Loading...
Searching...
No Matches
png_indexed_image_saver.cpp
Go to the documentation of this file.
2
3#include <filesystem>
4#include <format>
5#include <type_traits>
6
7#include "png++/png.hpp"
8
13
14namespace porytiles {
15
17 const Image<IndexPixel> &image, const std::filesystem::path &path, TilesPalMode mode) const
18{
19 using enum TilesPalMode;
20
21 const auto greyscale_pal = standard_greyscale_pal();
22
23 // Bail if given path exists already and isn't a file (i.e. it's a directory)
24 if (exists(path) && !is_regular_file(path)) {
25 return FormattableError{std::format("{}: exists but is not a file", path.filename().c_str())};
26 }
27
28 // Determine which palette to use
29 std::vector<Rgba32> palette_to_use;
30 if (mode == true_color && image.palette().has_value()) {
31 palette_to_use = image.palette().value();
32 }
33 else {
34 // Use greyscale palette for greyscale mode OR when true_color mode but no image palette exists
35 palette_to_use = greyscale_pal;
36 }
37
38 // Set up PNG palette
39 png::palette png_pal{0};
40 for (const auto &color : palette_to_use) {
41 png_pal.emplace_back(color.red(), color.green(), color.blue());
42 }
43
44 // Generic lambda to write indexed PNG with any pixel type.
45 // For 4-bit PNGs, we explicitly extract the lower 4 bits (color_index) to be self-documenting.
46 // For 8-bit PNGs, we use the full index value which may encode both palette and color index.
47 auto write_image = [&]<typename PixelType>(png::image<PixelType> &img) {
48 img.set_palette(png_pal);
49 for (std::size_t pixel_index = 0; pixel_index < image.size(); pixel_index++) {
50 const auto row = pixel_index / image.width();
51 const auto col = pixel_index % image.width();
52 if constexpr (std::is_same_v<PixelType, png::index_pixel_4>) {
53 // 4-bit PNG: extract only the lower 4 bits (color index within palette)
54 img[row][col] = image.at(pixel_index).color_index();
55 }
56 else {
57 // 8-bit PNG: preserve full value (may include palette index in upper 4 bits for true-color mode)
58 img[row][col] = image.at(pixel_index).index();
59 }
60 }
61 img.write(path);
62 };
63
64 // Write PNG to filesystem using the appropriate pixel type for mode
65 try {
66 // If internal palette is size 16 or less, use 4-bit PNG palette
67 if (palette_to_use.size() <= 16) {
68 png::image<png::index_pixel_4> out{image.width(), image.height()};
69 write_image(out);
70 }
71 else {
72 png::image<png::index_pixel> out{image.width(), image.height()};
73 write_image(out);
74 }
75 }
76 catch (const std::exception &e) {
77 return FormattableError{std::format("{}: save failed: {}", path.filename().c_str(), e.what())};
78 }
79
80 return {};
81}
82
83} // namespace porytiles
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:63
A template for two-dimensional images with arbitrarily typed pixel values.
Definition image.hpp:23
std::size_t size() const
Definition image.hpp:124
std::size_t width() const
Definition image.hpp:114
const std::optional< std::vector< Rgba32 > > & palette() const
Definition image.hpp:150
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
virtual ChainableResult< void > save_to_file(const Image< IndexPixel > &image, const std::filesystem::path &path, TilesPalMode mode) const
TilesPalMode
Controls how tiles.png is rendered.
@ true_color
Render tiles with true colors from the palette.
std::vector< Rgba32 > standard_greyscale_pal()
Returns the standard 16-color greyscale palette used for indexed tile output.
Definition rgba32.hpp:152