/*
 * pngxrbmp.c - libpng external I/O: BMP reader.
 * Copyright (C) 2003-2016 Cosmin Truta and the Contributing Authors.
 *
 * This code was derived from "bmp2png.c" by MIYASAKA Masaru, and
 * is distributed under the same copyright and warranty terms as libpng.
 */

#include "pngxtern.h"
#include "pngxutil.h"
#include <stdio.h>
#include <string.h>

#define PNGX_INTERNAL
#include "pngxpriv.h"


/*****************************************************************************/
/* BMP file header macros                                                    */
/*****************************************************************************/

/* BMP file signature */
#define BMP_SIGNATURE       0x4d42  /* "BM" */
#define BMP_SIG_BYTES       2

/* BITMAPFILEHEADER */
#define BFH_WTYPE           0       /* WORD   bfType;           */
#define BFH_DSIZE           2       /* DWORD  bfSize;           */
#define BFH_WRESERVED1      6       /* WORD   bfReserved1;      */
#define BFH_WRESERVED2      8       /* WORD   bfReserved2;      */
#define BFH_DOFFBITS        10      /* DWORD  bfOffBits;        */
#define BFH_DBIHSIZE        14      /* DWORD  biSize;           */
#define FILEHED_SIZE        14      /* sizeof(BITMAPFILEHEADER) */
#define BIHSIZE_SIZE        4       /* sizeof(biSize)           */

/* BITMAPINFOHEADER */
#define BIH_DSIZE           0       /* DWORD  biSize;             */
#define BIH_LWIDTH          4       /* LONG   biWidth;            */
#define BIH_LHEIGHT         8       /* LONG   biHeight;           */
#define BIH_WPLANES         12      /* WORD   biPlanes;           */
#define BIH_WBITCOUNT       14      /* WORD   biBitCount;         */
#define BIH_DCOMPRESSION    16      /* DWORD  biCompression;      */
#define BIH_DSIZEIMAGE      20      /* DWORD  biSizeImage;        */
#define BIH_LXPELSPERMETER  24      /* LONG   biXPelsPerMeter;    */
#define BIH_LYPELSPERMETER  28      /* LONG   biYPelsPerMeter;    */
#define BIH_DCLRUSED        32      /* DWORD  biClrUsed;          */
#define BIH_DCLRIMPORTANT   36      /* DWORD  biClrImportant;     */
#define B4H_DREDMASK        40      /* DWORD  bV4RedMask;         */
#define B4H_DGREENMASK      44      /* DWORD  bV4GreenMask;       */
#define B4H_DBLUEMASK       48      /* DWORD  bV4BlueMask;        */
#define B4H_DALPHAMASK      52      /* DWORD  bV4AlphaMask;       */
#define B4H_DCSTYPE         56      /* DWORD  bV4CSType;          */
#define B4H_XENDPOINTS      60      /* CIEXYZTRIPLE bV4Endpoints; */
#define B4H_DGAMMARED       96      /* DWORD  bV4GammaRed;        */
#define B4H_DGAMMAGREEN     100     /* DWORD  bV4GammaGreen;      */
#define B4H_DGAMMABLUE      104     /* DWORD  bV4GammaBlue;       */
#define B5H_DINTENT         108     /* DWORD  bV5Intent;          */
#define B5H_DPROFILEDATA    112     /* DWORD  bV5ProfileData;     */
#define B5H_DPROFILESIZE    116     /* DWORD  bV5ProfileSize;     */
#define B5H_DRESERVED       120     /* DWORD  bV5Reserved;        */
#define INFOHED_SIZE        40      /* sizeof(BITMAPINFOHEADER)   */
#define BMPV4HED_SIZE       108     /* sizeof(BITMAPV4HEADER)     */
#define BMPV5HED_SIZE       124     /* sizeof(BITMAPV5HEADER)     */

