]> Dogcows Code - chaz/yoink/blob - src/moof/image.cc
compression functions; fixed texture seams
[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;
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 int channels = png_get_channels(png.context, png.info);
174 int 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 (std::string(texts[i].key) == "X-Yoink-Texture")
182 {
183 attribs.init(texts[i].text);
184 texture = true;
185 break;
186 }
187 }
188
189 int width = png_get_image_width(png.context, png.info);
190 int height = png_get_image_height(png.context, png.info);
191 int pitch = png_get_rowbytes(png.context, png.info);
192 char* pixels = new char[height * pitch];
193
194 rows = new png_bytep[height];
195 if (texture)
196 {
197 log_debug("texture detected; loading flipped");
198 for (int i = 0; i < height; ++i)
199 {
200 rows[height-1-i] = (png_bytep)(pixels +
201 i * channels * width);
202 }
203 }
204 else
205 {
206 log_debug("no texture attributes found");
207 for (int i = 0; i < height; ++i)
208 {
209 rows[i] = (png_bytep)(pixels +
210 i * channels * width);
211 }
212 }
213
214 png_read_image(png.context, rows);
215 delete[] rows;
216
217 png_read_end(png.context, 0);
218
219 SDL_Surface* src = SDL_CreateRGBSurfaceFrom
220 (
221 pixels,
222 width,
223 height,
224 depth,
225 pitch,
226 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
227 0x000000FF,
228 0x0000FF00,
229 0x00FF0000,
230 0xFF000000
231 #else
232 0xFF000000,
233 0x00FF0000,
234 0x0000FF00,
235 0x000000FF
236 #endif
237 );
238 if (!src) throw std::runtime_error(SDL_GetError());
239 return src;
240 }
241
242 static SDL_Surface* load_bmp(const std::string& path)
243 {
244 return SDL_LoadBMP(path.c_str());
245 }
246
247
248 #if 0
249 static void save_bmp(SDL_Surface* image, const std::string& path)
250 {
251 if (SDL_SaveBMP(image, path.c_str()) != 0)
252 throw std::runtime_error(SDL_GetError());
253 }
254
255
256 static void destroy_context(SDL_Surface* context)
257 {
258 if (context->flags & SDL_PREALLOC) delete[] (char*)context->pixels;
259 SDL_FreeSurface(context);
260 }
261 #endif
262
263
264 image::image(const std::string& path) :
265 pixels_(0),
266 object_(0),
267 min_filter_(GL_NEAREST),
268 mag_filter_(GL_NEAREST),
269 tile_s_(1),
270 tile_t_(1),
271 wrap_s_(GL_CLAMP_TO_EDGE),
272 wrap_t_(GL_CLAMP_TO_EDGE)
273 {
274 std::string ext = stlplus::extension_part(path);
275
276 SDL_Surface* context = 0;
277 if (ext == "png")
278 {
279 texture_attributes attribs;
280 context = load_png(path, attribs);
281 pixels_ = (char*)context->pixels;
282 width_ = context->w;
283 height_ = context->h;
284 depth_ = 32;
285 pitch_ = context->pitch;
286 channels_ = 4;
287 min_filter_ = attribs.min_filter;
288 mag_filter_ = attribs.mag_filter;
289 tile_s_ = attribs.tile_s;
290 tile_t_ = attribs.tile_t;
291 wrap_s_ = attribs.wrap_s;
292 wrap_t_ = attribs.wrap_t;
293 postprocess();
294 }
295 else if (ext == "bmp")
296 {
297 context = load_bmp(path);
298 pixels_ = (char*)context->pixels;
299 width_ = context->w;
300 height_ = context->h;
301 depth_ = 32;
302 pitch_ = context->pitch;
303 channels_ = 4;
304 }
305 // TODO leaking context
306 }
307
308 image::~image()
309 {
310 unload_from_gl();
311 delete[] pixels_;
312 }
313
314
315 void image::postprocess()
316 {
317 if (1 == tile_s_ && 1 == tile_t_) return;
318
319 SDL_Surface* src = SDL_CreateRGBSurfaceFrom
320 (
321 pixels_,
322 width_,
323 height_,
324 depth_,
325 pitch_,
326 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
327 0x000000FF,
328 0x0000FF00,
329 0x00FF0000,
330 0xFF000000
331 #else
332 0xFF000000,
333 0x00FF0000,
334 0x0000FF00,
335 0x000000FF
336 #endif
337 );
338 SDL_Surface* dst = SDL_CreateRGBSurface(
339 SDL_SWSURFACE,
340 higher_power_of_two(width_),
341 higher_power_of_two(height_),
342 depth_,
343 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
344 0x000000FF,
345 0x0000FF00,
346 0x00FF0000,
347 0xFF000000
348 #else
349 0xFF000000,
350 0x00FF0000,
351 0x0000FF00,
352 0x000000FF
353 #endif
354 );
355 ASSERT(src && dst);
356
357 SDL_SetAlpha(src, 0, 128);
358 SDL_SetAlpha(dst, 0, 128);
359
360 int src_tilew = src->w / tile_s_;
361 int src_tileh = src->h / tile_t_;
362 int dst_tilew = dst->w / tile_s_;
363 int dst_tileh = dst->h / tile_t_;
364
365 for (int t = 0; t < tile_t_; ++t) for (int s = 0; s < tile_s_; ++s)
366 {
367 SDL_Rect srcrect;
368 SDL_Rect dstrect;
369 srcrect.x = s * src_tilew;
370 srcrect.y = t * src_tileh;
371 srcrect.w = src_tilew;
372 srcrect.h = src_tileh;
373 dstrect.x = s * dst_tilew + (src_tilew / 2);
374 dstrect.y = t * dst_tileh + (src_tileh / 2);
375 log_warning("SRC", srcrect.x, srcrect.y, srcrect.w, srcrect.h);
376 log_warning("DST", dstrect.x, dstrect.y);
377 SDL_BlitSurface(src, &srcrect, dst, &dstrect);
378
379 srcrect.x = s * src_tilew;
380 srcrect.y = t * src_tileh;
381 srcrect.w = 1;
382 srcrect.h = src_tileh;
383 dstrect.y = t * dst_tileh + (src_tileh / 2);
384 for (int x = s * dst_tilew + (src_tilew / 2) - 1; s * dst_tilew <= x; --x)
385 {
386 dstrect.x = x;
387 SDL_BlitSurface(src, &srcrect, dst, &dstrect);
388 }
389
390 srcrect.x = (s + 1) * src_tilew - 1;
391 srcrect.y = t * src_tileh;
392 srcrect.w = 1;
393 srcrect.h = src_tileh;
394 dstrect.y = t * dst_tileh + (src_tileh / 2);
395 for (int x = (s + 1) * dst_tilew - (src_tilew / 2); x < (s + 1) * dst_tilew; ++x)
396 {
397 dstrect.x = x;
398 SDL_BlitSurface(src, &srcrect, dst, &dstrect);
399 }
400
401 srcrect.x = s * src_tilew;
402 srcrect.y = t * src_tileh;
403 srcrect.w = src_tilew;
404 srcrect.h = 1;
405 dstrect.x = s * dst_tilew + (src_tilew / 2);
406 for (int y = t * dst_tileh + (src_tileh / 2) - 1; t * dst_tileh <= y; --y)
407 {
408 dstrect.y = y;
409 SDL_BlitSurface(src, &srcrect, dst, &dstrect);
410 }
411
412 srcrect.x = s * src_tilew;
413 srcrect.y = (t + 1) * src_tileh - 1;
414 srcrect.w = src_tilew;
415 srcrect.h = 1;
416 dstrect.x = s * dst_tilew + (src_tilew / 2);
417 for (int y = (t + 1) * dst_tileh - (src_tileh / 2); y < (t + 1) * dst_tileh; ++y)
418 {
419 dstrect.y = y;
420 SDL_BlitSurface(src, &srcrect, dst, &dstrect);
421 }
422 }
423 SDL_FreeSurface(src);
424
425 char* pixels = new char[dst->w * dst->pitch];
426 SDL_Surface* finaldst = SDL_CreateRGBSurfaceFrom
427 (
428 pixels,
429 dst->w,
430 dst->h,
431 depth_,
432 dst->pitch,
433 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
434 0x000000FF,
435 0x0000FF00,
436 0x00FF0000,
437 0xFF000000
438 #else
439 0xFF000000,
440 0x00FF0000,
441 0x0000FF00,
442 0x000000FF
443 #endif
444 );
445 SDL_BlitSurface(dst, 0, finaldst, 0);
446
447 //SDL_SaveBMP(dst, (stlplus::basename_part(path) + ".bmp").c_str());
448 SDL_FreeSurface(dst);
449
450 width_ = finaldst->w;
451 height_ = finaldst->h;
452 pitch_ = finaldst->pitch;
453
454 SDL_FreeSurface(finaldst);
455
456 delete[] pixels_;
457 pixels_ = pixels;
458 }
459
460
461 void image::fix_uv(std::vector<vector2>& p) const
462 {
463 vector2 mid(SCALAR(0.0), SCALAR(0.0));
464 for (int i = 0; i < p.size(); ++i)
465 {
466 mid[0] += p[i][0];
467 mid[1] += p[i][1];
468 }
469 mid[0] /= p.size();
470 mid[1] /= p.size();
471 mid[0] *= tile_s_;
472 mid[1] *= tile_t_;
473 mid[0] = std::floor(mid[0]);
474 mid[1] = std::floor(mid[1]);
475 log_warning("midpoint:", mid);
476 scalar s = mid[0];
477 scalar t = mid[1];
478
479 scalar src_width = width_ >> 1;
480 scalar src_height = height_ >> 1;
481 int src_tilew = src_width / tile_s_;
482 int src_tileh = src_height / tile_t_;
483 int dst_tilew = width_ / tile_s_;
484 int dst_tileh = height_ / tile_t_;
485
486 for (int i = 0; i < p.size(); ++i)
487 {
488 //scalar s = p[i][0] * src_width;
489 scalar x = s * dst_tilew + (src_tilew / 2) + (p[i][0] * src_width - s * src_tilew);
490 p[i][0] = x / width_;
491 //scalar t = p[i][1] * src_height;
492 scalar y = t * dst_tileh + (src_tileh / 2) + (p[i][1] * src_height - t * src_tileh);;
493 p[i][1] = y / height_;
494 }
495 }
496
497
498 void image::set_as_icon() const
499 {
500 backend backend;
501
502 SDL_Surface* context = SDL_CreateRGBSurfaceFrom
503 (
504 pixels_,
505 width_,
506 height_,
507 depth_,
508 pitch_,
509 #if SDL_BYTEORDER == SDL_LIL_ENDIAN
510 0x000000FF,
511 0x0000FF00,
512 0x00FF0000,
513 0xFF000000
514 #else
515 0xFF000000,
516 0x00FF0000,
517 0x0000FF00,
518 0x000000FF
519 #endif
520 );
521
522 SDL_WM_SetIcon(context, 0);
523 SDL_FreeSurface(context);
524 }
525
526 bool image::tile_coordinates(int index, scalar coords[8]) const
527 {
528 // make sure the index represents a real tile
529 if (index < 0 && index >= tile_s_ * tile_t_) return false;
530
531 scalar w = 1.0 / scalar(tile_s_);
532 scalar h = 1.0 / scalar(tile_t_);
533
534 coords[0] = scalar(index % tile_s_) * w;
535 coords[1] = (scalar(tile_t_ - 1) - scalar(index / tile_s_)) * h;
536 coords[2] = coords[0] + w;
537 coords[3] = coords[1];
538 coords[4] = coords[2];
539 coords[5] = coords[1] + h;
540 coords[6] = coords[0];
541 coords[7] = coords[5];
542
543 return true;
544 }
545
546 void image::bind() const
547 {
548 ASSERT(video::ready() && "should have a video context set");
549
550 if (object_ == 0)
551 {
552 upload_to_gl();
553 }
554 if (object_ != global_object_)
555 {
556 glBindTexture(GL_TEXTURE_2D, (GLuint)object_);
557 global_object_ = object_;
558 }
559 }
560
561 void image::reset_binding()
562 {
563 ASSERT(video::ready() && "should have a video context set");
564
565 glBindTexture(GL_TEXTURE_2D, 0);
566 global_object_ = 0;
567 }
568
569 /*
570 * Upload the image to GL so that it will be accessible by a much more
571 * manageable handle and hopefully reside in video memory.
572 */
573 void image::upload_to_gl() const
574 {
575 if (object_) return; // already loaded
576
577 glGenTextures(1, (GLuint*)&object_);
578 glBindTexture(GL_TEXTURE_2D, (GLuint)object_);
579
580 GLuint mode;
581 if (channels_ == 3) mode = GL_RGB;
582 else mode = GL_RGBA;
583
584 glTexImage2D
585 //gluBuild2DMipmaps
586 (
587 GL_TEXTURE_2D,
588 0,
589 mode,
590 //3,
591 width_,
592 height_,
593 0,
594 mode,
595 GL_UNSIGNED_BYTE,
596 pixels_
597 );
598
599 set_properties();
600
601 // we want to know when the GL context is recreated
602 //dispatcher& dispatcher = dispatcher::global();
603 //new_context_ = dispatcher.add_target("video.newcontext",
604 //boost::bind(&image::context_recreated, this));
605 // FIXME this has const issues
606 }
607
608 void image::unload_from_gl() const
609 {
610 if (object_)
611 {
612 if (object_ == global_object_) global_object_ = 0;
613 glDeleteTextures(1, (GLuint*)&object_);
614 object_ = 0;
615 }
616 }
617
618 void image::context_recreated()
619 {
620 object_ = global_object_ = 0;
621 upload_to_gl();
622 }
623
624 /*
625 * Sets some texture properties such as the filters and external
626 * coordinate behavior.
627 */
628 void image::set_properties() const
629 {
630 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_);
631 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter_);
632 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s_);
633 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t_);
634 }
635
636
637 unsigned image::global_object_ = 0;
638
639
640 } // namespace moof
641
This page took 0.06726 seconds and 4 git commands to generate.