PNGDIB

A DIB-PNG conversion library for Win32
By Jason Summers <jason1@pobox.com>
Version 3.1.0, Jul. 2010
Web site: <http://entropymine.com/jason/pngdib/>

This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Permission is hereby granted to use, copy, modify, and distribute this source code for any purpose, without fee.

Requirements

Introduction

PNGDIB is a library that makes it easy to convert between PNG images and Windows Device Independent Bitmaps (DIBs). PNGDIB is designed to be quick and easy to use, but it does not attempt to support every feature that one might want. If you wish use the PNG format to its full capabilities, you may need to use libpng directly, or write our own extensions to PNGDIB.

When using PNGDIB to read PNG files, it will create version 3.0 DIBs/BMPs. The DIB will be uncompressed, with either 1, 4, 8, or 24 bits-per-pixel.

(Note: A BMP image file is basically just a DIB copied directly from memory, with a 14-byte file header tacked onto the front. The terms DIB and BMP may sometimes be used almost interchangeably.)

When writing PNG files, PNGDIB supports approximately version 3.0 DIBs, and also OS/2-style DIBs. It supports bit depths of 1, 4, 8, 16, 24, and 32 bits-per-pixel. It supports 4 bpp and 8 bpp compressed DIBs (it uses Windows GDI functions to uncompress them).

The library itself currently consists of just two files: pngdib.c and pngdib.h.

Two sample programs, png2bmp.c and bmp2png.c, are included which demonstrate the use of library functions. They can be compiled as "console mode" Win32 applications.

A third sample, "smview", is a bare-bones Windows program that uses pngdib to read and display PNG files. This is intended to demonstrate that it is easy to add PNG support to an application that uses DIBs.

Using the library

Overview

First call pngdib_d2p_init() or pngdib_p2d_init() to create a PNGDIB object. A few functions can accept either type of object, but most only work with one of the two types. Call pngdib_done() when finished.

The code samples below show an overview of the process.

Sample code for writing a PNG:
void save_dib_to_png_file(char *png_filename,
      LPBITMAPINFOHEADER dib, int dib_size)
{
  PNGDIB *pngdib;

  pngdib = pngdib_d2p_init();
  pngdib_d2p_set_dib(pngdib,dib,dib_size,NULL,0);
  pngdib_d2p_set_png_filename(pngdib,png_filename);
  pngdib_d2p_run(pngdib);
  pngdib_done(pngdib);
}
Sample code for reading a PNG:
void read_png_from_file(char *png_filename)
{
  PNGDIB *pngdib;
  LPBITMAPINFOHEADER dib;

  pngdib = pngdib_p2d_init();
  pngdib_p2d_set_png_filename(pngdib,png_filename);
  pngdib_p2d_run(pngdib);
  pngdib_p2d_get_dib(pngdib,&dib,NULL);
  pngdib_done(pngdib);

  /* ... do something with dib ... */

  pngdib_p2d_free_dib(NULL,dib);
}

Unless otherwise specified, functions that return a value return nonzero on success, and zero on failure.

Note: If you are not using Unicode, "TCHAR" means exactly the same thing as "char".

Functions that don't require an object, or that accept either type

int pngdib_done(PNGDIB *xx);
Cleans up and frees any memory and other resources allocated by the library. The only exception is the DIB that may have been created, which must be freed by the caller.
TCHAR* pngdib_get_version_string(void);
Returns a pointer to a static string indicating the version, such as "6.7.8".
int pngdib_get_version(void);
Returns an integer representing the version. For example, 60708 would be version 6.7.8.
void pngdib_setcallback_malloc(PNGDIB *xx, pngdib_malloc_cb_type mallocfunc, pngdib_free_cb_type freefunc, pngdib_realloc_cb_type reallocfunc);
  typedef void* (PNGDIB_DECL *pngdib_malloc_cb_type)(void *userdata, int memblksize);
  typedef void (PNGDIB_DECL *pngdib_free_cb_type)(void *userdata, void *memblk);
  typedef void* (PNGDIB_DECL *pngdib_realloc_cb_type)(void *userdata, void *memblk, int memblksize);
Use your own memory allocation functions. This is currently only used when converting PNG-to-DIB. The pointer you set with pngdib_set_userdata will be passed to your functions when they are called by PNGDIB. reallocfunc is reserved for future use, and can be NULL. Your malloc function should zero out the memory that it allocates.
void pngdib_setcallback_pngptrhook(PNGDIB *xx, pngdib_pngptrhook_cb_type pngptrhookfn);
  typedef void (PNGDIB_DECL *pngdib_pngptrhook_cb_type)(void *userdata, void *pngptr);
