/* 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 . * ---------------------------------------------------------------------------- */ #include "config.h" #include #include #include #include #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; }