Rendering Engine 0.2.0
Modular Graphics Rendering Engine | v0.2.0
Loading...
Searching...
No Matches
image_codec_png.hpp
Go to the documentation of this file.
1// This file is part of the Rendering Engine project.
2// Author: Alexander Obzherin <alexanderobzherin@gmail.com>
3// Copyright (c) 2025 Alexander Obzherin
4// Distributed under the terms of the zlib License. See LICENSE.md for details.
5
6/**
7 * @file image_codec_png.hpp
8 * @brief PNG read/write backend using libpng.
9 *
10 * This module bridges the Rendering Engine's `ImageData` abstraction and the libpng C API.
11 * It provides direct read/write routines for 8-bit RGBA images.
12 *
13 * @details
14 * The implementation follows the official libpng examples and reference documentation:
15 * - https://www.libpng.org/pub/png/libpng.html
16 * - See "Simplified API for Reading and Writing" for modern usage examples.
17 *
18 * The function sequence mirrors the standard libpng workflow:
19 * 1. Create and initialize `png_structp` and `png_infop`.
20 * 2. Set up `setjmp` error handling.
21 * 3. Configure image metadata with `png_set_IHDR`.
22 * 4. Write/read rows with `png_write_row` / `png_image_finish_read`.
23 * 5. Clean up with `png_destroy_write_struct`.
24 *
25 * @note
26 * This file is considered an engine backend implementation detail.
27 * It is not intended for use outside `ImageData` and `ImageDataGpu`.
28 *
29 * @see rendering_engine::ImageData
30 */
31#pragma once
32
33#include <cstring>
34#include <vector>
35#include <stdlib.h>
36#include <png.h>
37
38/**
39 * @brief Saves image data to a PNG file.
40 *
41 * Uses libpng to write RGBA data from ImageData into a PNG file.
42 * The function performs full file initialization, metadata setup, and cleanup.
43 *
44 * @param imageData Source image to save.
45 * @param filename Path to the output PNG file.
46 */
47static void SaveTextureFilePng(rendering_engine::ImageData const& imageData, char const* filename)
48{
49 FILE* fp;
50 png_structp png_ptr;
51 png_infop info_ptr;
52 png_colorp palette;
53
54 fp = fopen(filename, "wb");
55 if( fp == NULL )
56 {
57 fprintf(stderr, "can't open %s\n", filename);
58 exit(1);
59 }
60
61 /* Create and initialize the png_struct with the desired error handler
62 * functions. */
63 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
64 NULL, NULL, NULL);
65
66 if( png_ptr == NULL )
67 {
68 fclose(fp);
69 return;
70 }
71
72 /* Allocate/initialize the image information data. */
73 info_ptr = png_create_info_struct(png_ptr);
74 if( info_ptr == NULL )
75 {
76 fclose(fp);
77 png_destroy_write_struct(&png_ptr, NULL);
78 return;
79 }
80
81 /* Set error handling. REQUIRED if you aren't supplying your own
82 * error handling functions in the png_create_write_struct() call.
83 */
84 if( setjmp(png_jmpbuf(png_ptr)) )
85 {
86 /* If we get here, we had a problem writing the file */
87 fclose(fp);
88 png_destroy_write_struct(&png_ptr, &info_ptr);
89 return;
90 }
91
92 /* I/O initialization */
93
94 png_init_io(png_ptr, fp);
95 auto const png_transforms = PNG_TRANSFORM_IDENTITY;
96
97 /* Set the image information. color_type is one of PNG_COLOR_TYPE_GRAY,
98 * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
99 * or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
100 * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
101 * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. */
102 int const bit_depth = 8;
103 png_set_IHDR(png_ptr, info_ptr, imageData.GetWidth(), imageData.GetHeight(), bit_depth, PNG_COLOR_TYPE_RGB_ALPHA,
104 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
105
106 /* Set the palette if there is one. REQUIRED for indexed-color images */
107 palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH
108 * sizeof(png_color));
109 /* ... Set palette colors ... */
110 png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH);
111
112 /* Optional significant bit (sBIT) chunk */
113 png_color_8 sig_bit;
114
115 sig_bit.red = 8; //true_red_bit_depth;
116 sig_bit.green = 8; //true_green_bit_depth;
117 sig_bit.blue = 8; // true_blue_bit_depth;
118
119 /* If the image has an alpha channel then */
120 sig_bit.alpha = 8; // true_alpha_bit_depth;
121
122 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
123
124 /* Write the file header information. REQUIRED */
125 png_write_info(png_ptr, info_ptr);
126
127 auto const imageDataVector = imageData.GetImageDataRGBA();
128
129 for( auto rowIterator = 0U; rowIterator < imageData.GetHeight(); ++rowIterator )
130 {
131 png_bytep row_pointer = new unsigned char[imageData.GetWidth() * 4];
132 auto const rowLength = imageData.GetWidth() * 4U;
133 auto const beginRow = imageDataVector.begin() + (rowIterator * rowLength);
134 auto const endRow = imageDataVector.begin() + (rowIterator * rowLength) + rowLength;
135 std::copy(beginRow, endRow, row_pointer);
136
137 png_write_row(png_ptr, row_pointer);
138
139 delete[] row_pointer;
140 }
141
142 /* It is REQUIRED to call this to finish writing the rest of the file */
143 png_write_end(png_ptr, info_ptr);
144
145 png_free(png_ptr, palette);
146 palette = NULL;
147
148 /* Clean up after the write, and free any memory allocated */
149 png_destroy_write_struct(&png_ptr, &info_ptr);
150
151 /* Close the file */
152 fclose(fp);
153}
154
155/**
156 * @brief Reads a PNG file into RGBA image data.
157 *
158 * Loads an image using libpng's simplified API and converts it to 8-bit RGBA format.
159 *
160 * @param filename Path to the PNG file.
161 * @param width [out] Image width.
162 * @param height [out] Image height.
163 * @param rgbaImageDataVector [out] Filled with 4-byte-per-pixel RGBA data.
164 * @return true if the image was successfully read, false otherwise.
165 */
166static bool ReadPngFile(char const* filename, unsigned int& width, unsigned int& height, std::vector<unsigned int>& rgbaImageDataVector)
167{
168 png_image image; /* The control structure used by libpng */
169
170 /* Initialize the 'png_image' structure. */
171 memset(&image, 0, (sizeof image));
172 image.version = PNG_IMAGE_VERSION;
173
174 /* The first argument is the file to read: */
175 if(png_image_begin_read_from_file(&image, filename) != 0)
176 {
177 png_bytep buffer;
178
179 /* Set the format in which to read the PNG file; this code chooses a
180 * simple sRGB format with a non-associated alpha channel, adequate to
181 * store most images.
182 */
183 image.format = PNG_FORMAT_RGBA;
184
185 /* Now allocate enough memory to hold the image in this format; the
186 * PNG_IMAGE_SIZE macro uses the information about the image (width,
187 * height and format) stored in 'image'.
188 */
189 buffer = new png_byte[(PNG_IMAGE_SIZE(image))];
190
191 /* If enough memory was available, read the image in the desired
192 * format, then write the result out to the new file. 'background' is
193 * not necessary when reading the image, because the alpha channel is
194 * preserved; if it were to be removed, for example if we requested
195 * PNG_FORMAT_RGB, then either a solid background color would have to
196 * be supplied, or the output buffer would have to be initialized to
197 * the actual background of the image.
198 *
199 * The fourth argument to png_image_finish_read is the 'row_stride' -
200 * this is the number of components allocated for the image in each
201 * row. It has to be at least as big as the value returned by
202 * PNG_IMAGE_ROW_STRIDE, but if you just allocate space for the
203 * default, minimum size, using PNG_IMAGE_SIZE as above, you can pass
204 * zero.
205 *
206 * The final argument is a pointer to a buffer for the colormap;
207 * colormaps have exactly the same format as a row of image pixels
208 * (so you choose what format to make the colormap by setting
209 * image.format). A colormap is only returned if
210 * PNG_FORMAT_FLAG_COLORMAP is also set in image.format, so in this
211 * case NULL is passed as the final argument. If you do want to force
212 * all images into an index/color-mapped format, then you can use:
213 *
214 * PNG_IMAGE_COLORMAP_SIZE(image)
215 *
216 * to find the maximum size of the colormap in bytes.
217 */
218
219 if (buffer != NULL &&
220 png_image_finish_read(&image, NULL/*background*/, buffer,
221 0/*row_stride*/, NULL/*colormap*/) != 0)
222 {
223 width = image.width;
224 height = image.height;
225 rgbaImageDataVector.clear();
226 for( int i = 0; i < width * height * 4; ++i )
227 {
228 rgbaImageDataVector.push_back(*(buffer + i));
229 }
230
231 delete[] buffer;
232 }
233 else
234 {
235 return false;
236 }
237 }
238 return true;
239}
240
241/**
242 * @brief Reads a PNG image from memory into RGBA image data.
243 *
244 * Works similarly to ReadPngFile(), but instead of loading from disk it
245 * accepts a raw memory buffer containing a complete PNG file. The data
246 * is decoded with libpng’s simplified API and converted to 8-bit RGBA format.
247 *
248 * @param memory Pointer to the PNG file data in memory.
249 * @param memorySize Size of the memory buffer in bytes.
250 * @param width [out] Image width in pixels.
251 * @param height [out] Image height in pixels.
252 * @param rgbaImageDataVector [out] Output buffer filled with RGBA pixel data
253 * (4 bytes per pixel, stored in a vector of 32-bit unsigned integers).
254 * @return true if decoding succeeded, false otherwise.
255 */
257 const unsigned char* memory,
258 size_t memorySize,
259 unsigned int& width,
260 unsigned int& height,
261 std::vector<unsigned int>& rgbaImageDataVector)
262{
263 png_image image;
264 memset(&image, 0, sizeof(image));
265 image.version = PNG_IMAGE_VERSION;
266
267 if (png_image_begin_read_from_memory(&image, memory, memorySize) == 0)
268 return false;
269
270 image.format = PNG_FORMAT_RGBA;
271
272 png_bytep buffer = new png_byte[PNG_IMAGE_SIZE(image)];
273 if (!buffer)
274 return false;
275
276 if (png_image_finish_read(&image, nullptr, buffer, 0, nullptr) == 0)
277 {
278 delete[] buffer;
279 return false;
280 }
281
282 width = image.width;
283 height = image.height;
284
285 rgbaImageDataVector.resize(width * height * 4);
286 for (size_t i = 0; i < width * height * 4; ++i)
287 rgbaImageDataVector[i] = buffer[i];
288
289 delete[] buffer;
290 return true;
291}
Represents raw 2D image data stored in memory.
unsigned int GetWidth() const
Returns the image width.
std::vector< uint8_t > GetImageDataRGBA() const
Gets raw image data in RGBA format.
unsigned int GetHeight() const
Returns the image height.
static bool ReadPngFromMemory(const unsigned char *memory, size_t memorySize, unsigned int &width, unsigned int &height, std::vector< unsigned int > &rgbaImageDataVector)
Reads a PNG image from memory into RGBA image data.
static bool ReadPngFile(char const *filename, unsigned int &width, unsigned int &height, std::vector< unsigned int > &rgbaImageDataVector)
Reads a PNG file into RGBA image data.
static void SaveTextureFilePng(rendering_engine::ImageData const &imageData, char const *filename)
Saves image data to a PNG file.