Set a callback function to be called immediately after the main libpng object has been created. Cast the "pngptr" param to a "png_structp" to use it. Not recommended if you can avoid using it, but sometimes you need a way to call png_set_user_limits() or similar functions.
TCHAR* pngdib_get_error_msg(PNGDIB *xx);
Returns a pointer to an error message reflecting the most recent error that occurred in pngdib_d2p_run or pndib_p2d_run.
void pngdib_set_userdata(PNGDIB *xx, void *userdata);
The client application can associate a pointer with the PNGDIB object, for its own use. This is useful in conjunction with callback functions.
void* pngdib_get_userdata(PNGDIB *xx);
Returns the pointer set with pngdib_set_userdata.
void pngdib_set_dibalpha32(PNGDIB *xx, int flag);
This is an experimental function that you probably shouldn't use. If flag is 1, it tells PNGDIB to use 32-bit DIBs, with 8 or the 32 bits used for an alpha channel (0=transparent, 255=opaque). This is not a standard type of DIB.

DIB-to-PNG functions

PNGDIB* pngdib_d2p_init(void)
Returns a pointer to a new PNG-to-DIB object, which can be passed to other functions. Returns NULL if memory allocation failed. Note: This is actually implemented as a macro.
int pngdib_d2p_set_dib(PNGDIB *d2p, const BITMAPINFOHEADER* pdib, int dibsize, const void* pbits, int bitssize);
Tells PNGDIB where to find your DIB in memory.
pdib: direct pointer to the beginning of your DIB's header.
dibsize: the number of bytes starting with pdib that are safe to read. Can be 0 if this is unknown.
pbits: direct pointer to the beginning of the "bits" section of your DIB. This can be NULL if it directly follows the DIB's header and palette information.
bitssize: the number of bytes starting with pbits that are safe to read. Can be 0 if you don't know. This is ignored if pbits is NULL.
void pngdib_d2p_set_interlace(PNGDIB *d2p, int interlaced);
interlaced: 0 (default) = write a noninterlaced PNG image; 1 = write an interlaced image.
int pngdib_d2p_set_png_filename(PNGDIB *d2p, const TCHAR *fn);
Pointer to null-terminated filename of the PNG file to write.
void pngdib_d2p_set_png_write_fn(PNGDIB *d2p, pngdib_write_cb_type writefunc);
Use this instead of pngdib_d2p_set_png_filename to use a custom write function. The pngdib_write_cb_type type is defined as:
typedef int (PNGDIB_DECL *pngdib_write_cb_type)(void *userdata, const void *buf, int nbytes);
Your write function must consume all the bytes supplied to it, and return the number of bytes written ('nbytes').
int pngdib_d2p_set_software_id(PNGDIB *d2p, const TCHAR *s);
Pointer to a brief NULL-terminated string identifying your application. Set to NULL (or don't call this function) if you don't want your app to be identified in the PNG file.
void pngdib_d2p_set_gamma_label(PNGDIB *d2p, int flag, double file_gamma);
flag: 0 = Do not write a gamma label to the PNG file; 1 (default): write a gamma label to the file; file_gamma: gamma value to write, default = PNGDIB_DEFAULT_FILE_GAMMA = 1/2.20.
int pngdib_d2p_run(PNGDIB *d2p);
Write the PNG file.

PNG-to-DIB functions

PNGDIB* pngdib_p2d_init(void)
Returns a pointer to a new DIB-to-PNG object, which can be passed to other functions. Returns NULL if memory allocation failed. Note: This is actually implemented as a macro.
int pngdib_p2d_set_png_filename(PNGDIB *p2d, const TCHAR *fn);
Set the filename of the input PNG file.
void pngdib_p2d_set_png_memblk(PNGDIB *p2d, const void *mem, int memsize);
Use this instead of pngdib_p2d_set_png_filename to have the PNG "file" read from a memory block instead of a disk file. You can set memsize to 0 if the size isn't known, but this could lead to buffer overruns if the image isn't a valid PNG file.
void pngdib_p2d_set_png_read_fn(PNGDIB *p2d, pngdib_read_cb_type readfunc);
Use this instead of pngdib_p2d_set_png_filename to use a custom read function. The pngdib_read_cb_type type is defined as:
typedef int (PNGDIB_DECL *pngdib_read_cb_type)(void *userdata, void *buf, int nbytes);
Your read function must try to fill the buffer with the number of bytes requested, and return the number of bytes written to it. (If not enough bytes are available, this indicates that the PNG file is invalid.)
void pngdib_p2d_set_use_file_bg(PNGDIB *p2d, int flag);
Indicates whether the background color stored with the image should be applied to transparent areas. Set flag to 1 to use the image's background color if possible, 0 to ignore it.
void pngdib_p2d_set_custom_bg(PNGDIB *p2d, unsigned char r, unsigned char g, unsigned char b);
Defines a background color to apply to tranparent parts of the image. If both this and pngdib_p2d_set_use_file_bg are called, the file background color will take precedence if it exists.
void pngdib_p2d_set_gamma_correction(PNGDIB *p2d, int flag, double screen_gamma);
flag: 0 (default) = do not do gamma correction; 1 = gamma correct colors when reading the PNG image.
For the screen_gamma parameter, you can use the PNGDIB_DEFAULT_SCREEN_GAMMA symbol, or set your own display gamma. PNG files with no gamma label are assumed have a gamma of 1/2.20.
int pngdib_p2d_run(PNGDIB *p2d);
Read the PNG file and create the DIB. If this function is successful, the caller is responsible for freeing the DIB. See pngdib_p2d_free_dib for details.
int pngdib_p2d_get_dib(PNGDIB *p2d, BITMAPINFOHEADER **ppdib, int *pdibsize);
Retrieve a pointer to the DIB, and its size in bytes.
int pngdib_p2d_get_dibbits(PNGDIB *p2d, void **ppbits, int *pbitsoffset, int *pbitssize);
Retrieve information about the "bits" (pixels) section of the DIB.
int pngdib_p2d_get_palette(PNGDIB *p2d, RGBQUAD **ppal, int *ppaloffset, int *ppalnumcolors);
Returns information about the DIB palette. ppal is a direct pointer to the palette, ppaloffset is the number of bytes from the beginning of the DIB to the beginning of the palette, ppalnumcolors is the number of colors in the palette. Any of the parameters may be NULL if the information is not needed.
int pngdib_p2d_get_colortype(PNGDIB *p2d);
Returns an integer representing the basic color type of the original PNG image.
  0 = grayscale
  2 = RGB
  3 = palette color
  4 = grayscale+alpha
  6 = RGB+alpha
int pngdib_p2d_get_bitspersample(PNGDIB *p2d);
Returns the number of bits-per-sample stored in the original PNG image. For paletted images, returns the number of bits-per-pixel.
int pngdib_p2d_get_bitsperpixel(PNGDIB *p2d);
Returns the number of bits-per-pixel stored in the original PNG image.
int pngdib_p2d_get_samplesperpixel(PNGDIB *p2d);
Returns the number of samples-per-pixel stored in the original PNG image. For paletted images, returns 1.
int pngdib_p2d_get_interlace(PNGDIB *p2d);
Returns 0 if the PNG image was noniterlaced; nonzero if it was interlaced.
int pngdib_p2d_get_density(PNGDIB *p2d, int *dens_x, int *dens_y, int *dens_units);
Retrieves the x and y density (a.k.a. "resolution" or "physical pixel size") label of the PNG image. If the label exists, returns nonzero and sets the parameters. The units parameter will be 1 meaning the x and y values are in pixels per meter, or 0 meaning pixels per unspecified unit.
int pngdib_p2d_get_file_gamma(PNGDIB *p2d, double *pgamma);
Retrieves the file gamma setting stored in the PNG file. Returns nonzero if successful; 0 if the file did not contain gamma information.
int pngdib_p2d_get_bgcolor(PNGDIB *p2d, unsigned char *pr, unsigned char *pg, unsigned char *pb);
Retrieves the background color used when processing the image, or returns 0 if none was used. If gamma correction was performed by PNGDIB, the background color will be gamma corrected if it came from the PNG file.
void pngdib_p2d_free_dib(PNGDIB *p2d, BITMAPINFOHEADER *pdib);
Free the DIB memory block. If you have not yet called pngdib_done, set pdib to NULL. If you have called pngdib_done, you can only call this function if you did not set your own custom memory allocation functions -- in that case, set p2d to NULL. If you did set your own memory allocation functions, then use your own "free" function instead of this one.

Obsolete functions

int read_png_to_dib(PNGD_P2DINFO *p2dp)
Provided only for backward compatibility.
int write_dib_to_png(PNGD_D2PINFO *d2pp)
Provided only for backward compatibility.

To Do

Some changes I might conceivably make in some future version...

History

CHANGES in PNGDIB v3.1.0

CHANGES in PNGDIB v3.0.2

CHANGES in PNGDIB v3.0.1 (vs. 3.0.0)

CHANGES in PNGDIB v3.0.0 (vs. 2.x)

Instructions for the sample PNG viewer (smview)

Load a PNG image by selecting File|Open from the menu, or by drag-and-drop from Windows Explorer.

Gamma Correction - Toggles gamma correction. If you turn this off, some images, or parts of images, will look too light or too dark.

Save As PNG - Saves the visible image to a file. This saves the image as it is currently being displayed. The saved image will not have any transparency, and the background color and gamma correction will be applied to the image, not saved as meta-data. This means that you can lose information by loading and then saving a PNG image.

Background colors - You can define a background color that will be used in certain situations. The following logic is used to select a background color: If "Use Image's Background Color" is checked, and the current PNG image file contains a suggested background color, that suggested background color will be used. Otherwise, if "Use Custom Background Color" is checked, your custom background color will be used (by default this is a very light gray color). Otherwise, no background color will be used at all, and transparency information in the image will be completely ignored. Some images will not look very good in this situation. (A default background color – usually white – will be used for the window, but will not be applied to the image.)

Any time you make a change to the gamma or background color settings, the PNG file will be reloaded from disk. That's not the ideal thing to do, but this is just a demo program...