/* BITMAPCOREHEADER */
#define BCH_DSIZE           0       /* DWORD  bcSize;           */
#define BCH_WWIDTH          4       /* WORD   bcWidth;          */
#define BCH_WHEIGHT         6       /* WORD   bcHeight;         */
#define BCH_WPLANES         8       /* WORD   bcPlanes;         */
#define BCH_WBITCOUNT       10      /* WORD   bcBitCount;       */
#define COREHED_SIZE        12      /* sizeof(BITMAPCOREHEADER) */

/* RGB */
#define RGB_BLUE            0       /* BYTE   rgbBlue;     */
#define RGB_GREEN           1       /* BYTE   rgbGreen;    */
#define RGB_RED             2       /* BYTE   rgbRed;      */
#define RGB_RESERVED        3       /* BYTE   rgbReserved; */
#define RGBTRIPLE_SIZE      3       /* sizeof(RGBTRIPLE)   */
#define RGBQUAD_SIZE        4       /* sizeof(RGBQUAD)     */

/* Constants for the biCompression field */
#ifndef BI_RGB
#define BI_RGB              0       /* Uncompressed format       */
#define BI_RLE8             1       /* RLE format (8 bits/pixel) */
#define BI_RLE4             2       /* RLE format (4 bits/pixel) */
#define BI_BITFIELDS        3       /* Bitfield format           */
#define BI_JPEG             4       /* JPEG format               */
#define BI_PNG              5       /* PNG format                */
#endif


/*****************************************************************************/
/* BMP memory utilities                                                      */
/*****************************************************************************/

static unsigned int
bmp_get_word(png_bytep ptr)
{
   return (unsigned int)ptr[0] + ((unsigned int)ptr[1] << 8);
}

static png_uint_32
bmp_get_dword(png_bytep ptr)
{
   return ((png_uint_32)ptr[0])       + ((png_uint_32)ptr[1] << 8) +
          ((png_uint_32)ptr[2] << 16) + ((png_uint_32)ptr[3] << 24);
}


/*****************************************************************************/
/* BMP helpers                                                               */
/*****************************************************************************/

static void
bmp_memset_bytes(png_bytep ptr, size_t offset, int ch, size_t len)
{
   memset(ptr + offset, ch, len);
}

static void
bmp_memset_halfbytes(png_bytep ptr, size_t offset, int ch, size_t len)
{
   if (len == 0)
      return;
   ptr += offset / 2;
   if (offset & 1)  /* use half-byte operations at odd offset */
   {
      *ptr = (png_byte)((*ptr & 0xf0) | (ch & 0x0f));
      ch = ((ch & 0xf0) >> 4) | ((ch & 0x0f) << 4);
      ++ptr;
      --len;
   }
   memset(ptr, ch, len / 2);
   if (len & 1)
      ptr[len / 2] = (png_byte)(ch & 0xf0);
}

static size_t
bmp_fread_bytes(png_bytep ptr, size_t offset, size_t len, FILE *stream)
{
   size_t result;

   result = fread(ptr + offset, 1, len, stream);
   if (len & 1)
      getc(stream);  /* skip padding */
   return result;
}

static size_t
bmp_fread_halfbytes(png_bytep ptr, size_t offset, size_t len, FILE *stream)
{
   size_t result;
   int ch;

   if (len == 0)
      return 0;
   ptr += offset / 2;
   if (offset & 1)  /* use half-byte operations at odd offset */
   {
      for (result = 0; result < len - 1; result += 2)
      {
         ch = getc(stream);
         if (ch == EOF)
            break;
         *ptr = (png_byte)((*ptr & 0xf0) | ((ch & 0xf0) >> 4));
         ++ptr;
         *ptr = (png_byte)((ch & 0x0f) << 4);
      }
   }
   else
   {
      result = fread(ptr, 1, (len + 1) / 2, stream) * 2;
   }
   if (len & 2)
      getc(stream);  /* skip padding */
   return (result <= len) ? result : len;
}


/*****************************************************************************/
/* BMP packbit (BITFIELDS) helpers                                           */
/*****************************************************************************/

