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