]> git.tdb.fi Git - libs/gui.git/blobdiff - source/graphics/bmploader.cpp
Implement a loader for BMP images
[libs/gui.git] / source / graphics / bmploader.cpp
diff --git a/source/graphics/bmploader.cpp b/source/graphics/bmploader.cpp
new file mode 100644 (file)
index 0000000..71de507
--- /dev/null
@@ -0,0 +1,109 @@
+#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