static void
bmp_process_mask(png_uint_32 bmp_mask, png_bytep sig_bit, png_bytep shift_bit)
{
   *sig_bit = *shift_bit = (png_byte)0;
   if (bmp_mask == 0)
      return;
   while ((bmp_mask & 1) == 0)
   {
      bmp_mask >>= 1;
      ++*shift_bit;
   }
   while (bmp_mask != 0)
   {
      if ((bmp_mask & 1) == 0 || *sig_bit >= 8)
      {
         *sig_bit = (png_byte)0;
         return;
      }
      bmp_mask >>= 1;
      ++*sig_bit;
   }
}


/*****************************************************************************/
/* BMP I/O utilities                                                         */
/*****************************************************************************/

static size_t
bmp_read_rows(png_bytepp begin_row, png_bytepp end_row, size_t row_size,
              unsigned int compression, FILE *stream)
{
   size_t result;
   png_bytepp crt_row;
   int inc;
   size_t crtn, dcrtn, endn;
   unsigned int len, b1, b2;
   int ch;
   void (*bmp_memset_fn)(png_bytep, size_t, int, size_t);
   size_t (*bmp_fread_fn)(png_bytep, size_t, size_t, FILE *);

   if (row_size == 0)
      return 0;  /* this should not happen */

   inc = (begin_row <= end_row) ? 1 : -1;
   crtn = 0;
   result = 0;
   if (compression == BI_RLE4)
   {
      endn = row_size * 2;
      if (endn <= row_size)
         return 0;  /* overflow */
      bmp_memset_fn = bmp_memset_halfbytes;
      bmp_fread_fn = bmp_fread_halfbytes;
   }
   else
   {
      endn = row_size;
      bmp_memset_fn = bmp_memset_bytes;
      bmp_fread_fn = bmp_fread_bytes;
   }

   if (compression == BI_RGB || compression == BI_BITFIELDS)
   {
      /* Read uncompressed bitmap. */
      for (crt_row = begin_row; crt_row != end_row; crt_row += inc)
      {
         crtn = bmp_fread_fn(*crt_row, 0, endn, stream);
         if (crtn != endn)
            break;
         ++result;
      }
   }
   else if (compression == BI_RLE8 || compression == BI_RLE4)
   {
      /* Read RLE-compressed bitmap. */
      if (compression == BI_RLE8)
      {
         endn = row_size;
      }
      else  /* BI_RLE4 */
      {
         endn = row_size * 2;
         if (endn <= row_size)
            return 0;  /* overflow */
      }
      for (crt_row = begin_row; crt_row != end_row; )
      {
         ch = getc(stream); b1 = (unsigned int)ch;
         ch = getc(stream); b2 = (unsigned int)ch;
         if (ch == EOF)
            break;
         if (b1 == 0)  /* escape */
         {
            if (b2 == 0)  /* end of line */
            {
               bmp_memset_fn(*crt_row, crtn, 0, endn - crtn);
               crt_row += inc;
               crtn = 0;
               ++result;
               if (crt_row == end_row)  /* all rows are read */
               {
                  ch = getc(stream);  /* check for the end of bitmap */
                  if (ch != EOF && ch != 0)
                  {
                     ungetc(ch, stream);  /* forget about the end of bitmap */
                     break;
                  }
                  getc(stream);  /* expect 1, but break the loop anyway */
                  break;
               }
            }
            else if (b2 == 1)  /* end of bitmap */
            {
               bmp_memset_fn(*crt_row, crtn, 0, endn - crtn);
               crt_row += inc;
               crtn = 0;
               result = (begin_row <= end_row) ?
                  (end_row - begin_row) : (begin_row - end_row);
               break;  /* the rest is wiped out at the end */
            }
            else if (b2 == 2)  /* delta */
            {
               ch = getc(stream); b1 = (unsigned int)ch;  /* horiz. offset */
               ch = getc(stream); b2 = (unsigned int)ch;  /* vert. offset */
               if (ch == EOF)
                  break;
               dcrtn = (b1 < endn - crtn) ? (crtn + b1) : endn;
               for ( ; b2 > 0; --b2)
               {
                  bmp_memset_fn(*crt_row, crtn, 0, endn - crtn);
                  crt_row += inc;
                  crtn = 0;
                  ++result;
                  if (crt_row == end_row)
                      break;
               }
               if (crt_row != end_row)
                  bmp_memset_fn(*crt_row, crtn, 0, dcrtn - crtn);
            }
            else  /* b2 >= 3 bytes in absolute mode */
            {
               len = (b2 <= endn - crtn) ? b2 : (unsigned int)(endn - crtn);
               if (bmp_fread_fn(*crt_row, crtn, len, stream) != len)
                  break;
               crtn += len;
            }
         }
         else  /* b1 > 0 bytes in run-length encoded mode */
         {
            len = (b1 <= endn - crtn) ? b1 : (unsigned int)(endn - crtn);
            bmp_memset_fn(*crt_row, crtn, (int)b2, len);
            crtn += len;
         }
      }
   }
   else
      return 0;  /* error: compression method not applicable. */

   /* Wipe out the portion left unread. */
   for ( ; crt_row != end_row; crt_row += inc)
   {
      bmp_memset_fn(*crt_row, crtn, 0, endn - crtn);
      crtn = 0;
   }

   return result;
}


