1278 lines
36 KiB
C
1278 lines
36 KiB
C
/* bmpread.c reads any bitmap I could get for testing */
|
|
/* Alexander.Schulz@stud.uni-karlsruhe.de */
|
|
|
|
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <libgimp/gimp.h>
|
|
|
|
#include "bmp.h"
|
|
#include "bmp-load.h"
|
|
|
|
#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"
|
|
|
|
enum Bmpformat
|
|
{
|
|
BMPFMT_RGB,
|
|
BMPFMT_RGB_64,
|
|
BMPFMT_INDEXED,
|
|
BMPFMT_RLE,
|
|
BMPFMT_HUFFMAN
|
|
};
|
|
|
|
struct Fileinfo
|
|
{
|
|
FILE *file;
|
|
gint width;
|
|
gint height;
|
|
guchar *rowbuf;
|
|
guint64 bytes_per_row;
|
|
gint bpp;
|
|
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,
|
|
guchar *dest);
|
|
static gint load_rgb (struct Fileinfo *fi,
|
|
guchar *dest);
|
|
static gint load_indexed (struct Fileinfo *fi,
|
|
guchar *dest);
|
|
static gint load_rle (struct Fileinfo *fi,
|
|
guchar *dest);
|
|
static gint load_huffman (struct Fileinfo *fi,
|
|
guchar *dest);
|
|
|
|
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 gint32
|
|
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)
|
|
{
|
|
return (buffer[0] | buffer[1] << 8);
|
|
}
|
|
|
|
static gboolean
|
|
read_colormap (FILE *fd,
|
|
guchar *buffer,
|
|
gint number,
|
|
gint size,
|
|
gboolean *gray)
|
|
{
|
|
gint i;
|
|
|
|
*gray = (number > 2);
|
|
|
|
for (i = 0; i < number ; i++)
|
|
{
|
|
guchar rgb[4];
|
|
|
|
if (! ReadOK (fd, rgb, size))
|
|
{
|
|
g_message (_("Bad colormap"));
|
|
return FALSE;
|
|
}
|
|
|
|
/* BMP colormap entries are in BGR order */
|
|
|
|
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]));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
set_default_masks (gushort biBitCnt,
|
|
BitmapChannel *masks)
|
|
{
|
|
switch (biBitCnt)
|
|
{
|
|
case 24:
|
|
case 32:
|
|
masks[0].mask = 0x00ff0000;
|
|
masks[1].mask = 0x0000ff00;
|
|
masks[2].mask = 0x000000ff;
|
|
masks[3].mask = 0x00000000;
|
|
break;
|
|
|
|
case 16:
|
|
/* 5 bits per channel */
|
|
masks[0].mask = 0x00007c00;
|
|
masks[1].mask = 0x000003e0;
|
|
masks[2].mask = 0x0000001f;
|
|
masks[3].mask = 0x00000000;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
read_masks (guint32 *tmp,
|
|
BitmapChannel *masks)
|
|
{
|
|
gint 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 = masks[i].mask;
|
|
gint nbits = 0;
|
|
gint offset = 0;
|
|
|
|
while (mask && ! (mask & 1))
|
|
{
|
|
mask >>= 1;
|
|
offset++;
|
|
}
|
|
|
|
while (mask)
|
|
{
|
|
mask >>= 1;
|
|
nbits++;
|
|
}
|
|
|
|
masks[i].shiftin = offset;
|
|
masks[i].max_value = (gfloat) ((1 << nbits) - 1);
|
|
masks[i].nbits = nbits;
|
|
}
|
|
}
|
|
|
|
GimpImage *
|
|
load_image (GFile *gfile, GError **error)
|
|
{
|
|
struct Fileinfo fi;
|
|
BitmapFileHead bitmap_file_head;
|
|
BitmapHead bitmap_head;
|
|
guchar buffer[124];
|
|
gint maxcolors;
|
|
gint colorsize;
|
|
GimpImage *image = NULL;
|
|
gchar magick[2];
|
|
|
|
gimp_progress_init_printf (_("Opening '%s'"), gimp_file_get_utf8_name (gfile));
|
|
|
|
memset (&fi, 0, sizeof fi);
|
|
memset (&bitmap_file_head, 0, sizeof bitmap_file_head);
|
|
memset (&bitmap_head, 0, sizeof bitmap_head);
|
|
|
|
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 (gfile), g_strerror (errno));
|
|
goto out;
|
|
}
|
|
|
|
if (! ReadOK (fi.file, magick, 2) ||
|
|
! (! strncmp (magick, "BA", 2) ||
|
|
! strncmp (magick, "BM", 2) ||
|
|
! strncmp (magick, "IC", 2) ||
|
|
! strncmp (magick, "PT", 2) ||
|
|
! strncmp (magick, "CI", 2) ||
|
|
! 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 (gfile));
|
|
goto out;
|
|
}
|
|
|
|
while (! strncmp (magick, "BA", 2))
|
|
{
|
|
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 (gfile));
|
|
goto out;
|
|
}
|
|
|
|
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 (gfile));
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
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 (gfile));
|
|
goto out;
|
|
}
|
|
|
|
/* 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 (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 (gfile));
|
|
goto out;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
/* BITMAPCOREHEADER and OS21XBITMAPHEADER use 16bit int for
|
|
* width and height and have 3-byte color table entries */
|
|
colorsize = 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 >= 16)
|
|
{
|
|
/* all others use 32bit ints and have 4-byte color 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.
|
|
* Likewise for the following V4 fields, which would only be used
|
|
* when the header size is larger than any valid OS/2 header.
|
|
*/
|
|
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++)
|
|
{
|
|
/* endpoints are stored as 2.30 */
|
|
bitmap_head.bV4Endpoints[i] = (gdouble) ToL (&buffer[60 + 4 * i]) / 0x40000000UL;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* 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.biSize > sizeof buffer)
|
|
{
|
|
/* 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,
|
|
_("'%s' is not a valid BMP file"),
|
|
gimp_file_get_utf8_name (gfile));
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
/* BCA_HUFFMAN1D */
|
|
bitmap_head.biCompr = BI_OS2_HUFFMAN;
|
|
}
|
|
else if (bitmap_head.biCompr == BI_JPEG && bitmap_head.biBitCnt == 24)
|
|
{
|
|
/* BCA_RLE24 */
|
|
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 (not as part of) 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)
|
|
{
|
|
case 1:
|
|
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);
|
|
digest_masks (fi.masks);
|
|
break;
|
|
|
|
case BI_RGB:
|
|
set_default_masks (bitmap_head.biBitCnt, fi.masks);
|
|
digest_masks (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;
|
|
}
|
|
break;
|
|
|
|
case 64:
|
|
if (bitmap_head.biCompr != BI_RGB)
|
|
{
|
|
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;
|
|
|
|
default:
|
|
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;
|
|
}
|
|
|
|
/* Sanity checks */
|
|
|
|
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 (gfile));
|
|
goto out;
|
|
}
|
|
|
|
fi.bytes_per_row = (((guint64) bitmap_head.biWidth * bitmap_head.biBitCnt + 31) / 32) * 4;
|
|
|
|
if (fi.bytes_per_row > G_MAXSIZE || bitmap_head.biWidth > GIMP_MAX_IMAGE_SIZE ||
|
|
bitmap_head.biWidth < 1)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Unsupported or invalid image width: %d"),
|
|
(int) bitmap_head.biWidth);
|
|
goto out;
|
|
}
|
|
|
|
if (bitmap_head.biHeight < G_MININT + 1 || /* +1 because |G_MININT| > G_MAXINT. */
|
|
ABS (bitmap_head.biHeight) > GIMP_MAX_IMAGE_SIZE || bitmap_head.biHeight == 0)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Unsupported or invalid image height: %d"),
|
|
(int) bitmap_head.biHeight);
|
|
goto out;
|
|
}
|
|
|
|
if (fseek (fi.file, bitmap_file_head.bfOffs, SEEK_SET))
|
|
{
|
|
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;
|
|
}
|
|
|
|
fi.width = bitmap_head.biWidth;
|
|
fi.height = ABS (bitmap_head.biHeight);
|
|
fi.bpp = bitmap_head.biBitCnt;
|
|
|
|
image = ReadImage (&fi, gfile, bitmap_head.biCompr, error);
|
|
|
|
if (! image)
|
|
goto out;
|
|
|
|
if (bitmap_head.biXPels > 0 &&
|
|
bitmap_head.biYPels > 0)
|
|
{
|
|
/* Fixed up from scott@asofyet's changes last year, njl195 */
|
|
gdouble xresolution;
|
|
gdouble yresolution;
|
|
|
|
/* I don't agree with scott's feeling that Gimp should be trying
|
|
* to "fix" metric resolution translations, in the long term
|
|
* Gimp should be SI (metric) anyway, but we haven't told the
|
|
* Americans that yet
|
|
*/
|
|
|
|
xresolution = bitmap_head.biXPels * 0.0254;
|
|
yresolution = bitmap_head.biYPels * 0.0254;
|
|
|
|
gimp_image_set_resolution (image, xresolution, yresolution);
|
|
}
|
|
|
|
if (bitmap_head.biHeight < 0)
|
|
gimp_image_flip (image, GIMP_ORIENTATION_VERTICAL);
|
|
|
|
out:
|
|
if (fi.file)
|
|
fclose (fi.file);
|
|
|
|
return image;
|
|
}
|
|
|
|
static GimpImage *
|
|
ReadImage (struct Fileinfo *fi,
|
|
GFile *gfile,
|
|
gint compression,
|
|
GError **error)
|
|
{
|
|
gint ypos;
|
|
GimpImage *image;
|
|
GimpLayer *layer;
|
|
GeglBuffer *buffer;
|
|
gint i, cur_progress, max_progress;
|
|
GimpImageBaseType base_type;
|
|
GimpImageType image_type;
|
|
GimpPrecision precision_type = GIMP_PRECISION_U8_NON_LINEAR;
|
|
const Babl *format = NULL;
|
|
guchar *dest = NULL;
|
|
gsize dest_size = 0;
|
|
gsize rowstride;
|
|
gint maxbits;
|
|
gint eof = FALSE;
|
|
enum Bmpformat bmpfmt = 0;
|
|
|
|
switch (fi->bpp)
|
|
{
|
|
case 64:
|
|
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 (fi->masks[3].mask != 0 || compression == BI_OS2_RLE24)
|
|
{
|
|
image_type = GIMP_RGBA_IMAGE;
|
|
fi->channels = 4;
|
|
}
|
|
else
|
|
{
|
|
image_type = GIMP_RGB_IMAGE;
|
|
fi->channels = 3;
|
|
}
|
|
|
|
for (i = 0, maxbits = 0; i < fi->channels; i++)
|
|
maxbits = MAX (maxbits, fi->masks[i].nbits);
|
|
|
|
if (maxbits <= 8 || compression == BI_OS2_RLE24)
|
|
{
|
|
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 (fi->gray)
|
|
{
|
|
base_type = GIMP_GRAY;
|
|
image_type = GIMP_GRAY_IMAGE;
|
|
}
|
|
else
|
|
{
|
|
base_type = GIMP_INDEXED;
|
|
image_type = GIMP_INDEXED_IMAGE;
|
|
}
|
|
|
|
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:
|
|
g_message (_("Unsupported or invalid bitdepth."));
|
|
return NULL;
|
|
}
|
|
|
|
fi->tile_height = MIN (gimp_tile_height (), fi->height);
|
|
|
|
if ((guint64) fi->width * fi->tile_height < G_MAXSIZE / (fi->channels * fi->bytesperchannel))
|
|
{
|
|
dest_size = (gsize) fi->width * fi->tile_height * fi->channels * fi->bytesperchannel;
|
|
dest = g_try_malloc0 (dest_size);
|
|
fi->rowbuf = g_try_malloc (MAX (4, fi->bytes_per_row));
|
|
}
|
|
|
|
if (! (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 (dest);
|
|
g_free (fi->rowbuf);
|
|
return NULL;
|
|
}
|
|
|
|
image = gimp_image_new_with_precision (fi->width, fi->height, base_type, precision_type);
|
|
|
|
layer = gimp_layer_new (image, _("Background"),
|
|
fi->width, fi->height,
|
|
image_type, 100,
|
|
gimp_image_get_default_new_layer_mode (image));
|
|
gimp_image_insert_layer (image, layer, NULL, 0);
|
|
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
|
|
|
|
rowstride = (gsize) fi->width * fi->channels * fi->bytesperchannel;
|
|
|
|
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) * rowstride;
|
|
|
|
switch (bmpfmt)
|
|
{
|
|
case BMPFMT_RGB:
|
|
if (! load_rgb (fi, dest + offset))
|
|
eof = TRUE;
|
|
break;
|
|
|
|
case BMPFMT_RGB_64:
|
|
if (! load_rgb_64 (fi, dest + offset))
|
|
eof = TRUE;
|
|
break;
|
|
|
|
case BMPFMT_INDEXED:
|
|
if (! load_indexed (fi, dest + offset))
|
|
eof = TRUE;
|
|
break;
|
|
|
|
case BMPFMT_RLE:
|
|
if (fi->file_ypos < ypos)
|
|
break;
|
|
|
|
if (! load_rle (fi, dest + offset))
|
|
eof = TRUE;
|
|
break;
|
|
|
|
case BMPFMT_HUFFMAN:
|
|
if (! load_huffman (fi, dest + 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)
|
|
*/
|
|
offset = (fi->tile_height - fi->tile_n) * rowstride;
|
|
|
|
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, ypos, fi->width, fi->tile_n), 0, format,
|
|
dest + offset, GEGL_AUTO_ROWSTRIDE);
|
|
memset (dest, 0, dest_size);
|
|
fi->tile_n = 0;
|
|
}
|
|
}
|
|
|
|
g_object_unref (buffer);
|
|
|
|
g_free (dest);
|
|
|
|
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, guchar *dest)
|
|
{
|
|
gint xpos, i;
|
|
gint32 px;
|
|
gdouble d;
|
|
guint16 *dest16;
|
|
guint32 *dest32;
|
|
|
|
if (! ReadOK (fi->file, fi->rowbuf, fi->bytes_per_row))
|
|
return FALSE;
|
|
|
|
dest16 = (guint16 *) dest;
|
|
dest32 = (guint32 *) dest;
|
|
|
|
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)
|
|
*dest++ = 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, guchar *dest)
|
|
{
|
|
gint xpos, i;
|
|
gfloat *destflt;
|
|
|
|
if (! ReadOK (fi->file, fi->rowbuf, fi->bytes_per_row))
|
|
return FALSE;
|
|
|
|
destflt = (gfloat *) dest;
|
|
|
|
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, guchar *dest)
|
|
{
|
|
gint xpos, val, shift;
|
|
|
|
if (! ReadOK (fi->file, fi->rowbuf, fi->bytes_per_row))
|
|
return FALSE;
|
|
|
|
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, guchar *basedest)
|
|
{
|
|
gint shift, i, j;
|
|
guchar *dest;
|
|
|
|
/* dest must be (re)calculated inside loop, because RLE can skip pixles */
|
|
|
|
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 = basedest + 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 = basedest + fi->xpos * fi->channels;
|
|
for (gint c = 0; c < 3; c++)
|
|
dest[2 - c] = fi->rowbuf[1 + c];
|
|
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 = basedest + 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 (gint 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, guchar *dest)
|
|
{
|
|
gint xpos = 0, len, i;
|
|
|
|
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)
|
|
{
|
|
for (i = 0; i < len; i++, xpos++)
|
|
*dest++ = 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;
|
|
}
|