【问题标题】:How to encode PNG to buffer using libpng?如何使用 libpng 将 PNG 编码为缓冲区?
【发布时间】:2010-12-21 18:20:18
【问题描述】:

我目前正在使用以下内容将 PNG 写入文件:

#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

/* Pixels in this bitmap structure are stored as BGR. */
typedef struct _RGBPixel {
    uint8_t blue;
    uint8_t green;
    uint8_t red;
} RGBPixel;

/* Structure for containing decompressed bitmaps. */
typedef struct _RGBBitmap {
    RGBPixel *pixels;
    size_t width;
    size_t height;
    size_t bytewidth;
    uint8_t bytes_per_pixel;
} RGBBitmap;

/* Returns pixel of bitmap at given point. */
#define RGBPixelAtPoint(image, x, y) \
    *(((image)->pixels) + (((image)->bytewidth * (y)) \
                        + ((x) * (image)->bytes_per_pixel)))

/* Attempts to save PNG to file; returns 0 on success, non-zero on error. */
int save_png_to_file(RGBBitmap *bitmap, const char *path)
{
    FILE *fp = fopen(path, "wb");
    png_structp png_ptr = NULL;
    png_infop info_ptr = NULL;
    size_t x, y;
    png_uint_32 bytes_per_row;
    png_byte **row_pointers = NULL;

    if (fp == NULL) return -1;

    /* Initialize the write struct. */
    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (png_ptr == NULL) {
        fclose(fp);
        return -1;
    }

    /* Initialize the info struct. */
    info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL) {
        png_destroy_write_struct(&png_ptr, NULL);
        fclose(fp);
        return -1;
    }

    /* Set up error handling. */
    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        fclose(fp);
        return -1;
    }

    /* Set image attributes. */
    png_set_IHDR(png_ptr,
                 info_ptr,
                 bitmap->width,
                 bitmap->height,
                 8,
                 PNG_COLOR_TYPE_RGB,
                 PNG_INTERLACE_NONE,
                 PNG_COMPRESSION_TYPE_DEFAULT,
                 PNG_FILTER_TYPE_DEFAULT);

    /* Initialize rows of PNG. */
    bytes_per_row = bitmap->width * bitmap->bytes_per_pixel;
    row_pointers = png_malloc(png_ptr, bitmap->height * sizeof(png_byte *));
    for (y = 0; y < bitmap->height; ++y) {
        uint8_t *row = png_malloc(png_ptr, sizeof(uint8_t) * bitmap->bytes_per_pixel);
        row_pointers[y] = (png_byte *)row;
        for (x = 0; x < bitmap->width; ++x) {
            RGBPixel color = RGBPixelAtPoint(bitmap, x, y);
            *row++ = color.red;
            *row++ = color.green;
            *row++ = color.blue;
        }
    }

    /* Actually write the image data. */
    png_init_io(png_ptr, fp);
    png_set_rows(png_ptr, info_ptr, row_pointers);
    png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

    /* Cleanup. */
    for (y = 0; y < bitmap->height; y++) {
        png_free(png_ptr, row_pointers[y]);
    }
    png_free(png_ptr, row_pointers);

    /* Finish writing. */
    png_destroy_write_struct(&png_ptr, &info_ptr);
    fclose(fp);
    return 0;
}

如何编写类似的函数(用 C 语言)将 PNG 编码到内存缓冲区?

原型看起来像这样:

uint8_t *encode_png_to_buffer(RGBBitmap *source);

看来我可能需要使用png_set_write_fn()

但除此之外,我不确定如何处理这个问题。有没有这样做的例子?当然我不是第一个需要这个功能的人。

