]> Pileus Git - ~andy/gtk/blobdiff - gdk-pixbuf/io-bmp.c
Updated German translation.
[~andy/gtk] / gdk-pixbuf / io-bmp.c
index e14a011276a5379659116cdf49afbfbdc5c8220e..21300e45d85d03e18c53b555f5c45b20e340c509 100644 (file)
@@ -24,7 +24,7 @@
  * Boston, MA 02111-1307, USA.
  */
 
-#include <config.h>
+#include "config.h"
 #include <stdio.h>
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
@@ -33,6 +33,8 @@
 #include "gdk-pixbuf-private.h"
 #include "gdk-pixbuf-io.h"
 
+#define DUMPBIH 0
+
 \f
 
 #if 0
@@ -157,6 +159,7 @@ struct bmp_progressive_state {
 
        guchar *buff;
        guint BufferSize;
+       guint BufferPadding;
        guint BufferDone;
 
        guchar (*Colormap)[3];
@@ -179,6 +182,7 @@ struct bmp_progressive_state {
        int r_mask, r_shift, r_bits;
        int g_mask, g_shift, g_bits;
        int b_mask, b_shift, b_bits;
+       int a_mask, a_shift, a_bits;
 
        GdkPixbuf *pixbuf;      /* Our "target" */
 };
@@ -217,27 +221,55 @@ lsb_16 (guchar *src)
 static gboolean grow_buffer (struct bmp_progressive_state *State,
                              GError **error)
 {
-  guchar *tmp = g_try_realloc (State->buff, State->BufferSize);
+  guchar *tmp;
+
+  if (State->BufferSize == 0) {
+    g_set_error_literal (error,
+                         GDK_PIXBUF_ERROR,
+                         GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                         _("BMP image has bogus header data"));
+    State->read_state = READ_STATE_ERROR;
+    return FALSE;
+  }
+
+  tmp = g_try_realloc (State->buff, State->BufferSize);
+
   if (!tmp) {
-    g_set_error (error,
-                GDK_PIXBUF_ERROR,
-                GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
-                _("Not enough memory to load bitmap image"));
+    g_set_error_literal (error,
+                         GDK_PIXBUF_ERROR,
+                         GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                         _("Not enough memory to load bitmap image"));
     State->read_state = READ_STATE_ERROR;
     return FALSE;
   }
+
   State->buff = tmp;
   return TRUE;
 }
 
+static gboolean
+decode_bitmasks (guchar *buf,
+                struct bmp_progressive_state *State, 
+                GError **error);
+
 static gboolean DecodeHeader(unsigned char *BFH, unsigned char *BIH,
                              struct bmp_progressive_state *State,
                              GError **error)
 {
        gint clrUsed;
 
-        /* FIXME this is totally unrobust against bogus image data. */
+       /* First check for the two first bytes content. A sane
+          BMP file must start with bytes 0x42 0x4D.  */
+       if (*BFH != 0x42 || *(BFH + 1) != 0x4D) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("BMP image has bogus header data"));
+               State->read_state = READ_STATE_ERROR;
+               return FALSE;
+       }
 
