MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_png.cpp
Go to the documentation of this file.
1/**
2 * @file mxvk_png.cpp
3 * @brief Implementation of mxvk PNG loading and saving utilities.
4 */
5#include "mxvk/mxvk_png.hpp"
6#include <SDL3/SDL.h>
7#include <algorithm>
8#include <cctype>
9#include <cmath>
10#include <cstdint>
11#include <cstdio>
12#include <iostream>
13#include <limits>
14#include <memory>
15#include <png.h>
16#include <ranges>
17#include <string>
18#include <vector>
19
20namespace mxvk {
21
22 [[nodiscard]] bool HasPngExtension(const char *file) {
23 if (file == nullptr) {
24 return false;
25 }
26
27 std::string lower(file);
28 std::ranges::transform(lower, lower.begin(),
29 [](const unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
30 return lower.ends_with(".png");
31 }
32
33 [[nodiscard]] bool WriteRgbaPng(const char *filename,
34 const std::uint8_t *pixels,
35 const int width,
36 const int height,
37 const int pitch,
38 const int bit_depth,
39 const bool swap_16bit_endianness) {
40 if (filename == nullptr || pixels == nullptr || width <= 0 || height <= 0 || pitch <= 0) {
41 std::cerr << "mx: Invalid PNG write parameters.\n";
42 return false;
43 }
44
45 FILE *file = std::fopen(filename, "wb");
46 if (file == nullptr) {
47 std::cerr << "mx: Failed to open file for writing: " << filename << "\n";
48 return false;
49 }
50
51 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
52 if (png == nullptr) {
53 std::cerr << "mx: Failed to create PNG write struct.\n";
54 std::fclose(file);
55 return false;
56 }
57
58 png_infop info = png_create_info_struct(png);
59 if (info == nullptr) {
60 std::cerr << "mx: Failed to create PNG info struct.\n";
61 png_destroy_write_struct(&png, nullptr);
62 std::fclose(file);
63 return false;
64 }
65
66 if (setjmp(png_jmpbuf(png))) {
67 std::cerr << "mx: Error during PNG creation.\n";
68 png_destroy_write_struct(&png, &info);
69 std::fclose(file);
70 return false;
71 }
72
73 png_init_io(png, file);
74 png_set_IHDR(png,
75 info,
76 width,
77 height,
78 bit_depth,
79 PNG_COLOR_TYPE_RGBA,
80 PNG_INTERLACE_NONE,
81 PNG_COMPRESSION_TYPE_DEFAULT,
82 PNG_FILTER_TYPE_DEFAULT);
83
84 if (bit_depth == 16 && swap_16bit_endianness) {
85 png_set_swap(png);
86 }
87
88 png_write_info(png, info);
89
90 std::vector<png_bytep> rows(static_cast<std::size_t>(height));
91 for (int y = 0; y < height; ++y) {
92 rows[static_cast<std::size_t>(y)] = const_cast<png_bytep>(pixels + (y * pitch));
93 }
94
95 png_write_image(png, rows.data());
96 png_write_end(png, nullptr);
97
98 png_destroy_write_struct(&png, &info);
99 std::fclose(file);
100 return true;
101 }
102
103 SDL_Surface *LoadPNG(const char *file) {
104 if (!HasPngExtension(file)) {
105 return nullptr;
106 }
107
108 FILE *fp = std::fopen(file, "rb");
109 if (fp == nullptr) {
110 std::cerr << "Failed to open PNG file: " << file << "\n";
111 return nullptr;
112 }
113
114 png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
115 if (png == nullptr) {
116 std::fclose(fp);
117 return nullptr;
118 }
119
120 png_infop info = png_create_info_struct(png);
121 if (info == nullptr) {
122 png_destroy_read_struct(&png, nullptr, nullptr);
123 std::fclose(fp);
124 return nullptr;
125 }
126
127 if (setjmp(png_jmpbuf(png))) {
128 png_destroy_read_struct(&png, &info, nullptr);
129 std::fclose(fp);
130 return nullptr;
131 }
132
133 png_init_io(png, fp);
134 png_read_info(png, info);
135
136 const int width = static_cast<int>(png_get_image_width(png, info));
137 const int height = static_cast<int>(png_get_image_height(png, info));
138 const png_byte color_type = png_get_color_type(png, info);
139 const png_byte bit_depth = png_get_bit_depth(png, info);
140
141 if (width <= 0 || height <= 0) {
142 png_destroy_read_struct(&png, &info, nullptr);
143 std::fclose(fp);
144 return nullptr;
145 }
146
147 if (bit_depth == 16) {
148 png_set_strip_16(png);
149 }
150
151 if (color_type == PNG_COLOR_TYPE_PALETTE) {
152 png_set_palette_to_rgb(png);
153 }
154
155 if (png_get_valid(png, info, PNG_INFO_tRNS) != 0) {
156 png_set_tRNS_to_alpha(png);
157 }
158
159 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
160 png_set_gray_to_rgb(png);
161 }
162
163 if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY ||
164 color_type == PNG_COLOR_TYPE_PALETTE) {
165 png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
166 }
167
168 png_read_update_info(png, info);
169
170 SDL_Surface *surface = SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32);
171 if (surface == nullptr) {
172 png_destroy_read_struct(&png, &info, nullptr);
173 std::fclose(fp);
174 return nullptr;
175 }
176
177 const png_size_t row_bytes = png_get_rowbytes(png, info);
178 if (row_bytes > static_cast<png_size_t>(surface->pitch)) {
179 std::cerr << "mx: Unexpected PNG row stride mismatch for file: " << file << "\n";
180 SDL_DestroySurface(surface);
181 png_destroy_read_struct(&png, &info, nullptr);
182 std::fclose(fp);
183 return nullptr;
184 }
185
186 auto *pixels = static_cast<std::uint8_t *>(surface->pixels);
187 std::vector<png_bytep> row_pointers(static_cast<std::size_t>(height));
188 for (int y = 0; y < height; ++y) {
189 row_pointers[static_cast<std::size_t>(y)] = pixels + (y * surface->pitch);
190 }
191
192 png_read_image(png, row_pointers.data());
193 png_read_end(png, nullptr);
194
195 png_destroy_read_struct(&png, &info, nullptr);
196 std::fclose(fp);
197 return surface;
198 }
199
200 bool SavePNG(SDL_Texture *texture, SDL_Renderer *renderer, const char *filename) {
201 if (texture == nullptr || renderer == nullptr || filename == nullptr) {
202 std::cerr << "mx: Invalid SavePNG parameters.\n";
203 return false;
204 }
205
206 float width_f = 0.0F;
207 float height_f = 0.0F;
208 if (!SDL_GetTextureSize(texture, &width_f, &height_f)) {
209 std::cerr << "mx: Failed to query texture size: " << SDL_GetError() << "\n";
210 return false;
211 }
212
213 if (!std::isfinite(width_f) || !std::isfinite(height_f) || width_f <= 0.0F || height_f <= 0.0F) {
214 std::cerr << "mx: Invalid texture size while saving PNG.\n";
215 return false;
216 }
217
218 const int width = static_cast<int>(width_f);
219 const int height = static_cast<int>(height_f);
220 if (width <= 0 || height <= 0) {
221 std::cerr << "mx: Invalid integer texture size while saving PNG.\n";
222 return false;
223 }
224
225 SDL_Texture *previous_target = SDL_GetRenderTarget(renderer);
226 if (!SDL_SetRenderTarget(renderer, texture)) {
227 std::cerr << "mx: Failed to set render target: " << SDL_GetError() << "\n";
228 return false;
229 }
230
231 SDL_Surface *captured = SDL_RenderReadPixels(renderer, nullptr);
232
233 if (!SDL_SetRenderTarget(renderer, previous_target)) {
234 std::cerr << "mx: Failed to restore render target: " << SDL_GetError() << "\n";
235 }
236
237 if (captured == nullptr) {
238 std::cerr << "mx: Failed to read pixels from renderer: " << SDL_GetError() << "\n";
239 return false;
240 }
241
242 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> captured_surface(captured, &SDL_DestroySurface);
243
244 SDL_Surface *rgba32_surface_raw = captured_surface.get();
245 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> converted_surface(nullptr, &SDL_DestroySurface);
246 if (captured_surface->format != SDL_PIXELFORMAT_RGBA32) {
247 rgba32_surface_raw = SDL_ConvertSurface(captured_surface.get(), SDL_PIXELFORMAT_RGBA32);
248 if (rgba32_surface_raw == nullptr) {
249 std::cerr << "mx: Failed to convert surface to RGBA32: " << SDL_GetError() << "\n";
250 return false;
251 }
252 converted_surface.reset(rgba32_surface_raw);
253 }
254
255 const auto *pixels = static_cast<const std::uint8_t *>(rgba32_surface_raw->pixels);
256 return WriteRgbaPng(filename,
257 pixels,
258 rgba32_surface_raw->w,
259 rgba32_surface_raw->h,
260 rgba32_surface_raw->pitch,
261 8,
262 false);
263 }
264
265 bool SaveRawBytes(const char *filename, const void *buffer, size_t w, size_t h, size_t bpp) {
266 if (filename == nullptr || buffer == nullptr || bpp == 0U) {
267 std::cerr << "mx: Invalid SaveRawBytes parameters.\n";
268 return false;
269 }
270
271 if (w != 0U && h > (std::numeric_limits<size_t>::max() / w)) {
272 std::cerr << "mx: Raw byte size overflow while writing: " << filename << "\n";
273 return false;
274 }
275
276 const size_t pixels = w * h;
277 if (pixels != 0U && bpp > (std::numeric_limits<size_t>::max() / pixels)) {
278 std::cerr << "mx: Raw byte size overflow while writing: " << filename << "\n";
279 return false;
280 }
281
282 const size_t size = pixels * bpp;
283 FILE *fptr = std::fopen(filename, "wb");
284 if (fptr == nullptr) {
285 std::cerr << "mx: Failed to open output file: " << filename << "\n";
286 return false;
287 }
288
289 const size_t written = std::fwrite(buffer, sizeof(unsigned char), size, fptr);
290 if (written != size) {
291 std::cerr << "mx: Error writing frame: " << filename << "\n";
292 std::fclose(fptr);
293 return false;
294 }
295
296 std::fclose(fptr);
297 return true;
298 }
299
300 bool SavePNG_RGBA(const char *filename, void *buffer, int w, int h) {
301 const int pitch = w * 4;
302 return WriteRgbaPng(filename,
303 static_cast<const std::uint8_t *>(buffer),
304 w,
305 h,
306 pitch,
307 8,
308 false);
309 }
310
311 bool SavePNG_RGBA16(const char *filename, const void *buffer, int w, int h) {
312 const int pitch = w * 8;
313#if SDL_BYTEORDER == SDL_LIL_ENDIAN
314 constexpr bool swap_16bit_endianness = true;
315#else
316 constexpr bool swap_16bit_endianness = false;
317#endif
318 return WriteRgbaPng(filename,
319 static_cast<const std::uint8_t *>(buffer),
320 w,
321 h,
322 pitch,
323 16,
324 swap_16bit_endianness);
325 }
326} // namespace mxvk
PNG image loading and saving utilities via SDL3.
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
bool SavePNG(SDL_Texture *texture, SDL_Renderer *renderer, const char *filename)
Save an SDL_Texture to a PNG file.
Definition mxvk_png.cpp:200
bool SavePNG_RGBA16(const char *filename, const void *buffer, int w, int h)
Save a raw 16-bit RGBA pixel buffer to a PNG file.
Definition mxvk_png.cpp:311
SDL_Surface * LoadPNG(const char *file)
Load a PNG file into an SDL_Surface.
Definition mxvk_png.cpp:103
bool SavePNG_RGBA(const char *filename, void *buffer, int w, int h)
Save a raw RGBA pixel buffer to a PNG file.
Definition mxvk_png.cpp:300
bool SaveRawBytes(const char *filename, const void *buffer, size_t w, size_t h, size_t bpp)
Save raw data to a file.
Definition mxvk_png.cpp:265
bool WriteRgbaPng(const char *filename, const std::uint8_t *pixels, const int width, const int height, const int pitch, const int bit_depth, const bool swap_16bit_endianness)
Definition mxvk_png.cpp:33
bool HasPngExtension(const char *file)
Definition mxvk_png.cpp:22