/*****************************************************************************/
/* BMP-to-PNG sample conversion                                              */
/*****************************************************************************/

static void
bmp_to_png_rows(png_bytepp row_pointers,
                png_uint_32 width, png_uint_32 height, unsigned int pixdepth,
                png_bytep rgba_sig, png_bytep rgba_shift)
{
   png_bytep src_ptr, dest_ptr;
   unsigned int rgba_mask[4];
   unsigned int num_samples, sample, mask;
   unsigned int wpix;
   png_uint_32 dwpix;
   png_uint_32 x, y;
   unsigned int i;

   if (pixdepth == 24)  /* BGR -> RGB */
   {
      for (y = 0; y < height; ++y)
      {
         src_ptr = row_pointers[y];
         for (x = 0; x < width; ++x, src_ptr += 3)
         {
            png_byte tmp = src_ptr[0];
            src_ptr[0] = src_ptr[2];
            src_ptr[2] = tmp;
         }
      }
      return;
   }

   num_samples = (rgba_sig[3] != 0) ? 4 : 3;
   for (i = 0; i < num_samples; ++i)
      rgba_mask[i] = (1U << rgba_sig[i]) - 1;

   if (pixdepth == 16)
   {
      for (y = 0; y < height; ++y)
      {
         src_ptr = row_pointers[y] + (width - 1) * 2;
         dest_ptr = row_pointers[y] + (width - 1) * num_samples;
         for (x = 0; x < width; ++x, src_ptr -= 2, dest_ptr -= num_samples)
         {
            /* Inline bmp_get_word() for performance reasons. */
            wpix = (unsigned int)src_ptr[0] + ((unsigned int)src_ptr[1] << 8);
            for (i = 0; i < num_samples; ++i)
            {
               mask = rgba_mask[i];
               sample = (wpix >> rgba_shift[i]) & mask;
               dest_ptr[i] = (png_byte)((sample * 255 + mask / 2) / mask);
            }
         }
      }
   }
   else if (pixdepth == 32)
   {
      for (y = 0; y < height; ++y)
      {
         src_ptr = dest_ptr = row_pointers[y];
         for (x = 0; x < width; ++x, src_ptr += 4, dest_ptr += num_samples)
         {
            /* Inline bmp_get_dword() for performance reasons. */
            dwpix = (png_uint_32)src_ptr[0] + ((png_uint_32)src_ptr[1] << 8) +
            ((png_uint_32)src_ptr[2] << 16) + ((png_uint_32)src_ptr[3] << 24);
            for (i = 0; i < num_samples; ++i)
            {
               mask = rgba_mask[i];
               sample = (dwpix >> rgba_shift[i]) & mask;
               dest_ptr[i] = (png_byte)((sample * 255 + mask / 2) / mask);
            }
         }
      }
   }
   /* else do nothing */
}


