1 /*
2     loadimag.cpp - loads images (so long as those images are pngs)
3     Copyright (C) 2006-8 Mark Boyd
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be fun to play,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 
20 #include <SDL_inc.h>
21 #include <png.h>
22 #include <cctype>
23 
24 #include <iostream>
25 #include <algorithm>
26 
27 #include "loadimag.h"
28 #include "etc.h"
29 #include "text.h"
30 #include "dirs.h"
31 
32 #include "debug.h"
33 
34 
uppercasify(char c)35 char uppercasify(char c)
36 {
37   return std::toupper(c);
38 }
39 
40 
get_extension(const std::string & filename)41 std::string get_extension(const std::string &filename)
42 {
43   std::string ext(std::find(filename.rbegin(), filename.rend(),'.').base(), filename.end());
44   std::transform(ext.begin(), ext.end(), ext.begin(), uppercasify);
45 
46   return ext;
47 }
48 
49 
50 //TODO: maybe make this modifiable (libpng docs say we should)
51 #define SCREEN_GAMMA 2.0
52 
53 //We might be able to load something other than pngs one day.
54 SDL_Surface *load_png(const std::string &filename);
55 
load_image(const std::string & filename)56 SDL_Surface *load_image(const std::string &filename)
57 {
58   std::string extension = get_extension(filename);
59   if (extension == "PNG")
60   {
61     return load_png(dirs::image_dir+"/"+filename);
62   }
63   else
64   {
65     DBG_WHINE("Unknown image extension: "+extension);
66   }
67 
68   return NULL;
69 }
70 
71 //should this be extern "C"?
whiny_png_user_error_fn(png_structp png_ptr,png_const_charp error_msg)72 void whiny_png_user_error_fn(png_structp png_ptr, png_const_charp error_msg)
73 {
74   //DBG_WARN(error_msg);
75   longjmp(png_jmpbuf(png_ptr), 1);
76 }
77 
78 //Slightly quick&dirty png reading using libpng
load_png(const std::string & filename)79 SDL_Surface *load_png(const std::string &filename)
80 {
81   SDL_Surface* target=0;
82   try
83   {
84     //~stuff ensures proper deallocation of allocated things (RRID, not RAII)
85     struct stuff
86     {
87       FILE *fp;
88       png_structp png_ptr;
89       png_infop info_ptr;
90       png_infop end_info;
91       png_bytep *row_pointers;
92 
93       stuff():fp(0),png_ptr(0),info_ptr(0),end_info(0),row_pointers(0){}
94 
95       ~stuff()
96       {
97         if (fp) fclose(fp);
98         //png_destroy_read_struct doesn't mind if some of these are 0.
99         png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
100         if(row_pointers)
101         {
102           int i=0;
103           while(row_pointers[i]) delete[] row_pointers[i++];
104           delete[] row_pointers;
105         }
106       }
107     } ptrs;
108 
109     ptrs.fp = fopen(filename.c_str(), "rb");
110 
111     if (!ptrs.fp) DBG_WHINE("Could not open "+filename);
112 
113     //check the header
114     const int header_bytes = 8;
115     unsigned char header[header_bytes];
116     fread(header, 1, header_bytes, ptrs.fp);
117     bool is_png = !png_sig_cmp(header, 0, header_bytes);
118 
119     if (!is_png) DBG_WHINE(filename+" is not a valid .PNG file!");
120 
121     //create stuff
122     ptrs.png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)0, 0, 0);
123     if (!ptrs.png_ptr) DBG_WHINE("png_create_read_struct failed!");
124     ptrs.info_ptr = png_create_info_struct(ptrs.png_ptr);
125     if (!ptrs.info_ptr) DBG_WHINE("png_create_info_struct failed!");
126     ptrs.end_info = png_create_info_struct(ptrs.png_ptr);
127     if (!ptrs.end_info) DBG_WHINE("png_create_info_struct failed!");
128 
129     //let libpng know we peeked at the first 8 bytes, so fp no longer points to the start of
130     //the file.
131     png_set_sig_bytes(ptrs.png_ptr, header_bytes);
132 
133     if (setjmp(png_jmpbuf(ptrs.png_ptr))) {DBG_WHINE("FATAL ERROR loading "+filename);}
134 
135     //Install our own error handler (which longjmps to the above line)
136     //Any errors from now on will call this function
137     png_set_error_fn(ptrs.png_ptr, png_get_error_ptr(ptrs.png_ptr), whiny_png_user_error_fn, 0);
138 
139     //???
140     png_init_io(ptrs.png_ptr, ptrs.fp);
141 
142 
143     //read the image header - we need width and height, and some of the png_* functions
144     //need it to be read.
145     png_read_info(ptrs.png_ptr, ptrs.info_ptr);
146     png_uint_32 width, height;
147     int bit_depth, color_type;
148     png_get_IHDR(ptrs.png_ptr, ptrs.info_ptr, &width, &height, &bit_depth, &color_type, 0, 0, 0);
149 
150     //Whatever the original format is, change it to 24-bit RGB
151     if (color_type == PNG_COLOR_TYPE_PALETTE)
152       png_set_palette_to_rgb(ptrs.png_ptr);
153     if (color_type == PNG_COLOR_TYPE_GRAY||
154         color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
155       png_set_gray_to_rgb(ptrs.png_ptr);
156     if (bit_depth == 16)
157       png_set_strip_16(ptrs.png_ptr);
158 
159     bool has_alpha = (color_type==PNG_COLOR_TYPE_GRAY_ALPHA ||
160                       color_type==PNG_COLOR_TYPE_RGB_ALPHA);
161 
162     //Gamma transformation
163     double gamma;
164     double screen_gamma = SCREEN_GAMMA;
165     if (png_get_gAMA(ptrs.png_ptr, ptrs.info_ptr, &gamma))
166       png_set_gamma(ptrs.png_ptr, screen_gamma, gamma);
167     else
168       png_set_gamma(ptrs.png_ptr, screen_gamma, 0.45455);
169     //since we've messed with the image, update the header to reflect the changes
170     png_read_update_info(ptrs.png_ptr, ptrs.info_ptr);
171 
172 
173 
174     //background colour
175     //todo: do we need this?  All our pngs use alpha for transparency
176     /*bool has_background;
177     png_color_16 *pbackground;
178     has_background = png_get_bKGD(ptrs.png_ptr, ptrs.info_ptr, &pbackground);*/
179 
180 
181     //create space for the image
182     ptrs.row_pointers = new png_bytep[height+1];
183     for(unsigned int i=0;i<=height;i++) ptrs.row_pointers[i]=0;
184     for(unsigned int i=0;i<height;i++) ptrs.row_pointers[i] = new png_byte[width*4];
185 
186     //Go on, guess what this function does
187     png_read_image(ptrs.png_ptr, ptrs.row_pointers);
188 
189     //Now fill the target bitmap
190     target=create_surface(width, height);
191 
192     for(unsigned int row=0; row<height; row++)
193       for(unsigned int col=0; col<width; col++)
194       {
195         Uint32 colour;
196         if (has_alpha)
197           {
198           colour = SDL_MapRGBA(target->format, ptrs.row_pointers[row][col*4+0],
199                                                ptrs.row_pointers[row][col*4+1],
200                                                ptrs.row_pointers[row][col*4+2],
201                                                ptrs.row_pointers[row][col*4+3]);
202           }
203         else
204           {
205           colour = SDL_MapRGBA(target->format, ptrs.row_pointers[row][col*3+0],
206                                                ptrs.row_pointers[row][col*3+1],
207                                                ptrs.row_pointers[row][col*3+2],
208                                                SDL_ALPHA_OPAQUE);
209           }
210 
211         putpixel(target, col,row, colour);
212       }
213 
214     //Is this necessary?
215     png_read_end(ptrs.png_ptr, ptrs.end_info);
216 
217   }
218   catch(...)
219   {
220     if (target) SDL_FreeSurface(target);
221 
222     //Return a bitmap with the filename and "FATAL ERROR!!" written on it!
223     target=create_surface(200, 20);
224     Uint32 red=SDL_MapRGB(target->format, 255,0,0);
225     text::draw(filename+"\nFATAL ERROR!!",target, 2,2, red);
226   }
227 
228   return target;
229 }
230 
231