]> git.tdb.fi Git - libs/gui.git/commitdiff
Make the image loading code more modular
authorMikko Rasa <tdb@tdb.fi>
Sat, 5 Oct 2013 23:00:57 +0000 (02:00 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sat, 5 Oct 2013 23:00:57 +0000 (02:00 +0300)
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.

14 files changed:
Build
source/graphics/devil/devilloader.cpp [new file with mode: 0644]
source/graphics/devil/devilloader.h [new file with mode: 0644]
source/graphics/image.cpp
source/graphics/image.h
source/graphics/image_devil.cpp [deleted file]
source/graphics/image_devil.h [deleted file]
source/graphics/image_png.cpp [deleted file]
source/graphics/image_png.h [deleted file]
source/graphics/image_private.h [deleted file]
source/graphics/imageloader.cpp [new file with mode: 0644]
source/graphics/imageloader.h [new file with mode: 0644]
source/graphics/png/pngloader.cpp [new file with mode: 0644]
source/graphics/png/pngloader.h [new file with mode: 0644]

diff --git a/Build b/Build
index 0eac10a2e357cceabe9710141208212b55bf047f..421a84ea4cc75c913ec55ffd4b603fda46ab215b 100644 (file)
--- 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 (file)
index 0000000..23b04b3
--- /dev/null
@@ -0,0 +1,168 @@
+#include <algorithm>
+#include <IL/il.h>
+#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<IOContext *>(handle)->io->eof();
+}
+
+int get(void *handle)
+{
+       IOContext *ctx = reinterpret_cast<IOContext *>(handle);
+       if(ctx->position<ctx->buf_fill)
+               return ctx->buffer[ctx->position++];
+       else
+       {
+               char c = ctx->io->get();
+               if(ctx->position<ctx->buf_max)
+                       ctx->buffer[ctx->position] = c;
+               ++ctx->position;
+               return c;
+       }
+}
+
+int read(void *buf, unsigned size, unsigned count, void *handle)
+{
+       IOContext *ctx = reinterpret_cast<IOContext *>(handle);
+       char *cbuf = reinterpret_cast<char *>(buf);
+       unsigned len = size*count;
+       unsigned ret = 0;
+
+       if(ctx->position<ctx->buf_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->position<ctx->buf_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<IOContext *>(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<IOContext *>(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 (file)
index 0000000..3791bb2
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef MSP_GRAPHICS_DEVILLOADER_H_
+#define MSP_GRAPHICS_DEVILLOADER_H_
+
+#include <msp/graphics/imageloader.h>
+
+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
index 46c2b3fab3c29f0ce952133b1d55966531252e94..3e1124faaf06800158413189c054529db2ca4060 100644 (file)
-#ifdef WITH_DEVIL
-#include <IL/il.h>
-#endif
+#include <msp/core/refptr.h>
 #include <msp/fs/utils.h>
 #include <msp/io/file.h>
 #include <msp/io/memory.h>
 #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<ImageLoader> 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<ImageLoader> 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
index 137f856f21f5736d40dbecae6275a26919c42536..bfaeef84a00fbf7b945dbac8cc51d0fc593673bd 100644 (file)
@@ -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 (file)
index b09ae3d..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-#ifdef WITH_DEVIL
-#include <IL/il.h>
-#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<char *>(ilGetData());
-
-       ilBindImage(0);
-}
-
-}
-
-
-namespace Msp {
-namespace Graphics {
-
-void load_devil_file(const string &fn, Image::Private &priv)
-{
-       init_load(priv);
-       if(!ilLoadImage(const_cast<char *>(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<char> 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 (file)
index 790f387..0000000
+++ /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 (file)
index 2838448..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-#ifdef WITH_LIBPNG
-#include <png.h>
-#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<Msp::IO::Base *>(png_get_io_ptr(png));
-       in->read(reinterpret_cast<char *>(data), size);
-}
-
-void error(png_struct *png, const char *msg)
-{
-       ErrorInfo *error_info = reinterpret_cast<ErrorInfo *>(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<png_byte *>(const_cast<char*>(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.height; ++y)
-                       png_read_row(png, reinterpret_cast<png_byte *>(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 (file)
index 8291a2f..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef MSP_GRAPHICS_IMAGE_PNG_H_
-#define MSP_GRAPHICS_IMAGE_PNG_H_
-
-#include <msp/io/base.h>
-#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 (file)
index 72ceedd..0000000
+++ /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 (file)
index 0000000..679f797
--- /dev/null
@@ -0,0 +1,75 @@
+#include <msp/core/refptr.h>
+#include <msp/io/file.h>
+#include <msp/strings/format.h>
+#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<IO::File> 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_len; ++i)
+               {
+                       if(i)
+                               sig_hex += ' ';
+                       sig_hex += format("%02X", static_cast<unsigned char>(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 (file)
index 0000000..0653275
--- /dev/null
@@ -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 (file)
index 0000000..8553065
--- /dev/null
@@ -0,0 +1,94 @@
+#include <png.h>
+#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<Msp::IO::Base *>(png_get_io_ptr(png));
+       in->read(reinterpret_cast<char *>(data), size);
+}
+
+void error(png_struct *png, const char *msg)
+{
+       string *message = reinterpret_cast<string *>(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<png_byte *>(const_cast<char*>(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; y<data.height; ++y)
+               png_read_row(priv->png, reinterpret_cast<png_byte *>(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 (file)
index 0000000..0bb5fd7
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef MSP_GRAPHICS_PNGLOADER_H_
+#define MSP_GRAPHICS_PNGLOADER_H_
+
+#include <msp/graphics/imageloader.h>
+
+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