/*****************************************************************************/
/* BMP read support for pngxtern                                             */
/*****************************************************************************/

int /* PRIVATE */
pngx_sig_is_bmp(png_bytep sig, size_t sig_size,
                png_const_charpp fmt_name_ptr,
                png_const_charpp fmt_long_name_ptr)
{
   static const char bmp_fmt_name[] = "BMP";
   static const char os2bmp_fmt_long_name[] = "OS/2 Bitmap";
   static const char winbmp_fmt_long_name[] = "Windows Bitmap";
   png_uint_32 bihsize;

   /* Require at least the bitmap file header and the subsequent 4 bytes. */
   if (sig_size < FILEHED_SIZE + 4)
      return -1;  /* insufficient data */
   if (bmp_get_word(sig) != BMP_SIGNATURE)
      return 0;  /* not BMP */
   /* Avoid using bfhsize because it is not reliable. */
   bihsize = bmp_get_dword(sig + FILEHED_SIZE);
   if ((bihsize > PNG_UINT_31_MAX) ||
       (bihsize != COREHED_SIZE && bihsize < INFOHED_SIZE))
      return 0;  /* garbage in bihsize, this cannot be BMP */

   /* Store the format name. */
   if (fmt_name_ptr != NULL)
      *fmt_name_ptr = bmp_fmt_name;
   if (fmt_long_name_ptr != NULL)
   {
      if (bihsize == COREHED_SIZE)
         *fmt_long_name_ptr = os2bmp_fmt_long_name;
      else
         *fmt_long_name_ptr = winbmp_fmt_long_name;
   }
   return 1;  /* BMP */
}

