text: /*====================================================================*
   -  Copyright (C) 2001 Leptonica.  All rights reserved.
   -
   -  Redistribution and use in source and binary forms, with or without
   -  modification, are permitted provided that the following conditions
   -  are met:
   -  1. Redistributions of source code must retain the above copyright
   -     notice, this list of conditions and the following disclaimer.
   -  2. Redistributions in binary form must reproduce the above
   -     copyright notice, this list of conditions and the following
   -     disclaimer in the documentation and/or other materials
   -     provided with the distribution.
   -
   -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
   -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
   -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *====================================================================*/
  
  /*!
   * \file pnmio.c
   * <pre>
   *
   *      Stream interface
   *          PIX             *pixReadStreamPnm()
   *          l_int32          readHeaderPnm()
   *          l_int32          freadHeaderPnm()
   *          l_int32          pixWriteStreamPnm()
   *          l_int32          pixWriteStreamAsciiPnm()
   *          l_int32          pixWriteStreamPam()
   *
   *      Read/write to memory
   *          PIX             *pixReadMemPnm()
   *          l_int32          readHeaderMemPnm()
   *          l_int32          pixWriteMemPnm()
   *          l_int32          pixWriteMemPam()
   *
   *      Local helpers
   *          static l_int32   pnmReadNextAsciiValue();
   *          static l_int32   pnmReadNextNumber();
   *          static l_int32   pnmReadNextString();
   *          static l_int32   pnmSkipCommentLines();
   *
   *      These are here by popular demand, with the help of Mattias
   *      Kregert (mattias@kregert.se), who provided the first implementation.
   *
   *      The pnm formats are exceedingly simple, because they have
   *      no compression and no colormaps.  They support images that
   *      are 1 bpp; 2, 4, 8 and 16 bpp grayscale; and rgb.
   *
   *      The original pnm formats ("ASCII") are included for completeness,
   *      but their use is deprecated for all but tiny iconic images.
   *      They are extremely wasteful of memory; for example, the P1 binary
   *      ASCII format is 16 times as big as the packed uncompressed
   *      format, because 2 characters are used to represent every bit
   *      (pixel) in the image.  Reading is slow because we check for extra
   *      white space and EOL at every sample value.
   *
   *      The packed pnm formats ("raw") give file sizes similar to
   *      bmp files, which are uncompressed packed.  However, bmp
   *      are more flexible, because they can support colormaps.
   *
   *      We don't differentiate between the different types ("pbm",
   *      "pgm", "ppm") at the interface level, because this is really a
   *      "distinction without a difference."  You read a file, you get
   *      the appropriate Pix.  You write a file from a Pix, you get the
   *      appropriate type of file.  If there is a colormap on the Pix,
   *      and the Pix is more than 1 bpp, you get either an 8 bpp pgm
   *      or a 24 bpp RGB pnm, depending on whether the colormap colors
   *      are gray or rgb, respectively.
   *
   *      This follows the general policy that the I/O routines don't
   *      make decisions about the content of the image -- you do that
   *      with image processing before you write it out to file.
   *      The I/O routines just try to make the closest connection
   *      possible between the file and the Pix in memory.
   *
   *      On systems like windows without fmemopen() and open_memstream(),
   *      we write data to a temp file and read it back for operations
   *      between pix and compressed-data, such as pixReadMemPnm() and
   *      pixWriteMemPnm().
   *
   *      The P7 format is new. It introduced a header with multiple
   *      lines containing distinct tags for the various fields.
   *      See: http://netpbm.sourceforge.net/doc/pam.html
   *
   *        WIDTH <int>         ; mandatory, exactly once
   *        HEIGHT <int>        ; mandatory, exactly once
   *        DEPTH <int>         ; mandatory, exactly once,
   *                            ; its meaning is equivalent to spp
   *        MAXVAL <int>        ; mandatory, one of 1, 3, 15, 255 or 65535
   *        TUPLTYPE <string>   ; optional; BLACKANDWHITE, GRAYSCALE, RGB
   *                            ; and optional suffix _ALPHA, e.g. RGB_ALPHA
   *        ENDHDR              ; mandatory, last header line
   *
   *      Reading BLACKANDWHITE_ALPHA and GRAYSCALE_ALPHA, which have a DEPTH
   *      value of 2, is supported. The original image is converted to a Pix
   *      with 32-bpp and alpha channel (spp == 4).
   *
   *      Writing P7 format is currently selected for 32-bpp with alpha
   *      channel, i.e. for Pix which have spp == 4, using pixWriteStreamPam().
   *      Jürgen Buchmüller provided the implementation for the P7 (pam) format.
   * </pre>
   */
  
  #ifdef HAVE_CONFIG_H
  #include "config_auto.h"
  #endif  /* HAVE_CONFIG_H */
  
  #include <string.h>
  #include <ctype.h>
  #include "allheaders.h"
  
  /* --------------------------------------------*/
  #if  USE_PNMIO   /* defined in environ.h */
  /* --------------------------------------------*/
  
  static l_int32 pnmReadNextAsciiValue(FILE  *fp, l_int32 *pval);
  static l_int32 pnmReadNextNumber(FILE *fp, l_int32 *pval);
  static l_int32 pnmReadNextString(FILE *fp, char *buff, l_int32 size);
  static l_int32 pnmSkipCommentLines(FILE  *fp);
  
      /* a sanity check on the size read from file */
  static const l_int32  MAX_PNM_WIDTH = 100000;
  static const l_int32  MAX_PNM_HEIGHT = 100000;
  
  
  /*--------------------------------------------------------------------*
   *                          Stream interface                          *
   *--------------------------------------------------------------------*/
  /*!
   * \brief   pixReadStreamPnm()
   *
   * \param[in]    fp   file stream opened for read
   * \return  pix, or NULL on error
   */
  PIX *
  pixReadStreamPnm(FILE  *fp)
  {
  l_uint8    val8, rval8, gval8, bval8, aval8, mask8;
  l_uint16   val16, rval16, gval16, bval16, aval16;
  l_int32    w, h, d, bps, spp, bpl, wpl, i, j, type;
  l_int32    val, rval, gval, bval;
  l_uint32   rgbval;
  l_uint32  *line, *data;
  PIX       *pix;
  
      PROCNAME("pixReadStreamPnm");
  
      if (!fp)
          return (PIX *)ERROR_PTR("fp not defined", procName, NULL);
  
      if (freadHeaderPnm(fp, &w, &h, &d, &type, &bps, &spp))
          return (PIX *)ERROR_PTR("header read failed", procName, NULL);
      if (bps < 1 || bps > 16)
          return (PIX *)ERROR_PTR("invalid bps", procName, NULL);
      if (spp < 1 || spp > 4)
          return (PIX *)ERROR_PTR("invalid spp", procName, NULL);
      if ((pix = pixCreate(w, h, d)) == NULL)
          return (PIX *)ERROR_PTR("pix not made", procName, NULL);
      pixSetInputFormat(pix, IFF_PNM);
      data = pixGetData(pix);
      wpl = pixGetWpl(pix);
  
          /* If type == 6 and bps == 16, we use the code in type 7
           * to read 6 bytes/pixel from the input file. */
      if (type == 6 && bps == 16)
          type = 7;
  
      switch (type) {
      case 1:
      case 2:
          /* Old "ASCII" binary or gray format */
          for (i = 0; i < h; i++) {
              for (j = 0; j < w; j++) {
                  if (pnmReadNextAsciiValue(fp, &val)) {
                      pixDestroy(&pix);
                      return (PIX *)ERROR_PTR("read abend", procName, NULL);
                  }
                  pixSetPixel(pix, j, i, val);
              }
          }
          break;
  
      case 3:
          /* Old "ASCII" rgb format */
          for (i = 0; i < h; i++) {
              for (j = 0; j < w; j++) {
                  if (pnmReadNextAsciiValue(fp, &rval)) {
                      pixDestroy(&pix);
                      return (PIX *)ERROR_PTR("read abend", procName, NULL);
                  }
                  if (pnmReadNextAsciiValue(fp, &gval)) {
                      pixDestroy(&pix);
                      return (PIX *)ERROR_PTR("read abend", procName, NULL);
                  }
                  if (pnmReadNextAsciiValue(fp, &bval)) {
                      pixDestroy(&pix);
                      return (PIX *)ERROR_PTR("read abend", procName, NULL);
                  }
                  composeRGBPixel(rval, gval, bval, &rgbval);
                  pixSetPixel(pix, j, i, rgbval);
              }
          }
          break;
  
      case 4:
          /* "raw" format for 1 bpp */
          bpl = (d * w + 7) / 8;
          for (i = 0; i < h; i++) {
              line = data + i * wpl;
              for (j = 0; j < bpl; j++) {
                  if (fread(&val8, 1, 1, fp) != 1) {
                      pixDestroy(&pix);
                      return (PIX *)ERROR_PTR("read error in 4", procName, NULL);
                  }
                  SET_DATA_BYTE(line, j, val8);
              }
          }
          break;
  
      case 5:
          /* "raw" format for grayscale */
          for (i = 0; i < h; i++) {
              line = data + i * wpl;
              if (d != 16) {
                  for (j = 0; j < w; j++) {
                      if (fread(&val8, 1, 1, fp) != 1) {
                          pixDestroy(&pix);
                          return (PIX *)ERROR_PTR("error in 5", procName, NULL);
                      }
                      if (d == 2)
                          SET_DATA_DIBIT(line, j, val8);
                      else if (d == 4)
                          SET_DATA_QBIT(line, j, val8);
                      else  /* d == 8 */
                          SET_DATA_BYTE(line, j, val8);
                  }
              } else {  /* d == 16 */
                  for (j = 0; j < w; j++) {
                      if (fread(&val16, 2, 1, fp) != 1) {
                          pixDestroy(&pix);
                          return (PIX *)ERROR_PTR("16 bpp error", procName, NULL);
                      }
                      SET_DATA_TWO_BYTES(line, j, val16);
                  }
              }
          }
          break;
  
      case 6:
          /* "raw" format, type == 6; 8 bps, rgb */
          for (i = 0; i < h; i++) {
              line = data + i * wpl;
              for (j = 0; j < wpl; j++) {
                  if (fread(&rval8, 1, 1, fp) != 1) {
                      pixDestroy(&pix);
                      return (PIX *)ERROR_PTR("read error type 6",
                                              procName, NULL);
                  }
                  if (fread(&gval8, 1, 1, fp) != 1) {
                      pixDestroy(&pix);
                      return (PIX *)ERROR_PTR("read error type 6",
                                              procName, NULL);
                  }
                  if (fread(&bval8, 1, 1, fp) != 1) {
                      pixDestroy(&pix);
                      return (PIX *)ERROR_PTR("read error type 6",
                                              procName, NULL);
                  }
                  composeRGBPixel(rval8, gval8, bval8, &rgbval);
                  line[j] = rgbval;
              }
          }
          break;
  
      case 7:
          /* "arbitrary" format; type == 7; */
          if (bps != 16) {
              mask8 = (1 << bps) - 1;
              switch (spp) {
              case 1: /* 1, 2, 4, 8 bpp grayscale */
                  for (i = 0; i < h; i++) {
                      for (j = 0; j < w; j++) {
                          if (fread(&val8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          val8 = val8 & mask8;
                          if (bps == 1) val8 ^= 1;  /* white-is-1 photometry */
                          pixSetPixel(pix, j, i, val8);
                      }
                  }
                  break;
  
              case 2: /* 1, 2, 4, 8 bpp grayscale + alpha */
                  for (i = 0; i < h; i++) {
                      for (j = 0; j < w; j++) {
                          if (fread(&val8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&aval8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          val8 = val8 & mask8;
                          aval8 = aval8 & mask8;
                          composeRGBAPixel(val8, val8, val8, aval8, &rgbval);
                          pixSetPixel(pix, j, i, rgbval);
                      }
                  }
                  pixSetSpp(pix, 4);
                  break;
  
              case 3: /* rgb */
                  for (i = 0; i < h; i++) {
                      line = data + i * wpl;
                      for (j = 0; j < wpl; j++) {
                          if (fread(&rval8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&gval8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&bval8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          rval8 = rval8 & mask8;
                          gval8 = gval8 & mask8;
                          bval8 = bval8 & mask8;
                          composeRGBPixel(rval8, gval8, bval8, &rgbval);
                          line[j] = rgbval;
                      }
                  }
                  break;
  
              case 4: /* rgba */
                  for (i = 0; i < h; i++) {
                      line = data + i * wpl;
                      for (j = 0; j < wpl; j++) {
                          if (fread(&rval8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&gval8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&bval8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&aval8, 1, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          rval8 = rval8 & mask8;
                          gval8 = gval8 & mask8;
                          bval8 = bval8 & mask8;
                          aval8 = aval8 & mask8;
                          composeRGBAPixel(rval8, gval8, bval8, aval8, &rgbval);
                          line[j] = rgbval;
                      }
                  }
                  pixSetSpp(pix, 4);
                  break;
              }
          } else {  /* bps == 16 */
                  /* I have only seen one example that is type 6, 16 bps.
                   * It was 3 spp (rgb), and the 8 bps of real data was stored
                   * in the second byte.  In the following, I make the wild
                   * assumption that for all 16 bpp pnm/pam files, we can
                   * take the second byte. */
              switch (spp) {
              case 1: /* 16 bps grayscale */
                  for (i = 0; i < h; i++) {
                      for (j = 0; j < w; j++) {
                          if (fread(&val16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          val8 = val16 & 0xff;
                          pixSetPixel(pix, j, i, val8);
                      }
                  }
                  break;
  
              case 2: /* 16 bps grayscale + alpha */
                  for (i = 0; i < h; i++) {
                      for (j = 0; j < w; j++) {
                          if (fread(&val16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&aval16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          val8 = val16 & 0xff;
                          aval8 = aval16 & 0xff;
                          composeRGBAPixel(val8, val8, val8, aval8, &rgbval);
                          pixSetPixel(pix, j, i, rgbval);
                      }
                  }
                  pixSetSpp(pix, 4);
                  break;
  
              case 3: /* 16bps rgb */
                  for (i = 0; i < h; i++) {
                      line = data + i * wpl;
                      for (j = 0; j < wpl; j++) {
                          if (fread(&rval16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&gval16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&bval16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          rval8 = rval16 & 0xff;
                          gval8 = gval16 & 0xff;
                          bval8 = bval16 & 0xff;
                          composeRGBPixel(rval8, gval8, bval8, &rgbval);
                          line[j] = rgbval;
                      }
                  }
                  break;
  
              case 4: /* 16bps rgba */
                  for (i = 0; i < h; i++) {
                      line = data + i * wpl;
                      for (j = 0; j < wpl; j++) {
                          if (fread(&rval16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&gval16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&bval16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          if (fread(&aval16, 2, 1, fp) != 1) {
                              pixDestroy(&pix);
                              return (PIX *)ERROR_PTR("read error type 7",
                                                      procName, NULL);
                          }
                          rval8 = rval16 & 0xff;
                          gval8 = gval16 & 0xff;
                          bval8 = bval16 & 0xff;
                          aval8 = aval16 & 0xff;
                          composeRGBAPixel(rval8, gval8, bval8, aval8, &rgbval);
                          line[j] = rgbval;
                      }
                  }
                  pixSetSpp(pix, 4);
                  break;
              }
          }
          break;
      }
      return pix;
  }
  
  
  /*!
   * \brief   readHeaderPnm()
   *
   * \param[in]    filename
   * \param[out]   pw       [optional]
   * \param[out]   ph       [optional]
   * \param[out]   pd       [optional]
   * \param[out]   ptype    [optional] pnm type
   * \param[out]   pbps     [optional] bits/sample
   * \param[out]   pspp     [optional] samples/pixel
   * \return  0 if OK, 1 on error
   */
  l_ok
  readHeaderPnm(const char *filename,
                l_int32    *pw,
                l_int32    *ph,
                l_int32    *pd,
                l_int32    *ptype,
                l_int32    *pbps,
                l_int32    *pspp)
  {
  l_int32  ret;
  FILE    *fp;
  
      PROCNAME("readHeaderPnm");
  
      if (pw) *pw = 0;
      if (ph) *ph = 0;
      if (pd) *pd = 0;
      if (ptype) *ptype = 0;
      if (pbps) *pbps = 0;
      if (pspp) *pspp = 0;
      if (!filename)
          return ERROR_INT("filename not defined", procName, 1);
  
      if ((fp = fopenReadStream(filename)) == NULL)
          return ERROR_INT("image file not found", procName, 1);
      ret = freadHeaderPnm(fp, pw, ph, pd, ptype, pbps, pspp);
      fclose(fp);
      return ret;
  }
  
  
  /*!
   * \brief   freadHeaderPnm()
   *
   * \param[in]    fp     file stream opened for read
   * \param[out]   pw     [optional]
   * \param[out]   ph     [optional]
   * \param[out]   pd     [optional]
   * \param[out]   ptype  [optional] pnm type
   * \param[out]   pbps   [optional]  bits/sample
   * \param[out]   pspp   [optional]  samples/pixel
   * \return  0 if OK, 1 on error
   */
  l_ok
  freadHeaderPnm(FILE     *fp,
                 l_int32  *pw,
                 l_int32  *ph,
                 l_int32  *pd,
                 l_int32  *ptype,
                 l_int32  *pbps,
                 l_int32  *pspp)
  {
  char     tag[16], tupltype[32];
  l_int32  i, w, h, d, bps, spp, type;
  l_int32  maxval;
  l_int32  ch;
  
      PROCNAME("freadHeaderPnm");
  
      if (pw) *pw = 0;
      if (ph) *ph = 0;
      if (pd) *pd = 0;
      if (ptype) *ptype = 0;
      if (pbps) *pbps = 0;
      if (pspp) *pspp = 0;
      if (!fp)
          return ERROR_INT("fp not defined", procName, 1);
  
      if (fscanf(fp, "P%d\n", &type) != 1)
          return ERROR_INT("invalid read for type", procName, 1);
      if (type < 1 || type > 7)
          return ERROR_INT("invalid pnm file", procName, 1);
  
      if (pnmSkipCommentLines(fp))
          return ERROR_INT("no data in file", procName, 1);
  
      if (type == 7) {
          w = h = d = bps = spp = maxval = 0;
          for (i = 0; i < 10; i++) {   /* limit to 10 lines of this header */
              if (pnmReadNextString(fp, tag, sizeof(tag)))
                  return ERROR_INT("found no next tag", procName, 1);
              if (!strcmp(tag, "WIDTH")) {
                  if (pnmReadNextNumber(fp, &w))
                      return ERROR_INT("failed reading width", procName, 1);
                  continue;
              }
              if (!strcmp(tag, "HEIGHT")) {
                  if (pnmReadNextNumber(fp, &h))
                      return ERROR_INT("failed reading height", procName, 1);
                  continue;
              }
              if (!strcmp(tag, "DEPTH")) {
                  if (pnmReadNextNumber(fp, &spp))
                      return ERROR_INT("failed reading depth", procName, 1);
                  continue;
              }
              if (!strcmp(tag, "MAXVAL")) {
                  if (pnmReadNextNumber(fp, &maxval))
                      return ERROR_INT("failed reading maxval", procName, 1);
                  continue;
              }
              if (!strcmp(tag, "TUPLTYPE")) {
                  if (pnmReadNextString(fp, tupltype, sizeof(tupltype)))
                      return ERROR_INT("failed reading tuple type", procName, 1);
                  continue;
              }
              if (!strcmp(tag, "ENDHDR")) {
                  if ('\n' != (ch = fgetc(fp)))
                      return ERROR_INT("missing LF after ENDHDR", procName, 1);
                  break;
              }
          }
          if (w <= 0 || h <= 0 || w > MAX_PNM_WIDTH || h > MAX_PNM_HEIGHT) {
              L_INFO("invalid size: w = %d, h = %d\n", procName, w, h);
              return 1;
          }
          if (maxval == 1) {
              d = bps = 1;
          } else if (maxval == 3) {
              d = bps = 2;
          } else if (maxval == 15) {
              d = bps = 4;
          } else if (maxval == 255) {
              d = bps = 8;
          } else if (maxval == 0xffff) {
              d = bps = 16;
          } else {
              L_INFO("invalid maxval = %d\n", procName, maxval);
              return 1;
          }
          switch (spp) {
          case 1:
              /* d and bps are already set */
              break;
          case 2:
          case 3:
          case 4:
              /* create a 32 bpp Pix */
              d = 32;
              break;
          default:
              L_INFO("invalid depth = %d\n", procName, spp);
              return 1;
          }
      } else {
  
          if (fscanf(fp, "%d %d\n", &w, &h) != 2)
              return ERROR_INT("invalid read for w,h", procName, 1);
          if (w <= 0 || h <= 0 || w > MAX_PNM_WIDTH || h > MAX_PNM_HEIGHT) {
              L_INFO("invalid size: w = %d, h = %d\n", procName, w, h);
              return 1;
          }
  
         /* Get depth of pix.  For types 2 and 5, we use the maxval.
          * Important implementation note:
          *   - You can't use fscanf(), which throws away whitespace,
          *     and will discard binary data if it starts with whitespace(s).
          *   - You can't use fgets(), which stops at newlines, but this
          *     dumb format doesn't require a newline after the maxval
          *     number -- it just requires one whitespace character.
          *   - Which leaves repeated calls to fgetc, including swallowing
          *     the single whitespace character. */
          if (type == 1 || type == 4) {
              d = 1;
              spp = 1;
              bps = 1;
          } else if (type == 2 || type == 5) {
              if (pnmReadNextNumber(fp, &maxval))
                  return ERROR_INT("invalid read for maxval (2,5)", procName, 1);
              if (maxval == 3) {
                  d = 2;
              } else if (maxval == 15) {
                  d = 4;
              } else if (maxval == 255) {
                  d = 8;
              } else if (maxval == 0xffff) {
                  d = 16;
              } else {
                  fprintf(stderr, "maxval = %d\n", maxval);
                  return ERROR_INT("invalid maxval", procName, 1);
              }
              bps = d;
              spp = 1;
          } else {  /* type == 3 || type == 6; this is rgb  */
              if (pnmReadNextNumber(fp, &maxval))
                  return ERROR_INT("invalid read for maxval (3,6)", procName, 1);
              if (maxval != 255 && maxval != 0xffff) {
                  L_ERROR("unexpected maxval = %d\n", procName, maxval);
                  return 1;
              }
              bps = (maxval == 255) ? 8 : 16;
              d = 32;
              spp = 3;
          }
      }
      if (pw) *pw = w;
      if (ph) *ph = h;
      if (pd) *pd = d;
      if (ptype) *ptype = type;
      if (pbps) *pbps = bps;
      if (pspp) *pspp = spp;
      return 0;
  }
  
  
  /*!
   * \brief   pixWriteStreamPnm()
   *
   * \param[in]   fp    file stream opened for write
   * \param[in]   pix
   * \return  0 if OK; 1 on error
   *
   * <pre>
   * Notes:
   *      (1) This writes "raw" packed format only:
   *          1 bpp --> pbm (P4)
   *          2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm (P5)
   *          2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm (P6)
   *      (2) 24 bpp rgb are not supported in leptonica, but this will
   *          write them out as a packed array of bytes (3 to a pixel).
   * </pre>
   */
  l_ok
  pixWriteStreamPnm(FILE  *fp,
                    PIX   *pix)
  {
  l_uint8    val8;
  l_uint8    pel[4];
  l_uint16   val16;
  l_int32    h, w, d, ds, i, j, wpls, bpl, filebpl, writeerror, maxval;
  l_uint32  *pword, *datas, *lines;
  PIX       *pixs;
  
      PROCNAME("pixWriteStreamPnm");
  
      if (!fp)
          return ERROR_INT("fp not defined", procName, 1);
      if (!pix)
          return ERROR_INT("pix not defined", procName, 1);
  
      pixGetDimensions(pix, &w, &h, &d);
      if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 24 && d != 32)
          return ERROR_INT("d not in {1,2,4,8,16,24,32}", procName, 1);
      if (d == 32 && pixGetSpp(pix) == 4)
          return pixWriteStreamPam(fp, pix);
  
          /* If a colormap exists, remove and convert to grayscale or rgb */
      if (pixGetColormap(pix) != NULL)
          pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
      else
          pixs = pixClone(pix);
      ds =  pixGetDepth(pixs);
      datas = pixGetData(pixs);
      wpls = pixGetWpl(pixs);
  
      writeerror = 0;
  
      if (ds == 1) {  /* binary */
          fprintf(fp, "P4\n# Raw PBM file written by leptonica "
                      "(www.leptonica.com)\n%d %d\n", w, h);
  
          bpl = (w + 7) / 8;
          for (i = 0; i < h; i++) {
              lines = datas + i * wpls;
              for (j = 0; j < bpl; j++) {
                  val8 = GET_DATA_BYTE(lines, j);
                  fwrite(&val8, 1, 1, fp);
              }
          }
      } else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) {  /* grayscale */
          maxval = (1 << ds) - 1;
          fprintf(fp, "P5\n# Raw PGM file written by leptonica "
                      "(www.leptonica.com)\n%d %d\n%d\n", w, h, maxval);
  
          if (ds != 16) {
              for (i = 0; i < h; i++) {
                  lines = datas + i * wpls;
                  for (j = 0; j < w; j++) {
                      if (ds == 2)
                          val8 = GET_DATA_DIBIT(lines, j);
                      else if (ds == 4)
                          val8 = GET_DATA_QBIT(lines, j);
                      else  /* ds == 8 */
                          val8 = GET_DATA_BYTE(lines, j);
                      fwrite(&val8, 1, 1, fp);
                  }
              }
          } else {  /* ds == 16 */
              for (i = 0; i < h; i++) {
                  lines = datas + i * wpls;
                  for (j = 0; j < w; j++) {
                      val16 = GET_DATA_TWO_BYTES(lines, j);
                      fwrite(&val16, 2, 1, fp);
                  }
              }
          }
      } else {  /* rgb color */
          fprintf(fp, "P6\n# Raw PPM file written by leptonica "
                      "(www.leptonica.com)\n%d %d\n255\n", w, h);
  
          if (d == 24) {   /* packed, 3 bytes to a pixel */
              filebpl = 3 * w;
              for (i = 0; i < h; i++) {  /* write out each raster line */
                  lines = datas + i * wpls;
                  if (fwrite(lines, 1, filebpl, fp) != filebpl)
                      writeerror = 1;
              }
          } else {  /* 32 bpp rgb */
              for (i = 0; i < h; i++) {
                  lines = datas + i * wpls;
                  for (j = 0; j < wpls; j++) {
                      pword = lines + j;
                      pel[0] = GET_DATA_BYTE(pword, COLOR_RED);
                      pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN);
                      pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE);
                      if (fwrite(pel, 1, 3, fp) != 3)
                          writeerror = 1;
                  }
              }
          }
      }
  
      pixDestroy(&pixs);
      if (writeerror)
          return ERROR_INT("image write fail", procName, 1);
      return 0;
  }
  
  
  /*!
   * \brief   pixWriteStreamAsciiPnm()
   *
   * \param[in]   fp    file stream opened for write
   * \param[in]   pix
   * \return  0 if OK; 1 on error
   *
   *  Writes "ASCII" format only:
   *      1 bpp --> pbm P1
   *      2, 4, 8, 16 bpp, no colormap or grayscale colormap --> pgm P2
   *      2, 4, 8 bpp with color-valued colormap, or rgb --> rgb ppm P3
   */
  l_ok
  pixWriteStreamAsciiPnm(FILE  *fp,
                         PIX   *pix)
  {
  char       buffer[256];
  l_uint8    cval[3];
  l_int32    h, w, d, ds, i, j, k, maxval, count;
  l_uint32   val;
  PIX       *pixs;
  
      PROCNAME("pixWriteStreamAsciiPnm");
  
      if (!fp)
          return ERROR_INT("fp not defined", procName, 1);
      if (!pix)
          return ERROR_INT("pix not defined", procName, 1);
  
      pixGetDimensions(pix, &w, &h, &d);
      if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 32)
          return ERROR_INT("d not in {1,2,4,8,16,32}", procName, 1);
  
          /* If a colormap exists, remove and convert to grayscale or rgb */
      if (pixGetColormap(pix) != NULL)
          pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
      else
          pixs = pixClone(pix);
      ds =  pixGetDepth(pixs);
  
      if (ds == 1) {  /* binary */
          fprintf(fp, "P1\n# Ascii PBM file written by leptonica "
                      "(www.leptonica.com)\n%d %d\n", w, h);
  
          count = 0;
          for (i = 0; i < h; i++) {
              for (j = 0; j < w; j++) {
                  pixGetPixel(pixs, j, i, &val);
                  if (val == 0)
                      fputc('0', fp);
                  else  /* val == 1 */
                      fputc('1', fp);
                  fputc(' ', fp);
                  count += 2;
                  if (count >= 70) {
                      fputc('\n', fp);
                      count = 0;
                  }
              }
          }
      } else if (ds == 2 || ds == 4 || ds == 8 || ds == 16) {  /* grayscale */
          maxval = (1 << ds) - 1;
          fprintf(fp, "P2\n# Ascii PGM file written by leptonica "
                      "(www.leptonica.com)\n%d %d\n%d\n", w, h, maxval);
  
          count = 0;
          for (i = 0; i < h; i++) {
              for (j = 0; j < w; j++) {
                  pixGetPixel(pixs, j, i, &val);
                  if (ds == 2) {
                      snprintf(buffer, sizeof(buffer), "%1d ", val);
                      fwrite(buffer, 1, 2, fp);
                      count += 2;
                  } else if (ds == 4) {
                      snprintf(buffer, sizeof(buffer), "%2d ", val);
                      fwrite(buffer, 1, 3, fp);
                      count += 3;
                  } else if (ds == 8) {
                      snprintf(buffer, sizeof(buffer), "%3d ", val);
                      fwrite(buffer, 1, 4, fp);
                      count += 4;
                  } else {  /* ds == 16 */
                      snprintf(buffer, sizeof(buffer), "%5d ", val);
                      fwrite(buffer, 1, 6, fp);
                      count += 6;
                  }
                  if (count >= 60) {
                      fputc('\n', fp);
                      count = 0;
                  }
              }
          }
      } else {  /* rgb color */
          fprintf(fp, "P3\n# Ascii PPM file written by leptonica "
                      "(www.leptonica.com)\n%d %d\n255\n", w, h);
          count = 0;
          for (i = 0; i < h; i++) {
              for (j = 0; j < w; j++) {
                  pixGetPixel(pixs, j, i, &val);
                  cval[0] = GET_DATA_BYTE(&val, COLOR_RED);
                  cval[1] = GET_DATA_BYTE(&val, COLOR_GREEN);
                  cval[2] = GET_DATA_BYTE(&val, COLOR_BLUE);
                  for (k = 0; k < 3; k++) {
                      snprintf(buffer, sizeof(buffer), "%3d ", cval[k]);
                      fwrite(buffer, 1, 4, fp);
                      count += 4;
                      if (count >= 60) {
                          fputc('\n', fp);
                          count = 0;
                      }
                  }
              }
          }
      }
  
      pixDestroy(&pixs);
      return 0;
  }
  
  
  /*!
   * \brief   pixWriteStreamPam()
   *
   * \param[in]   fp    file stream opened for write
   * \param[in]   pix
   * \return  0 if OK; 1 on error
   *
   * <pre>
   * Notes:
   *      (1) This writes arbitrary PAM (P7) packed format.
   *      (2) 24 bpp rgb are not supported in leptonica, but this will
   *          write them out as a packed array of bytes (3 to a pixel).
   * </pre>
   */
  l_ok
  pixWriteStreamPam(FILE  *fp,
                    PIX   *pix)
  {
  l_uint8    val8;
  l_uint8    pel[8];
  l_uint16   val16;
  l_int32    h, w, d, ds, i, j;
  l_int32    wpls, spps, filebpl, writeerror, maxval;
  l_uint32  *pword, *datas, *lines;
  PIX       *pixs;
  
      PROCNAME("pixWriteStreamPam");
  
      if (!fp)
          return ERROR_INT("fp not defined", procName, 1);
      if (!pix)
          return ERROR_INT("pix not defined", procName, 1);
  
      pixGetDimensions(pix, &w, &h, &d);
      if (d != 1 && d != 2 && d != 4 && d != 8 && d != 16 && d != 24 && d != 32)
          return ERROR_INT("d not in {1,2,4,8,16,24,32}", procName, 1);
  
          /* If a colormap exists, remove and convert to grayscale or rgb */
      if (pixGetColormap(pix) != NULL)
          pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
      else
          pixs = pixClone(pix);
      ds = pixGetDepth(pixs);
      datas = pixGetData(pixs);
      wpls = pixGetWpl(pixs);
      spps = pixGetSpp(pixs);
      if (ds < 24)
          maxval = (1 << ds) - 1;
      else
          maxval = 255;
  
      writeerror = 0;
      fprintf(fp, "P7\n# Arbitrary PAM file written by leptonica "
                  "(www.leptonica.com)\n");
      fprintf(fp, "WIDTH %d\n", w);
      fprintf(fp, "HEIGHT %d\n", h);
      fprintf(fp, "DEPTH %d\n", spps);
      fprintf(fp, "MAXVAL %d\n", maxval);
      if (spps == 1 && ds == 1)
          fprintf(fp, "TUPLTYPE BLACKANDWHITE\n");
      else if (spps == 1)
          fprintf(fp, "TUPLTYPE GRAYSCALE\n");
      else if (spps == 3)
          fprintf(fp, "TUPLTYPE RGB\n");
      else if (spps == 4)
          fprintf(fp, "TUPLTYPE RGB_ALPHA\n");
      fprintf(fp, "ENDHDR\n");
  
      switch (d) {
      case 1:
          for (i = 0; i < h; i++) {
              lines = datas + i * wpls;
              for (j = 0; j < w; j++) {
                  val8 = GET_DATA_BIT(lines, j);
                  val8 ^= 1;  /* pam apparently uses white-is-1 photometry */
                  if (fwrite(&val8, 1, 1, fp) != 1)
                      writeerror = 1;
              }
          }
          break;
  
      case 2:
          for (i = 0; i < h; i++) {
              lines = datas + i * wpls;
              for (j = 0; j < w; j++) {
                  val8 = GET_DATA_DIBIT(lines, j);
                  if (fwrite(&val8, 1, 1, fp) != 1)
                      writeerror = 1;
              }
          }
          break;
  
      case 4:
          for (i = 0; i < h; i++) {
              lines = datas + i * wpls;
              for (j = 0; j < w; j++) {
                  val8 = GET_DATA_QBIT(lines, j);
                  if (fwrite(&val8, 1, 1, fp) != 1)
                      writeerror = 1;
              }
          }
          break;
  
      case 8:
          for (i = 0; i < h; i++) {
              lines = datas + i * wpls;
              for (j = 0; j < w; j++) {
                  val8 = GET_DATA_BYTE(lines, j);
                  if (fwrite(&val8, 1, 1, fp) != 1)
                      writeerror = 1;
              }
          }
          break;
  
      case 16:
          for (i = 0; i < h; i++) {
              lines = datas + i * wpls;
              for (j = 0; j < w; j++) {
                  val16 = GET_DATA_TWO_BYTES(lines, j);
                  if (fwrite(&val16, 2, 1, fp) != 1)
                      writeerror = 1;
              }
          }
          break;
  
      case 24:
          filebpl = 3 * w;
          for (i = 0; i < h; i++) {
              lines = datas + i * wpls;
              if (fwrite(lines, 1, filebpl, fp) != filebpl)
                  writeerror = 1;
          }
          break;
  
      case 32:
          switch (spps) {
          case 3:
              for (i = 0; i < h; i++) {
                  lines = datas + i * wpls;
                  for (j = 0; j < wpls; j++) {
                      pword = lines + j;
                      pel[0] = GET_DATA_BYTE(pword, COLOR_RED);
                      pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN);
                      pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE);
                      if (fwrite(pel, 1, 3, fp) != 3)
                          writeerror = 1;
                  }
              }
              break;
          case 4:
              for (i = 0; i < h; i++) {
                  lines = datas + i * wpls;
                  for (j = 0; j < wpls; j++) {
                      pword = lines + j;
                      pel[0] = GET_DATA_BYTE(pword, COLOR_RED);
                      pel[1] = GET_DATA_BYTE(pword, COLOR_GREEN);
                      pel[2] = GET_DATA_BYTE(pword, COLOR_BLUE);
                      pel[3] = GET_DATA_BYTE(pword, L_ALPHA_CHANNEL);
                      if (fwrite(pel, 1, 4, fp) != 4)
                          writeerror = 1;
                  }
              }
              break;
          }
          break;
      }
  
      pixDestroy(&pixs);
      if (writeerror)
          return ERROR_INT("image write fail", procName, 1);
      return 0;
  }
  
  
  /*---------------------------------------------------------------------*
   *                         Read/write to memory                        *
   *---------------------------------------------------------------------*/
  
  /*!
   * \brief   pixReadMemPnm()
   *
   * \param[in]   data   const; pnm-encoded
   * \param[in]   size   of data
   * \return  pix, or NULL on error
   *
   * <pre>
   * Notes:
   *      (1) The %size byte of %data must be a null character.
   * </pre>
   */
  PIX *
  pixReadMemPnm(const l_uint8  *data,
                size_t          size)
  {
  FILE  *fp;
  PIX   *pix;
  
      PROCNAME("pixReadMemPnm");
  
      if (!data)
          return (PIX *)ERROR_PTR("data not defined", procName, NULL);
      if ((fp = fopenReadFromMemory(data, size)) == NULL)
          return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
      pix = pixReadStreamPnm(fp);
      fclose(fp);
      if (!pix) L_ERROR("pix not read\n", procName);
      return pix;
  }
  
  
  /*!
   * \brief   readHeaderMemPnm()
   *
   * \param[in]    data    const; pnm-encoded
   * \param[in]    size    of data
   * \param[out]   pw      [optional]
   * \param[out]   ph      [optional]
   * \param[out]   pd      [optional]
   * \param[out]   ptype   [optional] pnm type
   * \param[out]   pbps    [optional] bits/sample
   * \param[out]   pspp    [optional] samples/pixel
   * \return  0 if OK, 1 on error
   */
  l_ok
  readHeaderMemPnm(const l_uint8  *data,
                   size_t          size,
                   l_int32        *pw,
                   l_int32        *ph,
                   l_int32        *pd,
                   l_int32        *ptype,
                   l_int32        *pbps,
                   l_int32        *pspp)
  {
  l_int32  ret;
  FILE    *fp;
  
      PROCNAME("readHeaderMemPnm");
  
      if (!data)
          return ERROR_INT("data not defined", procName, 1);
  
      if ((fp = fopenReadFromMemory(data, size)) == NULL)
          return ERROR_INT("stream not opened", procName, 1);
      ret = freadHeaderPnm(fp, pw, ph, pd, ptype, pbps, pspp);
      fclose(fp);
      if (ret)
          return ERROR_INT("header data read failed", procName, 1);
      return 0;
  }
  
  
  /*!
   * \brief   pixWriteMemPnm()
   *
   * \param[out]   pdata   data of PNM image
   * \param[out]   psize   size of returned data
   * \param[in]    pix
   * \return  0 if OK, 1 on error
   *
   * <pre>
   * Notes:
   *      (1) See pixWriteStreamPnm() for usage.  This version writes to
   *          memory instead of to a file stream.
   * </pre>
   */
  l_ok
  pixWriteMemPnm(l_uint8  **pdata,
                 size_t    *psize,
                 PIX       *pix)
  {
  l_int32  ret;
  FILE    *fp;
  
      PROCNAME("pixWriteMemPnm");
  
      if (pdata) *pdata = NULL;
      if (psize) *psize = 0;
      if (!pdata)
          return ERROR_INT("&data not defined", procName, 1 );
      if (!psize)
          return ERROR_INT("&size not defined", procName, 1 );
      if (!pix)
          return ERROR_INT("&pix not defined", procName, 1 );
  
  #if HAVE_FMEMOPEN
      if ((fp = open_memstream((char **)pdata, psize)) == NULL)
          return ERROR_INT("stream not opened", procName, 1);
      ret = pixWriteStreamPnm(fp, pix);
  #else
      L_INFO("work-around: writing to a temp file\n", procName);
    #ifdef _WIN32
      if ((fp = fopenWriteWinTempfile()) == NULL)
          return ERROR_INT("tmpfile stream not opened", procName, 1);
    #else
      if ((fp = tmpfile()) == NULL)
          return ERROR_INT("tmpfile stream not opened", procName, 1);
    #endif  /* _WIN32 */
      ret = pixWriteStreamPnm(fp, pix);
      rewind(fp);
      *pdata = l_binaryReadStream(fp, psize);
  #endif  /* HAVE_FMEMOPEN */
      fclose(fp);
      return ret;
  }
  
  
  /*!
   * \brief   pixWriteMemPam()
   *
   * \param[out]   pdata   data of PAM image
   * \param[out]   psize   size of returned data
   * \param[in]    pix
   * \return  0 if OK, 1 on error
   *
   * <pre>
   * Notes:
   *      (1) See pixWriteStreamPnm() for usage.  This version writes to
   *          memory instead of to a file stream.
   * </pre>
   */
  l_ok
  pixWriteMemPam(l_uint8  **pdata,
                 size_t    *psize,
                 PIX       *pix)
  {
  l_int32  ret;
  FILE    *fp;
  
      PROCNAME("pixWriteMemPam");
  
      if (pdata) *pdata = NULL;
      if (psize) *psize = 0;
      if (!pdata)
          return ERROR_INT("&data not defined", procName, 1 );
      if (!psize)
          return ERROR_INT("&size not defined", procName, 1 );
      if (!pix)
          return ERROR_INT("&pix not defined", procName, 1 );
  
  #if HAVE_FMEMOPEN
      if ((fp = open_memstream((char **)pdata, psize)) == NULL)
          return ERROR_INT("stream not opened", procName, 1);
      ret = pixWriteStreamPam(fp, pix);
  #else
      L_INFO("work-around: writing to a temp file\n", procName);
    #ifdef _WIN32
      if ((fp = fopenWriteWinTempfile()) == NULL)
          return ERROR_INT("tmpfile stream not opened", procName, 1);
    #else
      if ((fp = tmpfile()) == NULL)
          return ERROR_INT("tmpfile stream not opened", procName, 1);
    #endif  /* _WIN32 */
      ret = pixWriteStreamPam(fp, pix);
      rewind(fp);
      *pdata = l_binaryReadStream(fp, psize);
  #endif  /* HAVE_FMEMOPEN */
      fclose(fp);
      return ret;
  }
  
  
  
  /*--------------------------------------------------------------------*
   *                          Static helpers                            *
   *--------------------------------------------------------------------*/
  /*!
   * \brief   pnmReadNextAsciiValue()
   *
   *      Return: 0 if OK, 1 on error or EOF.
   *
   *  Notes:
   *      (1) This reads the next sample value in ASCII from the file.
   */
  static l_int32
  pnmReadNextAsciiValue(FILE     *fp,
                        l_int32  *pval)
  {
  l_int32   c, ignore;
  
      PROCNAME("pnmReadNextAsciiValue");
  
      if (!pval)
          return ERROR_INT("&val not defined", procName, 1);
      *pval = 0;
      if (!fp)
          return ERROR_INT("stream not open", procName, 1);
      do {  /* skip whitespace */
          if ((c = fgetc(fp)) == EOF)
              return 1;
      } while (c == ' ' || c == '\t' || c == '\n' || c == '\r');
  
      fseek(fp, -1L, SEEK_CUR);        /* back up one byte */
      ignore = fscanf(fp, "%d", pval);
      return 0;
  }
  
  
  /*!
   * \brief   pnmReadNextNumber()
   *
   * \param[in]    fp    file stream
   * \param[out]   pval  value as an integer
   * \return  0 if OK, 1 on error or EOF.
   *
   * <pre>
   * Notes:
   *      (1) This reads the next set of numeric chars, returning
   *          the value and swallowing the trailing whitespace character.
   *          This is needed to read the maxval in the header, which
   *          precedes the binary data.
   * </pre>
   */
  static l_int32
  pnmReadNextNumber(FILE     *fp,
                    l_int32  *pval)
  {
  char      buf[8];
  l_int32   i, c, foundws;
  
      PROCNAME("pnmReadNextNumber");
  
      if (!pval)
          return ERROR_INT("&val not defined", procName, 1);
      *pval = 0;
      if (!fp)
          return ERROR_INT("stream not open", procName, 1);
  
          /* The ASCII characters for the number are followed by exactly
           * one whitespace character. */
      foundws = FALSE;
      for (i = 0; i < 8; i++)
          buf[i] = '\0';
      for (i = 0; i < 8; i++) {
          if ((c = fgetc(fp)) == EOF)
              return ERROR_INT("end of file reached", procName, 1);
          if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
              foundws = TRUE;
              buf[i] = '\n';
              break;
          }
          if (!isdigit(c))
              return ERROR_INT("char read is not a digit", procName, 1);
          buf[i] = c;
      }
      if (!foundws)
          return ERROR_INT("no whitespace found", procName, 1);
      if (sscanf(buf, "%d", pval) != 1)
          return ERROR_INT("invalid read", procName, 1);
      return 0;
  }
  
  /*!
   * \brief   pnmReadNextString()
   *
   * \param[in]    fp    file stream
   * \param[out]   buff  pointer to the string buffer
   * \param[in]    size  max. number of charactes in buffer
   * \return  0 if OK, 1 on error or EOF.
   *
   * <pre>
   * Notes:
   *      (1) This reads the next set of alphanumeric chars,
   *          returning the string and swallowing the trailing
   *          whitespace characters.
   *          This is needed to read header lines, which precede
   *          the P7 format binary data.
   * </pre>
   */
  static l_int32
  pnmReadNextString(FILE    *fp,
                    char    *buff,
                    l_int32  size)
  {
  l_int32   i, c;
  
      PROCNAME("pnmReadNextString");
  
      if (!buff)
          return ERROR_INT("buff not defined", procName, 1);
      *buff = '\0';
      if (!fp)
          return ERROR_INT("stream not open", procName, 1);
      if (size <= 0)
          return ERROR_INT("size is too small", procName, 1);
  
      do {  /* skip whitespace */
          if ((c = fgetc(fp)) == EOF)
              return ERROR_INT("end of file reached", procName, 1);
      } while (c == ' ' || c == '\t' || c == '\n' || c == '\r');
  
          /* Comment lines are allowed to appear
           * anywhere in the header lines */
      if (c == '#') {
          do {  /* each line starting with '#' */
              do {  /* this entire line */
                  if ((c = fgetc(fp)) == EOF)
                      return ERROR_INT("end of file reached", procName, 1);
              } while (c != '\n');
              if ((c = fgetc(fp)) == EOF)
                  return ERROR_INT("end of file reached", procName, 1);
          } while (c == '#');
      }
  
          /* The next string ends when there is
           * a whitespace character following. */
      for (i = 0; i < size - 1; i++) {
          if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
              break;
          buff[i] = c;
          if ((c = fgetc(fp)) == EOF)
              return ERROR_INT("end of file reached", procName, 1);
      }
      buff[i] = '\0';
  
          /* Back up one byte */
      fseek(fp, -1L, SEEK_CUR);
      if (i >= size - 1)
          return ERROR_INT("buff size too small", procName, 1);
  
          /* Skip over trailing spaces and tabs */
      for (;;) {
          if ((c = fgetc(fp)) == EOF)
              return ERROR_INT("end of file reached", procName, 1);
          if (c != ' ' && c != '\t')
              break;
      }
  
          /* Back up one byte */
      fseek(fp, -1L, SEEK_CUR);
      return 0;
  }
  
  
  /*!
   * \brief   pnmSkipCommentLines()
   *
   *      Return: 0 if OK, 1 on error or EOF
   *
   *  Notes:
   *      (1) Comment lines begin with '#'
   *      (2) Usage: caller should check return value for EOF
   */
  static l_int32
  pnmSkipCommentLines(FILE  *fp)
  {
  l_int32  c;
  
      PROCNAME("pnmSkipCommentLines");
  
      if (!fp)
          return ERROR_INT("stream not open", procName, 1);
      if ((c = fgetc(fp)) == EOF)
          return 1;
      if (c == '#') {
          do {  /* each line starting with '#' */
              do {  /* this entire line */
                  if ((c = fgetc(fp)) == EOF)
                      return 1;
              } while (c != '\n');
              if ((c = fgetc(fp)) == EOF)
                  return 1;
          } while (c == '#');
      }
  
          /* Back up one byte */
      fseek(fp, -1L, SEEK_CUR);
      return 0;
  }
  
  /* --------------------------------------------*/
  #endif  /* USE_PNMIO */
  /* --------------------------------------------*/
  