+        /* FIXME this is totally unrobust against bogus image data. */
        if (State->BufferSize < lsb_32 (&BIH[0]) + 14) {
                State->BufferSize = lsb_32 (&BIH[0]) + 14;
                if (!grow_buffer (State, error))
@@ -250,36 +282,60 @@ static gboolean DecodeHeader(unsigned char *BFH, unsigned char *BIH,
 #endif    
 
        State->Header.size = lsb_32 (&BIH[0]);
-       if (State->Header.size == 40) {
+       if (State->Header.size == 124) {
+                /* BMP v5 */
+               State->Header.width = lsb_32 (&BIH[4]);
+               State->Header.height = lsb_32 (&BIH[8]);
+               State->Header.depth = lsb_16 (&BIH[14]);
+               State->Compressed = lsb_32 (&BIH[16]);
+       } else if (State->Header.size == 108) {
+                /* BMP v4 */
+               State->Header.width = lsb_32 (&BIH[4]);
+               State->Header.height = lsb_32 (&BIH[8]);
+               State->Header.depth = lsb_16 (&BIH[14]);
+               State->Compressed = lsb_32 (&BIH[16]);
+       } else if (State->Header.size == 64) {
+                /* BMP OS/2 v2 */
+               State->Header.width = lsb_32 (&BIH[4]);
+               State->Header.height = lsb_32 (&BIH[8]);
+               State->Header.depth = lsb_16 (&BIH[14]);
+               State->Compressed = lsb_32 (&BIH[16]);
+       } else if (State->Header.size == 40) {
+                /* BMP v3 */ 
                State->Header.width = lsb_32 (&BIH[4]);
                State->Header.height = lsb_32 (&BIH[8]);
                State->Header.depth = lsb_16 (&BIH[14]);
                State->Compressed = lsb_32 (&BIH[16]);
        } else if (State->Header.size == 12) {
+                /* BMP OS/2 */
                State->Header.width = lsb_16 (&BIH[4]);
                State->Header.height = lsb_16 (&BIH[6]);
                State->Header.depth = lsb_16 (&BIH[10]);
                State->Compressed = BI_RGB;
        } else {
-               g_set_error (error,
-                            GDK_PIXBUF_ERROR,
-                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                            _("BMP image has unsupported header size"));
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("BMP image has unsupported header size"));
                State->read_state = READ_STATE_ERROR;
                return FALSE;
        }
 
-       clrUsed = (int) (BIH[35] << 24) + (BIH[34] << 16) + (BIH[33] << 8) + (BIH[32]);
+       if (State->Header.size == 12)
+               clrUsed = 1 << State->Header.depth;
+       else
+               clrUsed = (int) (BIH[35] << 24) + (BIH[34] << 16) + (BIH[33] << 8) + (BIH[32]);
+
        if (clrUsed != 0)
                State->Header.n_colors = clrUsed;
        else
-            State->Header.n_colors = 1 << State->Header.depth;
+            State->Header.n_colors = (1 << State->Header.depth);
        
-       if (State->Header.n_colors > 1 << State->Header.depth) {
-               g_set_error (error,
-                            GDK_PIXBUF_ERROR,
-                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                            _("BMP image has bogus header data"));
+       if (State->Header.n_colors > (1 << State->Header.depth)) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("BMP image has bogus header data"));
                State->read_state = READ_STATE_ERROR;
                return FALSE;
        }
@@ -291,20 +347,27 @@ static gboolean DecodeHeader(unsigned char *BFH, unsigned char *BIH,
                State->Header.height = -State->Header.height;
                State->Header.Negative = 1;
        }
-       if (State->Header.width < 0) {
-               State->Header.width = -State->Header.width;
-               State->Header.Negative = 0;
+
+       if (State->Header.Negative && 
+           (State->Compressed != BI_RGB && State->Compressed != BI_BITFIELDS))
+       {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("Topdown BMP images cannot be compressed"));
+               State->read_state = READ_STATE_ERROR;
+               return FALSE;
        }
 
-       if (State->Header.width == 0 || State->Header.height == 0 ||
+       if (State->Header.width <= 0 || State->Header.height == 0 ||
            (State->Compressed == BI_RLE4 && State->Type != 4)    ||
            (State->Compressed == BI_RLE8 && State->Type != 8)    ||
            (State->Compressed == BI_BITFIELDS && !(State->Type == 16 || State->Type == 32)) ||
-           State->Compressed > BI_BITFIELDS) {
-               g_set_error (error,
-                            GDK_PIXBUF_ERROR,
-                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                            _("BMP image has bogus header data"));
+           (State->Compressed > BI_BITFIELDS)) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("BMP image has bogus header data"));
                State->read_state = READ_STATE_ERROR;
                return FALSE;
        }
@@ -324,10 +387,10 @@ static gboolean DecodeHeader(unsigned char *BFH, unsigned char *BIH,
                if ((State->Header.width & 7) != 0)
                        State->LineWidth++;
        } else {
-               g_set_error (error,
-                            GDK_PIXBUF_ERROR,
-                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                            _("BMP image has bogus header data"));
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("BMP image has bogus header data"));
                State->read_state = READ_STATE_ERROR;
                return FALSE;
        }
@@ -364,10 +427,10 @@ static gboolean DecodeHeader(unsigned char *BFH, unsigned char *BIH,
                                               (gint) State->Header.height);
                
                if (State->pixbuf == NULL) {
-                       g_set_error (error,
-                                    GDK_PIXBUF_ERROR,
-                                    GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
-                                    _("Not enough memory to load bitmap image"));
+                       g_set_error_literal (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                             _("Not enough memory to load bitmap image"));
                        State->read_state = READ_STATE_ERROR;
                        return FALSE;
                        }
