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";
38 FILE *file = std::fopen(filename,
"wb");
39 if (file ==
nullptr) {
40 std::cerr <<
"mx: Failed to open file for writing: " << filename <<
"\n";
44 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
46 std::cerr <<
"mx: Failed to create PNG write struct.\n";
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);
59 if (setjmp(png_jmpbuf(png))) {
60 std::cerr <<
"mx: Error during PNG creation.\n";
61 png_destroy_write_struct(&png, &info);
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);
70 if (bit_depth == 16 && swap_16bit_endianness) {
74 png_write_info(png, info);
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));
81 png_write_image(png, rows.data());
82 png_write_end(png,
nullptr);
84 png_destroy_write_struct(&png, &info);
94 FILE *fp = std::fopen(file,
"rb");
96 std::cerr <<
"Failed to open PNG file: " << file <<
"\n";
100 png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
nullptr,
nullptr,
nullptr);
101 if (png ==
nullptr) {
106 png_infop info = png_create_info_struct(png);
107 if (info ==
nullptr) {
108 png_destroy_read_struct(&png,
nullptr,
nullptr);
113 if (setjmp(png_jmpbuf(png))) {
114 png_destroy_read_struct(&png, &info,
nullptr);
119 png_init_io(png, fp);
120 png_read_info(png, info);
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);
127 if (width <= 0 || height <= 0) {
128 png_destroy_read_struct(&png, &info,
nullptr);
133 if (bit_depth == 16) {
134 png_set_strip_16(png);
137 if (color_type == PNG_COLOR_TYPE_PALETTE) {
138 png_set_palette_to_rgb(png);
141 if (png_get_valid(png, info, PNG_INFO_tRNS) != 0) {
142 png_set_tRNS_to_alpha(png);
145 if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
146 png_set_gray_to_rgb(png);
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);
153 png_read_update_info(png, info);
155 SDL_Surface *
surface = SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGBA32);
157 png_destroy_read_struct(&png, &info,
nullptr);
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";
166 png_destroy_read_struct(&png, &info,
nullptr);
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);
177 png_read_image(png, row_pointers.data());
178 png_read_end(png,
nullptr);
180 png_destroy_read_struct(&png, &info,
nullptr);
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";
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";
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";
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";
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";
216 SDL_Surface *captured = SDL_RenderReadPixels(renderer,
nullptr);
218 if (!SDL_SetRenderTarget(renderer, previous_target)) {
219 std::cerr <<
"mx: Failed to restore render target: " << SDL_GetError() <<
"\n";
222 if (captured ==
nullptr) {
223 std::cerr <<
"mx: Failed to read pixels from renderer: " << SDL_GetError() <<
"\n";
227 std::unique_ptr<SDL_Surface,
decltype(&SDL_DestroySurface)> captured_surface(captured, &SDL_DestroySurface);
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";
237 converted_surface.reset(rgba32_surface_raw);
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);
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";
250 if (w != 0U && h > (std::numeric_limits<size_t>::max() / w)) {
251 std::cerr <<
"mx: Raw byte size overflow while writing: " << filename <<
"\n";
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";
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";
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";
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.
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.
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)
bool SavePNG(SDL_Texture *texture, SDL_Renderer *renderer, const char *filename, int compression_level=6)
Save an SDL_Texture to a PNG file.