-/*] 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 <fstream>
#include <stdexcept>
-
#include <png.h>
+
#include <SDL/SDL.h>
+#include <stlplus/portability/file_system.hpp>
+
#include "backend.hh"
+#include "debug.hh"
#include "image.hh"
#include "log.hh"
#include "opengl.hh"
namespace moof {
+MOOF_REGISTER_RESOURCE(image, bmp, textures);
MOOF_REGISTER_RESOURCE(image, png, textures);
-//static int power_of_two(int input)
-//{
- //int value = 1;
- //while (value < input)
- //{
- //value <<= 1;
- //}
- //return value;
-//}
+static int higher_power_of_two(int input)
+{
+ int value = 2;
+ while (value <= input) value <<= 1;
+ return value;
+}
-unsigned image::global_object_ = 0;
+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);
+}
-image::image(const std::string& path) :
- pixels_(0),
- object_(0),
- min_filter_(GL_NEAREST),
- mag_filter_(GL_NEAREST),
- wrap_s_(GL_CLAMP),
- wrap_t_(GL_CLAMP),
- tile_width_(1),
- tile_height_(1)
+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)
{
- FILE* fp = fopen(path.c_str(), "rb");
- if (!fp) throw std::runtime_error("image not found at " + path);
+ 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;
- png_infop pngInfo = 0;
- png_infop pngInfoEnd = 0;
- png_structp pngObj = 0;
-
- int bpp;
+ int bpp;
png_byte colors;
- png_bytepp rows = 0;
+ png_bytepp rows;
png_textp texts = 0;
- int nutext_s;
+ int nutext_s;
- bytesRead = fread(signature, 1, sizeof(signature), fp);
+ bytesRead = file.read((char*)signature, sizeof(signature)).gcount();
if (bytesRead < sizeof(signature) ||
- png_sig_cmp(signature, 0, sizeof(signature)) != 0) goto cleanup;
-
- pngObj = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
- if (!pngObj) goto cleanup;
+ png_sig_cmp(signature, 0, sizeof(signature)) != 0) throw 0;
- pngInfo = png_create_info_struct(pngObj);
- if (!pngInfo) goto cleanup;
-
- 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);
+ 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(pngObj);
- break;
+ 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);
+ int channels = png_get_channels(png.context, png.info);
+ int depth = bpp * channels;
// read comments
- png_get_text(pngObj, pngInfo, &texts, &nutext_s);
+ 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, "TextureInfo", 11) == 0)
+ if (std::string(texts[i].key) == "X-Yoink-Texture")
{
- set_texture_info(texts[i].text);
+ attribs.init(texts[i].text);
+ texture = true;
break;
}
}
- width_ = png_get_image_width(pngObj, pngInfo);
- height_ = png_get_image_height(pngObj, pngInfo);
+ 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];
- pitch_ = png_get_rowbytes(pngObj, pngInfo);
- pixels_ = new char[width_ * pitch_];
-
- rows = new png_bytep[height_];
- for (int i = 0; i < height_; ++i)
+ rows = new png_bytep[height];
+ if (texture)
{
- rows[height_-1-i] = (png_bytep)(pixels_ + i * channels_ * width_);
+ 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(pngObj, rows);
- png_read_end(pngObj, 0);
+ png_read_image(png.context, rows);
+ delete[] rows;
-cleanup:
+ png_read_end(png.context, 0);
- delete[] rows;
- png_destroy_read_struct(pngObj ? &pngObj : 0,
- pngInfo ? &pngInfo : 0,
- pngInfoEnd ? &pngInfoEnd : 0);
- fclose(fp);
+ 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()
}
+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<vector2>& 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_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_width_ * tile_height_) return false;
+ if (index < 0 && index >= tile_s_ * tile_t_) return false;
- scalar w = 1.0 / scalar(tile_width_);
- scalar h = 1.0 / scalar(tile_height_);
+ scalar w = 1.0 / scalar(tile_s_);
+ scalar h = 1.0 / scalar(tile_t_);
- coords[0] = scalar(index % tile_width_) * w;
- coords[1] = (scalar(tile_height_ - 1) - scalar(index / tile_width_)) * h;
+ 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];
return true;
}
-
void image::bind() const
{
- ASSERT(video::current() && "should have a video context set");
+ ASSERT(video::ready() && "should have a video context set");
if (object_ == 0)
{
void image::reset_binding()
{
- ASSERT(video::current() && "should have a video context set");
+ 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_)
- {
- // already loaded
- return;
- }
+ 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;
+ else mode = GL_RGBA;
glTexImage2D
//gluBuild2DMipmaps
{
if (object_)
{
- if (object_ == global_object_)
- {
- global_object_ = 0;
- }
-
+ if (object_ == global_object_) global_object_ = 0;
glDeleteTextures(1, (GLuint*)&object_);
object_ = 0;
}
}
-void image::set_texture_info(const std::string& info)
-{
- 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(tile_width_, "tiles_s");
- globals.get(tile_height_, "tiles_t");
- globals.get(min_filter_, "min_filter");
- globals.get(mag_filter_, "mag_filter");
- globals.get(wrap_s_, "wrap_s");
- globals.get(wrap_t_, "wrap_t");
- }
-}
+unsigned image::global_object_ = 0;
} // namespace moof