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