OpenTTD Source 20250524-master-gc366e6a48e
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 <http://www.gnu.org/licenses/>.
6 */
7
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
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) 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};
181
182static ScreenshotProvider_Png s_screenshot_provider_png;
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.
static const GraphicsSet * GetUsedSet()
Return the used set.
static std::optional< FileHandle > Open(const std::string &filename, std::string_view mode)
Open an RAII file handle if possible.
Definition fileio.cpp:1168
Base interface for a SoundLoader implementation.
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:75
static Pool::IterateWrapper< Titem > Iterate(size_t from=0)
Returns an iterable ensemble of all valid Titem.