How to encode PNG to buffer using libpng?
Asked Answered
S

4

28

I'm currently using the following to write a PNG to a file:

#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;
}

How can I write a similar function (in C) to encode a PNG to an in-memory buffer?

The prototype would look something like this:

uint8_t *encode_png_to_buffer(RGBBitmap *source);

And it seems that I would probably need to make some use of png_set_write_fn().

But apart from that I'm not sure how to approach this. Are there any examples of this being done? Surely I'm not the first to need this functionality.

Sizable answered 30/11, 2009 at 19:17 Comment(5)
Firstly, try and get it to compile. Check the definition of save_png_to_file for missing * ie RGBBitmap * bitmap (as you're treating bitmap like it is a pointer later. And where is bytes_per_pixel defined? (nowhere!)Buehler
What exactly do you mean by "encode a PNG to an in-memory buffer"? Do you mean encode an image to a PNG to a buffer? Do you mean decode a PNG to a buffer?Buehler
Ah, sorry for the typos, I've fixed them. Sorry if I wasn't clear; what I mean is: I have a decompressed bitmap in the "RGBBitmap" structure that I want to convert to a PNG. I do not want to write this PNG to a file, though, I just want a raw buffer of the data that would be written.Sizable
I think row_pointers = png_malloc(png_ptr, bitmap->height * sizeof(png_byte )); should be png_byte *row = (png_byte)png_malloc(png_ptr, sizeof(uint8_t) * bitmap->bytes_per_pixel * bitmap->width);Nyctophobia
please how have you had installed libpng, cause I couldn't be able to use #include <png.h> on windows.Mawson
W
18

Yes, using png_set_write_fn something like this - untested:

Updated with edits from comment

/* 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);
Washcloth answered 1/12, 2009 at 2:11 Comment(7)
Thanks, this is what I needed. There is a typo, though: &(p->buffer + p->size) should just be p->buffer + p->size. Also, I don't think there is a need for the empty my_png_flush() function; you can just pass NULL to the last arg of png_set_write_fn().Sizable
In reference to the comment about the io_ptr dereference not working, the correct method with newer versions of libpng is to call png_get_io_ptr(png_ptr) - see refspecs.linuxbase.org/LSB_3.1.0/LSB-Desktop-generic/…Award
Defining an empty my_png_flush function is not optional - if you'd use NULL instead, libpng would try to use flush() on your state struct.Gaffney
Can you please tell what is correct substitution for png_get_io_ptr() in libpng16?Reactivate
This code has the potential for a memory leak, also realloc() with a NULL pointer is essentially the same as malloc(). So the if is pointless.Monikamoniker
Hi, I'm newbie on png topic and I got stumbled upon one thing while using this solution. We routed the data into a buffer instead of an image file but there I saw a difference in the content like the buffer data is starting from ".IDAT" but the data in png file was started from ".png". Is there something I'm missing ? and if not is there any way to get the png header as well ? @WashclothPodolsk
This code is taken from here: newbedev.com/how-to-encode-png-to-buffer-using-libpng and unfortunately, it's so incomplete, it's just not fit for purpose.Belch
P
5
#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);
}
Peshawar answered 10/11, 2015 at 21:6 Comment(2)
This appears to be from here.Monies
For libpng, it’s size_t, not png_size_t. See the definition of png_rw_ptr.Option
E
2

The other answers don't seem to be complete, to my tastes. So, I used these answers and other research to write a black background to a buffer. Then to check, I wrote the buffer to a file. This was compiled with gcc. The library flag -lpng was added.

#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;
}
Epizoic answered 2/3, 2019 at 21:41 Comment(0)
P
0

I found an earlier version of this code and hacked it to generate and save a 16 bit grayscale PNG file. It runs and generates this PNG: Sample grayscale from near 0 to near 64k in a 400x400 png

Here is the full source and the GCC command to build the executable. It runs on my system, OpenSuse 42/64 with GCC -> gcc version 4.8.5 (SUSE Linux) AND libpng 1.6.23, but may melt your flux capacitor should anybody be so imprudent or impudent to actually try to run it. ;)

#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, I found the above, stripped down code helpful in getting 16 QBit PNG code working. This untested and potentially hazardous hack was added for reference purposes only with a disclaimer about the inherent risks of running it. The compiler error line was added so somebody would have to edit it to compile, making it their own, customized version. The post may have added an iota of useful info to an already good thread. Purely philosophical wonderings add noise but no light... Perhaps you could add a feature to the code?

NOTE: The first run got a weird result on Intel Skylake CPU:

You have been Endianed!

Wrong Endian!  PNGs are saved in sane, network byte order. This is what byte-scrambled dis-order really looks like!

Had to alter this line:

png_write_png(png_ptr,info_ptr, PNG_TRANSFORM_SWAP_ENDIAN,NULL); 
Petrillo answered 13/7, 2016 at 0:42 Comment(2)
I'll argue that it is more important to wonder about how the code differ from OP's rather than about the judgment of whether to run the code or not.Laevo
This answer has missed the point of the question - it's trivial to write pngs to disk files, but the question is about how to do it to a buffer in RAM, which fundamentally changes how you use libPNG, for instance, you can't use png_init_io, and where's your write callback function? Where's the png_set_write_fn() ?Belch

© 2022 - 2024 — McMap. All rights reserved.