@@ -386,19 +449,51 @@ static gboolean DecodeHeader(unsigned char *BFH, unsigned char *BIH,
        
        State->BufferDone = 0;
        if (State->Type <= 8) {
+                gint samples;
+
                State->read_state = READ_STATE_PALETTE;
-               State->BufferSize = lsb_32 (&BFH[10]) - 14 - State->Header.size; 
+
+               /* Allocate enough to hold the palette */
+               samples = (State->Header.size == 12 ? 3 : 4);
+               State->BufferSize = State->Header.n_colors * samples;
+
+               /* Skip over everything between the palette and the data.
+                  This protects us against a malicious BFH[10] value.
+                */
+               State->BufferPadding = (lsb_32 (&BFH[10]) - 14 - State->Header.size) - State->BufferSize;
+
        } else if (State->Compressed == BI_RGB) {
-               State->read_state = READ_STATE_DATA;
-               State->BufferSize = State->LineWidth;
+               if (State->BufferSize < lsb_32 (&BFH[10]))
+               {
+                       /* skip over padding between headers and image data */
+                       State->read_state = READ_STATE_HEADERS;
+                       State->BufferDone = State->BufferSize;
+                       State->BufferSize = lsb_32 (&BFH[10]);
+               }
+               else
+               {
+                       State->read_state = READ_STATE_DATA;
+                       State->BufferSize = State->LineWidth;
+               }
        } else if (State->Compressed == BI_BITFIELDS) {
-               State->read_state = READ_STATE_BITMASKS;
-               State->BufferSize = 12;
+               if (State->Header.size == 108 || State->Header.size == 124) 
+               {
+                       /* v4 and v5 have the bitmasks in the header */
+                       if (!decode_bitmasks (&BIH[40], State, error)) {
+                              State->read_state = READ_STATE_ERROR;
+                              return FALSE;
+                        }
+               }
+               else 
+               {
+                      State->read_state = READ_STATE_BITMASKS;
+                      State->BufferSize = 12;
+               }
        } else {
-               g_set_error (error,
-                            GDK_PIXBUF_ERROR,
-                            GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
-                            _("BMP image has bogus header data"));
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                     _("BMP image has bogus header data"));
                State->read_state = READ_STATE_ERROR;
                return FALSE;
        }
@@ -426,7 +521,7 @@ static gboolean DecodeColormap (guchar *buff,
                return TRUE;
        }
 
-       State->Colormap = g_malloc ((1 << State->Header.depth) * sizeof (*State->Colormap));
+       State->Colormap = g_malloc0 ((1 << State->Header.depth) * sizeof (*State->Colormap));
        for (i = 0; i < State->Header.n_colors; i++)
 
        {
@@ -470,12 +565,13 @@ find_bits (int n, int *lowest, int *n_set)
                }
 }
 
