/* * Copyright (c) 2010, Nicolas Limare * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * The views and conclusions contained in the software and * documentation are those of the authors and should not be * interpreted as representing official policies, either expressed * or implied, of the copyright holder. */ /** * @mainpage image read/write simplified interface * * README.txt: * @verbinclude README.txt */ /** * @file io_png.c * @brief PNG read/write simplified interface * * This is a front-end to libpng, with routines to: * @li read a PNG file as a deinterlaced 8bit integer or float array * @li write a 8bit integer or float array to a PNG file * * Multi-channel images are handled : grey, grey+alpha, rgb and * rgb+alpha, as well as on-the-fly color model conversion. * * @todo handle lossless 16bit data * @todo add a test suite * @todo internally handle RGB/gray conversion in read_png_raw() * @todo handle deinterlacing as a libpng transform function * * @author Nicolas Limare */ #include #include #include #include /* option to use a local version of the libpng */ #ifdef _LOCAL_LIBS #include "png.h" #else #include /* Guoshen Yu, Windows version, 2010.10.16 */ /* #include "./libs/png/png.h" */ #endif /* ensure consistency */ #include "io_png.h" #define PNG_SIG_LEN 4 /* internal only datatype identifiers */ #define IO_PNG_U8 0x0001 /* 8bit unsigned integer */ #define IO_PNG_F32 0x0002 /* 32bit float */ /* * READ */ /** * @brief internal function used to cleanup the memory when * png_read_raw() fails * * @param fp file pointer to close, ignored if NULL * @param png_ptr_p, info_ptr_p, pointers to PNG structure pointers, * ignored if NULL * @return NULL */ static void *read_png_abort(FILE * fp, png_structp * png_ptr_p, png_infop * info_ptr_p) { png_destroy_read_struct(png_ptr_p, info_ptr_p, NULL); if (NULL != fp && stdin != fp) (void) fclose(fp); return NULL; } /** * @brief internal function used to read a PNG file into an array * * @todo don't loose 16bit info * * @param fname PNG file name, "-" means stdin * @param nx, ny, nc pointers to variables to be filled * with the number of columns, lines and channels of the image * @param transform a PNG_TRANSFORM to be added to the default read transforms * @param dtype identifier for the data type to be used for output * @return pointer to an allocated array of pixels, * or NULL if an error happens */ static void *read_png_raw(const char *fname, size_t * nx, size_t * ny, size_t * nc, int transform, int dtype) { png_byte png_sig[PNG_SIG_LEN]; png_structp png_ptr; png_infop info_ptr; png_bytepp row_pointers; png_bytep row_ptr; /* volatile : because of setjmp/longjmp */ FILE *volatile fp = NULL; void *data = NULL; unsigned char *data_u8 = NULL; unsigned char *data_u8_ptr = NULL; float *data_f32 = NULL; float *data_f32_ptr = NULL; size_t size; size_t i, j, k; /* parameters check */ if (NULL == fname || NULL == nx || NULL == ny || NULL == nc) return NULL; if (IO_PNG_U8 != dtype && IO_PNG_F32 != dtype) return NULL; /* open the PNG input file */ if (0 == strcmp(fname, "-")) fp = stdin; else if (NULL == (fp = fopen(fname, "rb"))) return NULL; /* read in some of the signature bytes and check this signature */ if ((PNG_SIG_LEN != fread(png_sig, 1, PNG_SIG_LEN, fp)) || 0 != png_sig_cmp(png_sig, (png_size_t) 0, PNG_SIG_LEN)) return read_png_abort(fp, NULL, NULL); /* * create and initialize the png_struct * with the default stderr and error handling */ if (NULL == (png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) return read_png_abort(fp, NULL, NULL); /* allocate/initialize the memory for image information */ if (NULL == (info_ptr = png_create_info_struct(png_ptr))) return read_png_abort(fp, &png_ptr, NULL); /* set error handling */ if (0 != setjmp(png_jmpbuf(png_ptr))) /* if we get here, we had a problem reading the file */ /* free all of the memory associated with the png_ptr and info_ptr */ return read_png_abort(fp, &png_ptr, &info_ptr); /* set up the input control using standard C streams */ png_init_io(png_ptr, fp); /* let libpng know that some bytes have been read */ png_set_sig_bytes(png_ptr, PNG_SIG_LEN); /* * set the read filter transforms, to get 8bit RGB whatever the * original file may contain: * PNG_TRANSFORM_STRIP_16 strip 16-bit samples to 8 bits * PNG_TRANSFORM_PACKING expand 1, 2 and 4-bit * samples to bytes */ transform |= (PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING); /* read in the entire image at once */ png_read_png(png_ptr, info_ptr, transform, NULL); /* get image informations */ *nx = (size_t) png_get_image_width(png_ptr, info_ptr); *ny = (size_t) png_get_image_height(png_ptr, info_ptr); *nc = (size_t) png_get_channels(png_ptr, info_ptr); row_pointers = png_get_rows(png_ptr, info_ptr); /* * allocate the output data RGB array * deinterlace and convert png RGB RGB RGB 8bit to RRR GGG BBB * the image is deinterlaced layer after layer * this generic loop also works for one single channel */ size = *nx * *ny * *nc; switch (dtype) { case IO_PNG_U8: if (NULL == (data_u8 = (unsigned char *) malloc(size * sizeof(unsigned char)))) return read_png_abort(fp, &png_ptr, &info_ptr); data = (void *) data_u8; for (k = 0; k < *nc; k++) { /* channel loop */ data_u8_ptr = data_u8 + (size_t) (*nx * *ny * k); for (j = 0; j < *ny; j++) { /* row loop */ row_ptr = row_pointers[j] + k; for (i = 0; i < *nx; i++) { /* pixel loop */ *data_u8_ptr++ = (unsigned char) *row_ptr; row_ptr += *nc; } } } break; case IO_PNG_F32: if (NULL == (data_f32 = (float *) malloc(size * sizeof(float)))) return read_png_abort(fp, &png_ptr, &info_ptr); data = (void *) data_f32; for (k = 0; k < *nc; k++) { /* channel loop */ data_f32_ptr = data_f32 + (size_t) (*nx * *ny * k); for (j = 0; j < *ny; j++) { /* row loop */ row_ptr = row_pointers[j] + k; for (i = 0; i < *nx; i++) { /* pixel loop */ *data_f32_ptr++ = (float) *row_ptr; row_ptr += *nc; } } } break; } /* clean up and free any memory allocated, close the file */ (void) read_png_abort(fp, &png_ptr, &info_ptr); return data; } /** * @brief read a PNG file into a 8bit integer array * * The array contains the deinterlaced channels. * 1, 2 and 4bit images are converted to 8bit. * 16bit images are previously downscaled to 8bit. * * @todo don't downscale 16bit images. * * @param fname PNG file name * @param nx, ny, nc pointers to variables to be filled with the number of * columns, lines and channels of the image * @return pointer to an allocated unsigned char array of pixels, * or NULL if an error happens */ unsigned char *read_png_u8(const char *fname, size_t * nx, size_t * ny, size_t * nc) { /* read the image as unsigned char */ return (unsigned char *) read_png_raw(fname, nx, ny, nc, PNG_TRANSFORM_IDENTITY, IO_PNG_U8); } /** * @brief read a PNG file into a 8bit integer array, converted to RGB * * See read_png_u8() for details. */ unsigned char *read_png_u8_rgb(const char *fname, size_t * nx, size_t * ny) { size_t nc; unsigned char *img; /* read the image */ img = (unsigned char *) read_png_raw(fname, nx, ny, &nc, PNG_TRANSFORM_STRIP_ALPHA, IO_PNG_U8); if (NULL == img) /* error */ return NULL; if (3 == nc) /* already RGB */ return img; else { /* convert to RGB */ unsigned char *ptr_r, *ptr_g, *ptr_b, *ptr_end; /* resize the image */ img = realloc(img, 3 * *nx * *ny * sizeof(unsigned char)); /* gray->RGB conversion */ ptr_r = img; ptr_end = ptr_r + *nx * *ny; ptr_g = img + *nx * *ny; ptr_b = img + 2 * *nx * *ny; while (ptr_r < ptr_end) { *ptr_g++ = *ptr_r; *ptr_b++ = *ptr_r++; } return img; } } /** * @brief read a PNG file into a 8bit integer array, converted to gray * * See read_png_u8() for details. */ unsigned char *read_png_u8_gray(const char *fname, size_t * nx, size_t * ny) { size_t nc; unsigned char *img; /* read the image */ img = (unsigned char *) read_png_raw(fname, nx, ny, &nc, PNG_TRANSFORM_STRIP_ALPHA, IO_PNG_U8); if (NULL == img) /* error */ return NULL; if (1 == nc) /* already gray */ return img; else { /* convert to gray */ unsigned char *ptr_r, *ptr_g, *ptr_b, *ptr_gray, *ptr_end; /* * RGB->gray conversion * Y = (6969 * R + 23434 * G + 2365 * B)/32768 * integer approximation of * Y = 0.212671 * R + 0.715160 * G + 0.072169 * B */ ptr_r = img; ptr_g = img + *nx * *ny; ptr_b = img + 2 * *nx * *ny; ptr_gray = img; ptr_end = ptr_gray + *nx * *ny; while (ptr_gray < ptr_end) *ptr_gray++ = (unsigned char) (6969 * *ptr_r++ + 23434 * *ptr_g++ + 2365 * *ptr_b++) / 32768; /* resize and return the image */ img = realloc(img, *nx * *ny * sizeof(unsigned char)); return img; } } /** * @brief read a PNG file into a 32bit float array * * The array contains the deinterlaced channels. * 1, 2, 4 and 8bit images are converted to float values * between 0. and 1., 3., 15. or 255. * 16bit images are also downscaled to 8bit before conversion. * * @param fname PNG file name * @param nx, ny, nc pointers to variables to be filled with the number of * columns, lines and channels of the image * @return pointer to an allocated unsigned char array of pixels, * or NULL if an error happens */ float *read_png_f32(const char *fname, size_t * nx, size_t * ny, size_t * nc) { /* read the image as float */ return (float *) read_png_raw(fname, nx, ny, nc, PNG_TRANSFORM_IDENTITY, IO_PNG_F32); } /** * @brief read a PNG file into a 32bit float array, converted to RGB * * See read_png_f32() for details. */ float *read_png_f32_rgb(const char *fname, size_t * nx, size_t * ny) { size_t nc; float *img; /* read the image */ img = (float *) read_png_raw(fname, nx, ny, &nc, PNG_TRANSFORM_STRIP_ALPHA, IO_PNG_F32); if (NULL == img) /* error */ return NULL; if (3 == nc) /* already RGB */ return img; else { /* convert to RGB */ float *ptr_r, *ptr_g, *ptr_b, *ptr_end; /* resize the image */ img = realloc(img, 3 * *nx * *ny * sizeof(float)); /* gray->RGB conversion */ ptr_r = img; ptr_end = ptr_r + *nx * *ny; ptr_g = img + *nx * *ny; ptr_b = img + 2 * *nx * *ny; while (ptr_r < ptr_end) { *ptr_g++ = *ptr_r; *ptr_b++ = *ptr_r++; } return img; } } /** * @brief read a PNG file into a 32bit float array, converted to gray * * See read_png_f32() for details. */ float *read_png_f32_gray(const char *fname, size_t * nx, size_t * ny) { size_t nc; float *img; /* read the image */ img = (float *) read_png_raw(fname, nx, ny, &nc, PNG_TRANSFORM_STRIP_ALPHA, IO_PNG_F32); if (NULL == img) /* error */ return NULL; if (1 == nc) /* already gray */ return img; else { /* convert to gray */ float *ptr_r, *ptr_g, *ptr_b, *ptr_gray, *ptr_end; /* * RGB->gray conversion * Y = (6969 * R + 23434 * G + 2365 * B)/32768 * integer approximation of * Y = 0.212671 * R + 0.715160 * G + 0.072169 * B */ ptr_r = img; ptr_g = img + *nx * *ny; ptr_b = img + 2 * *nx * *ny; ptr_gray = img; ptr_end = ptr_gray + *nx * *ny; while (ptr_gray < ptr_end) *ptr_gray++ = (float) (6969 * *ptr_r++ + 23434 * *ptr_g++ + 2365 * *ptr_b++) / 32768; /* resize and return the image */ img = realloc(img, *nx * *ny * sizeof(float)); return img; } } /* * WRITE */ /** * @brief internal function used to cleanup the memory when * png_write_raw() fails * * @param fp file pointer to close, ignored if NULL * @param idata, row_pointers arrays to free, ignored if NULL * @param png_ptr_p, info_ptr_p, pointers to PNG structure pointers, * ignored if NULL * @return -1 */ static int write_png_abort(FILE * fp, png_byte * idata, png_bytep * row_pointers, png_structp * png_ptr_p, png_infop * info_ptr_p) { png_destroy_write_struct(png_ptr_p, info_ptr_p); if (NULL != row_pointers) free(row_pointers); if (NULL != idata) free(idata); if (NULL != fp && stdout != fp) (void) fclose(fp); return -1; } /** * @brief internal function used to write a byte array as a PNG file * * The PNG file is written as a 8bit image file, interlaced, * truecolor. Depending on the number of channels, the color model is * gray, gray+alpha, rgb, rgb+alpha. * * @todo handle 16bit * * @param fname PNG file name, "-" means stdout * @param data deinterlaced (RRR..GGG..BBB..AAA) image byte array * @param nx, ny, nc number of columns, lines and channels * @param dtype identifier for the data type to be used for output * @return 0 if everything OK, -1 if an error occured */ static int write_png_raw(const char *fname, const void *data, size_t nx, size_t ny, size_t nc, int dtype) { png_structp png_ptr; png_infop info_ptr; png_byte *idata = NULL, *idata_ptr = NULL; png_bytep *row_pointers = NULL; png_byte bit_depth; /* volatile : because of setjmp/longjmp */ FILE *volatile fp; const unsigned char *data_u8 = NULL; const unsigned char *data_u8_ptr = NULL; const float *data_f32 = NULL; const float *data_f32_ptr = NULL; float tmp; int color_type, interlace, compression, filter; size_t size; size_t i, j, k; /* parameters check */ if (0 >= nx || 0 >= ny || 0 >= nc) return -1; if (NULL == fname || NULL == data) return -1; if (IO_PNG_U8 != dtype && IO_PNG_F32 != dtype) return -1; /* open the PNG output file */ if (0 == strcmp(fname, "-")) fp = stdout; else if (NULL == (fp = fopen(fname, "wb"))) return -1; /* allocate the interlaced array and row pointers */ size = nx * ny * nc; if (NULL == (idata = (png_byte *) malloc(size * sizeof(png_byte)))) return write_png_abort(fp, NULL, NULL, NULL, NULL); if (NULL == (row_pointers = (png_bytep *) malloc(ny * sizeof(png_bytep)))) return write_png_abort(fp, idata, NULL, NULL, NULL); /* * create and initialize the png_struct * with the default stderr and error handling */ if (NULL == (png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) return write_png_abort(fp, idata, row_pointers, NULL, NULL); /* allocate/initialize the memory for image information */ if (NULL == (info_ptr = png_create_info_struct(png_ptr))) return write_png_abort(fp, idata, row_pointers, &png_ptr, NULL); /* set error handling */ if (0 != setjmp(png_jmpbuf(png_ptr))) /* if we get here, we had a problem reading the file */ return write_png_abort(fp, idata, row_pointers, &png_ptr, &info_ptr); /* set up the input control using standard C streams */ png_init_io(png_ptr, fp); /* set image informations */ bit_depth = 8; switch (nc) { case 1: color_type = PNG_COLOR_TYPE_GRAY; break; case 2: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; case 3: color_type = PNG_COLOR_TYPE_RGB; break; case 4: color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; default: png_destroy_read_struct(&png_ptr, NULL, NULL); free(row_pointers); free(idata); (void) fclose(fp); return -1; } interlace = PNG_INTERLACE_ADAM7; compression = PNG_COMPRESSION_TYPE_BASE; filter = PNG_FILTER_TYPE_BASE; /* set image header */ png_set_IHDR(png_ptr, info_ptr, (png_uint_32) nx, (png_uint_32) ny, bit_depth, color_type, interlace, compression, filter); /* TODO : significant bit (sBIT), gamma (gAMA), comments (text) chunks */ png_write_info(png_ptr, info_ptr); /* * interlace and convert RRR GGG BBB to RGB RGB RGB * the image is interlaced layer after layer * this involves more memory exchange, but allows a generic loop */ switch (dtype) { case IO_PNG_U8: data_u8 = (unsigned char *) data; for (k = 0; k < nc; k++) { /* channel loop */ data_u8_ptr = data_u8 + (size_t) (nx * ny * k); idata_ptr = idata + (size_t) k; for (j = 0; j < ny; j++) { /* row loop */ for (i = 0; i < nx; i++) { /* pixel loop */ *idata_ptr = (png_byte) * data_u8_ptr++; idata_ptr += nc; } } } break; case IO_PNG_F32: data_f32 = (float *) data; for (k = 0; k < nc; k++) { /* channel loop */ data_f32_ptr = data_f32 + (size_t) (nx * ny * k); idata_ptr = idata + (size_t) k; for (j = 0; j < ny; j++) { /* row loop */ for (i = 0; i < nx; i++) { /* pixel loop */ tmp = floor(*data_f32_ptr++ + .5); *idata_ptr = (png_byte) (tmp < 0. ? 0. : (tmp > 255. ? 255. : tmp)); idata_ptr += nc; } } } break; } /* set row pointers */ for (j = 0; j < ny; j++) row_pointers[j] = idata + (size_t) (nc * nx * j); /* write out the entire image and end it */ png_write_image(png_ptr, row_pointers); png_write_end(png_ptr, info_ptr); /* clean up and free any memory allocated, close the file */ (void) write_png_abort(fp, idata, row_pointers, &png_ptr, &info_ptr); return 0; } /** * @brief write a 8bit unsigned integer array into a PNG file * * @param fname PNG file name * @param data array to write * @param nx, ny, nc number of columns, lines and channels of the image * @return 0 if everything OK, -1 if an error occured */ int write_png_u8(const char *fname, const unsigned char *data, size_t nx, size_t ny, size_t nc) { return write_png_raw(fname, (void *) data, (png_uint_32) nx, (png_uint_32) ny, (png_byte) nc, IO_PNG_U8); } /** * @brief write a float array into a PNG file * * The float values are rounded to 8bit integers, and bounded to [0, 255]. * * @todo handle 16bit images and flexible min/max * * @param fname PNG file name * @param data array to write * @param nx, ny, nc number of columns, lines and channels of the image * @return 0 if everything OK, -1 if an error occured */ int write_png_f32(const char *fname, const float *data, size_t nx, size_t ny, size_t nc) { return write_png_raw(fname, (void *) data, (png_uint_32) nx, (png_uint_32) ny, (png_byte) nc, IO_PNG_F32); }