diff --git a/plug-ins/file-bmp/bmp-load.c b/plug-ins/file-bmp/bmp-load.c index dcd65aa0b5..3698b3bbe6 100644 --- a/plug-ins/file-bmp/bmp-load.c +++ b/plug-ins/file-bmp/bmp-load.c @@ -34,70 +34,118 @@ #include "libgimp/stdplugins-intl.h" +/* huffman.h is auto-generated by generate-huffman.c + * and defines: + * - blackroot + * - whiteroot + * - nodebuffer[] + * (look for it in the build directory) + */ +struct Huffnode +{ + signed short left; + signed short right; + unsigned short value : 13; + unsigned short leaf : 1; + unsigned short makeup : 1; +}; +#include "huffman.h" -static GimpImage * ReadImage (FILE *fd, - GFile *file, - gint width, - gint height, - guchar cmap[256][3], - gint ncols, - gint bpp, - gint compression, - gint rowbytes, - gboolean gray, - const BitmapChannel *masks, - GError **error); +enum Bmpformat +{ + BMPFMT_RGB, + BMPFMT_RGB_64, + BMPFMT_INDEXED, + BMPFMT_RLE, + BMPFMT_HUFFMAN +}; +struct Fileinfo +{ + FILE *file; + gint width; + gint height; + guchar *rowbuf; + guint64 rowbytes; + gint bpp; + guchar *dest; + gsize rowstride; + gint channels; + gint bytesperchannel; + gint ncolors; + guchar colormap[3 * 256]; + gboolean gray; + BitmapChannel masks[4]; + gint tile_height; + gint tile_n; + /* RLE state */ + gint xpos; + gint file_ypos; + gboolean needs_alpha; + /* Huffman state */ + guint32 bitbuf; + gint buflen; + gint black; +}; + +static GimpImage * ReadImage (struct Fileinfo *fi, + GFile *gfile, + gint compression, + GError **error); + +static gint load_rgb_64 (struct Fileinfo *fi, + gsize offset); +static gint load_rgb (struct Fileinfo *fi, + gsize offset); +static gint load_indexed (struct Fileinfo *fi, + gsize offset); +static gint load_rle (struct Fileinfo *fi, + gsize offset); +static gint load_huffman (struct Fileinfo *fi, + gsize offset); + +static gint huff_decode (guint32 *bitbuf, + gint *buflen, + FILE *file, + gint black); + +static gint huff_findnode (guint32 bitbuf, + gint buflen, + gint black, + gint *found); + +static void huff_fillbuf (guint32 *bitbuf, + gint *buflen, + FILE *file); + +static gint huff_find_eol (guint32 *bitbuf, + gint *buflen, + FILE *file); + +static gint huff_skip_eol (guint32 *bitbuf, + gint *buflen, + FILE *file); static void -setMasksDefault (gushort biBitCnt, - BitmapChannel *masks) +set_default_masks (gushort biBitCnt, + BitmapChannel *masks) { switch (biBitCnt) { - case 32: - masks[0].mask = 0x00ff0000; - masks[0].shiftin = 16; - masks[0].max_value = (gfloat)255.0; - masks[1].mask = 0x0000ff00; - masks[1].shiftin = 8; - masks[1].max_value = (gfloat)255.0; - masks[2].mask = 0x000000ff; - masks[2].shiftin = 0; - masks[2].max_value = (gfloat)255.0; - masks[3].mask = 0x00000000; - masks[3].shiftin = 0; - masks[3].max_value = (gfloat)0.0; - break; - case 24: - masks[0].mask = 0xff0000; - masks[0].shiftin = 16; - masks[0].max_value = (gfloat)255.0; - masks[1].mask = 0x00ff00; - masks[1].shiftin = 8; - masks[1].max_value = (gfloat)255.0; - masks[2].mask = 0x0000ff; - masks[2].shiftin = 0; - masks[2].max_value = (gfloat)255.0; - masks[3].mask = 0x0; - masks[3].shiftin = 0; - masks[3].max_value = (gfloat)0.0; + case 32: + masks[0].mask = 0x00ff0000; + masks[1].mask = 0x0000ff00; + masks[2].mask = 0x000000ff; + masks[3].mask = 0x00000000; break; case 16: - masks[0].mask = 0x7c00; - masks[0].shiftin = 10; - masks[0].max_value = (gfloat)31.0; - masks[1].mask = 0x03e0; - masks[1].shiftin = 5; - masks[1].max_value = (gfloat)31.0; - masks[2].mask = 0x001f; - masks[2].shiftin = 0; - masks[2].max_value = (gfloat)31.0; - masks[3].mask = 0x0; - masks[3].shiftin = 0; - masks[3].max_value = (gfloat)0.0; + /* 5 bits per channel */ + masks[0].mask = 0x00007c00; + masks[1].mask = 0x000003e0; + masks[2].mask = 0x0000001f; + masks[3].mask = 0x00000000; break; default: @@ -111,6 +159,19 @@ ToL (const guchar *buffer) return (buffer[0] | buffer[1] << 8 | buffer[2] << 16 | buffer[3] << 24); } +static guint32 +ToLn (const guchar *buffer, gint n) +{ + gint i; + guint32 result = 0; + + for (i = 0; i < n; i++) + { + result |= ((guint32) buffer[i]) << (8 * i); + } + return result; +} + static gint16 ToS (const guchar *buffer) { @@ -118,11 +179,11 @@ ToS (const guchar *buffer) } static gboolean -ReadColorMap (FILE *fd, - guchar buffer[256][3], - gint number, - gint size, - gboolean *gray) +read_colormap (FILE *fd, + guchar *buffer, + gint number, + gint size, + gboolean *gray) { gint i; @@ -140,9 +201,9 @@ ReadColorMap (FILE *fd, /* Bitmap save the colors in another order! But change only once! */ - buffer[i][0] = rgb[2]; - buffer[i][1] = rgb[1]; - buffer[i][2] = rgb[0]; + buffer[3 * i + 0] = rgb[2]; + buffer[3 * i + 1] = rgb[1]; + buffer[3 * i + 2] = rgb[0]; *gray = ((*gray) && (rgb[0] == rgb[1]) && (rgb[1] == rgb[2])); } @@ -150,21 +211,30 @@ ReadColorMap (FILE *fd, return TRUE; } -static gboolean -ReadChannelMasks (guint32 *tmp, - BitmapChannel *masks, - guint channels) +static void +read_masks (guint32 *tmp, + BitmapChannel *masks) { gint i; - for (i = 0; i < channels; i++) + for (i = 0; i < 4; i++) + { + masks[i].mask = tmp[i]; + } +} + +static void +digest_masks (BitmapChannel *masks) +{ + gint i; + + for (i = 0; i < 4; i++) { guint32 mask; gint nbits, offset, bit; - mask = tmp[i]; - masks[i].mask = mask; - nbits = 0; + mask = masks[i].mask; + nbits = 0; offset = -1; for (bit = 0; bit < 32; bit++) @@ -181,47 +251,39 @@ ReadChannelMasks (guint32 *tmp, masks[i].shiftin = offset; masks[i].max_value = (gfloat) ((1 << nbits) - 1); - -#ifdef DEBUG - g_print ("Channel %d mask %08x in %d max_val %d\n", - i, masks[i].mask, masks[i].shiftin, (gint)masks[i].max_value); -#endif + masks[i].nbits = nbits; } - - return TRUE; } GimpImage * -load_image (GFile *file, - GError **error) +load_image (GFile *gfile, GError **error) { - FILE *fd; + struct Fileinfo fi; BitmapFileHead bitmap_file_head; BitmapHead bitmap_head; guchar buffer[124]; - gint ColormapSize, rowbytes, Maps; - gboolean gray = FALSE; - guchar ColorMap[256][3]; + gint maxcolors; + gint colorsize; GimpImage *image = NULL; gchar magick[2]; - BitmapChannel masks[4]; - gimp_progress_init_printf (_("Opening '%s'"), - gimp_file_get_utf8_name (file)); + gimp_progress_init_printf (_("Opening '%s'"), gimp_file_get_utf8_name (gfile)); - fd = g_fopen (g_file_peek_path (file), "rb"); + memset (&fi, 0, sizeof fi); + memset (&bitmap_file_head, 0, sizeof bitmap_file_head); + memset (&bitmap_head, 0, sizeof bitmap_head); - if (! fd) + fi.file = g_fopen (g_file_peek_path (gfile), "rb"); + + if (! fi.file) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Could not open '%s' for reading: %s"), - gimp_file_get_utf8_name (file), g_strerror (errno)); + gimp_file_get_utf8_name (gfile), g_strerror (errno)); goto out; } - /* It is a File. Now is it a Bitmap? Read the shortest possible header */ - - if (! ReadOK (fd, magick, 2) || + if (! ReadOK (fi.file, magick, 2) || ! (! strncmp (magick, "BA", 2) || ! strncmp (magick, "BM", 2) || ! strncmp (magick, "IC", 2) || @@ -230,243 +292,186 @@ load_image (GFile *file, ! strncmp (magick, "CP", 2))) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); + _("'%s' is not a valid BMP file"), gimp_file_get_utf8_name (gfile)); goto out; } while (! strncmp (magick, "BA", 2)) { - if (! ReadOK (fd, buffer, 12)) + if (! ReadOK (fi.file, buffer, 12)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); + gimp_file_get_utf8_name (gfile)); goto out; } - if (! ReadOK (fd, magick, 2)) + if (! ReadOK (fi.file, magick, 2)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); + gimp_file_get_utf8_name (gfile)); goto out; } } - if (! ReadOK (fd, buffer, 12)) + if (! ReadOK (fi.file, buffer, 12)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); + _("'%s' is not a valid BMP file"), gimp_file_get_utf8_name (gfile)); goto out; } - /* bring them to the right byteorder. Not too nice, but it should work */ + /* bring them to the right byteorder. */ bitmap_file_head.bfSize = ToL (&buffer[0x00]); bitmap_file_head.zzHotX = ToS (&buffer[0x04]); bitmap_file_head.zzHotY = ToS (&buffer[0x06]); bitmap_file_head.bfOffs = ToL (&buffer[0x08]); - if (! ReadOK (fd, buffer, 4)) + if (ReadOK (fi.file, buffer, 4)) + bitmap_head.biSize = ToL (&buffer[0x00]); + + if (bitmap_head.biSize < 12) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); + _("'%s' is not a valid BMP file"), gimp_file_get_utf8_name (gfile)); goto out; } - bitmap_head.biSize = ToL (&buffer[0x00]); + memset (buffer, 0, sizeof buffer); + if (! ReadOK (fi.file, buffer + 4, MIN (bitmap_head.biSize, sizeof buffer) - 4)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading BMP file header from '%s'"), + gimp_file_get_utf8_name (gfile)); + goto out; + } /* What kind of bitmap is it? */ - if (bitmap_head.biSize == 12) /* OS/2 1.x ? */ + /* OS/2 headers store width and height as unsigned, Windows headers as signed. + * We make no attempt to distinguish between those (which would be possible + * in some but not all cases) but always err on the Windows/signed side. + * The only time we'll ever be wrong would be OS/2 1.x files with dimensions + * larger than 32k (1.x files are from an era when 1024x768 was huge!) or + * OS/2 2.x images wider or higher than 2 billion pixels -- which is way + * over GIMP's limit anyway. + */ + + if (bitmap_head.biSize == 12) { - if (! ReadOK (fd, buffer, 8)) - { - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Error reading BMP file header from '%s'"), - gimp_file_get_utf8_name (file)); - goto out; - } + /* BITMAPCOREHEADER and OS21XBITMAPHEADER use 16bit int for + * width and height and have 3-byte color table entries */ + colorsize = 3; - bitmap_head.biWidth = ToS (&buffer[0x00]); /* 12 */ - bitmap_head.biHeight = ToS (&buffer[0x02]); /* 14 */ - bitmap_head.biPlanes = ToS (&buffer[0x04]); /* 16 */ - bitmap_head.biBitCnt = ToS (&buffer[0x06]); /* 18 */ - bitmap_head.biCompr = 0; - bitmap_head.biSizeIm = 0; - bitmap_head.biXPels = bitmap_head.biYPels = 0; - bitmap_head.biClrUsed = 0; - bitmap_head.biClrImp = 0; - bitmap_head.masks[0] = 0; - bitmap_head.masks[1] = 0; - bitmap_head.masks[2] = 0; - bitmap_head.masks[3] = 0; - - memset (masks, 0, sizeof (masks)); - Maps = 3; + bitmap_head.biWidth = ToS (&buffer[4]); + bitmap_head.biHeight = ToS (&buffer[6]); + bitmap_head.biPlanes = ToS (&buffer[8]); + bitmap_head.biBitCnt = ToS (&buffer[10]); } - else if (bitmap_head.biSize == 40) /* Windows 3.x */ + else if (bitmap_head.biSize >= 16) { - if (! ReadOK (fd, buffer, 36)) + /* all others use 32bit ints and have 4-byte table entries */ + colorsize = 4; + + /* BITMAPINFOHEADER / OS22XBITMAPHEADER */ + bitmap_head.biWidth = ToL (&buffer[4]); + bitmap_head.biHeight = ToL (&buffer[8]); + bitmap_head.biPlanes = ToS (&buffer[12]); + bitmap_head.biBitCnt = ToS (&buffer[14]); + bitmap_head.biCompr = ToL (&buffer[16]); + bitmap_head.biSizeIm = ToL (&buffer[20]); + bitmap_head.biXPels = ToL (&buffer[24]); + bitmap_head.biYPels = ToL (&buffer[28]); + bitmap_head.biClrUsed = ToL (&buffer[32]); + bitmap_head.biClrImp = ToL (&buffer[36]); + + /* OS22XBITMAPHEADER might write garbage into mask values, but + * they will be ignored because there is no OS/2 BITFIELDS bmp */ + bitmap_head.masks[0] = ToL (&buffer[40]); + bitmap_head.masks[1] = ToL (&buffer[44]); + bitmap_head.masks[2] = ToL (&buffer[48]); + bitmap_head.masks[3] = ToL (&buffer[52]); + + /* the remaining fields (BITMAPV4HEADER/BITMAPV5HEADER) are + * currently not used, but in case color profile support is + * later added, here they are */ + + /* BITMAPV4HEADER */ + bitmap_head.bV4CSType = ToL (&buffer[56]); + for (int i = 0; i < 9; i++) { - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Error reading BMP file header from '%s'"), - gimp_file_get_utf8_name (file)); - goto out; + /* endpoints are stored as 2.30 */ + bitmap_head.bV4Endpoints[i] = (gdouble) ToL (&buffer[60 + 4 * i]) / 0x40000000UL; } - bitmap_head.biWidth = ToL (&buffer[0x00]); /* 12 */ - bitmap_head.biHeight = ToL (&buffer[0x04]); /* 16 */ - bitmap_head.biPlanes = ToS (&buffer[0x08]); /* 1A */ - bitmap_head.biBitCnt = ToS (&buffer[0x0A]); /* 1C */ - bitmap_head.biCompr = ToL (&buffer[0x0C]); /* 1E */ - bitmap_head.biSizeIm = ToL (&buffer[0x10]); /* 22 */ - bitmap_head.biXPels = ToL (&buffer[0x14]); /* 26 */ - bitmap_head.biYPels = ToL (&buffer[0x18]); /* 2A */ - bitmap_head.biClrUsed = ToL (&buffer[0x1C]); /* 2E */ - bitmap_head.biClrImp = ToL (&buffer[0x20]); /* 32 */ - bitmap_head.masks[0] = 0; - bitmap_head.masks[1] = 0; - bitmap_head.masks[2] = 0; - bitmap_head.masks[3] = 0; + /* gamma is stored as 16.16 */ + bitmap_head.bV4GammaRed = ToL (&buffer[96]) / 65536.0; + bitmap_head.bV4GammaGreen = ToL (&buffer[100]) / 65536.0; + bitmap_head.bV4GammaBlue = ToL (&buffer[104]) / 65536.0; - Maps = 4; - memset (masks, 0, sizeof (masks)); + /* BITMAPV5HEADER */ + bitmap_head.bV5Intent = ToL (&buffer[108]); + bitmap_head.bV5ProfileData = ToL (&buffer[112]); + bitmap_head.bV5ProfileSize = ToL (&buffer[116]); + bitmap_head.bV5Reserved = ToL (&buffer[120]); - if (bitmap_head.biCompr == BI_BITFIELDS) + if (bitmap_head.biSize > sizeof buffer) { -#ifdef DEBUG - g_print ("Got BI_BITFIELDS compression\n"); -#endif - - if (! ReadOK (fd, buffer, 3 * sizeof (guint32))) + /* fast-forward to end of (future) larger headers */ + if (fseek (fi.file, bitmap_head.biSize - sizeof buffer, SEEK_CUR)) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Error reading BMP file header from '%s'"), - gimp_file_get_utf8_name (file)); + _("'%s' is not a valid BMP file"), + gimp_file_get_utf8_name (gfile)); goto out; } - - bitmap_head.masks[0] = ToL (&buffer[0x00]); - bitmap_head.masks[1] = ToL (&buffer[0x04]); - bitmap_head.masks[2] = ToL (&buffer[0x08]); - - ReadChannelMasks (&bitmap_head.masks[0], masks, 3); - } - else if (bitmap_head.biCompr == BI_RGB) - { -#ifdef DEBUG - g_print ("Got BI_RGB compression\n"); -#endif - - setMasksDefault (bitmap_head.biBitCnt, masks); - } - else if ((bitmap_head.biCompr != BI_RLE4) && - (bitmap_head.biCompr != BI_RLE8)) - { - /* BI_ALPHABITFIELDS, etc. */ - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Unsupported compression (%u) in BMP file from '%s'"), - bitmap_head.biCompr, - gimp_file_get_utf8_name (file)); - goto out; - } - -#ifdef DEBUG - g_print ("Got BI_RLE4 or BI_RLE8 compression\n"); -#endif - } - else if (bitmap_head.biSize >= 56 && - bitmap_head.biSize <= 64) - { - /* enhanced Windows format with bit masks */ - - if (! ReadOK (fd, buffer, bitmap_head.biSize - 4)) - { - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Error reading BMP file header from '%s'"), - gimp_file_get_utf8_name (file)); - goto out; - } - - bitmap_head.biWidth = ToL (&buffer[0x00]); /* 12 */ - bitmap_head.biHeight = ToL (&buffer[0x04]); /* 16 */ - bitmap_head.biPlanes = ToS (&buffer[0x08]); /* 1A */ - bitmap_head.biBitCnt = ToS (&buffer[0x0A]); /* 1C */ - bitmap_head.biCompr = ToL (&buffer[0x0C]); /* 1E */ - bitmap_head.biSizeIm = ToL (&buffer[0x10]); /* 22 */ - bitmap_head.biXPels = ToL (&buffer[0x14]); /* 26 */ - bitmap_head.biYPels = ToL (&buffer[0x18]); /* 2A */ - bitmap_head.biClrUsed = ToL (&buffer[0x1C]); /* 2E */ - bitmap_head.biClrImp = ToL (&buffer[0x20]); /* 32 */ - bitmap_head.masks[0] = ToL (&buffer[0x24]); /* 36 */ - bitmap_head.masks[1] = ToL (&buffer[0x28]); /* 3A */ - bitmap_head.masks[2] = ToL (&buffer[0x2C]); /* 3E */ - bitmap_head.masks[3] = ToL (&buffer[0x30]); /* 42 */ - - Maps = 4; - ReadChannelMasks (&bitmap_head.masks[0], masks, 4); - } - else if (bitmap_head.biSize == 108 || - bitmap_head.biSize == 124) - { - /* BMP Version 4 or 5 */ - - if (! ReadOK (fd, buffer, bitmap_head.biSize - 4)) - { - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Error reading BMP file header from '%s'"), - gimp_file_get_utf8_name (file)); - goto out; - } - - bitmap_head.biWidth = ToL (&buffer[0x00]); - bitmap_head.biHeight = ToL (&buffer[0x04]); - bitmap_head.biPlanes = ToS (&buffer[0x08]); - bitmap_head.biBitCnt = ToS (&buffer[0x0A]); - bitmap_head.biCompr = ToL (&buffer[0x0C]); - bitmap_head.biSizeIm = ToL (&buffer[0x10]); - bitmap_head.biXPels = ToL (&buffer[0x14]); - bitmap_head.biYPels = ToL (&buffer[0x18]); - bitmap_head.biClrUsed = ToL (&buffer[0x1C]); - bitmap_head.biClrImp = ToL (&buffer[0x20]); - bitmap_head.masks[0] = ToL (&buffer[0x24]); - bitmap_head.masks[1] = ToL (&buffer[0x28]); - bitmap_head.masks[2] = ToL (&buffer[0x2C]); - bitmap_head.masks[3] = ToL (&buffer[0x30]); - - Maps = 4; - - if (bitmap_head.biCompr == BI_BITFIELDS) - { -#ifdef DEBUG - g_print ("Got BI_BITFIELDS compression\n"); -#endif - - ReadChannelMasks (&bitmap_head.masks[0], masks, 4); - } - else if (bitmap_head.biCompr == BI_RGB) - { -#ifdef DEBUG - g_print ("Got BI_RGB compression\n"); -#endif - - setMasksDefault (bitmap_head.biBitCnt, masks); } } else { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("Error reading BMP file header from '%s'"), - gimp_file_get_utf8_name (file)); + _("'%s' is not a valid BMP file"), gimp_file_get_utf8_name (gfile)); goto out; } - /* Valid bit depth is 1, 4, 8, 16, 24, 32 */ - /* 16 is awful, we should probably shoot whoever invented it */ + /* identify OS/2 specific compressions and assign our own values */ + + if (bitmap_head.biSize <= 64) + { + if (bitmap_head.biCompr == BI_BITFIELDS && bitmap_head.biBitCnt == 1) + { + bitmap_head.biCompr = BI_OS2_HUFFMAN; + } + else if (bitmap_head.biCompr == BI_JPEG && bitmap_head.biBitCnt == 24) + { + bitmap_head.biCompr = BI_OS2_RLE24; + } + } + + if (bitmap_head.biSize <= 40 && + (bitmap_head.biCompr == BI_BITFIELDS || bitmap_head.biCompr == BI_ALPHABITFIELDS)) + { + /* BITMAPINFOHEADER stores masks right after header */ + + gint nmasks; + + nmasks = bitmap_head.biCompr == BI_BITFIELDS ? 3 : 4; + + if (! ReadOK (fi.file, buffer, nmasks * sizeof (guint32))) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error reading BMP file header from '%s'"), + gimp_file_get_utf8_name (gfile)); + goto out; + } + + for (int i = 0; i < nmasks; i++) + { + bitmap_head.masks[i] = ToL (&buffer[4 * i]); + } + } switch (bitmap_head.biBitCnt) { @@ -474,126 +479,148 @@ load_image (GFile *file, case 2: case 4: case 8: + switch (bitmap_head.biCompr) + { + case BI_RGB: + case BI_RLE4: + case BI_RLE8: + case BI_OS2_HUFFMAN: + if ((bitmap_head.biCompr == BI_RLE4 && bitmap_head.biBitCnt != 4) || + (bitmap_head.biCompr == BI_RLE8 && bitmap_head.biBitCnt != 8) || + (bitmap_head.biCompr == BI_OS2_HUFFMAN && bitmap_head.biBitCnt != 1)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s' is not a valid BMP file"), + gimp_file_get_utf8_name (gfile)); + goto out; + } + + maxcolors = (bitmap_file_head.bfOffs - bitmap_head.biSize - 14) / colorsize; + fi.ncolors = bitmap_head.biClrUsed == 0 ? 1 << bitmap_head.biBitCnt + : bitmap_head.biClrUsed; + fi.ncolors = MIN (256, MIN (fi.ncolors, maxcolors)); + + if (fi.ncolors < 1 || + ! read_colormap (fi.file, fi.colormap, fi.ncolors, colorsize, &fi.gray)) + { + if (bitmap_head.biCompr == BI_OS2_HUFFMAN) + { + /* It's unclear whether Huffman files require a color map + * or if they can be implicitly black & white. We'll create + * one if the file didn't contain a color map. + */ + fi.ncolors = 2; + for (int c = 0; c < 3; c++) + { + fi.colormap[0 + c] = 0; + fi.colormap[3 + c] = 255; + } + } + else + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s' is not a valid BMP file"), + gimp_file_get_utf8_name (gfile)); + goto out; + } + } + break; + + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported compression (%u) in BMP file from '%s'"), + bitmap_head.biCompr > 100 ? bitmap_head.biCompr - 100 + : bitmap_head.biCompr, + gimp_file_get_utf8_name (gfile)); + goto out; + } + break; + case 16: case 24: case 32: + switch (bitmap_head.biCompr) + { + case BI_BITFIELDS: + case BI_ALPHABITFIELDS: + read_masks (&bitmap_head.masks[0], fi.masks); + break; + + case BI_RGB: + set_default_masks (bitmap_head.biBitCnt, fi.masks); + break; + + case BI_OS2_RLE24: + if (bitmap_head.biBitCnt != 24) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s' is not a valid BMP file"), + gimp_file_get_utf8_name (gfile)); + goto out; + } + break; + + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported compression (%u) in BMP file from '%s'"), + bitmap_head.biCompr > 100 ? bitmap_head.biCompr - 100 + : bitmap_head.biCompr, + gimp_file_get_utf8_name (gfile)); + goto out; + } + digest_masks (fi.masks); + break; + case 64: break; + default: g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); + _("'%s' is not a valid BMP file"), gimp_file_get_utf8_name (gfile)); goto out; } - /* There should be some colors used! */ - - ColormapSize = - (bitmap_file_head.bfOffs - bitmap_head.biSize - 14) / Maps; - - if ((bitmap_head.biClrUsed == 0) && - (bitmap_head.biBitCnt <= 8)) - { - ColormapSize = bitmap_head.biClrUsed = 1 << bitmap_head.biBitCnt; - } - - if (ColormapSize > 256) - ColormapSize = 256; - /* Sanity checks */ - if (bitmap_head.biHeight == 0 || - bitmap_head.biWidth == 0) - { - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); - goto out; - } - - /* biHeight may be negative, but G_MININT32 is dangerous because: - G_MININT32 == -(G_MININT32) */ - if (bitmap_head.biWidth < 0 || - bitmap_head.biHeight == G_MININT32) - { - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); - goto out; - } - if (bitmap_head.biPlanes != 1) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); + _("'%s' is not a valid BMP file"), gimp_file_get_utf8_name (gfile)); goto out; } - if (bitmap_head.biClrUsed > 256 && - bitmap_head.biBitCnt <= 8) + fi.rowbytes = (((guint64) bitmap_head.biWidth * bitmap_head.biBitCnt + 31) / 32) * 4; + + if (fi.rowbytes > G_MAXSIZE || bitmap_head.biWidth > GIMP_MAX_IMAGE_SIZE || + bitmap_head.biWidth < 1) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); + _("Unsupported or invalid image width: %d"), + (int) bitmap_head.biWidth); goto out; } - /* protect against integer overflows caused by malicious BMPs */ - /* use divisions in comparisons to avoid type overflows */ - - if (((guint64) bitmap_head.biWidth) > G_MAXINT32 / bitmap_head.biBitCnt || - ((guint64) bitmap_head.biWidth) > (G_MAXINT32 / ABS (bitmap_head.biHeight)) / 4) + if (bitmap_head.biHeight < G_MININT + 1 || /* +1 because |G_MININT| > G_MAXINT. */ + bitmap_head.biHeight > GIMP_MAX_IMAGE_SIZE || bitmap_head.biHeight == 0) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - _("'%s' is not a valid BMP file"), - gimp_file_get_utf8_name (file)); + _("Unsupported or invalid image height: %d"), + (int) bitmap_head.biHeight); goto out; } - /* Windows and OS/2 declare filler so that rows are a multiple of - * word length (32 bits == 4 bytes) - */ - - rowbytes = ((bitmap_head.biWidth * bitmap_head.biBitCnt - 1) / 32) * 4 + 4; - -#ifdef DEBUG - printf ("\nSize: %lu, Colors: %lu, Bits: %hu, Width: %ld, Height: %ld, " - "Comp: %lu, Zeile: %d\n", - bitmap_file_head.bfSize, - bitmap_head.biClrUsed, - bitmap_head.biBitCnt, - bitmap_head.biWidth, - bitmap_head.biHeight, - bitmap_head.biCompr, - rowbytes); -#endif - - if (bitmap_head.biBitCnt <= 8) + if (fseek (fi.file, bitmap_file_head.bfOffs, SEEK_SET)) { -#ifdef DEBUG - printf ("Colormap read\n"); -#endif - /* Get the Colormap */ - if (! ReadColorMap (fd, ColorMap, ColormapSize, Maps, &gray)) - goto out; + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("'%s' is not a valid BMP file"), gimp_file_get_utf8_name (gfile)); + goto out; } - fseek (fd, bitmap_file_head.bfOffs, SEEK_SET); + fi.width = bitmap_head.biWidth; + fi.height = ABS (bitmap_head.biHeight); + fi.bpp = bitmap_head.biBitCnt; - /* Get the Image and return the image or NULL on error*/ - image = ReadImage (fd, - file, - bitmap_head.biWidth, - ABS (bitmap_head.biHeight), - ColorMap, - bitmap_head.biClrUsed, - bitmap_head.biBitCnt, - bitmap_head.biCompr, - rowbytes, - gray, - masks, - error); + image = ReadImage (&fi, gfile, bitmap_head.biCompr, error); if (! image) goto out; @@ -621,105 +648,126 @@ load_image (GFile *file, gimp_image_flip (image, GIMP_ORIENTATION_VERTICAL); out: - if (fd) - fclose (fd); + if (fi.file) + fclose (fi.file); return image; } static GimpImage * -ReadImage (FILE *fd, - GFile *file, - gint width, - gint height, - guchar cmap[256][3], - gint ncols, - gint bpp, - gint compression, - gint rowbytes, - gboolean gray, - const BitmapChannel *masks, - GError **error) +ReadImage (struct Fileinfo *fi, + GFile *gfile, + gint compression, + GError **error) { - guchar v, n; - gint xpos = 0; - gint ypos = 0; + gint ypos; GimpImage *image; GimpLayer *layer; GeglBuffer *buffer; - guchar *dest = NULL; - guchar *temp, *row_buf; - gushort *dest16 = NULL; - gushort *temp16; - guchar gimp_cmap[768]; - gushort rgb; - glong rowstride, channels; - gint i, i_max, j, cur_progress, max_progress; - gint total_bytes_read; + gint i, cur_progress, max_progress; GimpImageBaseType base_type; GimpImageType image_type; GimpPrecision precision_type = GIMP_PRECISION_U8_NON_LINEAR; const Babl *format = NULL; - guint32 px32; + gsize dest_size = 0; + gint maxbits; + gint eof = FALSE; + enum Bmpformat bmpfmt = 0; - if (! (compression == BI_RGB || compression == BI_BITFIELDS || - (bpp == 8 && compression == BI_RLE8) || - (bpp == 4 && compression == BI_RLE4))) - { - g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, - "%s", - _("Unrecognized or invalid BMP compression format.")); - return NULL; - } - - /* Make a new image in GIMP */ - - switch (bpp) + switch (fi->bpp) { case 64: - base_type = GIMP_RGB; - image_type = GIMP_RGBA_IMAGE; - channels = 4; - format = babl_format ("RGBA u16"); - precision_type = GIMP_PRECISION_U16_NON_LINEAR; + base_type = GIMP_RGB; + image_type = GIMP_RGBA_IMAGE; + fi->channels = 4; + format = babl_format ("RGBA float"); + precision_type = GIMP_PRECISION_FLOAT_LINEAR; + bmpfmt = BMPFMT_RGB_64; + fi->bytesperchannel = 4; break; case 32: case 24: case 16: base_type = GIMP_RGB; - if (masks[3].mask != 0) + if (fi->masks[3].mask != 0 || compression == BI_OS2_RLE24) { - image_type = GIMP_RGBA_IMAGE; - channels = 4; + image_type = GIMP_RGBA_IMAGE; + fi->channels = 4; } else { - image_type = GIMP_RGB_IMAGE; - channels = 3; + image_type = GIMP_RGB_IMAGE; + fi->channels = 3; } - if (bpp == 24 && compression == BI_BITFIELDS) - g_printerr ("Loading BMP with invalid combination of 24 bpp and BI_BITFIELDS compression.\n"); + + for (i = 0, maxbits = 0; i < fi->channels; i++) + maxbits = MAX (maxbits, fi->masks[i].nbits); + + if (maxbits <= 8) + { + fi->bytesperchannel = 1; + precision_type = GIMP_PRECISION_U8_NON_LINEAR; + } + else if (maxbits <= 16) + { + fi->bytesperchannel = 2; + precision_type = GIMP_PRECISION_U16_NON_LINEAR; + } + else + { + fi->bytesperchannel = 4; + precision_type = GIMP_PRECISION_U32_NON_LINEAR; + } + + if (compression == BI_OS2_RLE24) + bmpfmt = BMPFMT_RLE; + else + bmpfmt = BMPFMT_RGB; + break; case 8: case 4: + case 2: case 1: - if (gray) + if (fi->gray) { - base_type = GIMP_GRAY; + base_type = GIMP_GRAY; image_type = GIMP_GRAY_IMAGE; } else { - base_type = GIMP_INDEXED; + base_type = GIMP_INDEXED; image_type = GIMP_INDEXED_IMAGE; } - if (compression == BI_BITFIELDS) - g_printerr ("Loading BMP with invalid combination of %d bpp and BI_BITFIELDS compression.\n", - bpp); - channels = 1; + fi->channels = 1; + fi->bytesperchannel = 1; + + switch (compression) + { + case BI_RGB: + bmpfmt = BMPFMT_INDEXED; + break; + + case BI_RLE4: + case BI_RLE8: + if (fi->gray) + image_type = GIMP_GRAYA_IMAGE; + else + image_type = GIMP_INDEXEDA_IMAGE; + + fi->channels = 2; + fi->bytesperchannel = 1; + + bmpfmt = BMPFMT_RLE; + break; + + case BI_OS2_HUFFMAN: + bmpfmt = BMPFMT_HUFFMAN; + break; + } break; default: @@ -727,370 +775,507 @@ ReadImage (FILE *fd, return NULL; } - if ((width < 0) || (width > GIMP_MAX_IMAGE_SIZE)) + fi->tile_height = MIN (gimp_tile_height (), fi->height); + + if ((guint64) fi->width * fi->tile_height < G_MAXSIZE / (fi->channels * fi->bytesperchannel)) { - g_message (_("Unsupported or invalid image width: %d"), width); + dest_size = (gsize) fi->width * fi->tile_height * fi->channels * fi->bytesperchannel; + fi->dest = g_try_malloc0 (dest_size); + fi->rowbuf = g_try_malloc (MAX (4, fi->rowbytes)); + } + + if (! (fi->dest && fi->rowbuf)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Image dimensions too large: width %d x height %d"), + fi->width, fi->height); + g_free (fi->dest); + g_free (fi->rowbuf); return NULL; } - if ((height < 0) || (height > GIMP_MAX_IMAGE_SIZE)) - { - g_message (_("Unsupported or invalid image height: %d"), height); - return NULL; - } - - image = gimp_image_new_with_precision (width, height, base_type, - precision_type); + image = gimp_image_new_with_precision (fi->width, fi->height, base_type, precision_type); layer = gimp_layer_new (image, _("Background"), - width, height, + fi->width, fi->height, image_type, 100, gimp_image_get_default_new_layer_mode (image)); gimp_image_insert_layer (image, layer, NULL, 0); - - /* use g_malloc0 to initialize the dest buffer so that unspecified - pixels in RLE bitmaps show up as the zeroth element in the palette. - */ - if (bpp != 64) - dest = g_malloc0 (width * height * channels); - else - dest16 = g_malloc0 (width * height * channels * 2); - - row_buf = g_malloc (rowbytes); - rowstride = width * channels; - - ypos = height - 1; /* Bitmaps begin in the lower left corner */ - cur_progress = 0; - max_progress = height; - - switch (bpp) - { - case 64: - { - gint32 temp_int; - - while (ReadOK (fd, row_buf, rowbytes)) - { - temp16 = dest16 + (ypos * rowstride); - - for (xpos = 0; xpos < width; ++xpos) - { - /* Values are stored in s2.13 fixed point format; - * 13 bits for fractional value. - * s2.13 has a range of -4.0 to 3.999... - * we clip to 0.0 to 1.0 */ - for (i = 4; i >= 0; i -= 2) - { - rgb = ToS (&row_buf[(xpos * 8) + i]); - - if (rgb & 0x8000) /* < 0.0 */ - rgb = 0; - else if (rgb > 0x2000) /* > 1.0 */ - rgb = 0x2000; - - temp_int = (gint) rgb * 0xffff; - temp_int >>= 13; - - *(temp16++) = (guint16) temp_int; - } - - /* Alpha Channel */ - rgb = ToS (&row_buf[(xpos * 8) + 6]); - - if (rgb & 0x8000) - rgb = 0; - else if (rgb > 0x2000) - rgb = 0x2000; - - temp_int = (gint) rgb * 0xffff; - temp_int >>= 13; - - *(temp16++) = (guint16) temp_int; - } - - if (ypos == 0) - break; - - --ypos; /* next line */ - - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); - } - } - break; - - case 32: - { - while (ReadOK (fd, row_buf, rowbytes)) - { - temp = dest + (ypos * rowstride); - - for (xpos= 0; xpos < width; ++xpos) - { - px32 = ToL(&row_buf[xpos*4]); - *(temp++) = ((px32 & masks[0].mask) >> masks[0].shiftin) * 255.0 / masks[0].max_value + 0.5; - *(temp++) = ((px32 & masks[1].mask) >> masks[1].shiftin) * 255.0 / masks[1].max_value + 0.5; - *(temp++) = ((px32 & masks[2].mask) >> masks[2].shiftin) * 255.0 / masks[2].max_value + 0.5; - if (channels > 3) - *(temp++) = ((px32 & masks[3].mask) >> masks[3].shiftin) * 255.0 / masks[3].max_value + 0.5; - } - - if (ypos == 0) - break; - - --ypos; /* next line */ - - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); - } - } - break; - - case 24: - { - while (ReadOK (fd, row_buf, rowbytes)) - { - temp = dest + (ypos * rowstride); - - for (xpos= 0; xpos < width; ++xpos) - { - *(temp++) = row_buf[xpos * 3 + 2]; - *(temp++) = row_buf[xpos * 3 + 1]; - *(temp++) = row_buf[xpos * 3]; - } - - if (ypos == 0) - break; - - --ypos; /* next line */ - - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); - } - } - break; - - case 16: - { - while (ReadOK (fd, row_buf, rowbytes)) - { - temp = dest + (ypos * rowstride); - - for (xpos= 0; xpos < width; ++xpos) - { - rgb= ToS(&row_buf[xpos * 2]); - *(temp++) = ((rgb & masks[0].mask) >> masks[0].shiftin) * 255.0 / masks[0].max_value + 0.5; - *(temp++) = ((rgb & masks[1].mask) >> masks[1].shiftin) * 255.0 / masks[1].max_value + 0.5; - *(temp++) = ((rgb & masks[2].mask) >> masks[2].shiftin) * 255.0 / masks[2].max_value + 0.5; - if (channels > 3) - *(temp++) = ((rgb & masks[3].mask) >> masks[3].shiftin) * 255.0 / masks[3].max_value + 0.5; - } - - if (ypos == 0) - break; - - --ypos; /* next line */ - - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); - } - } - break; - - case 8: - case 4: - case 1: - { - if (compression == BI_RGB || compression == BI_BITFIELDS) - /* no compression */ - { - while (ReadOK (fd, &v, 1)) - { - for (i = 1; (i <= (8 / bpp)) && (xpos < width); i++, xpos++) - { - temp = dest + (ypos * rowstride) + (xpos * channels); - *temp=( v & ( ((1<> (8-(i*bpp)); - if (gray) - *temp = cmap[*temp][0]; - } - - if (xpos == width) - { - fread (row_buf, rowbytes - 1 - (width * bpp - 1) / 8, 1, fd); - - if (ypos == 0) - break; - - ypos--; - xpos = 0; - - cur_progress++; - if ((cur_progress % 5) == 0) - gimp_progress_update ((gdouble) cur_progress / - (gdouble) max_progress); - } - - if (ypos < 0) - break; - } - break; - } - else - { - /* compressed image (either RLE8 or RLE4) */ - while (ypos >= 0 && xpos <= width) - { - if (! ReadOK (fd, row_buf, 2)) - { - g_message (_("The bitmap ends unexpectedly.")); - break; - } - - if (row_buf[0] != 0) - /* Count + Color - record */ - { - /* encoded mode run - - * row_buf[0] == run_length - * row_buf[1] == pixel data - */ - for (j = 0; - ((guchar) j < row_buf[0]) && (xpos < width);) - { -#ifdef DEBUG2 - printf("%u %u | ",xpos,width); -#endif - for (i = 1; - ((i <= (8 / bpp)) && - (xpos < width) && - ((guchar) j < row_buf[0])); - i++, xpos++, j++) - { - temp = dest + (ypos * rowstride) + (xpos * channels); - *temp = (row_buf[1] & - (((1<> (8 - (i * bpp)); - if (gray) - *temp = cmap[*temp][0]; - } - } - } - - if ((row_buf[0] == 0) && (row_buf[1] > 2)) - /* uncompressed record */ - { - n = row_buf[1]; - total_bytes_read = 0; - - for (j = 0; j < n; j += (8 / bpp)) - { - /* read the next byte in the record */ - if (! ReadOK (fd, &v, 1)) - { - g_message (_("The bitmap ends unexpectedly.")); - break; - } - - total_bytes_read++; - - /* read all pixels from that byte */ - i_max = 8 / bpp; - if (n - j < i_max) - { - i_max = n - j; - } - - i = 1; - while ((i <= i_max) && (xpos < width)) - { - temp = - dest + (ypos * rowstride) + (xpos * channels); - *temp = (v >> (8-(i*bpp))) & ((1<rowstride = (gsize) fi->width * fi->channels; + + cur_progress = 0; + max_progress = fi->height; + + fi->tile_n = 0; + fi->file_ypos = fi->height - 1; + + for (ypos = fi->height - 1; ypos >= 0 && ! eof; ypos--) + { + gsize offset = (fi->tile_height - fi->tile_n - 1) * fi->rowstride; + + if (! eof) + { + switch (bmpfmt) + { + case BMPFMT_RGB: + if (! load_rgb (fi, offset)) + eof = TRUE; + break; + + case BMPFMT_RGB_64: + if (! load_rgb_64 (fi, offset)) + eof = TRUE; + break; + + case BMPFMT_INDEXED: + if (! load_indexed (fi, offset)) + eof = TRUE; + break; + + case BMPFMT_RLE: + if (fi->file_ypos < ypos) + break; + + if (! load_rle (fi, offset)) + eof = TRUE; + break; + + case BMPFMT_HUFFMAN: + if (! load_huffman (fi, offset)) + eof = TRUE; + break; + } + } + + cur_progress++; + if ((cur_progress % 5) == 0) + gimp_progress_update ((gdouble) cur_progress / max_progress); + + if (++fi->tile_n == fi->tile_height || ypos <= 0 || eof) + { + /* we are filling the dest buffer backwards, so we need an offset to dest in + * case the tile_height isn't fully used (when tile_n < tile_height) + */ + gsize offs = (fi->tile_height - fi->tile_n) * fi->rowstride * fi->bytesperchannel; + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, ypos, fi->width, fi->tile_n), 0, format, + fi->dest + offs, GEGL_AUTO_ROWSTRIDE); + memset (fi->dest, 0, dest_size); + fi->tile_n = 0; + } + } g_object_unref (buffer); - g_free (dest); - g_free (dest16); + g_free (fi->dest); - if ((! gray) && (bpp <= 8)) - gimp_palette_set_colormap (gimp_image_get_palette (image), babl_format ("R'G'B' u8"), gimp_cmap, ncols * 3); + if (! fi->gray && fi->bpp <= 8) + gimp_palette_set_colormap (gimp_image_get_palette (image), babl_format ("R'G'B' u8"), + fi->colormap, fi->ncolors * 3); gimp_progress_update (1.0); + if (bmpfmt == BMPFMT_RLE && ! fi->needs_alpha) + gimp_layer_flatten (layer); + return image; } + +static gint +load_rgb (struct Fileinfo *fi, gsize offset) +{ + gint xpos, i; + gint32 px; + gdouble d; + guchar *dest8; + guint16 *dest16; + guint32 *dest32; + + if (! ReadOK (fi->file, fi->rowbuf, fi->rowbytes)) + return FALSE; + + dest8 = fi->dest + fi->bytesperchannel * offset; + dest16 = (guint16 *) dest8; + dest32 = (guint32 *) dest8; + + for (xpos = 0; xpos < fi->width; xpos++) + { + px = ToLn (&fi->rowbuf[xpos * (fi->bpp / 8)], fi->bpp / 8); + for (i = 0; i < fi->channels; i++) + { + d = ((px & fi->masks[i].mask) >> fi->masks[i].shiftin) / fi->masks[i].max_value; + + if (fi->bytesperchannel == 1) + *dest8++ = d * 0x00ff + 0.5; + else if (fi->bytesperchannel == 2) + *dest16++ = d * 0xffffUL + 0.5; + else + *dest32++ = d * 0xffffffffUL + 0.5; + } + } + return TRUE; +} + +static gint +load_rgb_64 (struct Fileinfo *fi, gsize offset) +{ + gint xpos, i; + gfloat *destflt; + + if (! ReadOK (fi->file, fi->rowbuf, fi->rowbytes)) + return FALSE; + + destflt = (gfloat *) fi->dest + offset; + + for (xpos = 0; xpos < fi->width; ++xpos) + { + /* Values are stored as BGRA in s2.13 fixed point format; + * s2.13 has a range of -4.0 to 3.999... + */ + for (i = 4; i >= 0; i -= 2) + { + *destflt++ = ToS (&fi->rowbuf[(xpos * 8) + i]) / 8192.0; + } + + /* Alpha Channel */ + *destflt++ = ToS (&fi->rowbuf[(xpos * 8) + 6]) / 8192.0; + } + return TRUE; +} + +static gint +load_indexed (struct Fileinfo *fi, gsize offset) +{ + gint xpos, val, shift; + guchar *dest; + + if (! ReadOK (fi->file, fi->rowbuf, fi->rowbytes)) + return FALSE; + + dest = fi->dest + offset; + for (xpos = 0; xpos < fi->width; xpos++) + { + val = fi->rowbuf[xpos / (8 / fi->bpp)]; + shift = 8 - fi->bpp * ((xpos % (8 / fi->bpp)) + 1); + *dest = (val & (((1 << fi->bpp) - 1) << shift)) >> shift; + *dest = MIN (*dest, fi->ncolors - 1); + if (fi->gray) + *dest = fi->colormap[*dest * 3]; + dest++; + } + + return TRUE; +} + +static gint +load_rle (struct Fileinfo *fi, gsize offset) +{ + gint shift, i, j; + guchar *dest; + + while (fi->xpos <= fi->width) + { + if (! ReadOK (fi->file, fi->rowbuf, 2)) + { + g_message (_("The bitmap ends unexpectedly.")); + fi->needs_alpha = TRUE; + return FALSE; + } + + if (fi->rowbuf[0] != 0) + { + /* encoded run, row_buf[0] == number of repeats */ + if (fi->bpp <= 8) + { + for (j = 0; (j < fi->rowbuf[0]) && (fi->xpos < fi->width);) + { + for (i = 1; (i <= 8 / fi->bpp) && (fi->xpos < fi->width) && + (j < fi->rowbuf[0]); + i++, fi->xpos++, j++) + { + shift = 8 - i * fi->bpp; + dest = fi->dest + offset + fi->xpos * fi->channels; + dest[0] = (fi->rowbuf[1] & (((1 << fi->bpp) - 1) << shift)) >> shift; + dest[0] = MIN (dest[0], fi->ncolors - 1); + if (fi->gray) + dest[0] = fi->colormap[dest[0] * 3]; + dest[1] = 0xff; /* alpha */ + + } + } + } + else + { + /* read remaining 2 rgb components */ + if (! ReadOK (fi->file, fi->rowbuf + 2, 2)) + { + g_message (_("The bitmap ends unexpectedly.")); + fi->needs_alpha = TRUE; + return FALSE; + } + for (i = 0; (i < fi->rowbuf[0]) && (fi->xpos < fi->width); i++, fi->xpos++) + { + dest = fi->dest + offset + fi->xpos * fi->channels; + for (gint c = 0; c < 3; c++) + { + dest[2 - c] = fi->rowbuf[c + 1]; + } + dest[3] = 0xff; /* alpha */ + } + } + } + + else if (fi->rowbuf[0] == 0 && fi->rowbuf[1] > 2) + { + /* literal run */ + gint n = fi->rowbuf[1]; + + for (i = 0; i < n && fi->xpos < fi->width; i++, fi->xpos++) + { + if (fi->bpp >= 8 || i % 2 == 0) /* RLE4 only needs new byte every 2 pixels */ + { + if (! ReadOK (fi->file, fi->rowbuf, fi->bpp <= 8 ? 1 : 3)) + { + g_message (_("The bitmap ends unexpectedly.")); + fi->needs_alpha = TRUE; + return FALSE; + } + } + + dest = fi->dest + offset + fi->xpos * fi->channels; + switch (fi->bpp) + { + case 8: + dest[0] = fi->rowbuf[0]; + break; + case 4: + dest[0] = (fi->rowbuf[0] >> (4 * ((i + 1) % 2))) & 0x0f; + break; + case 24: + for (int c = 0; c < 3; c++) + { + dest[2 - c] = fi->rowbuf[c]; + } + break; + } + dest[fi->channels - 1] = 0xff; /* alpha */ + } + + /* pad to 16-bit boundary */ + if ((fi->bpp == 4 ? (n + 1) / 2 : n) % 2) + { + if (EOF == getc (fi->file)) + { + g_message (_("The bitmap ends unexpectedly.")); + fi->needs_alpha = TRUE; + return FALSE; + } + } + } + + else if ((fi->rowbuf[0] == 0) && (fi->rowbuf[1] == 0)) + /* Line end */ + { + if (fi->xpos < fi->width) + fi->needs_alpha = TRUE; + + fi->xpos = 0; + fi->file_ypos--; + break; + } + + else if ((fi->rowbuf[0] == 0) && (fi->rowbuf[1] == 1)) + /* Bitmap end */ + { + if (fi->xpos < fi->width || fi->file_ypos > 0) + fi->needs_alpha = TRUE; + return FALSE; + } + + else if ((fi->rowbuf[0] == 0) && (fi->rowbuf[1] == 2)) + /* Deltarecord */ + { + if (! ReadOK (fi->file, fi->rowbuf, 2)) + { + g_message (_("The bitmap ends unexpectedly.")); + fi->needs_alpha = TRUE; + return FALSE; + } + if (fi->rowbuf[0] >= fi->width - fi->xpos || fi->rowbuf[1] >= fi->file_ypos) + { + /* delta points outside image, we cannot reasonably recover */ + fi->needs_alpha = TRUE; + return FALSE; + } + + if (fi->rowbuf[0] + fi->rowbuf[1] > 0) + fi->needs_alpha = TRUE; + + fi->xpos += fi->rowbuf[0]; + fi->file_ypos -= fi->rowbuf[1]; + + if (fi->rowbuf[1] > 0) + break; + } + } + return TRUE; +} + +static gint +load_huffman (struct Fileinfo *fi, gsize offset) +{ + gint xpos = 0, len, i; + guchar *dest; + + while (xpos < fi->width) + { + huff_fillbuf (&fi->bitbuf, &fi->buflen, fi->file); + if (fi->buflen == 0) + return FALSE; + + if ((fi->bitbuf & 0xff000000UL) == 0) + { + /* any sequence that starts with 8 or more 0s is considered eol */ + if (! huff_skip_eol (&fi->bitbuf, &fi->buflen, fi->file)) + return FALSE; + + if (xpos == 0) /* ignore eol at beginning of line */ + continue; + } + else + { + len = huff_decode (&fi->bitbuf, &fi->buflen, fi->file, fi->black); + if (len >= 0 && len <= fi->width - xpos) + { + dest = fi->dest + offset + xpos; + for (i = 0; i < len; i++, xpos++) + { + dest[i] = fi->black; + } + fi->black = ! fi->black; + continue; + } + else + { + /* Invalid bit sequence, look for next eol to re-sync */ + if (! huff_find_eol (&fi->bitbuf, &fi->buflen, fi->file)) + return FALSE; + break; + } + } + } + fi->black = 0; /* every new row starts with white sequence */ + return TRUE; +} + +static gint +huff_decode (guint32 *bitbuf, gint *buflen, FILE *file, gint black) +{ + gint idx; + gint bits_used = 0; + gint result = 0; + + do + { + huff_fillbuf (bitbuf, buflen, file); + bits_used = huff_findnode (*bitbuf, *buflen, black, &idx); + if (idx == -1) + { + /* invalid bit sequenct encountered */ + return -1; + } + + result += nodebuffer[idx].value; + *bitbuf <<= bits_used; + *buflen -= bits_used; + } + while (nodebuffer[idx].makeup && result < G_MAXINT - 2560); + + return nodebuffer[idx].makeup ? -1 : result; +} + +static gint +huff_findnode (guint32 bitbuf, gint buflen, gint black, gint *found) +{ + gint idx; + gint bits_used = 0; + + idx = black ? blackroot : whiteroot; + + while (idx != -1 && ! nodebuffer[idx].leaf && bits_used < buflen) + { + if (bitbuf & 0x80000000UL) + idx = nodebuffer[idx].right; + else + idx = nodebuffer[idx].left; + bits_used++; + bitbuf <<= 1; + } + *found = idx; + return idx != -1 ? bits_used : 0; +} + +static void +huff_fillbuf (guint32 *bitbuf, gint *buflen, FILE *file) +{ + gint byte; + + while (*buflen <= 24) + { + if (EOF == (byte = fgetc (file))) + return; + *bitbuf |= (guint32) byte << (24 - *buflen); + *buflen += 8; + } +} + +static gint +huff_find_eol (guint32 *bitbuf, gint *buflen, FILE *file) +{ + /* look for the next full 12-bit eol sequence, + * discarding anything else + */ + huff_fillbuf (bitbuf, buflen, file); + while (*buflen > 11) + { + if ((*bitbuf & 0xffe00000UL) == 0) + { + *bitbuf <<= 11; + *buflen -= 11; + return huff_skip_eol (bitbuf, buflen, file); + } + *bitbuf <<= 1; + *buflen -= 1; + if (*buflen < 12) + huff_fillbuf (bitbuf, buflen, file); + } + return FALSE; +} + +static gint +huff_skip_eol (guint32 *bitbuf, gint *buflen, FILE *file) +{ + /* skip any number of 0s and the next 1 */ + + huff_fillbuf (bitbuf, buflen, file); + while (*buflen > 0) + { + if (*bitbuf == 0) + { + *buflen = 0; + huff_fillbuf (bitbuf, buflen, file); + continue; + } + while ((*bitbuf & 0x80000000UL) == 0) + { + *bitbuf <<= 1; + *buflen -= 1; + } + *bitbuf <<= 1; + *buflen -= 1; + return TRUE; + } + return FALSE; +} diff --git a/plug-ins/file-bmp/bmp.h b/plug-ins/file-bmp/bmp.h index b861db358d..3c030bd62c 100644 --- a/plug-ins/file-bmp/bmp.h +++ b/plug-ins/file-bmp/bmp.h @@ -25,9 +25,7 @@ #define PLUG_IN_BINARY "file-bmp" #define PLUG_IN_ROLE "gimp-file-bmp" -#define MAXCOLORS 256 - -#define BitSet(byte, bit) (((byte) & (bit)) == (bit)) +#define MAXCOLORS 256 #define ReadOK(file,buffer,len) (fread(buffer, len, 1, file) != 0) @@ -36,9 +34,17 @@ #define BI_RLE8 1 #define BI_RLE4 2 #define BI_BITFIELDS 3 -#define BI_ALPHABITFIELDS 4 +#define BI_JPEG 4 +#define BI_PNG 5 +#define BI_ALPHABITFIELDS 6 #endif - +/* The following two are OS/2 BMP compression methods. + * Their values (3 and 4) clash with MS values for + * BI_BITFIELDS and BI_JPEG. We make up our own distinct + * values and assign them as soon as we identify these + * methods. */ +#define BI_OS2_HUFFMAN (100 + BI_BITFIELDS) +#define BI_OS2_RLE24 (100 + BI_JPEG) typedef struct { @@ -63,6 +69,15 @@ typedef struct guint32 biClrUsed; /* 2E */ guint32 biClrImp; /* 32 */ guint32 masks[4]; /* 36 */ + guint32 bV4CSType; + gdouble bV4Endpoints[9]; + gdouble bV4GammaRed; + gdouble bV4GammaGreen; + gdouble bV4GammaBlue; + guint32 bV5Intent; + guint32 bV5ProfileData; + guint32 bV5ProfileSize; + guint32 bV5Reserved; } BitmapHead; typedef struct @@ -70,6 +85,7 @@ typedef struct guint32 mask; guint32 shiftin; gfloat max_value; + gint nbits; } BitmapChannel; diff --git a/plug-ins/file-bmp/generate-huffman.c b/plug-ins/file-bmp/generate-huffman.c new file mode 100644 index 0000000000..5fbeae5cb2 --- /dev/null +++ b/plug-ins/file-bmp/generate-huffman.c @@ -0,0 +1,209 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* This program generates the header file "huffman.h". + * huffman.h will contain an array of Huffnode structs which make up two + * Huffman code trees for black and white pixels, respectively. + */ + +#include +#include +#include + +#include "generate-huffman.h" + +#define ARR_SIZE(a) (sizeof a / sizeof a[0]) + +struct Node +{ + int l; + int r; + int value; + int leaf; + int makeup; +}; + +static int nnodes = 0; +static struct Node nodebuffer[416]; + +static int blackroot = -1; +static int whiteroot = -1; + +static void s_buildtree (void); +static void add_node (int *idx, + const char *bits, + int value, + int makeup); +static void find_empty_nodes (int idx, + int depth); + +int +main (int argc, char *argv[]) +{ + int i; + struct Node *n = nodebuffer; + FILE *file; + + if (argc == 2) + { + if (! strcmp (argv[1], "-")) + { + file = stdout; + } + else if (! (file = fopen (argv[1], "w"))) + { + perror (argv[1]); + return 1; + } + } + else + { + fprintf (stderr, "Need filespec to save generated huffman header!\n"); + return 1; + } + + fprintf (file, "/* This file was auto-generated by\n * %s\n */\n\n", argv[0]); + + s_buildtree (); + + /* sanity checks */ + find_empty_nodes (blackroot, 0); + find_empty_nodes (whiteroot, 0); + if (nnodes != 416) + { + fprintf (stderr, "*** have %d nodes, but should have 416.\n", nnodes); + return 1; + } + + fprintf (file, "static const int blackroot = %d;\n", blackroot); + fprintf (file, "static const int whiteroot = %d;\n\n", whiteroot); + fprintf (file, "static const struct Huffnode nodebuffer[] = {\n "); + for (i = 0; i < ARR_SIZE (nodebuffer); i++) + { + if ((i + 1) % 2 == 0 && i < ARR_SIZE (nodebuffer) - 1) + { + fprintf (file, "{ %3d, %3d, %4d, %2d, %2d },\n ", n[i].l, n[i].r, + n[i].value, n[i].leaf, n[i].makeup); + } + else + { + fprintf (file, "{ %3d, %3d, %4d, %2d, %2d }, ", n[i].l, n[i].r, + n[i].value, n[i].leaf, n[i].makeup); + } + } + fprintf (file, "\n};\n"); + return 0; +} + +static void +s_buildtree (void) +{ + int i; + + memset (nodebuffer, 0, sizeof nodebuffer); + + for (i = 0; i < ARR_SIZE (huff_term_black); i++) + add_node (&blackroot, huff_term_black[i].bits, huff_term_black[i].number, 0); + + for (i = 0; i < ARR_SIZE (huff_makeup_black); i++) + add_node (&blackroot, huff_makeup_black[i].bits, huff_makeup_black[i].number, 1); + + for (i = 0; i < ARR_SIZE (huff_term_white); i++) + add_node (&whiteroot, huff_term_white[i].bits, huff_term_white[i].number, 0); + + for (i = 0; i < ARR_SIZE (huff_makeup_white); i++) + add_node (&whiteroot, huff_makeup_white[i].bits, huff_makeup_white[i].number, 1); +} + +static void +add_node (int *idx, const char *bits, int value, int makeup) +{ + if (*idx == -1) + { + if (nnodes >= ARR_SIZE (nodebuffer)) + { + fprintf (stderr, "*** too many nodes (have %d, max is %d)\n", nnodes, + (int) ARR_SIZE (nodebuffer)); + exit (1); + } + *idx = nnodes++; + nodebuffer[*idx].l = -1; + nodebuffer[*idx].r = -1; + } + + if (! *bits) + { + /* we are on the final bit of the sequence */ + nodebuffer[*idx].value = value; + nodebuffer[*idx].leaf = 1; + nodebuffer[*idx].makeup = makeup; + } + else + { + switch (*bits) + { + case '0': + add_node (&nodebuffer[*idx].l, bits + 1, value, makeup); + break; + case '1': + add_node (&nodebuffer[*idx].r, bits + 1, value, makeup); + break; + } + } +} + +static void +find_empty_nodes (int idx, int depth) +{ + static char str[30]; + const struct Node *node; + + if (depth >= (int) sizeof str) + { + fprintf (stderr, "*** panic!, str[] is too short\n"); + exit (1); + } + + str[depth] = 0; + + node = &nodebuffer[idx]; + if (! node->leaf) + { + if (! (node->l != -1 && node->r != -1)) + { + if (node->r != -1 && ! strcmp ("0000000", str)) + { + ; /* ok, that's eol */ + } + else + { + fprintf (stderr, "*** node %s not full. l=%d r=%d\n", str, node->l, node->r); + exit (1); + } + } + if (node->l != -1) + { + str[depth] = '0'; + find_empty_nodes (node->l, depth + 1); + } + if (node->r != -1) + { + str[depth] = '1'; + find_empty_nodes (node->r, depth + 1); + } + } +} diff --git a/plug-ins/file-bmp/generate-huffman.h b/plug-ins/file-bmp/generate-huffman.h new file mode 100644 index 0000000000..b14c9b56e9 --- /dev/null +++ b/plug-ins/file-bmp/generate-huffman.h @@ -0,0 +1,96 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* This header file is used by generate-huffman.c to auto-generate + * the actual "huffman.h". + * + * The bit sequences for terminating and make-up codes can be found in + * ITU-T Rec T.4: + * https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-T.4-200307-I!!PDF-E&type=items + */ + +struct Huffcode +{ + int number; + const char *bits; +}; + +/* clang-format off */ + +static struct Huffcode huff_term_white[] = { + { 0, "00110101" }, { 1, "000111" }, { 2, "0111" }, { 3, "1000" }, + { 4, "1011" }, { 5, "1100" }, { 6, "1110" }, { 7, "1111" }, + { 8, "10011" }, { 9, "10100" }, { 10, "00111" }, { 11, "01000" }, + { 12, "001000" }, { 13, "000011" }, { 14, "110100" }, { 15, "110101" }, + { 16, "101010" }, { 17, "101011" }, { 18, "0100111" }, { 19, "0001100" }, + { 20, "0001000" }, { 21, "0010111" }, { 22, "0000011" }, { 23, "0000100" }, + { 24, "0101000" }, { 25, "0101011" }, { 26, "0010011" }, { 27, "0100100" }, + { 28, "0011000" }, { 29, "00000010" }, { 30, "00000011" }, { 31, "00011010" }, + { 32, "00011011" }, { 33, "00010010" }, { 34, "00010011" }, { 35, "00010100" }, + { 36, "00010101" }, { 37, "00010110" }, { 38, "00010111" }, { 39, "00101000" }, + { 40, "00101001" }, { 41, "00101010" }, { 42, "00101011" }, { 43, "00101100" }, + { 44, "00101101" }, { 45, "00000100" }, { 46, "00000101" }, { 47, "00001010" }, + { 48, "00001011" }, { 49, "01010010" }, { 50, "01010011" }, { 51, "01010100" }, + { 52, "01010101" }, { 53, "00100100" }, { 54, "00100101" }, { 55, "01011000" }, + { 56, "01011001" }, { 57, "01011010" }, { 58, "01011011" }, { 59, "01001010" }, + { 60, "01001011" }, { 61, "00110010" }, { 62, "00110011" }, { 63, "00110100" }, +}; + +static struct Huffcode huff_term_black[] = { + { 0, "0000110111" }, { 1, "010" }, { 2, "11" }, { 3, "10" }, + { 4, "011" }, { 5, "0011" }, { 6, "0010" }, { 7, "00011" }, + { 8, "000101" }, { 9, "000100" }, { 10, "0000100" }, { 11, "0000101" }, + { 12, "0000111" }, { 13, "00000100" }, { 14, "00000111" }, { 15, "000011000" }, + { 16, "0000010111" }, { 17, "0000011000" }, { 18, "0000001000" }, { 19, "00001100111" }, + { 20, "00001101000" }, { 21, "00001101100" }, { 22, "00000110111" }, { 23, "00000101000" }, + { 24, "00000010111" }, { 25, "00000011000" }, { 26, "000011001010" }, { 27, "000011001011" }, + { 28, "000011001100" }, { 29, "000011001101" }, { 30, "000001101000" }, { 31, "000001101001" }, + { 32, "000001101010" }, { 33, "000001101011" }, { 34, "000011010010" }, { 35, "000011010011" }, + { 36, "000011010100" }, { 37, "000011010101" }, { 38, "000011010110" }, { 39, "000011010111" }, + { 40, "000001101100" }, { 41, "000001101101" }, { 42, "000011011010" }, { 43, "000011011011" }, + { 44, "000001010100" }, { 45, "000001010101" }, { 46, "000001010110" }, { 47, "000001010111" }, + { 48, "000001100100" }, { 49, "000001100101" }, { 50, "000001010010" }, { 51, "000001010011" }, + { 52, "000000100100" }, { 53, "000000110111" }, { 54, "000000111000" }, { 55, "000000100111" }, + { 56, "000000101000" }, { 57, "000001011000" }, { 58, "000001011001" }, { 59, "000000101011" }, + { 60, "000000101100" }, { 61, "000001011010" }, { 62, "000001100110" }, { 63, "000001100111" }, +}; + +static struct Huffcode huff_makeup_white[] = { + { 64, "11011" }, { 128, "10010" }, { 192, "010111" }, { 256, "0110111" }, + { 320, "00110110" }, { 384, "00110111" }, { 448, "01100100" }, { 512, "01100101" }, + { 576, "01101000" }, { 640, "01100111" }, { 704, "011001100" }, { 768, "011001101" }, + { 832, "011010010" }, { 896, "011010011" }, { 960, "011010100" }, { 1024, "011010101" }, + { 1088, "011010110" }, { 1152, "011010111" }, { 1216, "011011000" }, { 1280, "011011001" }, + { 1344, "011011010" }, { 1408, "011011011" }, { 1472, "010011000" }, { 1536, "010011001" }, + { 1600, "010011010" }, { 1664, "011000" }, { 1728, "010011011" }, { 1792, "00000001000" }, + { 1856, "00000001100" }, { 1920, "00000001101" }, { 1984, "000000010010" }, { 2048, "000000010011" }, + { 2112, "000000010100" }, { 2176, "000000010101" }, { 2240, "000000010110" }, { 2304, "000000010111" }, + { 2368, "000000011100" }, { 2432, "000000011101" }, { 2496, "000000011110" }, { 2560, "000000011111" }, +}; + +static struct Huffcode huff_makeup_black[] = { + { 64, "0000001111" }, { 128, "000011001000" }, { 192, "000011001001" }, { 256, "000001011011" }, + { 320, "000000110011" }, { 384, "000000110100" }, { 448, "000000110101" }, { 512, "0000001101100" }, + { 576, "0000001101101" }, { 640, "0000001001010" }, { 704, "0000001001011" }, { 768, "0000001001100" }, + { 832, "0000001001101" }, { 896, "0000001110010" }, { 960, "0000001110011" }, { 1024, "0000001110100" }, + { 1088, "0000001110101" }, { 1152, "0000001110110" }, { 1216, "0000001110111" }, { 1280, "0000001010010" }, + { 1344, "0000001010011" }, { 1408, "0000001010100" }, { 1472, "0000001010101" }, { 1536, "0000001011010" }, + { 1600, "0000001011011" }, { 1664, "0000001100100" }, { 1728, "0000001100101" }, { 1792, "00000001000" }, + { 1856, "00000001100" }, { 1920, "00000001101" }, { 1984, "000000010010" }, { 2048, "000000010011" }, + { 2112, "000000010100" }, { 2176, "000000010101" }, { 2240, "000000010110" }, { 2304, "000000010111" }, + { 2368, "000000011100" }, { 2432, "000000011101" }, { 2496, "000000011110" }, { 2560, "000000011111" }, +}; diff --git a/plug-ins/file-bmp/meson.build b/plug-ins/file-bmp/meson.build index e6a7bf0bed..8af34e9d30 100644 --- a/plug-ins/file-bmp/meson.build +++ b/plug-ins/file-bmp/meson.build @@ -1,9 +1,19 @@ plugin_name = 'file-bmp' +gen_huffman = executable('generate-huffman', + 'generate-huffman.c' +) + +huffman = custom_target('huffman.h', + output: 'huffman.h', + command: [gen_huffman, '@OUTPUT@'], +) + plugin_sources = [ 'bmp-load.c', 'bmp-export.c', 'bmp.c', + huffman, ] if platform_windows