* Boston, MA 02111-1307, USA.
*/
-#include <config.h>
+#include "config.h"
#include <stdio.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#include "gdk-pixbuf-private.h"
#include "gdk-pixbuf-io.h"
+#define DUMPBIH 0
+
\f
#if 0
guchar *buff;
guint BufferSize;
+ guint BufferPadding;
guint BufferDone;
guchar (*Colormap)[3];
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" */
};
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))
#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;
}
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;
}
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;
}
(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;
}
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;
}
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++)
{
}
}
-/* 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;
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;
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 */
*/
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;
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;
}
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;
}
*pixels++ = src[2];
*pixels++ = src[1];
*pixels++ = src[0];
- *pixels++ = src[3];
+ *pixels++ = 0xff;
src += 4;
}
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;
}
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);
+ }
}
(struct bmp_progressive_state *) data;
gint BytesToCopy;
+ gint BytesToRemove;
if (context->read_state == READ_STATE_DONE)
return TRUE;
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,
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 },
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";
}