【问题讨论】:

  • 首先,尝试编译它。检查save_png_to_file 的定义是否缺少*RGBBitmap * bitmap(因为您稍后将位图视为指针。bytes_per_pixel 定义在哪里?(无处!)
  • “将 PNG 编码到内存缓冲区”到底是什么意思?您的意思是将图像编码为PNG到缓冲区吗?您的意思是将 PNG 解码为缓冲区吗?
  • 啊,错别字抱歉,我已经改正了。对不起,如果我不清楚;我的意思是:我在“RGBBitmap”结构中有一个解压缩的位图,我想将其转换为 PNG。不过,我不想将此 PNG 写入文件,我只想要将要写入的数据的原始缓冲区。
  • 我认为 row_pointers = png_malloc(png_ptr, bitmap->height * sizeof(png_byte ));应该是 png_byte *row = (png_byte)png_malloc(png_ptr, sizeof(uint8_t) * bitmap->bytes_per_pixel * bitmap->width);
  • 请问您是如何安装 libpng 的,因为我无法在 Windows 上使用 #include &lt;png.h&gt;

标签: c png libpng


【解决方案1】:

根据我的口味,其他答案似乎并不完整。因此,我使用这些答案和其他研究将黑色背景写入缓冲区。然后检查,我将缓冲区写入文件。这是用 gcc 编译的。添加了库标志 -lpng。

#define PNG_SETJMP_NOT_SUPPORTED
#include <stdio.h>
#include <stdlib.h>
#include <png.h>

struct libpng_inmem_write_struct { /* This is from png.c */
  unsigned char * pngBfr;  /* destination memory */
  unsigned long pngSiz;  /* destination memory size (bytes) */
};

void freeExit_w_msg(char * msg);
void wrtBgPng(png_structp pngWrtPtr, png_bytep data, png_size_t length);

png_structp pngWrtPtr; /* The pointer that points the PNG write structure */
png_infop pngWrtInfoPtr; /* The pointer that points the PNG write information */ 
struct libpng_inmem_write_struct p_io; /* Holds the encoded PNG data */
FILE * fw; /* The file pointer of the test file that will be wrote. */

void freeExit_w_msg(char * msg) {
    if (pngWrtPtr) png_destroy_write_struct(&pngWrtPtr, &pngWrtInfoPtr);
    if (p_io.pngBfr) free(p_io.pngBfr);
    fclose(fw); 
    printf("%s\n", msg);
    exit(0);
}

int main(int argc, char *argv[])
{
    pngWrtInfoPtr = NULL;   /* write_info_ptr */
    p_io.pngBfr = NULL;
    p_io.pngSiz = 0;
    int imgWdth = 2558;
    int imgHght = 1438;
    fw = fopen (argv[1], "wb"); /* argv[1] is the name of the test file */

    if (!fw) {
        char msg[300];
        sprintf(msg, "The file, %s, did not correctly open.\n", argv[1]);
        freeExit_w_msg(msg);
    }

    pngWrtPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); /* write_ptr */
    if (!pngWrtPtr) freeExit_w_msg((char *) "The PNG write memory did not correctly allocate.");
    pngWrtInfoPtr = png_create_info_struct(pngWrtPtr);
    if (!pngWrtInfoPtr) freeExit_w_msg((char *) "The PNG write information memory did not correctly allocate.");
    png_set_IHDR(pngWrtPtr, pngWrtInfoPtr, imgWdth, imgHght, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);    
    png_byte ** row_pointers = (png_byte **) png_malloc(pngWrtPtr, imgHght * sizeof(png_byte *));
    size_t bytesPerRow = imgWdth << 2; /* 4 Bytes per pixel */
    unsigned char * imgBfr = (unsigned char *) calloc(1, imgHght * bytesPerRow * sizeof(unsigned char)); 

    for (int rw = 0; rw < imgHght; rw++) {
        png_byte * rwPtr = row_pointers[rw] = (png_byte *) (imgBfr + rw * bytesPerRow); 

        for (int pxl = 0, byt = 0; pxl < imgWdth; pxl++) { /* Write a black background */
            for (int clr = 0; clr < 3; clr++) rwPtr[byt++] = 0;
            rwPtr[byt++] = 0xff;
        }
    }   

    p_io.pngBfr = (unsigned char *) malloc(4); /* Defines final PNG data location */
    p_io.pngSiz = 4;
    png_init_io(pngWrtPtr, (png_FILE_p) &p_io);
    png_set_rows(pngWrtPtr, pngWrtInfoPtr, &row_pointers[0]);
    png_set_write_fn(pngWrtPtr, &p_io, wrtBgPng, NULL);
    png_write_png(pngWrtPtr, pngWrtInfoPtr, PNG_TRANSFORM_IDENTITY, NULL);
    fwrite(p_io.pngBfr + 4, 1, p_io.pngSiz, fw); /* Test file */
    freeExit_w_msg((char *) "The exit was normal.");
}

