77b15c13903bead2a4bd05bc33e3a0081fbd2f91
[chaz/yoink] / src / moof / image.cc
1
2 /*] Copyright (c) 2009-2011, Charles McGarvey [*****************************
3 **] All rights reserved.
4 *
5 * Distributable under the terms and conditions of the 2-clause BSD license;
6 * see the file COPYING for a complete text of the license.
7 *
8 *****************************************************************************/
9
10 #include <fstream>
11 #include <stdexcept>
12 #include <png.h>
13
14 #include <SDL/SDL.h>
15
16 #include <stlplus/portability/file_system.hpp>
17
18 #include "backend.hh"
19 #include "debug.hh"
20 #include "image.hh"
21 #include "log.hh"
22 #include "opengl.hh"
23 #include "script.hh"
24 #include "video.hh"
25
26
27 namespace moof {
28
29
30 MOOF_REGISTER_RESOURCE(image, bmp, textures);
31 MOOF_REGISTER_RESOURCE(image, png, textures);
32
33
34 static int higher_power_of_two(int input)
35 {
36 int value = 2;
37 while (value <= input) value <<= 1;
38 return value;
39 }
40
41 static void read_from_stream(png_structp context,
42 png_bytep data, png_size_t length)
43 {
44 std::istream& stream(*(std::istream*)png_get_io_ptr(context));
45 stream.read((char*)data, length);
46 }
47
48
49 struct texture_attributes
50 {
51 texture_attributes() :
52 min_filter(GL_NEAREST),
53 mag_filter(GL_NEAREST),
54 tile_s(1),
55 tile_t(1),
56 wrap_s(GL_CLAMP_TO_EDGE),
57 wrap_t(GL_CLAMP_TO_EDGE) {}
58
59 void init(const std::string& info)
60 {
61 script script;
62 log::import(script);
63
64 script::slot g = script.globals();
65 #define EXPORT_CONSTANT(K) g.set_field(#K, GL_##K)
66 EXPORT_CONSTANT(CLAMP);
67 EXPORT_CONSTANT(CLAMP_TO_EDGE);
68 EXPORT_CONSTANT(REPEAT);
69 EXPORT_CONSTANT(LINEAR);
70 EXPORT_CONSTANT(NEAREST);
71 EXPORT_CONSTANT(LINEAR_MIPMAP_LINEAR);
72 EXPORT_CONSTANT(LINEAR_MIPMAP_NEAREST);
73 EXPORT_CONSTANT(NEAREST_MIPMAP_LINEAR);
74 EXPORT_CONSTANT(NEAREST_MIPMAP_NEAREST);
75 #undef export_constant
76
77 if (script.do_string(info) != script::success)
78 {
79 std::string str;
80 script[-1].get(str);
81 log_warning(str);
82 }
83 else
84 {
85 log_info("loading texture information...");
86
87 script::slot globals = script.globals();
88 globals.get(min_filter, "min_filter");
89 globals.get(mag_filter, "mag_filter");
90 globals.get(tile_s, "tile_s");
91 globals.get(tile_t, "tile_t");
92 globals.get(wrap_s, "wrap_s");
93 globals.get(wrap_t, "wrap_t");
94 }
95 }
96
97 GLuint min_filter;
98 GLuint mag_filter;
99 int tile_s;
100 int tile_t;
101 GLuint wrap_s;
102 GLuint wrap_t;
103 };
104
105
106 static SDL_Surface* load_png(const std::string& path, texture_attributes& attribs)
107 {
108 std::ifstream file(path.c_str(), std::ifstream::binary);
109 if (!file.good())
110 throw std::runtime_error("no valid image found at " + path);
111
112 png_byte signature[8];
113 size_t bytesRead;
114
115 int bpp;
116
117 png_byte colors;
118 png_bytepp rows = 0;
119
120 png_textp texts = 0;
121 int nutext_s;
122
123 bytesRead = file.read((char*)signature, sizeof(signature)).gcount();
124 if (bytesRead < sizeof(signature) ||
125 png_sig_cmp(signature, 0, sizeof(signature)) != 0) throw 0;
126
127 struct png
128 {
129 png_structp context;
130 png_infop info;
131 png() :
132 context(png_create_read_struct(PNG_LIBPNG_VER_STRING,
133 0, 0, 0)),
134 info(png_create_info_struct(context))
135 {
136 if (!context || !info) throw 0;
137 }
138 ~png()
139 {
140 png_destroy_read_struct(context ? &context : 0,
141 info ? &info : 0, 0);
142 }
143 } png;
144
145 if (setjmp(png_jmpbuf(png.context))) throw 0;
146
147 png_set_read_fn(png.context, (void*)&file, read_from_stream);
148 png_set_sig_bytes(png.context, sizeof(signature));
149 png_read_info(png.context, png.info);
150
151 bpp = png_get_bit_depth(png.context, png.info);
152 colors = png_get_color_type(png.context, png.info);
153 switch (colors)
154 {
155 case PNG_COLOR_TYPE_PALETTE:
156 png_set_palette_to_rgb(png.context);
157 break;
158
159 case PNG_COLOR_TYPE_GRAY:
160 if (bpp < 8) png_set_expand(png.context);
161 break;
162
163 case PNG_COLOR_TYPE_GRAY_ALPHA:
164 png_set_gray_to_rgb(png.context);
165 break;
166 }
167
168 if (bpp == 16) png_set_strip_16(png.context);
169
170 png_read_update_info(png.context, png.info);
171
172 bpp = png_get_bit_depth(png.context, png.info);
173 channels_ = png_get_channels(png.context, png.info);
174 depth_ = bpp * channels_;
175
176 // read comments
177 bool texture = false;
178 png_get_text(png.context, png.info, &texts, &nutext_s);
179 for (int i = 0; i < nutext_s; ++i)
180 {
181 if (strncmp(texts[i].key, "X-Yoink-Texture", 11) == 0)
182 {
183 set_texture_info(texts[i].text);
184 texture = true;
185 break;
186 }
187 }
188
189 width_ = png_get_image_width(png.context, png.info);
190 height_ = png_get_image_height(png.context, png.info);
191
192 pitch_ = png_get_rowbytes(png.context, png.info);
193 pixels_ = new char[width_ * pitch_];
194
195 rows = new png_bytep[height_];
196 if (texture)
197 {
198 log_debug("texture detected; loading flipped");
199 for (int i = 0; i < height_; ++i)
200 {
201 rows[height_-1-i] = (png_bytep)(pixels_ +
202 i * channels_ * width_);
203 }
204 }
205 else
206 {
207 log_debug("no texture attributes found");
208 for (int i = 0; i < height_; ++i)
209 {
210 rows[i] = (png_bytep)(pixels_ +
211 i * channels_ * width_);
212 }
213 }
214
215 png_read_image(png.context, rows);
216 png_read_end(png.context, 0);
217
218 // TODO leak
219 delete[] rows;
220 }
221
222 image::~image()
223 {
224 unload_from_gl();
225 delete[] pixels_;
226 }
227
228
229 void image::set_as_icon() const
230 {
231 backend backend;
232
233 SDL_Surface* context = SDL_CreateRGBSurfaceFrom
234 (
235 pixels_,
236 width_,
237 height_,
238 depth_,
239 pitch_,
240 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
241 0x000000FF,
242 0x0000FF00,
243 0x00FF0000,
244 0xFF000000
245 #else
246 0xFF000000,
247 0x00FF0000,
248 0x0000FF00,
249 0x000000FF
250 #endif
251 );
252
253 SDL_WM_SetIcon(context, 0);
254 SDL_FreeSurface(context);
255 }
256
257 bool image::tile_coordinates(int index, scalar coords[8]) const
258 {
259 // make sure the index represents a real tile
260 if (index < 0 && index >= tile_s_ * tile_t_) return false;
261
262 scalar w = 1.0 / scalar(tile_s_);
263 scalar h = 1.0 / scalar(tile_t_);
264
265 coords[0] = scalar(index % tile_s_) * w;
266 coords[1] = (scalar(tile_t_ - 1) - scalar(index / tile_s_)) * h;
267 coords[2] = coords[0] + w;
268 coords[3] = coords[1];
269 coords[4] = coords[2];
270 coords[5] = coords[1] + h;
271 coords[6] = coords[0];
272 coords[7] = coords[5];
273
274 return true;
275 }
276
277 void image::bind() const
278 {
279 ASSERT(video::current() && "should have a video context set");
280
281 if (object_ == 0)
282 {
283 upload_to_gl();
284 }
285 if (object_ != global_object_)
286 {
287 glBindTexture(GL_TEXTURE_2D, (GLuint)object_);
288 global_object_ = object_;
289 }
290 }
291
292 void image::reset_binding()
293 {
294 ASSERT(video::current() && "should have a video context set");
295
296 glBindTexture(GL_TEXTURE_2D, 0);
297 global_object_ = 0;
298 }
299
300 /*
301 * Upload the image to GL so that it will be accessible by a much more
302 * manageable handle and hopefully reside in video memory.
303 */
304 void image::upload_to_gl() const
305 {
306 if (object_) return; // already loaded
307
308 glGenTextures(1, (GLuint*)&object_);
309 glBindTexture(GL_TEXTURE_2D, (GLuint)object_);
310
311 GLuint mode;
312 if (channels_ == 3) mode = GL_RGB;
313 else mode = GL_RGBA;
314
315 glTexImage2D
316 //gluBuild2DMipmaps
317 (
318 GL_TEXTURE_2D,
319 0,
320 mode,
321 //3,
322 width_,
323 height_,
324 0,
325 mode,
326 GL_UNSIGNED_BYTE,
327 pixels_
328 );
329
330 set_properties();
331
332 // we want to know when the GL context is recreated
333 //dispatcher& dispatcher = dispatcher::global();
334 //new_context_ = dispatcher.add_target("video.newcontext",
335 //boost::bind(&image::context_recreated, this));
336 // FIXME this has const issues
337 }
338
339 void image::unload_from_gl() const
340 {
341 if (object_)
342 {
343 if (object_ == global_object_) global_object_ = 0;
344 glDeleteTextures(1, (GLuint*)&object_);
345 object_ = 0;
346 }
347 }
348
349 void image::context_recreated()
350 {
351 object_ = global_object_ = 0;
352 upload_to_gl();
353 }
354
355 /*
356 * Sets some texture properties such as the filters and external
357 * coordinate behavior.
358 */
359 void image::set_properties() const
360 {
361 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_);
362 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter_);
363 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s_);
364 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t_);
365 }
366
367 void image::set_texture_info(const std::string& info)
368 {
369 script script;
370 log::import(script);
371
372 script::slot g = script.globals();
373 g.set_field("CLAMP", GL_CLAMP);
374 g.set_field("REPEAT", GL_REPEAT);
375 g.set_field("LINEAR", GL_LINEAR);
376 g.set_field("NEAREST", GL_NEAREST);
377 g.set_field("LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR);
378 g.set_field("LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST);
379 g.set_field("NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR);
380 g.set_field("NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST);
381
382 if (script.do_string(info) != script::success)
383 {
384 std::string str;
385 script[-1].get(str);
386 log_warning(str);
387 }
388 else
389 {
390 log_info("loading texture information...");
391
392 script::slot globals = script.globals();
393 globals.get(min_filter_, "min_filter");
394 globals.get(mag_filter_, "mag_filter");
395 globals.get(tile_s_, "tile_s");
396 globals.get(tile_t_, "tile_t");
397 globals.get(wrap_s_, "wrap_s");
398 globals.get(wrap_t_, "wrap_t");
399 }
400 }
401
402
403 } // namespace moof
404
This page took 0.043709 seconds and 3 git commands to generate.