//------------------------------------------------------------------------ // PNG Image Handling //------------------------------------------------------------------------ // // Copyright (c) 2003-2005 The EDGE Team. // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program 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. See the // GNU General Public License for more details. // //------------------------------------------------------------------------ #include "image_png.h" #include "errors.h" #include "macros.h" #include #include #undef _SETJMP_H // workaround for some weirdness in pngconf.h #include namespace epi { namespace PNG { #define CHECK_PNG_BYTES 4 void local_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) { file_c *f = (file_c *) png_get_io_ptr(png_ptr); if (f->Read(data, length) != length) { png_error(png_ptr, "Read Error"); } } } // namespace PNG basicimage_c *PNG::Load(file_c *f, bool invert, bool round_pow2) { /* -AJA- all these volatiles here may seem strange. They are needed * because the ANSI C standard (which GCC adheres to) says that when * setjmp/longjmp is being used, only volatile local variables are * guaranteed to keep their state if longjmp() gets called. */ basicimage_c * volatile img = 0; png_bytep * volatile row_pointers = 0; /* we take the address of these two, so we shouldn't need the * volatile. (GCC complains about discarding qualifiers if the * volatile is there). */ png_structp /*volatile*/ png_ptr = 0; png_infop /*volatile*/ info_ptr = 0; png_byte sig_buf[CHECK_PNG_BYTES]; /* check the signature */ if (f->Read(sig_buf, CHECK_PNG_BYTES) != CHECK_PNG_BYTES || (png_sig_cmp(sig_buf, (png_size_t)0, CHECK_PNG_BYTES) != 0)) { fprintf(stderr, "PNG::Load - File is not a PNG image !\n"); goto failed; } /* pass NULLs for the error functions -- therefore use setjump */ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (! png_ptr) goto failed; info_ptr = png_create_info_struct(png_ptr); if (! info_ptr) goto failed; /* set error handling since we are using the setjmp/longjmp method * (this is the normal method of doing things with libpng). */ if (setjmp(png_ptr->jmpbuf)) { fprintf(stderr, "PNG::Load - Error loading PNG image !\n"); goto failed; } png_set_read_fn(png_ptr, (void *)f, &local_read_fn); png_set_sig_bytes(png_ptr, CHECK_PNG_BYTES); /* read the header information */ png_read_info(png_ptr, info_ptr); png_uint_32 width, height; int bit_depth, color_type, interlace_type; int tot_W, tot_H; bool solid; png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); solid = ((color_type & PNG_COLOR_MASK_ALPHA) == 0); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) solid = false; tot_W = (int)width; tot_H = (int)height; if (round_pow2) { tot_W = 1; while (tot_W < (int)width) tot_W <<= 1; tot_H = 1; while (tot_H < (int)height) tot_H <<= 1; } img = new basicimage_c(tot_W, tot_H, solid ? 3 : 4); /* tell libpng to strip 16 bits/color down to 8 bits/color */ png_set_strip_16(png_ptr); /* convert low depths (1, 2 or 4) */ if (bit_depth < 8) png_set_packing(png_ptr); /* expand paletted colors into true RGB triplets */ if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png_ptr); /* expand greyscale into RGB */ if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_ptr); } /* read alpha channel */ if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png_ptr); ///--- /* set alpha position and filler value */ ///--- png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); /* let all the above calls take effect */ png_read_update_info(png_ptr, info_ptr); row_pointers = new png_bytep[height]; for (png_uint_32 y = 0; y < height; y++) { if (invert) row_pointers[y] = (png_bytep) (img->pixels + (img->height - 1 - y) * img->width * img->bpp); else row_pointers[y] = (png_bytep) (img->pixels + y * img->width * img->bpp); } /* now read in the image. Yeah baby ! */ png_read_image(png_ptr, row_pointers); png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); delete[] row_pointers; return img; /* -AJA- Normally I don't like gotos. In this situation where there * are lots of points of possible failure and a growing set of * things to be undone, it makes for nicer code. */ failed: if (img) { delete img; img = 0; } if (png_ptr) { if (info_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); else png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); } if (row_pointers) delete[] row_pointers; return 0; } bool PNG::GetInfo(file_c *f, int *width, int *height, bool *solid) { png_structp /*volatile*/ png_ptr = 0; png_infop /*volatile*/ info_ptr = 0; png_byte sig_buf[CHECK_PNG_BYTES]; /* check the signature */ if (f->Read(sig_buf, CHECK_PNG_BYTES) != CHECK_PNG_BYTES || (png_sig_cmp(sig_buf, (png_size_t)0, CHECK_PNG_BYTES) != 0)) { fprintf(stderr, "PNG: File is not a PNG image !\n"); goto failed; } /* pass NULLs for the error functions -- therefore use setjump */ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (! png_ptr) goto failed; info_ptr = png_create_info_struct(png_ptr); if (! info_ptr) goto failed; /* set error handling since we are using the setjmp/longjmp method * (this is the normal method of doing things with libpng). */ if (setjmp(png_ptr->jmpbuf)) { fprintf(stderr, "PNG: Error loading PNG image !\n"); goto failed; } png_set_read_fn(png_ptr, (void *)f, &local_read_fn); png_set_sig_bytes(png_ptr, CHECK_PNG_BYTES); /* read the header information */ png_read_info(png_ptr, info_ptr); png_uint_32 pu_width, pu_height; int bit_depth, color_type, interlace_type; png_get_IHDR(png_ptr, info_ptr, &pu_width, &pu_height, &bit_depth, &color_type, &interlace_type, NULL, NULL); ///--- png_read_end(png_ptr, info_ptr); *width = (int)pu_width; *height = (int)pu_height; *solid = ((color_type & PNG_COLOR_MASK_ALPHA) == 0); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { *solid = false; } png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); return true; /* -AJA- Normally I don't like gotos. In this situation where there * are lots of points of possible failure and a growing set of * things to be undone, it makes for nicer code. */ failed: if (png_ptr) { if (info_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); else png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); } return false; } //------------------------------------------------------------------------ bool PNG::Save(const basicimage_c& image, FILE *fp, int compress) { /// FIXME: asserts /// ASSERT(compress >= Z_NO_COMPRESSION); /// ASSERT(compress <= Z_BEST_COMPRESSION); if (image.bpp < 3) throw error_c(EPI_ERRGEN_ASSERTION, "[epi::PNG::Save] image.bpp < 3", true); png_bytep * volatile row_pointers = 0; png_structp /*volatile*/ png_ptr = 0; png_infop /*volatile*/ info_ptr = 0; /* pass NULLs for the error functions -- therefore use setjump */ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (! png_ptr) goto failed; info_ptr = png_create_info_struct(png_ptr); if (! info_ptr) goto failed; /* set error handling since we are using the setjmp/longjmp method * (this is the normal method of doing things with libpng). */ if (setjmp(png_ptr->jmpbuf)) { fprintf(stderr, "PNG: Error saving PNG image !\n"); goto failed; } png_init_io(png_ptr, fp); png_set_compression_level(png_ptr, compress); png_set_IHDR(png_ptr, info_ptr, image.width, image.height, 8, (image.bpp == 4) ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); row_pointers = new png_bytep[image.height]; for (int y = 0; y < image.height; y++) { row_pointers[y] = (png_bytep) (image.pixels + y * image.width * image.bpp); } png_set_rows(png_ptr, info_ptr, (png_bytep*) row_pointers); png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); //png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL); delete[] row_pointers; return true; failed: if (png_ptr) { if (info_ptr) png_destroy_write_struct(&png_ptr, &info_ptr); else png_destroy_write_struct(&png_ptr, (png_infopp)NULL); } if (row_pointers) delete[] row_pointers; return false; } }; // namespace epi