void wrtBgPng(png_structp pngWrtPtr, png_bytep data, png_size_t length) {
    struct libpng_inmem_write_struct * p = (struct libpng_inmem_write_struct *) png_get_io_ptr(pngWrtPtr);
    p->pngBfr = (unsigned char *) realloc(p->pngBfr, p->pngSiz + length); /* From png.c */
    if (!p->pngBfr) freeExit_w_msg((char *) "The PNG write memory did not correctly allocate.");
    memmove(p->pngBfr + p->pngSiz, data, length);
    p->pngSiz += length;
}

【讨论】:

    【解决方案2】:

    我找到了这段代码的早期版本,并破解了它来生成并保存一个 16 位灰度 PNG 文件。它运行并生成这个 PNG:

    这里是完整的源代码和构建可执行文件的 GCC 命令。它在我的系统上运行,OpenSuse 42/64 和 GCC -> gcc 版本 4.8.5 (SUSE Linux) 和 libpng 1.6.23,但如果有人如此轻率或无耻地实际尝试运行它,它可能会熔化你的通量电容器。 ;)

    #include <png.h>
    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include "string.h"
    
    /* 8 QBit RGB/24 to 16 QBit Grayscale hack
     * based on code found at
     * http://www.lemoda.net/c/write-png/  and png.h libpng version 1.6.23
     */
    /*
     gcc -L/usr/local/static -I/usr/local/static/include -lpng16 /home/photog/bin/png.test.gray16.c  -lm -o  /home/photog/bin/png.tg16
    
     */
    // =============================================================================
    typedef struct {
        uint8_t red;  uint8_t green;  uint8_t blue;  // A colored pixel
    } pixel_t;
    
    typedef struct {
        uint16_t gray;  // A GRAY pixel
    } pixel_gray_16_t;
    
    typedef struct  {  // A picture
        pixel_gray_16_t *pixels;
        size_t width;
        size_t height;
    } bitmap_t;
    
    // =============================================================================
    // Write "bitmap" to a PNG file specified by "path"; returns 0 on
    //   success, non-zero on error
    static int save_png_to_file (bitmap_t *bitmap, const char *path)  {
        FILE * fp;
        png_structp png_ptr = NULL;
        png_infop  info_ptr = NULL;
        size_t x, y;
        int pidx=0;  // Pixel_Index
        png_byte **row_pointers = NULL;  // KLUDGE!!
    
        /* "status" contains the return value of this function. At first
           it is set to a value which means 'failure'. When the routine
           has finished its work, it is set to a value which means
           'success'. */
        int status = -1;
        /* The following number is set by trial and error only. I cannot
           see where it it is documented in the libpng manual  */
        int pixel_size = 2;  // 3 for RGB/24;
        int depth = 16;      // 8 for RGB/24;
    
        fp = fopen (path, "wb");  if (! fp) {  goto fopen_failed;  }
    
        png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
        if (png_ptr == NULL) {  goto png_create_write_struct_failed;  }
    
        info_ptr = png_create_info_struct (png_ptr);
        if (info_ptr == NULL) {  goto png_create_info_struct_failed;  }
    
        /* Set up error handling. */
        if (setjmp (png_jmpbuf (png_ptr))) {  goto png_failure;  }
    
        // Set image attributes;  # de fine PNG_COLOR_TYPE_GRAY 0
        png_set_IHDR (png_ptr, info_ptr, bitmap->width, bitmap->height, depth,
            PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
            PNG_FILTER_TYPE_DEFAULT);
    
        /* Initialize rows of PNG. */
        row_pointers=png_malloc(png_ptr, bitmap->height * sizeof(png_uint_16 *));
    
        // Copy system Callocated user data to PNG owned space  
        for (y=0, pidx=0; y < bitmap->height; ++y) {
            png_byte *row = 
                png_malloc(png_ptr, sizeof(uint8_t) * bitmap->width * pixel_size);
            row_pointers[y] = row;
            memcpy((void *)row, bitmap->pixels+pidx, bitmap->width * 2);
            pidx += bitmap->width;  // Move to next row
        }
        /* Write the image data to "fp". */
        png_init_io (png_ptr, fp);
        png_set_rows (png_ptr, info_ptr, row_pointers);
        png_write_png(png_ptr,info_ptr, PNG_TRANSFORM_SWAP_ENDIAN,NULL); 
        //png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
    
        // The routine has successfully written the file, so we set "status" to a 
        // value which indicates success
        status = 0;
        for(y=0; y < bitmap->height; y++)  png_free (png_ptr, row_pointers[y]);  
        png_free (png_ptr, row_pointers);
    
     png_failure:
     png_create_info_struct_failed:
        png_destroy_write_struct (&png_ptr, &info_ptr);
     png_create_write_struct_failed:
        fclose (fp);
     fopen_failed:
        return status;
    }
    
    // =============================================================================
    // =============================================================================
    
    
    int main ()  {
        bitmap_t fruit;
        const char ofn[]={ "fruit.g16.png" };  // Output FileName
        int x, y, pidx=0;
        uint16_t gray_u16;
        float graysf;  // Gray Scale factor. 0->0, last_pix -> QMax(16)
    
        fruit.width  = 400;  // Size the image
        fruit.height = 400;
        graysf=(65535.0f/fruit.width)/fruit.height;  // Last pix => 65535
        fruit.pixels=calloc(sizeof(pixel_gray_16_t), fruit.width * fruit.height);
    
        // Create linear black -> white gradient
        for(y=0; y < fruit.height; y++)  {
            for(x=0; x < fruit.width; x++)  {
                gray_u16=(uint16_t)lrintf((y*fruit.width+x)*graysf);
                fruit.pixels[pidx++].gray = gray_u16;
            }
        }
    
        // Write the image to a file
        save_png_to_file (&fruit, ofn);
        printf("Wrote gray/16 PNG file %s\n", ofn);
        return 0;
    }  this line may  not compile
    // =============================================================================
    

    groch,我发现上面的精简代码有助于使 16 QBit PNG 代码正常工作。添加此未经测试且具有潜在危险的黑客攻击仅供参考,并附有关于运行它的固有风险的免责声明。添加了编译器错误行,因此有人必须对其进行编辑才能编译,使其成为自己的定制版本。该帖子可能已经为已经很好的线程添加了一些有用的信息。纯粹的哲学思考会增加噪音但没有光...也许您可以在代码中添加一个功能?

    注意:第一次运行在 Intel Skylake CPU 上得到了一个奇怪的结果:

    你已经被字节序排列了!

    不得不改变这一行:

    png_write_png(png_ptr,info_ptr, PNG_TRANSFORM_SWAP_ENDIAN,NULL); 
    

    【讨论】:

    • 我认为更重要的是思考代码与 OP 有何不同,而不是判断是否运行代码。
    • 这个答案错过了问题的重点——将 png 写入磁盘文件很简单,但问题是如何将它写入 RAM 中的缓冲区,这从根本上改变了您使用 libPNG 的方式,例如,你不能使用 png_init_io,你的 write 回调函数在哪里? png_set_write_fn() 在哪里?
    【解决方案3】:
    #include <png.h>
    #include <vector>
    #include <iostream>
    #include <stdlib.h>
    
    //encode and write PNG to memory (std::vector) with libpng on C++
    
    typedef unsigned char ui8;
    #define ASSERT_EX(cond, error_message) do { if (!(cond)) { std::cerr << error_message; exit(1);} } while(0)
    
    static void PngWriteCallback(png_structp  png_ptr, png_bytep data, png_size_t length) {
        std::vector<ui8> *p = (std::vector<ui8>*)png_get_io_ptr(png_ptr);
        p->insert(p->end(), data, data + length);
    }
    
    struct TPngDestructor {
        png_struct *p;
        TPngDestructor(png_struct *p) : p(p)  {}
        ~TPngDestructor() { if (p) {  png_destroy_write_struct(&p, NULL); } }
    };
    
    void WritePngToMemory(size_t w, size_t h, const ui8 *dataRGBA, std::vector<ui8> *out) {
        out->clear();
        png_structp p = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
        ASSERT_EX(p, "png_create_write_struct() failed");
        TPngDestructor destroyPng(p);
        png_infop info_ptr = png_create_info_struct(p);
        ASSERT_EX(info_ptr, "png_create_info_struct() failed");
        ASSERT_EX(0 == setjmp(png_jmpbuf(p)), "setjmp(png_jmpbuf(p) failed");
        png_set_IHDR(p, info_ptr, w, h, 8,
                PNG_COLOR_TYPE_RGBA,
                PNG_INTERLACE_NONE,
                PNG_COMPRESSION_TYPE_DEFAULT,
                PNG_FILTER_TYPE_DEFAULT);
        //png_set_compression_level(p, 1);
        std::vector<ui8*> rows(h);
        for (size_t y = 0; y < h; ++y)
            rows[y] = (ui8*)dataRGBA + y * w * 4;
        png_set_rows(p, info_ptr, &rows[0]);
        png_set_write_fn(p, out, PngWriteCallback, NULL);
        png_write_png(p, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
    }
    

    【讨论】:

    • 这似乎来自here
    • 对于 libpng,它是 size_t,而不是 png_size_t。见png_rw_ptr的定义。
    【解决方案4】:

    是的,使用 png_set_write_fn 这样的东西 - 未经测试:

    更新评论编辑

    /* structure to store PNG image bytes */
    struct mem_encode
    {
      char *buffer;
      size_t size;
    }
    
    
    void
    my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
    {
      /* with libpng15 next line causes pointer deference error; use libpng12 */
      struct mem_encode* p=(struct mem_encode*)png_get_io_ptr(png_ptr); /* was png_ptr->io_ptr */
      size_t nsize = p->size + length;
    
      /* allocate or grow buffer */
      if(p->buffer)
        p->buffer = realloc(p->buffer, nsize);
      else
        p->buffer = malloc(nsize);
    
      if(!p->buffer)
        png_error(png_ptr, "Write Error");
    
      /* copy new bytes to end of buffer */
      memcpy(p->buffer + p->size, data, length);
      p->size += length;
    }
    
    /* This is optional but included to show how png_set_write_fn() is called */
    void
    my_png_flush(png_structp png_ptr)
    {
    }
    
    
    
    int save_png_to_file(RGBBitmap *bitmap, const char *path)
    {
    ...
    /* static */
    struct mem_encode state;
    
    /* initialise - put this before png_write_png() call */
    state.buffer = NULL;
    state.size = 0;
    
    /* if my_png_flush() is not needed, change the arg to NULL */
    png_set_write_fn(png_ptr, &state, my_png_write_data, my_png_flush);
    
    ... call png_write_png() ...
    
    /* now state.buffer contains the PNG image of size s.size bytes */
    
    /* cleanup */
    if(state.buffer)
      free(state.buffer);
    

    【讨论】:

    • 谢谢,这正是我所需要的。但是有一个错字:&(p->buffer + p->size) 应该只是 p->buffer + p->size。另外,我认为不需要空的 my_png_flush() 函数;您可以将 NULL 传递给 png_set_write_fn() 的最后一个参数。
    • 关于 io_ptr 取消引用不起作用的评论,较新版本的 libpng 的正确方法是调用 png_get_io_ptr(png_ptr) - 请参阅 refspecs.linuxbase.org/LSB_3.1.0/LSB-Desktop-generic/…
    • 定义一个空的 my_png_flush 函数不是可选的 - 如果你使用 NULL 代替,libpng 会尝试在你的状态结构上使用 flush()。
    • 你能告诉我什么是 libpng16 中 png_get_io_ptr() 的正确替换吗?
    • 这段代码可能存在内存泄漏,而且带有NULL 指针的realloc()malloc() 基本相同。所以if 毫无意义。
    猜你喜欢
    • 2014-04-16
    • 2021-01-14
    • 2013-02-24
    • 1970-01-01
    • 2019-04-13
    • 2017-12-08
    • 2019-06-15
    • 2011-11-29
    • 2022-08-05
    相关资源
    最近更新 更多