/* loadimag.cpp - loads images (so long as those images are pngs) Copyright (C) 2006-7 Mark boyd 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 fun to play, 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include "loadimag.h" #include "etc.h" #include "text.h" #include "dirs.h" #include "debug.h" char uppercasify(char c) { return std::toupper(c); } std::string get_extension(const std::string &filename) { std::string ext(std::find(filename.rbegin(), filename.rend(),'.').base(), filename.end()); std::transform(ext.begin(), ext.end(), ext.begin(), uppercasify); return ext; } //TODO: maybe make this modifiable (libpng docs say we should) #define SCREEN_GAMMA 2.0 //We might be able to load something other than pngs one day. SDL_Surface *load_png(const std::string &filename); SDL_Surface *load_image(const std::string &filename) { std::string extension = get_extension(filename); if (extension == "PNG") { return load_png(dirs::image_dir+"/"+filename); } else { DBG_WHINE("Unknown image extension: "+extension); } return false; } //should this be extern "C"? void whiny_png_user_error_fn(png_structp png_ptr, png_const_charp error_msg) { //DBG_WARN(error_msg); longjmp(png_jmpbuf(png_ptr), 1); } //Slightly quick&dirty png reading using libpng SDL_Surface *load_png(const std::string &filename) { SDL_Surface* target=0; try { //~stuff ensures proper deallocation of allocated things (RRID, not RAII) struct stuff { FILE *fp; png_structp png_ptr; png_infop info_ptr; png_infop end_info; png_bytep *row_pointers; stuff():fp(0),png_ptr(0),info_ptr(0),end_info(0),row_pointers(0){} ~stuff() { if (fp) fclose(fp); //png_destroy_read_struct doesn't mind if some of these are 0. png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); if(row_pointers) { int i=0; while(row_pointers[i]) delete[] row_pointers[i++]; delete[] row_pointers; } } } ptrs; ptrs.fp = fopen(filename.c_str(), "rb"); if (!ptrs.fp) DBG_WHINE("Could not open "+filename); //check the header const int header_bytes = 8; unsigned char header[header_bytes]; fread(header, 1, header_bytes, ptrs.fp); bool is_png = !png_sig_cmp(header, 0, header_bytes); if (!is_png) DBG_WHINE(filename+" is not a valid .PNG file!"); //create stuff ptrs.png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)0, 0, 0); if (!ptrs.png_ptr) DBG_WHINE("png_create_read_struct failed!"); ptrs.info_ptr = png_create_info_struct(ptrs.png_ptr); if (!ptrs.info_ptr) DBG_WHINE("png_create_info_struct failed!"); ptrs.end_info = png_create_info_struct(ptrs.png_ptr); if (!ptrs.end_info) DBG_WHINE("png_create_info_struct failed!"); //let libpng know we peeked at the first 8 bytes, so fp no longer points to the start of //the file. png_set_sig_bytes(ptrs.png_ptr, header_bytes); if (setjmp(png_jmpbuf(ptrs.png_ptr))) {DBG_WHINE("FATAL ERROR loading "+filename);} //Install our own error handler (which longjmps to the above line) //Any errors from now on will call this function png_set_error_fn(ptrs.png_ptr, png_get_error_ptr(ptrs.png_ptr), whiny_png_user_error_fn, 0); //??? png_init_io(ptrs.png_ptr, ptrs.fp); //read the image header - we need width and height, and some of the png_* functions //need it to be read. png_read_info(ptrs.png_ptr, ptrs.info_ptr); png_uint_32 width, height; int bit_depth, color_type; png_get_IHDR(ptrs.png_ptr, ptrs.info_ptr, &width, &height, &bit_depth, &color_type, 0, 0, 0); //Whatever the original format is, change it to 24-bit RGB if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(ptrs.png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY|| color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(ptrs.png_ptr); if (bit_depth == 16) png_set_strip_16(ptrs.png_ptr); bool has_alpha = (color_type==PNG_COLOR_TYPE_GRAY_ALPHA || color_type==PNG_COLOR_TYPE_RGB_ALPHA); //Gamma transformation double gamma; double screen_gamma = SCREEN_GAMMA; if (png_get_gAMA(ptrs.png_ptr, ptrs.info_ptr, &gamma)) png_set_gamma(ptrs.png_ptr, screen_gamma, gamma); else png_set_gamma(ptrs.png_ptr, screen_gamma, 0.45455); //since we've messed with the image, update the header to reflect the changes png_read_update_info(ptrs.png_ptr, ptrs.info_ptr); //background colour //todo: do we need this? All our pngs use alpha for transparency /*bool has_background; png_color_16 *pbackground; has_background = png_get_bKGD(ptrs.png_ptr, ptrs.info_ptr, &pbackground);*/ //create space for the image ptrs.row_pointers = new png_bytep[height+1]; for(unsigned int i=0;i<=height;i++) ptrs.row_pointers[i]=0; for(unsigned int i=0;iformat, ptrs.row_pointers[row][col*4+0], ptrs.row_pointers[row][col*4+1], ptrs.row_pointers[row][col*4+2], ptrs.row_pointers[row][col*4+3]); } else { colour = SDL_MapRGBA(target->format, ptrs.row_pointers[row][col*3+0], ptrs.row_pointers[row][col*3+1], ptrs.row_pointers[row][col*3+2], SDL_ALPHA_OPAQUE); } putpixel(target, col,row, colour); } //Is this necessary? png_read_end(ptrs.png_ptr, ptrs.end_info); } catch(...) { if (target) SDL_FreeSurface(target); //Return a bitmap with the filename and "FATAL ERROR!!" written on it! target=create_surface(200, 20); Uint32 red=SDL_MapRGB(target->format, 255,0,0); text::draw(filename+"\nFATAL ERROR!!",target, 2,2, red); } return target; }