int /* PRIVATE */
pngx_read_bmp(png_structp png_ptr, png_infop info_ptr, FILE *stream)
{
   png_byte bfh[FILEHED_SIZE + BMPV5HED_SIZE];
   png_bytep const bih = bfh + FILEHED_SIZE;
   png_byte rgbq[RGBQUAD_SIZE];
   png_uint_32 offbits, bihsize, skip;
   png_uint_32 width, height, rowsize;
   int topdown;
   unsigned int pixdepth;
   png_uint_32 compression;
   unsigned int palsize, palnum;
   png_uint_32 rgba_mask[4];
   png_byte rgba_sig[4], rgba_shift[4];
   int bit_depth, color_type;
   png_color palette[256];
   png_color_8 sig_bit;
   png_bytepp row_pointers, begin_row, end_row;
   unsigned int i;
   size_t y;

   /* Find the BMP header. */
   for (i = 0; ; ++i)  /* skip macbinary header */
   {
      if (fread(bfh, FILEHED_SIZE + BIHSIZE_SIZE, 1, stream) != 1)
         ++i;
      else if (bmp_get_word(bfh + BFH_WTYPE) == BMP_SIGNATURE)
         break;
      if (fread(bfh, 128 - FILEHED_SIZE - BIHSIZE_SIZE, 1, stream) != 1)
         ++i;
      if (i > 0)
         return 0;  /* not a BMP file */
   }

   /* Read the BMP header. */
   offbits = bmp_get_dword(bfh + BFH_DOFFBITS);
   bihsize = bmp_get_dword(bfh + BFH_DBIHSIZE);
   if ((offbits > PNG_UINT_31_MAX) || (bihsize > PNG_UINT_31_MAX) ||
       (offbits < bihsize + FILEHED_SIZE) ||
       (bihsize != COREHED_SIZE && bihsize < INFOHED_SIZE))
      return 0;  /* not a BMP file, just a file with a matching signature */
   if (bihsize > BMPV5HED_SIZE)
   {
      skip = bihsize - BMPV5HED_SIZE;
      bihsize = BMPV5HED_SIZE;
   }
   else
      skip = 0;
   if (fread(bih + BIHSIZE_SIZE, bihsize - BIHSIZE_SIZE, 1, stream) != 1)
      return 0;
   if (skip > 0)
      if (fseek(stream, (long)skip, SEEK_CUR) != 0)
         return 0;
   skip = offbits - bihsize - FILEHED_SIZE;  /* new skip */
   topdown = 0;
   if (bihsize < INFOHED_SIZE)  /* OS/2 BMP */
   {
      width       = bmp_get_word(bih + BCH_WWIDTH);
      height      = bmp_get_word(bih + BCH_WHEIGHT);
      pixdepth    = bmp_get_word(bih + BCH_WBITCOUNT);
      compression = BI_RGB;
      palsize     = RGBTRIPLE_SIZE;
   }
   else  /* Windows BMP */
   {
      width       = bmp_get_dword(bih + BIH_LWIDTH);
      height      = bmp_get_dword(bih + BIH_LHEIGHT);
      pixdepth    = bmp_get_word(bih + BIH_WBITCOUNT);
      compression = bmp_get_dword(bih + BIH_DCOMPRESSION);
      palsize     = RGBQUAD_SIZE;
      if (height > PNG_UINT_31_MAX)  /* top-down BMP */
      {
         height  = PNG_UINT_32_MAX - height + 1;
         topdown = 1;
      }
      if (bihsize == INFOHED_SIZE && compression == BI_BITFIELDS)
      {
         /* Read the RGB[A] mask. */
         i = (skip <= 16) ? (unsigned int)skip : 16;
         if (fread(bih + B4H_DREDMASK, i, 1, stream) != 1)
            return 0;
         bihsize += i;
         skip -= i;
      }
   }

   memset(rgba_mask, 0, sizeof(rgba_mask));
   if (pixdepth > 8)
   {
      if (compression == BI_RGB)
      {
         if (pixdepth == 16)
         {
            compression  = BI_BITFIELDS;
            rgba_mask[0] = 0x7c00;
            rgba_mask[1] = 0x03e0;
            rgba_mask[2] = 0x001f;
         }
         else  /* pixdepth == 24 || pixdepth == 32 */
         {
            rgba_mask[0] = (png_uint_32)0x00ff0000L;
            rgba_mask[1] = (png_uint_32)0x0000ff00L;
            rgba_mask[2] = (png_uint_32)0x000000ffL;
         }
      }
      else if (compression == BI_BITFIELDS)
      {
         if (bihsize >= INFOHED_SIZE + 12)
         {
            rgba_mask[0] = bmp_get_dword(bih + B4H_DREDMASK);
            rgba_mask[1] = bmp_get_dword(bih + B4H_DGREENMASK);
            rgba_mask[2] = bmp_get_dword(bih + B4H_DBLUEMASK);
         }
         else
            png_error(png_ptr, "Missing color mask in BMP file");
      }
      if (bihsize >= INFOHED_SIZE + 16)
         rgba_mask[3] = bmp_get_dword(bih + B4H_DALPHAMASK);
   }

   switch (compression)
   {
   case BI_RGB:
      /* Allow pixel depth values 1, 2, 4, 8, 16, 24, 32.
       * (Although the BMP spec does not mention pixel depth = 2,
       * it is supported for robustness reasons, at no extra cost.)
       */
      if (pixdepth > 0 && 32 % pixdepth != 0 && pixdepth != 24)
         pixdepth = 0;
      break;
   case BI_RLE8:
      if (pixdepth != 8)
         pixdepth = 0;
      break;
   case BI_RLE4:
      if (pixdepth != 4)
         pixdepth = 0;
      break;
   case BI_BITFIELDS:
      if (pixdepth != 16 && pixdepth != 32)
         pixdepth = 0;
      break;
   case BI_JPEG:
      png_error(png_ptr, "JPEG-compressed BMP files are not supported");
      /* NOTREACHED */
      break;
   case BI_PNG:
      if (ungetc(getc(stream), stream) == 0)  /* IHDR is likely to follow */
         png_set_sig_bytes(png_ptr, 8);
      png_set_read_fn(png_ptr, stream, NULL);
      png_read_png(png_ptr, info_ptr, 0, NULL);
      /* TODO: Check for mismatches between the BMP and PNG info. */
      return 1;
   default:
      png_error(png_ptr, "Unsupported compression method in BMP file");
   }

   /* Check the BMP image parameters. */
   if (width == 0 || width > PNG_UINT_31_MAX || height == 0)
      png_error(png_ptr, "Invalid image dimensions in BMP file");
   if (pixdepth == 0)
      png_error(png_ptr, "Invalid pixel depth in BMP file");

   /* Compute the PNG image parameters. */
   if (pixdepth <= 8)
   {
      palnum = skip / palsize;
      if (palnum > 256)
         palnum = 256;
      skip -= palsize * palnum;
      rowsize = (width + (32 / pixdepth) - 1) / (32 / pixdepth) * 4;
      /* rowsize becomes 0 on overflow. */
      bit_depth = pixdepth;
      color_type = (palnum > 0) ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_GRAY;
   }
   else
   {
      palnum = 0;
      bit_depth = 8;
      switch (pixdepth)
      {
      case 16:
         rowsize = (width * 2 + 3) & (~3);
         break;
      case 24:
         rowsize = (width * 3 + 3) & (~3);
         break;
      case 32:
         rowsize = width * 4;
         break;
      default:  /* never get here */
         bit_depth = 0;
         rowsize = 0;
      }
      if (rowsize / width < pixdepth / 8)
         rowsize = 0;  /* overflow */
      color_type = (rgba_mask[3] != 0) ?
         PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB;
   }
   if (rowsize == 0)
      png_error(png_ptr, "Can't handle exceedingly large BMP dimensions");

   /* Set the PNG image type. */
   png_set_IHDR(png_ptr, info_ptr,
      width, height, bit_depth, color_type,
      PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
   if (pixdepth > 8)
   {
      for (i = 0; i < 4; ++i)
         bmp_process_mask(rgba_mask[i], &rgba_sig[i], &rgba_shift[i]);
      if (rgba_sig[0] == 0 || rgba_sig[1] == 0 || rgba_sig[2] == 0)
         png_error(png_ptr, "Invalid color mask in BMP file");
      if (rgba_sig[0] != 8 || rgba_sig[1] != 8 ||
          rgba_sig[2] != 8 || (rgba_sig[3] != 0 && rgba_sig[3] != 8))
      {
         sig_bit.red   = rgba_sig[0];
         sig_bit.green = rgba_sig[1];
         sig_bit.blue  = rgba_sig[2];
         sig_bit.alpha = rgba_sig[3];
         png_set_sBIT(png_ptr, info_ptr, &sig_bit);
      }
   }

   /* Read the color palette (if applicable). */
   if (palnum > 0)
   {
      for (i = 0; i < palnum; ++i)
      {
         if (fread(rgbq, palsize, 1, stream) != 1)
            break;
         palette[i].red   = rgbq[RGB_RED];
         palette[i].green = rgbq[RGB_GREEN];
         palette[i].blue  = rgbq[RGB_BLUE];
      }
      png_set_PLTE(png_ptr, info_ptr, palette, i);
      if (i != palnum)
         png_error(png_ptr, "Error reading color palette in BMP file");
   }

   /* Allocate memory and read the image data. */
   row_pointers = pngx_malloc_rows_extended(png_ptr, info_ptr, rowsize, -1);
   if (topdown)
   {
      begin_row = row_pointers;
      end_row = row_pointers + height;
   }
   else
   {
      begin_row = row_pointers + height - 1;
      end_row = row_pointers - 1;
   }
   if (skip > 0)
      fseek(stream, (long)skip, SEEK_CUR);
   y = bmp_read_rows(begin_row, end_row, rowsize, compression, stream);

   /* Postprocess the image data, even if it has not been read entirely. */
   if (pixdepth > 8)
      bmp_to_png_rows(row_pointers, width, height, pixdepth,
         rgba_sig, rgba_shift);

   /* Check the result. */
   if (y != (size_t)height)
      png_error(png_ptr, "Error reading BMP file");

   return 1;  /* one image has been successfully read */
}
