How to encode PNG to buffer using libpng?
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;
}
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:
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!
Had to alter this line:
png_write_png(png_ptr,info_ptr, PNG_TRANSFORM_SWAP_ENDIAN,NULL);
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);
#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);
}