/*] Copyright (c) 2009-2011, Charles McGarvey [***************************** **] All rights reserved. * * Distributable under the terms and conditions of the 2-clause BSD license; * see the file COPYING for a complete text of the license. * *****************************************************************************/ #include #include #include #include #include #include "backend.hh" #include "debug.hh" #include "image.hh" #include "log.hh" #include "opengl.hh" #include "script.hh" #include "video.hh" namespace moof { MOOF_REGISTER_RESOURCE(image, bmp, textures); MOOF_REGISTER_RESOURCE(image, png, textures); static int higher_power_of_two(int input) { int value = 2; while (value <= input) value <<= 1; return value; } static void read_from_stream(png_structp context, png_bytep data, png_size_t length) { std::istream& stream(*(std::istream*)png_get_io_ptr(context)); stream.read((char*)data, length); } struct texture_attributes { texture_attributes() : min_filter(GL_NEAREST), mag_filter(GL_NEAREST), tile_s(1), tile_t(1), wrap_s(GL_CLAMP_TO_EDGE), wrap_t(GL_CLAMP_TO_EDGE) {} void init(const std::string& info) { script script; log::import(script); script::slot g = script.globals(); #define EXPORT_CONSTANT(K) g.set_field(#K, GL_##K) EXPORT_CONSTANT(CLAMP); EXPORT_CONSTANT(CLAMP_TO_EDGE); EXPORT_CONSTANT(REPEAT); EXPORT_CONSTANT(LINEAR); EXPORT_CONSTANT(NEAREST); EXPORT_CONSTANT(LINEAR_MIPMAP_LINEAR); EXPORT_CONSTANT(LINEAR_MIPMAP_NEAREST); EXPORT_CONSTANT(NEAREST_MIPMAP_LINEAR); EXPORT_CONSTANT(NEAREST_MIPMAP_NEAREST); #undef export_constant if (script.do_string(info) != script::success) { std::string str; script[-1].get(str); log_warning(str); } else { log_info("loading texture information..."); script::slot globals = script.globals(); globals.get(min_filter, "min_filter"); globals.get(mag_filter, "mag_filter"); globals.get(tile_s, "tile_s"); globals.get(tile_t, "tile_t"); globals.get(wrap_s, "wrap_s"); globals.get(wrap_t, "wrap_t"); } } GLuint min_filter; GLuint mag_filter; int tile_s; int tile_t; GLuint wrap_s; GLuint wrap_t; }; static SDL_Surface* load_png(const std::string& path, texture_attributes& attribs) { std::ifstream file(path.c_str(), std::ifstream::binary); if (!file.good()) throw std::runtime_error("no valid image found at " + path); png_byte signature[8]; size_t bytesRead; int bpp; png_byte colors; png_bytepp rows; png_textp texts = 0; int nutext_s; bytesRead = file.read((char*)signature, sizeof(signature)).gcount(); if (bytesRead < sizeof(signature) || png_sig_cmp(signature, 0, sizeof(signature)) != 0) throw 0; struct png { png_structp context; png_infop info; png() : context(png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0)), info(png_create_info_struct(context)) { if (!context || !info) throw 0; } ~png() { png_destroy_read_struct(context ? &context : 0, info ? &info : 0, 0); } } png; if (setjmp(png_jmpbuf(png.context))) throw 0; png_set_read_fn(png.context, (void*)&file, read_from_stream); png_set_sig_bytes(png.context, sizeof(signature)); png_read_info(png.context, png.info); bpp = png_get_bit_depth(png.context, png.info); colors = png_get_color_type(png.context, png.info); switch (colors) { case PNG_COLOR_TYPE_PALETTE: png_set_palette_to_rgb(png.context); break; case PNG_COLOR_TYPE_GRAY: if (bpp < 8) png_set_expand(png.context); break; case PNG_COLOR_TYPE_GRAY_ALPHA: png_set_gray_to_rgb(png.context); break; } if (bpp == 16) png_set_strip_16(png.context); png_read_update_info(png.context, png.info); bpp = png_get_bit_depth(png.context, png.info); int channels = png_get_channels(png.context, png.info); int depth = bpp * channels; // read comments bool texture = false; png_get_text(png.context, png.info, &texts, &nutext_s); for (int i = 0; i < nutext_s; ++i) { if (std::string(texts[i].key) == "X-Yoink-Texture") { attribs.init(texts[i].text); texture = true; break; } } int width = png_get_image_width(png.context, png.info); int height = png_get_image_height(png.context, png.info); int pitch = png_get_rowbytes(png.context, png.info); char* pixels = new char[height * pitch]; rows = new png_bytep[height]; if (texture) { log_debug("texture detected; loading flipped"); for (int i = 0; i < height; ++i) { rows[height-1-i] = (png_bytep)(pixels + i * channels * width); } } else { log_debug("no texture attributes found"); for (int i = 0; i < height; ++i) { rows[i] = (png_bytep)(pixels + i * channels * width); } } png_read_image(png.context, rows); delete[] rows; png_read_end(png.context, 0); SDL_Surface* src = SDL_CreateRGBSurfaceFrom ( pixels, width, height, depth, pitch, #if SDL_BYTEORDER == SDL_LIL_ENDIAN 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); if (!src) throw std::runtime_error(SDL_GetError()); return src; } static SDL_Surface* load_bmp(const std::string& path) { return SDL_LoadBMP(path.c_str()); } #if 0 static void save_bmp(SDL_Surface* image, const std::string& path) { if (SDL_SaveBMP(image, path.c_str()) != 0) throw std::runtime_error(SDL_GetError()); } static void destroy_context(SDL_Surface* context) { if (context->flags & SDL_PREALLOC) delete[] (char*)context->pixels; SDL_FreeSurface(context); } #endif image::image(const std::string& path) : pixels_(0), object_(0), min_filter_(GL_NEAREST), mag_filter_(GL_NEAREST), tile_s_(1), tile_t_(1), wrap_s_(GL_CLAMP_TO_EDGE), wrap_t_(GL_CLAMP_TO_EDGE) { std::string ext = stlplus::extension_part(path); SDL_Surface* context = 0; if (ext == "png") { texture_attributes attribs; context = load_png(path, attribs); pixels_ = (char*)context->pixels; width_ = context->w; height_ = context->h; depth_ = 32; pitch_ = context->pitch; channels_ = 4; min_filter_ = attribs.min_filter; mag_filter_ = attribs.mag_filter; tile_s_ = attribs.tile_s; tile_t_ = attribs.tile_t; wrap_s_ = attribs.wrap_s; wrap_t_ = attribs.wrap_t; postprocess(); } else if (ext == "bmp") { context = load_bmp(path); pixels_ = (char*)context->pixels; width_ = context->w; height_ = context->h; depth_ = 32; pitch_ = context->pitch; channels_ = 4; } // TODO leaking context } image::~image() { unload_from_gl(); delete[] pixels_; } void image::postprocess() { if (1 == tile_s_ && 1 == tile_t_) return; SDL_Surface* src = SDL_CreateRGBSurfaceFrom ( pixels_, width_, height_, depth_, pitch_, #if SDL_BYTEORDER == SDL_LIL_ENDIAN 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); SDL_Surface* dst = SDL_CreateRGBSurface( SDL_SWSURFACE, higher_power_of_two(width_), higher_power_of_two(height_), depth_, #if SDL_BYTEORDER == SDL_LIL_ENDIAN 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); ASSERT(src && dst); SDL_SetAlpha(src, 0, 128); SDL_SetAlpha(dst, 0, 128); int src_tilew = src->w / tile_s_; int src_tileh = src->h / tile_t_; int dst_tilew = dst->w / tile_s_; int dst_tileh = dst->h / tile_t_; for (int t = 0; t < tile_t_; ++t) for (int s = 0; s < tile_s_; ++s) { SDL_Rect srcrect; SDL_Rect dstrect; srcrect.x = s * src_tilew; srcrect.y = t * src_tileh; srcrect.w = src_tilew; srcrect.h = src_tileh; dstrect.x = s * dst_tilew + (src_tilew / 2); dstrect.y = t * dst_tileh + (src_tileh / 2); log_warning("SRC", srcrect.x, srcrect.y, srcrect.w, srcrect.h); log_warning("DST", dstrect.x, dstrect.y); SDL_BlitSurface(src, &srcrect, dst, &dstrect); srcrect.x = s * src_tilew; srcrect.y = t * src_tileh; srcrect.w = 1; srcrect.h = src_tileh; dstrect.y = t * dst_tileh + (src_tileh / 2); for (int x = s * dst_tilew + (src_tilew / 2) - 1; s * dst_tilew <= x; --x) { dstrect.x = x; SDL_BlitSurface(src, &srcrect, dst, &dstrect); } srcrect.x = (s + 1) * src_tilew - 1; srcrect.y = t * src_tileh; srcrect.w = 1; srcrect.h = src_tileh; dstrect.y = t * dst_tileh + (src_tileh / 2); for (int x = (s + 1) * dst_tilew - (src_tilew / 2); x < (s + 1) * dst_tilew; ++x) { dstrect.x = x; SDL_BlitSurface(src, &srcrect, dst, &dstrect); } srcrect.x = s * src_tilew; srcrect.y = t * src_tileh; srcrect.w = src_tilew; srcrect.h = 1; dstrect.x = s * dst_tilew + (src_tilew / 2); for (int y = t * dst_tileh + (src_tileh / 2) - 1; t * dst_tileh <= y; --y) { dstrect.y = y; SDL_BlitSurface(src, &srcrect, dst, &dstrect); } srcrect.x = s * src_tilew; srcrect.y = (t + 1) * src_tileh - 1; srcrect.w = src_tilew; srcrect.h = 1; dstrect.x = s * dst_tilew + (src_tilew / 2); for (int y = (t + 1) * dst_tileh - (src_tileh / 2); y < (t + 1) * dst_tileh; ++y) { dstrect.y = y; SDL_BlitSurface(src, &srcrect, dst, &dstrect); } } SDL_FreeSurface(src); char* pixels = new char[dst->w * dst->pitch]; SDL_Surface* finaldst = SDL_CreateRGBSurfaceFrom ( pixels, dst->w, dst->h, depth_, dst->pitch, #if SDL_BYTEORDER == SDL_LIL_ENDIAN 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); SDL_BlitSurface(dst, 0, finaldst, 0); //SDL_SaveBMP(dst, (stlplus::basename_part(path) + ".bmp").c_str()); SDL_FreeSurface(dst); width_ = finaldst->w; height_ = finaldst->h; pitch_ = finaldst->pitch; SDL_FreeSurface(finaldst); delete[] pixels_; pixels_ = pixels; } void image::fix_uv(std::vector& p) const { vector2 mid(SCALAR(0.0), SCALAR(0.0)); for (int i = 0; i < p.size(); ++i) { mid[0] += p[i][0]; mid[1] += p[i][1]; } mid[0] /= p.size(); mid[1] /= p.size(); mid[0] *= tile_s_; mid[1] *= tile_t_; mid[0] = std::floor(mid[0]); mid[1] = std::floor(mid[1]); log_warning("midpoint:", mid); scalar s = mid[0]; scalar t = mid[1]; scalar src_width = width_ >> 1; scalar src_height = height_ >> 1; int src_tilew = src_width / tile_s_; int src_tileh = src_height / tile_t_; int dst_tilew = width_ / tile_s_; int dst_tileh = height_ / tile_t_; for (int i = 0; i < p.size(); ++i) { //scalar s = p[i][0] * src_width; scalar x = s * dst_tilew + (src_tilew / 2) + (p[i][0] * src_width - s * src_tilew); p[i][0] = x / width_; //scalar t = p[i][1] * src_height; scalar y = t * dst_tileh + (src_tileh / 2) + (p[i][1] * src_height - t * src_tileh);; p[i][1] = y / height_; } } void image::set_as_icon() const { backend backend; SDL_Surface* context = SDL_CreateRGBSurfaceFrom ( pixels_, width_, height_, depth_, pitch_, #if SDL_BYTEORDER == SDL_LIL_ENDIAN 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 #else 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF #endif ); SDL_WM_SetIcon(context, 0); SDL_FreeSurface(context); } bool image::tile_coordinates(int index, scalar coords[8]) const { // make sure the index represents a real tile if (index < 0 && index >= tile_s_ * tile_t_) return false; scalar w = 1.0 / scalar(tile_s_); scalar h = 1.0 / scalar(tile_t_); coords[0] = scalar(index % tile_s_) * w; coords[1] = (scalar(tile_t_ - 1) - scalar(index / tile_s_)) * h; coords[2] = coords[0] + w; coords[3] = coords[1]; coords[4] = coords[2]; coords[5] = coords[1] + h; coords[6] = coords[0]; coords[7] = coords[5]; return true; } void image::bind() const { ASSERT(video::ready() && "should have a video context set"); if (object_ == 0) { upload_to_gl(); } if (object_ != global_object_) { glBindTexture(GL_TEXTURE_2D, (GLuint)object_); global_object_ = object_; } } void image::reset_binding() { ASSERT(video::ready() && "should have a video context set"); glBindTexture(GL_TEXTURE_2D, 0); global_object_ = 0; } /* * Upload the image to GL so that it will be accessible by a much more * manageable handle and hopefully reside in video memory. */ void image::upload_to_gl() const { if (object_) return; // already loaded glGenTextures(1, (GLuint*)&object_); glBindTexture(GL_TEXTURE_2D, (GLuint)object_); GLuint mode; if (channels_ == 3) mode = GL_RGB; else mode = GL_RGBA; glTexImage2D //gluBuild2DMipmaps ( GL_TEXTURE_2D, 0, mode, //3, width_, height_, 0, mode, GL_UNSIGNED_BYTE, pixels_ ); set_properties(); // we want to know when the GL context is recreated //dispatcher& dispatcher = dispatcher::global(); //new_context_ = dispatcher.add_target("video.newcontext", //boost::bind(&image::context_recreated, this)); // FIXME this has const issues } void image::unload_from_gl() const { if (object_) { if (object_ == global_object_) global_object_ = 0; glDeleteTextures(1, (GLuint*)&object_); object_ = 0; } } void image::context_recreated() { object_ = global_object_ = 0; upload_to_gl(); } /* * Sets some texture properties such as the filters and external * coordinate behavior. */ void image::set_properties() const { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t_); } unsigned image::global_object_ = 0; } // namespace moof