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