OpenTTD Source 20260208-master-g43af8e94d0
screenshot_png.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "stdafx.h"
11#include "core/math_func.hpp"
12#include "debug.h"
13#include "fileio_func.h"
14#include "screenshot_type.h"
15#include "3rdparty/fmt/ranges.h"
16
17#include <png.h>
18
19#ifdef PNG_TEXT_SUPPORTED
20#include "rev.h"
21#include "newgrf_config.h"
22#include "ai/ai_info.hpp"
23#include "company_base.h"
24#include "base_media_base.h"
25#include "base_media_graphics.h"
26#endif /* PNG_TEXT_SUPPORTED */
27
28#include "safeguards.h"
29
30class ScreenshotProvider_Png : public ScreenshotProvider {
31public:
32 ScreenshotProvider_Png() : ScreenshotProvider("png", "PNG", 0) {}
33
34 bool MakeImage(std::string_view name, const ScreenshotCallback &callb, uint w, uint h, int pixelformat, const Colour *palette) const override
35 {
36 png_color rq[256];
37 uint i, y, n;
38 uint maxlines;
39 uint bpp = pixelformat / 8;
40 png_structp png_ptr;
41 png_infop info_ptr;
42
43 /* only implemented for 8bit and 32bit images so far. */
44 if (pixelformat != 8 && pixelformat != 32) return false;
45
46 auto of = FileHandle::Open(name, "wb");
47 if (!of.has_value()) return false;
48 auto &f = *of;
49
50 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, &name, png_my_error, png_my_warning);
51
52 if (png_ptr == nullptr) {
53 return false;
54 }
55
56 info_ptr = png_create_info_struct(png_ptr);
57 if (info_ptr == nullptr) {
58 png_destroy_write_struct(&png_ptr, (png_infopp)nullptr);
59 return false;
60 }
61
62 if (setjmp(png_jmpbuf(png_ptr))) {
63 png_destroy_write_struct(&png_ptr, &info_ptr);
64 return false;
65 }
66
67 png_init_io(png_ptr, f);
68
69 png_set_filter(png_ptr, 0, PNG_FILTER_NONE);
70
71 png_set_IHDR(png_ptr, info_ptr, w, h, 8, pixelformat == 8 ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_RGB,
72 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
73
74#ifdef PNG_TEXT_SUPPORTED
75
76 /* Try to add some game metadata to the PNG screenshot so
77 * it's more useful for debugging and archival purposes. */
78 png_text_struct text[2]{};
79 text[0].key = const_cast<char *>("Software");
80 text[0].text = const_cast<char *>(_openttd_revision.c_str());
81 text[0].text_length = _openttd_revision.size();
82 text[0].compression = PNG_TEXT_COMPRESSION_NONE;
83
84 std::string message;
85 message.reserve(1024);
86 format_append(message, "Graphics set: {} ({})\n", BaseGraphics::GetUsedSet()->name, fmt::join(BaseGraphics::GetUsedSet()->version, "."));
87 message += "NewGRFs:\n";
88 if (_game_mode != GM_MENU) {
89 for (const auto &c : _grfconfig) {
90 format_append(message, "{:08X} {} {}\n", std::byteswap(c->ident.grfid), FormatArrayAsHex(c->ident.md5sum), c->filename);
91 }
92 }
93 message += "\nCompanies:\n";
94 for (const Company *c : Company::Iterate()) {
95 if (c->ai_info == nullptr) {
96 format_append(message, "{:2d}: Human\n", c->index);
97 } else {
98 format_append(message, "{:2d}: {} (v{})\n", c->index, c->ai_info->GetName(), c->ai_info->GetVersion());
99 }
100 }
101 text[1].key = const_cast<char *>("Description");
102 text[1].text = const_cast<char *>(message.c_str());
103 text[1].text_length = message.size();
104 text[1].compression = PNG_TEXT_COMPRESSION_zTXt;
105 png_set_text(png_ptr, info_ptr, text, 2);
106
107#endif /* PNG_TEXT_SUPPORTED */
108
109 if (pixelformat == 8) {
110 /* convert the palette to the .PNG format. */
111 for (i = 0; i != 256; i++) {
112 rq[i].red = palette[i].r;
113 rq[i].green = palette[i].g;
114 rq[i].blue = palette[i].b;
115 }
116
117 png_set_PLTE(png_ptr, info_ptr, rq, 256);
118 }
119
120 png_write_info(png_ptr, info_ptr);
121 png_set_flush(png_ptr, 512);
122
123 if (pixelformat == 32) {
124 png_color_8 sig_bit;
125
126 /* Save exact colour/alpha resolution */
127 sig_bit.alpha = 0;
128 sig_bit.blue = 8;
129 sig_bit.green = 8;
130 sig_bit.red = 8;
131 sig_bit.gray = 8;
132 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
133
134 if constexpr (std::endian::native == std::endian::little) {
135 png_set_bgr(png_ptr);
136 png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
137 } else {
138 png_set_filler(png_ptr, 0, PNG_FILLER_BEFORE);
139 }
140 }
141
142 /* use by default 64k temp memory */
143 maxlines = Clamp(65536 / w, 16, 128);
144
145 /* now generate the bitmap bits */
146 std::vector<uint8_t> buff(static_cast<size_t>(w) * maxlines * bpp); // by default generate 128 lines at a time.
147
148 y = 0;
149 do {
150 /* determine # lines to write */
151 n = std::min(h - y, maxlines);
152
153 /* render the pixels into the buffer */
154 callb(buff.data(), y, w, n);
155 y += n;
156
157 /* write them to png */
158 for (i = 0; i != n; i++) {
159 png_write_row(png_ptr, (png_bytep)buff.data() + i * w * bpp);
160 }
161 } while (y != h);
162
163 png_write_end(png_ptr, info_ptr);
164 png_destroy_write_struct(&png_ptr, &info_ptr);
165
166 return true;
167 }
168
169private:
170 static void PNGAPI png_my_error(png_structp png_ptr, png_const_charp message)
171 {
172 Debug(misc, 0, "[libpng] error: {} - {}", message, *static_cast<std::string_view *>(png_get_error_ptr(png_ptr)));
173 longjmp(png_jmpbuf(png_ptr), 1);
174 }
175
176 static void PNGAPI png_my_warning(png_structp png_ptr, png_const_charp message)
177 {
178 Debug(misc, 1, "[libpng] warning: {} - {}", message, *static_cast<std::string_view *>(png_get_error_ptr(png_ptr)));
179 }
180
181private:
182 static ScreenshotProvider_Png instance;
183};
184
185/* static */ ScreenshotProvider_Png ScreenshotProvider_Png::instance{};
AIInfo keeps track of all information of an AI, like Author, Description, ...
Generic functions for replacing base data (graphics, sounds).
Generic functions for replacing base graphics data.
constexpr enable_if_t< is_integral_v< T >, T > byteswap(T x) noexcept
Custom implementation of std::byteswap; remove once we build with C++23.
static const GraphicsSet * GetUsedSet()
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1173
bool MakeImage(std::string_view name, const ScreenshotCallback &callb, uint w, uint h, int pixelformat, const Colour *palette) const override
Create and write an image to a file.
Definition of stuff that is very close to a company, like the company struct itself.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
Functions for standard in/out file operations.
Integer math functions.
constexpr T Clamp(const T a, const T min, const T max)
Clamp a value between an interval.
Definition math_func.hpp:79
GRFConfigList _grfconfig
First item in list of current GRF set up.
Functions to find and configure NewGRFs.
Declaration of OTTD revision dependent variables.
A number of safeguards to prevent using unsafe methods.
Types related to screenshot providers.
std::function< void(void *buf, uint y, uint pitch, uint n)> ScreenshotCallback
Callback function signature for generating lines of pixel data to be written to the screenshot file.
Definition of base types and functions in a cross-platform compatible way.
std::string FormatArrayAsHex(std::span< const uint8_t > data)
Format a byte array into a continuous hex string.
Definition string.cpp:77
static Pool::IterateWrapper< Company > Iterate(size_t from=0)