--- /dev/null
+#include <msp/core/inttypes.h>
+#include "bmploader.h"
+
+using namespace std;
+
+namespace {
+
+void read_full(Msp::IO::Base &io, char *buffer, unsigned size)
+{
+ unsigned len = io.read(buffer, size);
+ if(len<size)
+ throw Msp::Graphics::bad_image_data("premature end of file");
+}
+
+template<typename T>
+T decode(const char *data)
+{
+ const Msp::UInt8 *udata = reinterpret_cast<const Msp::UInt8 *>(data);
+ T result = 0;
+ for(unsigned i=0; i<sizeof(T); ++i)
+ result |= udata[i]<<(i*8);
+ return result;
+}
+
+}
+
+namespace Msp {
+namespace Graphics {
+
+BmpLoader::BmpLoader(IO::Base &i, unsigned sb):
+ io(i),
+ sig_bytes(sb)
+{
+ // Image data location is stored at offset 10 and can't be skipped
+ if(sig_bytes>10)
+ throw invalid_argument("BmpLoader::BmpLoader");
+}
+
+bool BmpLoader::detect(const std::string &sig)
+{
+ static const char bmp_sig[] = "BM";
+ if(sig.size()<sizeof(bmp_sig))
+ return false;
+ return !sig.compare(0, 2, bmp_sig);
+}
+
+void BmpLoader::load(Image::Data &data)
+{
+ char bm_header[14];
+ read_full(io, bm_header+sig_bytes, sizeof(bm_header)-sig_bytes);
+
+ if(!sig_bytes && (bm_header[0]!='B' || bm_header[1]!='M'))
+ throw bad_image_data("bitmap header mismatch");
+
+ unsigned data_offset = decode<UInt32>(bm_header+10);
+
+ char dib_header[124];
+ read_full(io, dib_header, 12);
+ unsigned dib_length = min<unsigned>(decode<UInt32>(dib_header), sizeof(dib_header));
+ if(dib_length<40)
+ throw bad_image_data("DIB header too short (very old bmp file?)");
+
+ read_full(io, dib_header+12, dib_length-12);
+
+ data.width = decode<UInt32>(dib_header+4);
+ Int32 height = decode<UInt32>(dib_header+8);
+ data.height = abs(height);
+
+ unsigned color_planes = decode<UInt16>(dib_header+12);
+ if(color_planes!=1)
+ throw bad_image_data("color_planes!=1");
+ unsigned bits_per_pixel = decode<UInt16>(dib_header+14);
+ unsigned compression = decode<UInt32>(dib_header+16);
+ if(compression)
+ throw unsupported_image_format("compression not supported");
+
+ data.stride = (data.width*bits_per_pixel+31)/32*4;
+ switch(bits_per_pixel)
+ {
+ case 8: data.fmt = COLOR_INDEX; break;
+ case 24: data.fmt = BGR; break;
+ case 32: data.fmt = BGRX; break; // TODO is it BGRX or XBGR?
+ default: throw unsupported_image_format("bits per pixel value not supported");
+ }
+
+ unsigned skip = data_offset-sizeof(bm_header)-dib_length;
+ while(skip>0)
+ {
+ char buffer[1024];
+ unsigned size = min<unsigned>(sizeof(buffer), skip);
+ read_full(io, buffer, size);
+ skip -= size;
+ }
+
+ data.data = new char[data.stride*data.height];
+ if(height<0)
+ {
+ for(unsigned y=0; y<data.height; ++y)
+ read_full(io, data.data+(data.height-1-y)*data.stride, data.stride);
+ }
+ else
+ {
+ for(unsigned y=0; y<data.height; ++y)
+ read_full(io, data.data+y*data.stride, data.stride);
+ }
+}
+
+} // namespace Graphics
+} // namespace Msp