-/* Decodes the 3 shorts that follow for the bitmasks for BI_BITFIELDS coding */
+/* Decodes the bitmasks for BI_BITFIELDS coding */
 static gboolean
 decode_bitmasks (guchar *buf,
                 struct bmp_progressive_state *State, 
                 GError **error)
 {
+        State->a_mask = State->a_shift = State->a_bits = 0;
        State->r_mask = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
        buf += 4;
 
@@ -488,17 +584,55 @@ decode_bitmasks (guchar *buf,
        find_bits (State->g_mask, &State->g_shift, &State->g_bits);
        find_bits (State->b_mask, &State->b_shift, &State->b_bits);
 
+        /* v4 and v5 have an alpha mask */
+        if (State->Header.size == 108 || State->Header.size == 124) {
+             buf += 4;
+             State->a_mask = buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+             find_bits (State->a_mask, &State->a_shift, &State->a_bits);
+        }
+
        if (State->r_bits == 0 || State->g_bits == 0 || State->b_bits == 0) {
-               State->r_mask = 0x7c00;
-               State->r_shift = 10;
-               State->g_mask = 0x03e0;
-               State->g_shift = 5;
-               State->b_mask = 0x001f;
-               State->b_shift = 0;
-
-               State->r_bits = State->g_bits = State->b_bits = 5;
+                if (State->Type == 16) {
+                      State->r_mask = 0x7c00;
+                      State->r_shift = 10;
+                      State->g_mask = 0x03e0;
+                      State->g_shift = 5;
+                      State->b_mask = 0x001f;
+                      State->b_shift = 0;
+
+                      State->r_bits = State->g_bits = State->b_bits = 5;
+                }
+                else {
+                      State->r_mask = 0x00ff0000;
+                      State->r_shift = 16;
+                      State->g_mask = 0x0000ff00;
+                      State->g_shift = 8;
+                      State->b_mask = 0x000000ff;
+                      State->b_shift = 0;
+                      State->a_mask = 0xff000000;
+                      State->a_shift = 24;
+
+                      State->r_bits = State->g_bits = State->b_bits = State->a_bits = 8;
+                }
        }
 
+        if (State->r_bits > 8) {
+          State->r_shift += State->r_bits - 8;
+          State->r_bits = 8;
+        }
+        if (State->g_bits > 8) {
+          State->g_shift += State->g_bits - 8;
+          State->g_bits = 8;
+        }
+        if (State->b_bits > 8) {
+          State->b_shift += State->b_bits - 8;
+          State->b_bits = 8;
+        }
+        if (State->a_bits > 8) {
+          State->a_shift += State->a_bits - 8;
+          State->a_bits = 8;
+        }
+
        State->read_state = READ_STATE_DATA;
        State->BufferDone = 0;
        State->BufferSize = State->LineWidth;
@@ -532,6 +666,7 @@ gdk_pixbuf__bmp_image_begin_load(GdkPixbufModuleSizeFunc size_func,
        context->read_state = READ_STATE_HEADERS;
 
        context->BufferSize = 26;
+       context->BufferPadding = 0;
        context->buff = g_malloc(26);
        context->BufferDone = 0;
        /* 14 for the BitmapFileHeader, 12 for the BitmapImageHeader */
@@ -558,6 +693,8 @@ gdk_pixbuf__bmp_image_begin_load(GdkPixbufModuleSizeFunc size_func,
  */
 static gboolean gdk_pixbuf__bmp_image_stop_load(gpointer data, GError **error)
 {
+       gboolean retval = TRUE;
+       
        struct bmp_progressive_state *context =
            (struct bmp_progressive_state *) data;
 
@@ -567,16 +704,25 @@ static gboolean gdk_pixbuf__bmp_image_stop_load(gpointer data, GError **error)
        
        g_return_val_if_fail(context != NULL, TRUE);
 
-       if (context->Colormap != NULL)
-               g_free(context->Colormap);
+       g_free(context->Colormap);
 
        if (context->pixbuf)
                g_object_unref(context->pixbuf);
 
+       if (context->read_state == READ_STATE_HEADERS) {
+                if (error && *error == NULL) {
+                        g_set_error_literal (error,
+                                             GDK_PIXBUF_ERROR,
+                                             GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
+                                             _("Premature end-of-file encountered"));
+                }
+               retval = FALSE;
+       }
+       
        g_free(context->buff);
        g_free(context);
 
-        return TRUE;
+        return retval;
 }
 
 
@@ -603,28 +749,35 @@ static void OneLine32(struct bmp_progressive_state *context)
                int r_lshift, r_rshift;
                int g_lshift, g_rshift;
                int b_lshift, b_rshift;
+               int a_lshift, a_rshift;
 
                r_lshift = 8 - context->r_bits;
                g_lshift = 8 - context->g_bits;
                b_lshift = 8 - context->b_bits;
+               a_lshift = 8 - context->a_bits;
 
                r_rshift = context->r_bits - r_lshift;
                g_rshift = context->g_bits - g_lshift;
                b_rshift = context->b_bits - b_lshift;
+               a_rshift = context->a_bits - a_lshift;
 
                for (i = 0; i < context->Header.width; i++) {
-                       int v, r, g, b;
+                       unsigned int v, r, g, b, a;
 
-                       v = src[0] | (src[1] << 8) | (src[2] << 16);
+                       v = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
 
                        r = (v & context->r_mask) >> context->r_shift;
                        g = (v & context->g_mask) >> context->g_shift;
                        b = (v & context->b_mask) >> context->b_shift;
+                       a = (v & context->a_mask) >> context->a_shift;
 
                        *pixels++ = (r << r_lshift) | (r >> r_rshift);
                        *pixels++ = (g << g_lshift) | (g >> g_rshift);
                        *pixels++ = (b << b_lshift) | (b >> b_rshift);
-                       *pixels++ = src[3]; /* alpha */
+                        if (context->a_bits)
+                         *pixels++ = (a << a_lshift) | (a >> a_rshift);
+                        else
+                          *pixels++ = 0xff;
 
                        src += 4;
                }
@@ -633,7 +786,7 @@ static void OneLine32(struct bmp_progressive_state *context)
                        *pixels++ = src[2];
                        *pixels++ = src[1];
                        *pixels++ = src[0];
-                       *pixels++ = src[3];
+                       *pixels++ = 0xff;
 
                        src += 4;
                }
@@ -872,8 +1025,18 @@ DoCompressed(struct bmp_progressive_state *context, GError **error)
        guchar c;
        gint idx;
 
-       if (context->compr.y >= context->Header.height)
+       /* context->compr.y might be past the last line because we are
+        * on padding past the end of a valid data, or we might have hit
+        * out-of-bounds data. Either way we just eat-and-ignore the
+        * rest of the file. Doing the check only here and not when
+        * we change y below is fine since BufferSize is always 2 here
+        * and the BMP file format always starts new data on 16-bit
+        * boundaries.
+        */
+       if (context->compr.y >= context->Header.height) {
+               context->BufferDone = 0;
                return TRUE;
+       }
 
        y = context->compr.y;
 
@@ -994,12 +1157,15 @@ DoCompressed(struct bmp_progressive_state *context, GError **error)
        }
        if (context->updated_func != NULL) {
                if (context->compr.y > y)
+               {
+                       gint new_y = MIN (context->compr.y, context->Header.height);
                        (*context->updated_func) (context->pixbuf,
                                                  0,
-                                                 y,
+                                                 context->Header.height - new_y,
                                                  context->Header.width,
-                                                 context->compr.y - y,
+                                                 new_y - y,
                                                  context->user_data);
+               }
 
        }
 
@@ -1024,6 +1190,7 @@ gdk_pixbuf__bmp_image_load_increment(gpointer data,
            (struct bmp_progressive_state *) data;
 
        gint BytesToCopy;
+       gint BytesToRemove;
 
        if (context->read_state == READ_STATE_DONE)
                return TRUE;
@@ -1049,6 +1216,19 @@ gdk_pixbuf__bmp_image_load_increment(gpointer data,
                                break;
                }
 
+               /* context->buff is full.  Now we discard all "padding" */
+               if (context->BufferPadding != 0) {
+                       BytesToRemove = context->BufferPadding - size;
+                       if (BytesToRemove > size) {
+                               BytesToRemove = size;
+                       }
+                       size -= BytesToRemove;
+                       context->BufferPadding -= BytesToRemove;
+
+                       if (context->BufferPadding != 0)
+                               break;
+               }
+
                switch (context->read_state) {
                case READ_STATE_HEADERS:
                        if (!DecodeHeader (context->buff,
@@ -1087,16 +1267,141 @@ gdk_pixbuf__bmp_image_load_increment(gpointer data,
        return TRUE;
 }
 
-void
-MODULE_ENTRY (bmp, fill_vtable) (GdkPixbufModule *module)
+/* for our convenience when filling file header */
+#define put16(buf,data)        { guint16 x; \
+                         x = GUINT16_TO_LE (data); \
+                         memcpy(buf, &x, 2); \
+                         buf += 2; }
+#define put32(buf,data)        { guint32 x; \
+                         x = GUINT32_TO_LE (data); \
+                         memcpy(buf, &x, 4); \
+                         buf += 4; }
+
+static gboolean
+gdk_pixbuf__bmp_image_save_to_callback (GdkPixbufSaveFunc   save_func,
+                                       gpointer            user_data,
+                                       GdkPixbuf          *pixbuf, 
+                                       gchar             **keys,
+                                       gchar             **values,
+                                       GError            **error)
+{
+       guint width, height, channel, size, stride, src_stride, x, y;
+       guchar BFH_BIH[54], *pixels, *buf, *src, *dst, *dst_line;
+       gboolean ret;
+
+       width = gdk_pixbuf_get_width (pixbuf);
+       height = gdk_pixbuf_get_height (pixbuf);
+       channel = gdk_pixbuf_get_n_channels (pixbuf);
+       pixels = gdk_pixbuf_get_pixels (pixbuf);
+       src_stride = gdk_pixbuf_get_rowstride (pixbuf);
+       stride = (width * 3 + 3) & ~3;
+       size = stride * height;
+
+       /* filling BFH */
+       dst = BFH_BIH;
+       *dst++ = 'B';                   /* bfType */
+       *dst++ = 'M';
+       put32 (dst, size + 14 + 40);    /* bfSize */
+       put32 (dst, 0);                 /* bfReserved1 + bfReserved2 */
+       put32 (dst, 14 + 40);           /* bfOffBits */
+
+       /* filling BIH */
+       put32 (dst, 40);                /* biSize */
+       put32 (dst, width);             /* biWidth */
+       put32 (dst, height);            /* biHeight */
+       put16 (dst, 1);                 /* biPlanes */
+       put16 (dst, 24);                /* biBitCount */
+       put32 (dst, BI_RGB);            /* biCompression */
+       put32 (dst, size);              /* biSizeImage */
+       put32 (dst, 0);                 /* biXPelsPerMeter */
+       put32 (dst, 0);                 /* biYPelsPerMeter */
+       put32 (dst, 0);                 /* biClrUsed */
+       put32 (dst, 0);                 /* biClrImportant */
+
+       if (!save_func (BFH_BIH, 14 + 40, error, user_data))
+               return FALSE;
+
+       dst_line = buf = g_try_malloc (size);
+       if (!buf) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
+                                     _("Couldn't allocate memory for saving BMP file"));
+               return FALSE;
+       }
+
+       /* saving as a bottom-up bmp */
+       pixels += (height - 1) * src_stride;
+       for (y = 0; y < height; ++y, pixels -= src_stride, dst_line += stride) {
+               dst = dst_line;
+               src = pixels;
+               for (x = 0; x < width; ++x, dst += 3, src += channel) {
+                       dst[0] = src[2];
+                       dst[1] = src[1];
+                       dst[2] = src[0];
+               }
+       }
+       ret = save_func (buf, size, error, user_data);
+       g_free (buf);
+
+       return ret;
+}
+
+static gboolean
+save_to_file_cb (const gchar *buf,
+                gsize count,
+                GError **error,
+                gpointer data)
+{
+       gint bytes;
+       
+       while (count > 0) {
+               bytes = fwrite (buf, sizeof (gchar), count, (FILE *) data);
+               if (bytes <= 0)
+                       break;
+               count -= bytes;
+               buf += bytes;
+       }
+
+       if (count) {
+               g_set_error_literal (error,
+                                     GDK_PIXBUF_ERROR,
+                                     GDK_PIXBUF_ERROR_FAILED,
+                                     _("Couldn't write to BMP file"));
+               return FALSE;
+       }
+       
+       return TRUE;
+}
+
+static gboolean
+gdk_pixbuf__bmp_image_save (FILE          *f, 
+                            GdkPixbuf     *pixbuf, 
+                            gchar        **keys,
+                            gchar        **values,
+                            GError       **error)
+{
+       return gdk_pixbuf__bmp_image_save_to_callback (save_to_file_cb,
+                                                      f, pixbuf, keys,
+                                                      values, error);
+}
+
+#ifndef INCLUDE_bmp
+#define MODULE_ENTRY(function) G_MODULE_EXPORT void function
+#else
+#define MODULE_ENTRY(function) void _gdk_pixbuf__bmp_ ## function
+#endif
+
+MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
 {
        module->begin_load = gdk_pixbuf__bmp_image_begin_load;
        module->stop_load = gdk_pixbuf__bmp_image_stop_load;
        module->load_increment = gdk_pixbuf__bmp_image_load_increment;
+       module->save = gdk_pixbuf__bmp_image_save;
+       module->save_to_callback = gdk_pixbuf__bmp_image_save_to_callback;
 }
 
-void
-MODULE_ENTRY (bmp, fill_info) (GdkPixbufFormat *info)
+MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
 {
        static GdkPixbufModulePattern signature[] = {
                { "BM", NULL, 100 },
@@ -1118,6 +1423,7 @@ MODULE_ENTRY (bmp, fill_info) (GdkPixbufFormat *info)
        info->description = N_("The BMP image format");
        info->mime_types = mime_types;
        info->extensions = extensions;
-       info->flags = 0;
+       info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
+       info->license = "LGPL";
 }