1
0
Fork 0
gimp/plug-ins/file-bmp/bmp-load.c
Daniel Baumann 554424e00a
Adding upstream version 3.0.4.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-23 00:14:50 +02:00

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;
}