-/*] Copyright (c) 2009-2010, Charles McGarvey [**************************
+/*] Copyright (c) 2009-2011, Charles McGarvey [*****************************
**] All rights reserved.
*
-* vi:ts=4 sw=4 tw=75
-*
* Distributable under the terms and conditions of the 2-clause BSD license;
* see the file COPYING for a complete text of the license.
*
-**************************************************************************/
+*****************************************************************************/
-#include <cstdio> // FILE
#include <cstring> // strncmp
-
-#include <boost/algorithm/string.hpp>
+#include <fstream>
+#include <stdexcept>
#include <png.h>
#include <SDL/SDL.h>
#include "backend.hh"
#include "image.hh"
#include "log.hh"
-#include "manager.hh"
+#include "opengl.hh"
+#include "script.hh"
+#include "video.hh"
namespace moof {
-class image::impl : public manager<impl>
-{
-public:
-
- explicit impl() :
- context_(0),
- pixels_(0) {}
-
- ~impl()
- {
- SDL_FreeSurface(context_);
- delete[] pixels_;
- }
-
-
- void flip()
- {
- unsigned char* pixels = (Uint8*)(context_->pixels);
-
- unsigned pitch = context_->pitch;
- unsigned char line[pitch];
-
- int yBegin = 0;
- int yEnd = context_->h - 1;
+MOOF_REGISTER_RESOURCE(image, png, textures);
- if (SDL_MUSTLOCK(context_)) SDL_LockSurface(context_);
- while (yBegin < yEnd)
- {
- memcpy(line, pixels + pitch * yBegin, pitch);
- memcpy(pixels + pitch * yBegin, pixels + pitch * yEnd, pitch);
- memcpy(pixels + pitch * yEnd, line, pitch);
- yBegin++;
- yEnd--;
- }
- if (SDL_MUSTLOCK(context_)) SDL_UnlockSurface(context_);
- }
-
- void set_as_icon() const
- {
- SDL_WM_SetIcon(context_, 0);
- }
+//static int power_of_two(int input)
+//{
+ //int value = 1;
+ //while (value < input)
+ //{
+ //value <<= 1;
+ //}
+ //return value;
+//}
- void init(const std::string& name, bool flipped = false)
- {
- std::string path(name);
+unsigned image::global_object_ = 0;
- FILE* fp = resource::open_file(path);
- if (!fp) return;
- png_byte signature[8];
- size_t bytesRead;
+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);
+}
- png_infop pngInfo = 0;
- png_infop pngInfoEnd = 0;
- png_structp pngObj = 0;
- int width;
- int height;
- int pitch;
- int bpp;
- int channels;
+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),
+ wrap_t_(GL_CLAMP)
+{
+ std::ifstream file(path.c_str(), std::ifstream::binary);
+ if (!file.good())
+ throw std::runtime_error("no valid image found at " + path);
- png_byte colors;
- png_bytepp rows = 0;
+ png_byte signature[8];
+ size_t bytesRead;
- png_textp texts = 0;
- int nutext_s;
+ int bpp;
- bytesRead = fread(signature, 1, sizeof(signature), fp);
- if (bytesRead < sizeof(signature) ||
- png_sig_cmp(signature, 0, sizeof(signature)) != 0) goto cleanup;
+ png_byte colors;
+ png_bytepp rows = 0;
- pngObj = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
- if (!pngObj) goto cleanup;
+ png_textp texts = 0;
+ int nutext_s;
- pngInfo = png_create_info_struct(pngObj);
- if (!pngInfo) goto cleanup;
+ bytesRead = file.read((char*)signature, sizeof(signature)).gcount();
+ if (bytesRead < sizeof(signature) ||
+ png_sig_cmp(signature, 0, sizeof(signature)) != 0) throw 0;
- pngInfoEnd = png_create_info_struct(pngObj);
- if (!pngInfoEnd) goto cleanup;
+ 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(pngObj))) goto cleanup;
+ if (setjmp(png_jmpbuf(png.context))) throw 0;
- png_init_io(pngObj, fp);
- png_set_sig_bytes(pngObj, sizeof(signature));
- png_read_info(pngObj, pngInfo);
+ 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(pngObj, pngInfo);
- colors = png_get_color_type(pngObj, pngInfo);
- switch (colors)
- {
- case PNG_COLOR_TYPE_PALETTE:
- png_set_palette_to_rgb(pngObj);
- break;
+ 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(pngObj);
- 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(pngObj);
- break;
- }
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ png_set_gray_to_rgb(png.context);
+ break;
+ }
- if (bpp == 16) png_set_strip_16(pngObj);
+ if (bpp == 16) png_set_strip_16(png.context);
- png_read_update_info(pngObj, pngInfo);
+ png_read_update_info(png.context, png.info);
- bpp = png_get_bit_depth(pngObj, pngInfo);
- channels = png_get_channels(pngObj, pngInfo);
- depth_ = bpp * channels;
+ bpp = png_get_bit_depth(png.context, png.info);
+ channels_ = png_get_channels(png.context, png.info);
+ depth_ = bpp * channels_;
- if (channels == 3) color_mode_ = GL_RGB;
- else color_mode_ = GL_RGBA;
-
- // read comments
- png_get_text(pngObj, pngInfo, &texts, &nutext_s);
- for (int i = 0; i < nutext_s; ++i)
+ // read comments
+ bool texture = false;
+ png_get_text(png.context, png.info, &texts, &nutext_s);
+ for (int i = 0; i < nutext_s; ++i)
+ {
+ if (strncmp(texts[i].key, "X-Yoink-Texture", 11) == 0)
{
- if (strncmp(texts[i].key, "TextureInfo", 11) == 0)
- {
- comment_ = texts[i].text;
- break;
- }
+ set_texture_info(texts[i].text);
+ texture = true;
+ break;
}
+ }
- width = png_get_image_width(pngObj, pngInfo);
- height = png_get_image_height(pngObj, pngInfo);
+ width_ = png_get_image_width(png.context, png.info);
+ height_ = png_get_image_height(png.context, png.info);
- pitch = png_get_rowbytes(pngObj, pngInfo);
- pixels_ = new char[width * pitch];
+ pitch_ = png_get_rowbytes(png.context, png.info);
+ pixels_ = new char[width_ * pitch_];
- rows = new png_bytep[height];
- if (flipped)
+ rows = new png_bytep[height_];
+ if (texture)
+ {
+ log_debug("texture detected; loading flipped");
+ for (int i = 0; i < height_; ++i)
{
- for (int i = 0; i < height; ++i)
- {
- rows[height - 1 - i] = (png_bytep)(pixels_ +
- i * channels * width);
- }
+ rows[height_-1-i] = (png_bytep)(pixels_ +
+ i * channels_ * width_);
}
- else
+ }
+ else
+ {
+ log_debug("no texture attributes found");
+ for (int i = 0; i < height_; ++i)
{
- for (int i = 0; i < height; ++i)
- {
- rows[i] = (png_bytep)(pixels_ + i * channels * width);
- }
+ rows[i] = (png_bytep)(pixels_ +
+ i * channels_ * width_);
}
-
- png_read_image(pngObj, rows);
- png_read_end(pngObj, 0);
-
- context_ = SDL_CreateRGBSurfaceFrom
- (
- pixels_,
- width,
- height,
- bpp * channels,
- pitch,
-#if SDL_BYTEORDER == SDL_LIL_ENDIAN
- 0x000000FF,
- 0x0000FF00,
- 0x00FF0000,
- 0xFF000000
-#else
- 0xFF000000,
- 0x00FF0000,
- 0x0000FF00,
- 0x000000FF
-#endif
- );
-
- cleanup:
-
- delete[] rows;
- png_destroy_read_struct(pngObj ? &pngObj : 0,
- pngInfo ? &pngInfo : 0,
- pngInfoEnd ? &pngInfoEnd : 0);
- fclose(fp);
}
+ png_read_image(png.context, rows);
+ png_read_end(png.context, 0);
- SDL_Surface* context_;
- char* pixels_;
-
- unsigned depth_;
- GLuint color_mode_;
-
- std::string comment_;
-
-private:
-
- backend backend_;
-};
-
-
-image::image(const std::string& name) :
- // pass through
- impl_(image::impl::instance(name)) {}
-
-
-bool image::is_valid() const
-{
- return impl_->context_;
+ // TODO leak
+ delete[] rows;
}
-int image::width() const
+image::~image()
{
- return impl_->context_->w;
+ unload_from_gl();
+ delete[] pixels_;
}
-int image::height() const
+
+void image::set_as_icon() const
{
- return impl_->context_->h;
+ 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);
}
-unsigned image::depth() const
+bool image::tile_coordinates(int index, scalar coords[8]) const
{
- return impl_->depth_;
+ // 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;
}
-unsigned image::pitch() const
+void image::bind() const
{
- return impl_->context_->pitch;
+ ASSERT(video::current() && "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_;
+ }
}
-GLuint image::mode() const
+void image::reset_binding()
{
- return impl_->color_mode_;
+ ASSERT(video::current() && "should have a video context set");
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+ global_object_ = 0;
}
-std::string image::comment() const
+/*
+ * 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
{
- return impl_->comment_;
+ 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
}
-const char* image::pixels() const
+void image::unload_from_gl() const
{
- return impl_->pixels_;
+ if (object_)
+ {
+ if (object_ == global_object_) global_object_ = 0;
+ glDeleteTextures(1, (GLuint*)&object_);
+ object_ = 0;
+ }
}
-char* image::pixels()
+void image::context_recreated()
{
- return impl_->pixels_;
+ object_ = global_object_ = 0;
+ upload_to_gl();
}
-
-void image::flip()
+/*
+ * Sets some texture properties such as the filters and external
+ * coordinate behavior.
+ */
+void image::set_properties() const
{
- // pass through
- impl_->flip();
+ 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_);
}
-void image::set_as_icon() const
+void image::set_texture_info(const std::string& info)
{
- // pass through
- impl_->set_as_icon();
+ script script;
+ log::import(script);
+
+ script::slot g = script.globals();
+ g.set_field("CLAMP", GL_CLAMP);
+ g.set_field("REPEAT", GL_REPEAT);
+ g.set_field("LINEAR", GL_LINEAR);
+ g.set_field("NEAREST", GL_NEAREST);
+ g.set_field("LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR);
+ g.set_field("LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST);
+ g.set_field("NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR);
+ g.set_field("NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST);
+
+ 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");
+ }
}