使用libpng将OpenGL屏幕像素保存到PNG

我一直在使用SOIL来保存图像作为BMP,但事实certificateSOIL(或stbi更具体)可以节省~5MB图像(大约1366×768分辨率图像或更多),这非常疯狂。

原始BMP保存代码( 注意一切都在渲染function中完成):

uint8_t *pixels = new uint8_t[w * h * 3]; // copy pixels from screen glBindTexture(GL_TEXTURE_2D, screenTex); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, w, h); glPixelStorei(GL_PACK_ALIGNMENT, 1); glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)pixels); // invert pixels (stolen from SOILs source code) for (int j = 0; j * 2  0; --i) { uint8_t tmp = pixels[x]; pixels[x] = pixels[y]; pixels[y] = tmp; ++x; ++y; } } // save the image int err = SOIL_save_image(fileName, SOIL_SAVE_TYPE_BMP, w, h, 3, pixels); if (err) printf("Done\n"); else printf("Failed\n"); 

保存PNG的代码:

 bool save_png_libpng(const char *filename, uint8_t *pixels, int w, int h) { png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) return false; png_infop info = png_create_info_struct(png); if (!info) { png_destroy_write_struct(&png, &info); return false; } FILE *fp = fopen(filename, "wb"); if (!fp) { png_destroy_write_struct(&png, &info); return false; } png_init_io(png, fp); png_set_IHDR(png, info, w, h, 8 /* depth */, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_colorp palette = (png_colorp)png_malloc(png, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); if (!palette) { fclose(fp); png_destroy_write_struct(&png, &info); return false; } png_set_PLTE(png, info, palette, PNG_MAX_PALETTE_LENGTH); png_write_info(png, info); png_set_packing(png); png_bytepp rows = (png_bytepp)png_malloc(png, h * sizeof(png_bytep)); for (int i = 0; i < h; ++i) rows[i] = (png_bytep)(pixels + (h - i) * w * 3); png_write_image(png, rows); png_write_end(png, info); png_free(png, palette); png_destroy_write_struct(&png, &info); fclose(fp); delete[] rows; return true; } 

注意 :我没有更改任何原始代码,只是用SOIL_save_image替换了save_png

代码在以下行中失败:

png_write_image(png, rows)

在PNG的源代码中,此函数在突出显示的行处失败:

 void PNGAPI png_write_image(png_structrp png_ptr, png_bytepp image) { png_uint_32 i; /* row index */ int pass, num_pass; /* pass variables */ png_bytepp rp; /* points to current row */ if (png_ptr == NULL) return; png_debug(1, "in png_write_image"); #ifdef PNG_WRITE_INTERLACING_SUPPORTED /* Initialize interlace handling. If image is not interlaced, * this will set pass to 1 */ num_pass = png_set_interlace_handling(png_ptr); #else num_pass = 1; #endif /* Loop through passes */ for (pass = 0; pass < num_pass; pass++) { /* Loop through image */ for (i = 0, rp = image; i height; i++, rp++) { png_write_row(png_ptr, *rp); // HERE } } } 

png_write_row然后在这里失败:( png_write_row的代码在这里发布很长,所以如果你对这行之前发生的事情感到好奇,你可以在png的源代码中查看pngwrite.c。)

  /* Copy user's row into buffer, leaving room for filter byte. */ memcpy(png_ptr->row_buf + 1, row, row_info.rowbytes); 

PS:我在MinGW上使用完全相同的代码并且它工作100%正常,当我切换到MSVC它开始失败。 我不确定GCC是否在这里神奇地做了什么,或者这是我代码的错,所以我想知道为了学习。

以下行:

rows[i] = (png_bytep)(pixels + (h - i) * w * 3);

不幸的是经过了内存块( pixels ),因此以下编辑修复了它:

rows[i] = (png_bytep)(pixels + (h - i - 1) * w * 3);

相当琐碎但无论如何。