From: Mikko Rasa Date: Sat, 5 Oct 2013 23:00:57 +0000 (+0300) Subject: Make the image loading code more modular X-Git-Url: http://git.tdb.fi/?a=commitdiff_plain;h=cfd3548464e6424fc9decf0539d6cd04b031ba10;p=libs%2Fgui.git Make the image loading code more modular Instead of being embedded in the Image class, there's now a separate ImageLoader class with subclasses for each image type. In the future some kind of plugin system may be implemented. DevIL code was also extensively reworked to get rid of the need to read the entire file to memory. --- diff --git a/Build b/Build index 0eac10a..421a84e 100644 --- a/Build +++ b/Build @@ -71,6 +71,14 @@ package "mspgui" { source "source/graphics"; source "source/input"; + if_feature "libpng" + { + source "source/graphics/png"; + }; + if_feature "devil" + { + source "source/graphics/devil"; + }; if_arch "windows" { overlay "windows"; diff --git a/source/graphics/devil/devilloader.cpp b/source/graphics/devil/devilloader.cpp new file mode 100644 index 0000000..23b04b3 --- /dev/null +++ b/source/graphics/devil/devilloader.cpp @@ -0,0 +1,168 @@ +#include +#include +#include "devilloader.h" + +using namespace std; + +namespace { + +struct IOContext +{ + Msp::IO::Base *io; + char *buffer; + unsigned buf_fill; + unsigned buf_max; + unsigned position; +}; + +unsigned char eof(void *handle) +{ + return reinterpret_cast(handle)->io->eof(); +} + +int get(void *handle) +{ + IOContext *ctx = reinterpret_cast(handle); + if(ctx->positionbuf_fill) + return ctx->buffer[ctx->position++]; + else + { + char c = ctx->io->get(); + if(ctx->positionbuf_max) + ctx->buffer[ctx->position] = c; + ++ctx->position; + return c; + } +} + +int read(void *buf, unsigned size, unsigned count, void *handle) +{ + IOContext *ctx = reinterpret_cast(handle); + char *cbuf = reinterpret_cast(buf); + unsigned len = size*count; + unsigned ret = 0; + + if(ctx->positionbuf_fill) + { + unsigned copy_len = min(ctx->buf_fill-ctx->position, len); + copy(ctx->buffer+ctx->position, ctx->buffer+ctx->position+copy_len, cbuf); + ctx->position += copy_len; + ret += copy_len; + cbuf += copy_len; + len -= copy_len; + } + + if(len) + { + unsigned read_len = ctx->io->read(cbuf, len); + if(ctx->positionbuf_max) + { + unsigned copy_len = min(ctx->buf_max-ctx->position, read_len); + copy(cbuf, cbuf+copy_len, ctx->buffer+ctx->position); + ctx->buf_fill += copy_len; + } + ret += read_len; + ctx->position += read_len; + } + + return ret; +} + +int seek(void *handle, int offset, int type) +{ + IOContext *ctx = reinterpret_cast(handle); + + unsigned new_pos = ctx->position; + if(type==IL_SEEK_SET) + new_pos = offset; + else if(type==IL_SEEK_CUR) + new_pos += offset; + else + return -1; + + if(new_pos>ctx->buf_fill) + new_pos = ctx->buf_fill; + ctx->position = new_pos; + + return 0; +} + +int tell(void *handle) +{ + return reinterpret_cast(handle)->position; +} + +} // namespace + + +namespace Msp { +namespace Graphics { + +DevilLoader::DevilLoader(IO::Base &i, const string &s): + io(i), + signature(s) +{ + static bool il_init_done = false; + + if(!il_init_done) + { + ilInit(); + ilEnable(IL_ORIGIN_SET); + ilOriginFunc(IL_ORIGIN_LOWER_LEFT); + il_init_done = true; + } + + ilGenImages(1, &id); +} + +DevilLoader::~DevilLoader() +{ + ilDeleteImages(1, &id); +} + +bool DevilLoader::detect(const string &sig) +{ + unsigned type = ilDetermineTypeL(sig.data(), sig.size()); + return type!=IL_TYPE_UNKNOWN; +} + +void DevilLoader::load(Image::Data &data) +{ + IOContext ctx; + ctx.io = &io; + char buffer[4096]; + ctx.buffer = buffer; + ctx.buf_max = sizeof(buffer); + copy(signature.begin(), signature.end(), buffer); + ctx.buf_fill = signature.size(); + ctx.position = 0; + + ilSetRead(0, 0, eof, get, read, seek, tell); + ilBindImage(id); + ilLoadF(IL_TYPE_UNKNOWN, &ctx); + + switch(ilGetInteger(IL_IMAGE_FORMAT)) + { + case IL_COLOR_INDEX: data.fmt = COLOR_INDEX; break; + case IL_LUMINANCE: data.fmt = LUMINANCE; break; + case IL_LUMINANCE_ALPHA: data.fmt = LUMINANCE_ALPHA; break; + case IL_RGB: data.fmt = RGB; break; + case IL_RGBA: data.fmt = RGBA; break; + case IL_BGR: data.fmt = BGR; break; + case IL_BGRA: data.fmt = BGRA; break; + default: throw unsupported_image_format("unknown pixel format"); + } + + data.width = ilGetInteger(IL_IMAGE_WIDTH); + data.height = ilGetInteger(IL_IMAGE_HEIGHT); + unsigned data_size = data.width*data.height*ilGetInteger(IL_IMAGE_BYTES_PER_PIXEL); + data.data = new char[data_size]; + ILubyte *il_data = ilGetData(); + copy(il_data, il_data+data_size, data.data); + + ilBindImage(0); + ilResetRead(); +} + +} // namespace Graphics +} // namespace Msp diff --git a/source/graphics/devil/devilloader.h b/source/graphics/devil/devilloader.h new file mode 100644 index 0000000..3791bb2 --- /dev/null +++ b/source/graphics/devil/devilloader.h @@ -0,0 +1,28 @@ +#ifndef MSP_GRAPHICS_DEVILLOADER_H_ +#define MSP_GRAPHICS_DEVILLOADER_H_ + +#include + +namespace Msp { +namespace Graphics { + +class DevilLoader: public ImageLoader +{ +private: + IO::Base &io; + std::string signature; + unsigned id; + +public: + DevilLoader(IO::Base &, const std::string &); + virtual ~DevilLoader(); + + static bool detect(const std::string &); + + virtual void load(Image::Data &); +}; + +} // namespace Graphics +} // namespace Msp + +#endif diff --git a/source/graphics/image.cpp b/source/graphics/image.cpp index 46c2b3f..3e1124f 100644 --- a/source/graphics/image.cpp +++ b/source/graphics/image.cpp @@ -1,107 +1,43 @@ -#ifdef WITH_DEVIL -#include -#endif +#include #include #include #include #include "image.h" -#include "image_devil.h" -#include "image_png.h" -#include "image_private.h" +#include "imageloader.h" using namespace std; namespace Msp { namespace Graphics { -Image::Private::Private() -{ -#ifdef WITH_DEVIL - id = 0; -#endif - fmt = RGB; - width = 0; - height = 0; - data = 0; -} +Image::Data::Data(): + fmt(RGB), + width(0), + height(0), + data(0) +{ } - -Image::Image(): - priv(new Private) +Image::Data::~Data() { -#if !defined(WITH_DEVIL) && !defined(WITH_LIBPNG) - throw runtime_error("no image support"); -#endif + delete[] data; } -Image::~Image() -{ -#ifdef WITH_DEVIL - if(priv->id) - ilDeleteImages(1, &priv->id); - else -#endif - delete[] priv->data; - delete priv; -} void Image::load_file(const string &fn) { - string ext = FS::extpart(fn); -#ifdef WITH_LIBPNG - if(ext==".png") - { - IO::BufferedFile file(fn); - load_png(file, *priv, 0, 0); - } - else -#endif - { -#ifdef WITH_DEVIL - load_devil_file(fn, *priv); -#else - throw unsupported_image_format("DevIL needed for non-PNG images"); -#endif - } - (void)fn; + RefPtr loader = ImageLoader::open_file(fn); + load(*loader); } void Image::load_io(IO::Base &io) { - char sig_buf[8]; - unsigned sig_len = io.read(sig_buf, sizeof(sig_buf)); -#ifdef WITH_LIBPNG - if(sig_len==sizeof(sig_buf) && is_png(sig_buf, sig_len)) - load_png(io, *priv, sig_buf, sig_len); - else -#endif - { -#ifdef WITH_DEVIL - load_devil_io(io, *priv, sig_buf, sig_len); -#else - throw unsupported_image_format("DevIL needed for non-PNG images"); -#endif - } -} - -PixelFormat Image::get_format() const -{ - return priv->fmt; -} - -unsigned Image::get_width() const -{ - return priv->width; -} - -unsigned Image::get_height() const -{ - return priv->height; + RefPtr loader = ImageLoader::open_io(io); + load(*loader); } -const void *Image::get_data() const +void Image::load(ImageLoader &loader) { - return priv->data; + loader.load(data); } } // namespace Graphics diff --git a/source/graphics/image.h b/source/graphics/image.h index 137f856..bfaeef8 100644 --- a/source/graphics/image.h +++ b/source/graphics/image.h @@ -9,39 +9,34 @@ namespace Msp { namespace Graphics { -class unsupported_image_format: public std::runtime_error -{ -public: - unsupported_image_format(const std::string &w): std::runtime_error(w) { } - virtual ~unsupported_image_format() throw() { } -}; - -class bad_image_data: public std::runtime_error -{ -public: - bad_image_data(const std::string &w): std::runtime_error(w) { } - virtual ~bad_image_data() throw() { } -}; - +class ImageLoader; class Image { public: - struct Private; + struct Data + { + PixelFormat fmt; + unsigned width; + unsigned height; + char *data; + + Data(); + ~Data(); + }; private: - Private *priv; + Data data; public: - Image(); - ~Image(); - void load_file(const std::string &); void load_io(IO::Base &); - PixelFormat get_format() const; - unsigned get_width() const; - unsigned get_height() const; - const void *get_data() const; + void load(ImageLoader &); + + PixelFormat get_format() const { return data.fmt; } + unsigned get_width() const { return data.width; } + unsigned get_height() const { return data.height; } + const void *get_data() const { return data.data; } }; } // namespace Graphics diff --git a/source/graphics/image_devil.cpp b/source/graphics/image_devil.cpp deleted file mode 100644 index b09ae3d..0000000 --- a/source/graphics/image_devil.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#ifdef WITH_DEVIL -#include -#include "image_devil.h" -#include "image_private.h" - -using namespace std; - -namespace { - -using namespace Msp::Graphics; - -void init_load(Image::Private &priv) -{ - static bool il_init_done = false; - - if(!il_init_done) - { - ilInit(); - ilEnable(IL_ORIGIN_SET); - ilOriginFunc(IL_ORIGIN_LOWER_LEFT); - il_init_done = true; - } - - if(!priv.id) - ilGenImages(1, &priv.id); - ilBindImage(priv.id); -} - -void finish_load(Image::Private &priv) -{ - switch(ilGetInteger(IL_IMAGE_FORMAT)) - { - case IL_COLOR_INDEX: priv.fmt = COLOR_INDEX; break; - case IL_LUMINANCE: priv.fmt = LUMINANCE; break; - case IL_LUMINANCE_ALPHA: priv.fmt = LUMINANCE_ALPHA; break; - case IL_RGB: priv.fmt = RGB; break; - case IL_RGBA: priv.fmt = RGBA; break; - case IL_BGR: priv.fmt = BGR; break; - case IL_BGRA: priv.fmt = BGRA; break; - default: throw unsupported_image_format("unknown pixel format"); - } - - priv.width = ilGetInteger(IL_IMAGE_WIDTH); - priv.height = ilGetInteger(IL_IMAGE_HEIGHT); - priv.data = reinterpret_cast(ilGetData()); - - ilBindImage(0); -} - -} - - -namespace Msp { -namespace Graphics { - -void load_devil_file(const string &fn, Image::Private &priv) -{ - init_load(priv); - if(!ilLoadImage(const_cast(fn.c_str()))) - throw bad_image_data("IL error"); - finish_load(priv); -} - -void load_devil_io(IO::Base &io, Image::Private &priv, const char *sig_buf, unsigned sig_len) -{ - vector data(sig_buf, sig_buf+sig_len); - while(1) - { - unsigned pos = data.size(); - data.resize(pos+16384); - unsigned len = io.read(&data[pos], 16384); - data.resize(pos+len); - if(len==0) - break; - } - - init_load(priv); - if(!ilLoadL(IL_TYPE_UNKNOWN, &data.front(), data.size())) - throw bad_image_data("IL error"); - finish_load(priv); -} - -} // namespace Graphics -} // namespace Msp - -#endif diff --git a/source/graphics/image_devil.h b/source/graphics/image_devil.h deleted file mode 100644 index 790f387..0000000 --- a/source/graphics/image_devil.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef MSP_GRAPHICS_IMAGE_DEVIL_H_ -#define MSP_GRAPHICS_IMAGE_DEVIL_H_ - -#include "image.h" - -namespace Msp { -namespace Graphics { - -#ifdef WITH_DEVIL -void load_devil_file(const std::string &, Image::Private &); -void load_devil_io(IO::Base &, Image::Private &, const char *, unsigned); -#endif - -} // namespace Graphics -} // namespace Msp - -#endif diff --git a/source/graphics/image_png.cpp b/source/graphics/image_png.cpp deleted file mode 100644 index 2838448..0000000 --- a/source/graphics/image_png.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#ifdef WITH_LIBPNG -#include -#include "image_png.h" -#include "image_private.h" - -using namespace std; - -namespace { - -struct ErrorInfo -{ - string msg; -}; - -void read(png_struct *png, png_byte *data, png_size_t size) -{ - Msp::IO::Base *in = reinterpret_cast(png_get_io_ptr(png)); - in->read(reinterpret_cast(data), size); -} - -void error(png_struct *png, const char *msg) -{ - ErrorInfo *error_info = reinterpret_cast(png_get_error_ptr(png)); - error_info->msg = msg; - longjmp(png_jmpbuf(png), 1); -} - -} - - -namespace Msp { -namespace Graphics { - -bool is_png(const char *buf, unsigned len) -{ - return !png_sig_cmp(reinterpret_cast(const_cast(buf)), 0, len); -} - -void load_png(IO::Base &in, Image::Private &priv, const char *, unsigned sig_len) -{ - png_struct *png = 0; - png_info *info = 0; - priv.data = 0; - - try - { - ErrorInfo error_info; - png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &error_info, error, 0); - info = png_create_info_struct(png); - - if(setjmp(png_jmpbuf(png))) - throw bad_image_data(error_info.msg); - - png_set_read_fn(png, &in, read); - png_set_sig_bytes(png, sig_len); - png_read_info(png, info); - png_uint_32 width; - png_uint_32 height; - int depth; - int color; - png_get_IHDR(png, info, &width, &height, &depth, &color, 0, 0, 0); - priv.width = width; - priv.height = height; - if(depth!=8) - throw unsupported_image_format("depth!=8"); - switch(color) - { - case PNG_COLOR_TYPE_PALETTE: priv.fmt = COLOR_INDEX; break; - case PNG_COLOR_TYPE_GRAY: priv.fmt = LUMINANCE; break; - case PNG_COLOR_TYPE_GRAY_ALPHA: priv.fmt = LUMINANCE_ALPHA; break; - case PNG_COLOR_TYPE_RGB: priv.fmt = RGB; break; - case PNG_COLOR_TYPE_RGB_ALPHA: priv.fmt = RGBA; break; - default: throw unsupported_image_format("unknown color type"); - } - - unsigned nchans = png_get_channels(png, info); - if(nchans==4 && priv.fmt==RGB) - png_set_strip_alpha(png); - - unsigned rowstride = priv.width*nchans; - priv.data = new char[rowstride*priv.height]; - for(unsigned y=0; y(priv.data+rowstride*(priv.height-1-y)), 0); - - png_read_end(png, 0); - png_destroy_read_struct(&png, &info, 0); - } - catch(...) - { - png_destroy_read_struct(&png, &info, 0); - delete[] priv.data; - throw; - } -} - -} // namespace Graphics -} // namespace Msp -#endif diff --git a/source/graphics/image_png.h b/source/graphics/image_png.h deleted file mode 100644 index 8291a2f..0000000 --- a/source/graphics/image_png.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef MSP_GRAPHICS_IMAGE_PNG_H_ -#define MSP_GRAPHICS_IMAGE_PNG_H_ - -#include -#include "image.h" - -namespace Msp { -namespace Graphics { - -#ifdef WITH_LIBPNG -bool is_png(const char *, unsigned); -void load_png(IO::Base &, Image::Private &, const char *, unsigned); -#endif - -} // namespace Graphics -} // namespace Msp - -#endif diff --git a/source/graphics/image_private.h b/source/graphics/image_private.h deleted file mode 100644 index 72ceedd..0000000 --- a/source/graphics/image_private.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef MSP_GRAPHICS_IMAGE_PRIVATE_H_ -#define MSP_GRAPHICS_IMAGE_PRIVATE_H_ - -#include "image.h" - -namespace Msp { -namespace Graphics { - -struct Image::Private -{ -#ifdef WITH_DEVIL - unsigned id; -#endif - PixelFormat fmt; - unsigned width; - unsigned height; - char *data; - - Private(); -}; - -} // namespace Graphics -} // namespace Msp - -#endif diff --git a/source/graphics/imageloader.cpp b/source/graphics/imageloader.cpp new file mode 100644 index 0000000..679f797 --- /dev/null +++ b/source/graphics/imageloader.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include "imageloader.h" +#ifdef WITH_LIBPNG +#include "png/pngloader.h" +#endif +#ifdef WITH_DEVIL +#include "devil/devilloader.h" +#endif + +using namespace std; + +namespace Msp { +namespace Graphics { + +ImageLoader::ImageLoader(): + source(0) +{ } + +ImageLoader::~ImageLoader() +{ + delete source; +} + +ImageLoader *ImageLoader::open_file(const string &fn) +{ + try + { + RefPtr file = new IO::BufferedFile(fn); + ImageLoader *loader = open_io(*file); + loader->source = file.release(); + return loader; + } + catch(const unsupported_image_format &e) + { + throw unsupported_image_format(format("%s: %s", fn, e.what())); + } +} + +ImageLoader *ImageLoader::open_io(IO::Base &io) +{ + char sig_buf[8]; + unsigned sig_len = io.read(sig_buf, sizeof(sig_buf)); + string sig(sig_buf, sig_len); + + // TODO register loader classes automatically + ImageLoader *loader = 0; + if(0) + ; +#ifdef WITH_LIBPNG + else if(PngLoader::detect(sig)) + loader = new PngLoader(io, sig); +#endif +#ifdef WITH_DEVIL + else if(DevilLoader::detect(sig)) + loader = new DevilLoader(io, sig); +#endif + else + { + string sig_hex; + for(unsigned i=0; i(sig_buf[i])); + } + throw unsupported_image_format(sig_hex); + } + + return loader; +} + +} // namespace Graphics +} // namespace Msp diff --git a/source/graphics/imageloader.h b/source/graphics/imageloader.h new file mode 100644 index 0000000..0653275 --- /dev/null +++ b/source/graphics/imageloader.h @@ -0,0 +1,43 @@ +#ifndef MSP_GRAPHICS_IMAGELOADER_H_ +#define MSP_GRAPHICS_IMAGELOADER_H_ + +#include "image.h" + +namespace Msp { +namespace Graphics { + +class unsupported_image_format: public std::runtime_error +{ +public: + unsupported_image_format(const std::string &w): std::runtime_error(w) { } + virtual ~unsupported_image_format() throw() { } +}; + +class bad_image_data: public std::runtime_error +{ +public: + bad_image_data(const std::string &w): std::runtime_error(w) { } + virtual ~bad_image_data() throw() { } +}; + + +class ImageLoader +{ +private: + IO::Base *source; +protected: + + ImageLoader(); +public: + virtual ~ImageLoader(); + + static ImageLoader *open_file(const std::string &); + static ImageLoader *open_io(IO::Base &); + + virtual void load(Image::Data &) = 0; +}; + +} // namespace Graphics +} // namespace Msp + +#endif diff --git a/source/graphics/png/pngloader.cpp b/source/graphics/png/pngloader.cpp new file mode 100644 index 0000000..8553065 --- /dev/null +++ b/source/graphics/png/pngloader.cpp @@ -0,0 +1,94 @@ +#include +#include "pngloader.h" + +using namespace std; + +namespace { + +void read(png_struct *png, png_byte *data, png_size_t size) +{ + Msp::IO::Base *in = reinterpret_cast(png_get_io_ptr(png)); + in->read(reinterpret_cast(data), size); +} + +void error(png_struct *png, const char *msg) +{ + string *message = reinterpret_cast(png_get_error_ptr(png)); + *message = msg; + longjmp(png_jmpbuf(png), 1); +} + +} // namespace + + +namespace Msp { +namespace Graphics { + +struct PngLoader::Private +{ + std::string message; + png_struct *png; + png_info *info; +}; + +PngLoader::PngLoader(IO::Base &io, const string &sig): + priv(new Private) +{ + priv->png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &priv->message, error, 0); + priv->info = png_create_info_struct(priv->png); + + // These probably won't give any errors + png_set_read_fn(priv->png, &io, read); + png_set_sig_bytes(priv->png, sig.size()); +} + +PngLoader::~PngLoader() +{ + png_destroy_read_struct(&priv->png, &priv->info, 0); + delete priv; +} + +bool PngLoader::detect(const std::string &sig) +{ + return !png_sig_cmp(reinterpret_cast(const_cast(sig.data())), 0, sig.size()); +} + +void PngLoader::load(Image::Data &data) +{ + if(setjmp(png_jmpbuf(priv->png))) + throw bad_image_data(priv->message); + + png_read_info(priv->png, priv->info); + png_uint_32 width; + png_uint_32 height; + int depth; + int color; + png_get_IHDR(priv->png, priv->info, &width, &height, &depth, &color, 0, 0, 0); + data.width = width; + data.height = height; + if(depth!=8) + throw unsupported_image_format("depth!=8"); + switch(color) + { + case PNG_COLOR_TYPE_PALETTE: data.fmt = COLOR_INDEX; break; + case PNG_COLOR_TYPE_GRAY: data.fmt = LUMINANCE; break; + case PNG_COLOR_TYPE_GRAY_ALPHA: data.fmt = LUMINANCE_ALPHA; break; + case PNG_COLOR_TYPE_RGB: data.fmt = RGB; break; + case PNG_COLOR_TYPE_RGB_ALPHA: data.fmt = RGBA; break; + default: throw unsupported_image_format("unknown color type"); + } + + unsigned nchans = png_get_channels(priv->png, priv->info); + if(nchans==4 && data.fmt==RGB) + png_set_strip_alpha(priv->png); + + unsigned rowstride = data.width*nchans; + data.data = new char[rowstride*data.height]; + for(unsigned y=0; ypng, reinterpret_cast(data.data+rowstride*(data.height-1-y)), 0); + + png_read_end(priv->png, 0); +} + +} // namespace Graphics +} // namespace Msp diff --git a/source/graphics/png/pngloader.h b/source/graphics/png/pngloader.h new file mode 100644 index 0000000..0bb5fd7 --- /dev/null +++ b/source/graphics/png/pngloader.h @@ -0,0 +1,28 @@ +#ifndef MSP_GRAPHICS_PNGLOADER_H_ +#define MSP_GRAPHICS_PNGLOADER_H_ + +#include + +namespace Msp { +namespace Graphics { + +class PngLoader: public ImageLoader +{ +private: + struct Private; + + Private *priv; + +public: + PngLoader(IO::Base &, const std::string &); + virtual ~PngLoader(); + + static bool detect(const std::string &); + + virtual void load(Image::Data &); +}; + +} // namespace Graphics +} // namespace Msp + +#endif