📜 ⬆️ ⬇️

Save image using libpng


Having fun at leisure with OpenGL, I decided to learn how to take screenshots by means of the program, not the system. Pretty quickly google the glReadPixels function, but with the saving of the picture, the problem came out. I remembered the old days, when I saved my code completely in bmp, I found the save function in tga, I realized that all these options are smelling of bicycling and decided to use a widespread library. The choice fell on libpng.
Then came the rake.

It turned out that there is no description of the library in Russian (that’s why I’m writing this post now), and the English documentation is not written in the most convenient way and does not contain even the simplest full-fledged example of use.
Below I will try to explain how to save the image by means of libpng in the simplest case.


First you need to connect the header file
')
#include <png.h> 


In the function / method in which we will save the image, open the file in which we will save and create the png structure.

 void Renderer::screenshoot(const std::string& name) { FILE *fp = fopen(name.c_str(), "wb"); if (!fp) { return; } png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_ptr) { goto close_file; } 


Now you need to create a png information structure, call setjmp, in case of errors, and initialize the output to a file.

  png_infop png_info; if (!(png_info = png_create_info_struct(png_ptr))) { goto destroy_write; } if (setjmp(png_jmpbuf(png_ptr))) { goto destroy_write; } png_init_io(png_ptr, fp); 


Next you need to set the image parameters (size, number of colors, number of bits per color channel, interlaced and compressed. As well as form an image and an array of pointers to individual lines of the image.

Here the rake begins.

Rake number 1: the function glReadPixels gives the image, the lines in which go from the bottom up, although usually working with graphics, we mean the reverse order.
Rake number 2: if you specify glReadPixels functions that we want to get RGBA colors, then in fact we get them in the order of ARGB. It's a shame, but it took me an hour to figure it out, during which I did not understand why my colors were not displayed correctly in the screenshot.

Let's declare an array of data, in which there will be data for libpng, and an array of argb_data, in which there will be data from OpenGL, well, let's not forget about an array of pointers rows.

  png_set_IHDR(png_ptr, png_info, width, height, 0, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); unsigned char data[width*height*3], argb_data[width*height*4]; unsigned char *rows[height]; render(); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, argb_data); for (int i = 0; i < height; ++i) { rows[height - i - 1] = data + (i*width*3); for (int j = 0; j < width; ++j) { int i1 = (i*width+j)*3; int i2 = (i*width+j)*4; data[i1++] = argb_data[++i2]; data[i1++] = argb_data[++i2]; data[i1++] = argb_data[++i2]; } } 


Now it's up to you to save the image, complete the input and process errors.

  png_set_rows(png_ptr, png_info, rows); png_write_png(png_ptr, png_info, PNG_TRANSFORM_IDENTITY, nullptr); png_write_end(png_ptr, png_info); destroy_write: png_destroy_write_struct(&png_str, nullptr); close_file: fclose(fp); } 


In such a relatively simple way, you can save the image in png. However, do not think that this is all that this library is capable of. You can follow the process of saving, apply filters, use interlacing, compression ... But to understand this you should read the original documentation .

UPD. Thanks to MrGobus for the comment about glReadPixels and the order of the rows.

Source: https://habr.com/ru/post/176163/


All Articles