]> git.tdb.fi Git - libs/gui.git/blob - source/graphics/bmploader.cpp
Make it possible to load an image into an externally allocated buffer
[libs/gui.git] / source / graphics / bmploader.cpp
1 #include <msp/core/inttypes.h>
2 #include "bmploader.h"
3
4 using namespace std;
5
6 namespace {
7
8 void read_full(Msp::IO::Base &io, char *buffer, unsigned size)
9 {
10         unsigned len = io.read(buffer, size);
11         if(len<size)
12                 throw Msp::Graphics::bad_image_data("premature end of file");
13 }
14
15 template<typename T>
16 T decode(const char *data)
17 {
18         const Msp::UInt8 *udata = reinterpret_cast<const Msp::UInt8 *>(data);
19         T result = 0;
20         for(unsigned i=0; i<sizeof(T); ++i)
21                 result |= udata[i]<<(i*8);
22         return result;
23 }
24
25 }
26
27 namespace Msp {
28 namespace Graphics {
29
30 BmpLoader::BmpLoader(IO::Base &i, unsigned sb):
31         io(i),
32         sig_bytes(sb),
33         invert_row_order(false)
34 {
35         // Image data location is stored at offset 10 and can't be skipped
36         if(sig_bytes>10)
37                 throw invalid_argument("BmpLoader::BmpLoader");
38 }
39
40 bool BmpLoader::detect(const std::string &sig)
41 {
42         static const char bmp_sig[] = { 'B', 'M' };
43         if(sig.size()<sizeof(bmp_sig))
44                 return false;
45         return !sig.compare(0, sizeof(bmp_sig), bmp_sig, sizeof(bmp_sig));
46 }
47
48 void BmpLoader::load_headers_(Image::Data &data)
49 {
50         char bm_header[14];
51         read_full(io, bm_header+sig_bytes, sizeof(bm_header)-sig_bytes);
52
53         if(!sig_bytes && (bm_header[0]!='B' || bm_header[1]!='M'))
54                 throw bad_image_data("bitmap header mismatch");
55
56         unsigned data_offset = decode<UInt32>(bm_header+10);
57
58         char dib_header[124];
59         read_full(io, dib_header, 12);
60         unsigned dib_length = min<unsigned>(decode<UInt32>(dib_header), sizeof(dib_header));
61         if(dib_length<40)
62                 throw bad_image_data("DIB header too short (very old bmp file?)");
63
64         read_full(io, dib_header+12, dib_length-12);
65
66         data.width = decode<UInt32>(dib_header+4);
67         Int32 height = decode<UInt32>(dib_header+8);
68         data.height = abs(height);
69
70         unsigned color_planes = decode<UInt16>(dib_header+12);
71         if(color_planes!=1)
72                 throw bad_image_data("color_planes!=1");
73         unsigned bits_per_pixel = decode<UInt16>(dib_header+14);
74         unsigned compression = decode<UInt32>(dib_header+16);
75         if(compression)
76                 throw unsupported_image_format("compression not supported");
77
78         data.stride = (data.width*bits_per_pixel+31)/32*4;
79         switch(bits_per_pixel)
80         {
81         case 8:  data.fmt = COLOR_INDEX; break;
82         case 24: data.fmt = BGR; break;
83         case 32: data.fmt = BGRX; break;  // TODO is it BGRX or XBGR?
84         default: throw unsupported_image_format("bits per pixel value not supported");
85         }
86
87         unsigned skip = data_offset-sizeof(bm_header)-dib_length;
88         while(skip>0)
89         {
90                 char buffer[1024];
91                 unsigned size = min<unsigned>(sizeof(buffer), skip);
92                 read_full(io, buffer, size);
93                 skip -= size;
94         }
95
96         invert_row_order = (height<0);
97 }
98
99 void BmpLoader::load_pixels_(Image::Data &data)
100 {
101         if(invert_row_order)
102         {
103                 for(unsigned y=0; y<data.height; ++y)
104                         read_full(io, data.pixels+(data.height-1-y)*data.stride, data.stride);
105         }
106         else
107         {
108                 for(unsigned y=0; y<data.height; ++y)
109                         read_full(io, data.pixels+y*data.stride, data.stride);
110         }
111 }
112
113 } // namespace Graphics
114 } // namespace Msp