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

Posted

技术标签:

【中文标题】使用 libpng 将 OpenGL 屏幕像素保存为 PNG【英文标题】:Save OpenGL screen pixels to PNG using libpng 【发布时间】:2014-03-18 04:10:50 【问题描述】:

我一直在使用 SOIL 将图像保存为 BMP,但事实证明 SOIL(或者更具体的 stbi)保存了大约 5MB 的图像(大约是 1366x768 分辨率图像或更高),这非常疯狂。

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

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 < h; ++j) 
    int x = j * w * 3;
    int y = (h - 1 - j) * w * 3;
    for (int i = w * 3; i > 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 < png_ptr->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);

P.S:我在 MinGW 上使用完全相同的代码,它工作 100% 正常,当我切换到 MSVC 时它开始失败。我不确定 GCC 是否在这里做了一些神奇的事情,或者是我的代码的错,所以为了学习,我想知道。

【问题讨论】:

【参考方案1】:

下面一行:

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

不幸地超过了内存块 (pixels),所以下面的编辑修复了它:

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

相当琐碎,但无论如何。

【讨论】:

以上是关于使用 libpng 将 OpenGL 屏幕像素保存为 PNG的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL获取像素的原始3d坐标

OpenGL中纹素和屏幕像素之间的一对一映射

OpenGL-渲染流程

OpenGL使用libPng读取png图片

如何使用 OpenGL 制作渐变到黑色的效果?

有没有办法直接在 iPhone 上渲染像素?