/*
% Copyright (C) 2003-2023 GraphicsMagick Group
% Copyright (C) 2002 ImageMagick Studio
% Copyright 1991-1999 E. I. du Pont de Nemours and Company
%
% This program is covered by multiple licenses, which are described in
% Copyright.txt. You should have received a copy of Copyright.txt with this
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                            PPPP   N   N  M   M                              %
%                            P   P  NN  N  MM MM                              %
%                            PPPP   N N N  M M M                              %
%                            P      N  NN  M   M                              %
%                            P      N   N  M   M                              %
%                                                                             %
%                                                                             %
%              Read/Write PBMPlus Portable Anymap Image Format.               %
%                                                                             %
%                                                                             %
%                              Software Design                                %
%                                John Cristy                                  %
%                                 July 1992                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

/*
  Include declarations.
*/
#include "magick/studio.h"
#include "magick/analyze.h"
#include "magick/attribute.h"
#include "magick/blob.h"
#include "magick/color.h"
#include "magick/colormap.h"
#include "magick/constitute.h"
#include "magick/log.h"
#include "magick/magick.h"
#include "magick/monitor.h"
#include "magick/omp_data_view.h"
#include "magick/pixel_cache.h"
#include "magick/utility.h"

/*
  Forward declarations.
*/
static unsigned int
  WritePNMImage(const ImageInfo *,Image *);


/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   I s P N M                                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method IsPNM returns True if the image format type, identified by the
%  magick string, is PNM.
%
%  The format of the IsPNM method is:
%
%      unsigned int IsPNM(const unsigned char *magick,const size_t length)
%
%  A description of each parameter follows:
%
%    o status:  Method IsPNM returns True if the image format type is PNM.
%
%    o magick: This string is generally the first few bytes of an image file
%      or blob.
%
%    o length: Specifies the length of the magick string.
%
%
*/
static unsigned int IsPNM(const unsigned char *magick,const size_t length)
{
  if (length < 2)
    return(False);
  if ((*magick == 'P') && isdigit((int) magick[1]))
    return(True);
  return(False);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d P N M I m a g e                                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method ReadPNMImage reads a Portable Anymap image file and returns it.
%  It allocates the memory necessary for the new Image structure and returns
%  a pointer to the new image.
%
%  The format of the ReadPNMImage method is:
%
%      Image *ReadPNMImage(const ImageInfo *image_info,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image:  Method ReadPNMImage returns a pointer to the image after
%      reading.  A null image is returned if there is a memory shortage or
%      if the image cannot be read.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o exception: return any errors or warnings in this structure.
%
%
*/

static unsigned int PNMInteger(Image *image,const unsigned int base)
{
  int
    c;

  unsigned long
    value;

  /*
    Skip any leading whitespace.
  */
  do
  {
    c=ReadBlobByte(image);
    if (c == EOF)
      return(0);
  } while (!isdigit(c));
  c &= 0xff;
  if (base == 2)
    return(c-'0');
  /*
    Evaluate number.
  */
  value=0;
  while(1)
  {
    value*=10;
    value+=c-'0';
    c=ReadBlobByte(image);
    if ((c == EOF) || !(isdigit(c)))
      break;
    c &= 0xff;
  }

  return(value);
}

static unsigned int PNMIntegerOrComment(Image *image,const unsigned int base)
{
  int
    c;

  unsigned long
    value;

  static const char P7Comment[] = "END_OF_COMMENTS\n";

  /*
    Skip any leading whitespace.
  */
  do
  {
    c=ReadBlobByte(image);
    if (c == EOF)
      return(0);
    c &= 0xff;
    if (c == '#')
      {
        char
          *comment;

        const ImageAttribute
          *comment_attr;

        ExtendedSignedIntegralType
          offset;

        register char
          *p,
          *q;

        size_t
          length;

        /*
          Read comment.
        */
        if ((comment_attr=GetImageAttribute(image,"comment")) != (const ImageAttribute *) NULL)
          {
            /*
              If existing comment text length exceeds arbitrary limit,
              then do no further comment processing for this file.
            */
            if (comment_attr->length > MaxTextExtent*2)
              {
                for ( ; (c != EOF) && (c != '\n'); )
                  c=ReadBlobByte(image);
                return 0;
              }
          }
        length=MaxTextExtent;
        comment=MagickAllocateResourceLimitedMemory(char *,length+sizeof(P7Comment));
        p=comment;
        offset=p-comment;
        if (comment != (char *) NULL)
          for ( ; (c != EOF) && (c != '\n'); p++)
          {
            if ((size_t) (p-comment) >= length)
              {
                size_t
                  text_length;

                char
                  *new_comment;

                text_length=(size_t) (p-comment);
                length+=MaxTextExtent;
                new_comment=MagickReallocateResourceLimitedMemory(char *,comment,length+sizeof(P7Comment));
                if (new_comment == (char *) NULL)
                  {
                    MagickFreeResourceLimitedMemory(comment);
                    break;
                  }
                comment=new_comment;
                p=comment+text_length;
              }
            c=ReadBlobByte(image);
            *p=c;
            *(p+1)='\0';
          }
        if (comment == (char *) NULL)
          return(0);
        q=comment+offset;
        if (LocaleCompare(q,P7Comment) == 0)
          *q='\0';
        /*
          FIXME:
          Implicitly extend existing comment attribute since comments
          can span multiple lines.
        */
        (void) SetImageAttribute(image,"comment",comment);
        MagickFreeResourceLimitedMemory(comment);
        continue;
      }
  } while (!isdigit(c));
  if (base == 2)
    return(c-'0');
  /*
    Evaluate number.
  */
  value=0;
  do
  {
    value*=10;
    value+=c-'0';
    c=ReadBlobByte(image);
    if (c == EOF)
      return(value);
  }
  while (isdigit(c));
  return(value);
}

#define ValidateScalingIndex(image, index, max)                         \
  do                                                                    \
    {                                                                   \
      if (index > max)                                                  \
        ThrowReaderException(CorruptImageError,CorruptImage, image);    \
    } while (0)

#define ValidateScalingPixel(image, pixel, max)         \
  do                                                    \
    {                                                   \
      ValidateScalingIndex(image, pixel.red, max);      \
      ValidateScalingIndex(image, pixel.green, max);    \
      ValidateScalingIndex(image, pixel.blue, max);     \
    } while (0)

typedef enum
  {
    Undefined_PNM_Format,
    PBM_ASCII_Format, /* P1 */
    PGM_ASCII_Format, /* P2 */
    PPM_ASCII_Format, /* P3 */
    PBM_RAW_Format, /* P4 */
    PGM_RAW_Format, /* P5 */
    PPM_RAW_Format, /* P6 */
    PAM_Format, /* P7 */
    XV_332_Format /* P7 332 */
  } PNMSubformat;

static const char *PNMSubformatToString(const PNMSubformat f)
{
  const char *s = "unknown";

  switch (f)
    {
    case Undefined_PNM_Format:
      s = "Undefined";
      break;
    case PBM_ASCII_Format: /* P1 */
      s = "PBM ASCII";
      break;
    case PGM_ASCII_Format: /* P2 */
      s = "PGM ASCII";
      break;
    case PPM_ASCII_Format: /* P3 */
      s = "PPM ASCII";
      break;
    case PBM_RAW_Format: /* P4 */
      s = "PBM RAW";
      break;
    case PGM_RAW_Format: /* P5 */
      s = "PGM RAW";
      break;
    case PPM_RAW_Format: /* P6 */
      s = "PPM RAW";
      break;
    case PAM_Format: /* P7 */
      s = "PAM";
      break;
    case XV_332_Format: /* P7 332 */
      s = "XV 332 icon";
      break;
    }
  return s;
}

#if defined(HAVE_OPENMP)
#  define PNMReadUseOpenMP 1

static int PNMReadThreads(const Image* image, const size_t bytes_per_row)
{
  const int omp_max_threads = omp_get_max_threads();
  int threads;
  ARG_NOT_USED(image);
  threads=(int)(Min(bytes_per_row/4096U,(size_t) Max(0,omp_max_threads)));
  if (0 == threads)
    threads=1;
  return threads;
}
#endif /* defined(HAVE_OPENMP) */

static Image *ReadPNMImage(const ImageInfo *image_info,ExceptionInfo *exception)
{
  char
    c;

  PNMSubformat
    format;

  Image
    *image;

  long
    y;

  LongPixelPacket
    pixel;

  register IndexPacket
    *indexes;

  register unsigned long
    i;

  size_t
    count,
    number_pixels;

  unsigned int
    index,
    bits_per_sample;

  MagickPassFail
    status;

  unsigned int
    max_value,
    samples_per_pixel;

  /*
    Open image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  image=AllocateImage(image_info);
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == False)
    ThrowReaderException(FileOpenError,UnableToOpenFile,image);
  /*
    Read PNM image.
  */
  count=ReadBlob(image,1,(char *) &c);
  do
    {
      /*
        Initialize image structure.
      */
      max_value=0;
      bits_per_sample=0;
      samples_per_pixel=0;

      if (count == 0)
        ThrowReaderException(CorruptImageError,ImproperImageHeader,image);

      if (c != 'P')
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Read %c rather than expected 'P'!",c);
          ThrowReaderException(CorruptImageError,ImproperImageHeader,image);
        }

      c=ReadBlobByte(image);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"PNM Format Id: P%c",
                            c);

      switch (c)
        {
        case '1': format=PBM_ASCII_Format; break;
        case '2': format=PGM_ASCII_Format; break;
        case '3': format=PPM_ASCII_Format; break;
        case '4': format=PBM_RAW_Format; break;
        case '5': format=PGM_RAW_Format; break;
        case '6': format=PPM_RAW_Format; break;
        case '7':
          {
            if ((ReadBlobByte(image) == ' ') &&
                (PNMIntegerOrComment(image,10) == 332))
              format=XV_332_Format;
            else
              format=PAM_Format;
            break;
          }
        default:
          {
            format=Undefined_PNM_Format;
          }
        }

      if (PAM_Format == format)
        {
          /*
            PAM header format

            P7
            WIDTH 227
            HEIGHT 149
            DEPTH 3
            MAXVAL 255
            TUPLTYPE RGB
            ENDHDR
          */

          char
            keyword[MaxTextExtent];

          register char
            *p;

          int
            c;

          while (1)
            {
              p=keyword;
              c=ReadBlobByte(image);
              do
                {
                  if (isalnum(c) || ('#' == c))
                    if ((p-keyword) < (MaxTextExtent-1))
                      {
                        *p++=c;
                        if ('#' == c)
                          break;
                      }
                  c=ReadBlobByte(image);
                } while (isalnum(c) || ('#' == c));
              *p='\0';

              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Keyword \"%s\"",keyword);
              if ((EOF == c) || (LocaleCompare(keyword,"ENDHDR") == 0))
                {
                  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                        "Exiting header!");
                  break;
                }
              else if (LocaleCompare(keyword,"HEIGHT") == 0)
                {
                  image->rows=PNMIntegerOrComment(image,10);
                }
              else if (LocaleCompare(keyword,"WIDTH") == 0)
                {
                  image->columns=PNMInteger(image,10);
                }
              else if (LocaleCompare(keyword,"DEPTH") == 0)
                {
                  samples_per_pixel=PNMInteger(image,10);
                }
              else if (LocaleCompare(keyword,"MAXVAL") == 0)
                {
                  max_value=PNMInteger(image,10);
                }
              else if (LocaleCompare(keyword,"TUPLTYPE") == 0)
                {
                  /* Skip white space */
                  do
                    {
                      c=ReadBlobByte(image);
                    } while (isspace(c) && (EOF != c));
                  if (EOF == c)
                    break;
                  /* Tupletype argument */
                  p=keyword;
                  do
                    {
                      if ((p-keyword) < (MaxTextExtent-1))
                        *p++=c;
                      c=ReadBlobByte(image);
                    } while (('\n' != c) && (EOF != c));
                  *p='\0';
                  if (EOF == c)
                    break;
                  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                        "TUPLTYPE \"%s\"",keyword);
                  if (LocaleNCompare(keyword,"BLACKANDWHITE",13) == 0)
                    {
                      image->colorspace=GRAYColorspace;
                      image->is_monochrome=MagickTrue;
                    }
                  else if (LocaleNCompare(keyword,"CMYK",4) == 0)
                    {
                      image->colorspace=CMYKColorspace;
                    }
                  else if (LocaleNCompare(keyword,"GRAYSCALE",9) == 0)
                    {
                      image->colorspace=GRAYColorspace;
                    }
                  else if (LocaleNCompare(keyword,"RGB",3) == 0)
                    {
                    }

                  /*
                    Check for alpha flag.
                  */
                  count=strlen(keyword);
                  if ((count > 6) && (LocaleNCompare(keyword+count-6,"_ALPHA",6) == 0))
                    {
                      image->matte=MagickTrue;
                    }
                }
              else if (LocaleNCompare(keyword,"#",1) == 0)
                {
                  /* Skip leading white space */
                  do
                    {
                      c=ReadBlobByte(image);
                    } while (isspace(c) && (EOF != c));
                  if (EOF == c)
                    break;

                  /* Comment */
                  p=keyword;
                  while ((p-keyword) < (MaxTextExtent-2))
                    {
                      *p++=c;
                      c=ReadBlobByte(image);
                      if (c == '\n')
                        {
                          *p++=c;
                          break;
                        }
                      if (c == EOF)
                        break;
                    }
                  *p='\0';
                  /*
                    FIXME:
                    Implicitly extend existing comment attribute since comments
                    can span multiple lines.
                  */
                  (void) SetImageAttribute(image,"comment",keyword);
                  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                        "Comment: \"%s\"",keyword);
                }
              else
                {
                  /* Unknown! */
                  do
                    {
                      c=ReadBlobByte(image);
                    } while (('\n' != c) && (EOF != c));
                  break;
                }
            }
        }
      else
        {
          /*
            PNM header type format

            P1
            # feep.pbm
            24 7

            P3
            # feep.ppm
            4 4
            15
          */
          image->columns=PNMIntegerOrComment(image,10);
          image->rows=PNMInteger(image,10);

          if ((format == PBM_ASCII_Format) || (format == PBM_RAW_Format))
            max_value=1;  /* bitmap */
          else
            max_value=PNMInteger(image,10);

          switch (format)
            {
            case PBM_ASCII_Format:
            case PBM_RAW_Format:
            case PGM_ASCII_Format:
            case PGM_RAW_Format:
            case XV_332_Format:
              {
                samples_per_pixel=1;
                break;
              }
            case PPM_ASCII_Format:
            case PPM_RAW_Format:
              {
                samples_per_pixel=3;
                break;
              }
            default:
              {
              }
            }
        }

      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Dimensions: %lux%lu",
                            image->columns,image->rows);
      if ((image->columns == 0) || (image->rows == 0))
        ThrowReaderException(CorruptImageError,ImproperImageHeader,image);

      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Max Value: %u",
                            max_value);
      if ((max_value == 0) || (max_value > 4294967295U))
        ThrowReaderException(CorruptImageError,ImproperImageHeader,image);

      if ((format == XV_332_Format) && (max_value != 255))
        ThrowReaderException(CorruptImageError,ImproperImageHeader,image);

      bits_per_sample=0;
      if (max_value <= 1)
        bits_per_sample=1;
      else if (max_value <= 255U)
        bits_per_sample=8;
      else if (max_value <= 65535U)
        bits_per_sample=16;
      else if (max_value <= 4294967295U)
        bits_per_sample=32;

      image->depth=Min(bits_per_sample,QuantumDepth);

      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Image Depth: %u",
                            image->depth);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Samples Per Pixel: %u",
                            samples_per_pixel);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Bits Per Sample: %u",
                            bits_per_sample);

      if (EOFBlob(image))
        ThrowException(exception,CorruptImageError,UnexpectedEndOfFile,
                       image->filename);

      if ((1 == samples_per_pixel) && (max_value < MaxColormapSize) &&
          ((size_t) image->columns*image->rows >= max_value))
        {
          image->storage_class=PseudoClass;
          image->colors=
            max_value >= MaxColormapSize ? MaxColormapSize : max_value+1;
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colors: %u",
                                image->colors);
        }
      number_pixels=MagickArraySize(image->columns,image->rows);
      if (number_pixels == 0)
        ThrowReaderException(CorruptImageError,NegativeOrZeroImageSize,image);
      if (image->storage_class == PseudoClass)
        {
          /*
            Create colormap.
          */
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Allocating colormap with %u colors",
                                image->colors);
          if (!AllocateImageColormap(image,image->colors))
            ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,
                                 image);
          if ((format == XV_332_Format) && (image->colors == 256))
            {
              /*
                Initialize 332 colormap.
              */
              i=0;
              for (pixel.red=0; pixel.red < 8; pixel.red++)
                for (pixel.green=0; pixel.green < 8; pixel.green++)
                  for (pixel.blue=0; pixel.blue < 4; pixel.blue++)
                    {
                      image->colormap[i].red=(Quantum)
                        (((double) MaxRGB*pixel.red)/0x07+0.5);
                      image->colormap[i].green=(Quantum)
                        (((double) MaxRGB*pixel.green)/0x07+0.5);
                      image->colormap[i].blue=(Quantum)
                        (((double) MaxRGB*pixel.blue)/0x03+0.5);
                      i++;
                    }
            }
        }
      if (image_info->ping && (image_info->subrange != 0))
        if (image->scene >= (image_info->subimage+image_info->subrange-1))
          break;

      if (CheckImagePixelLimits(image, exception) != MagickPass)
        ThrowReaderException(ResourceLimitError,ImagePixelLimitExceeded,image);

      /*
        Convert PNM pixels to runlength-encoded MIFF packets.
      */
      switch (format)
        {
        case PBM_ASCII_Format:
          {
            /*
              Convert PBM image to pixel packets.
            */
            register unsigned long
              x;

            register PixelPacket
              *q;

            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Reading %s pixel data",PNMSubformatToString(format));
            for (y=0; y < (long) image->rows; y++)
              {
                q=SetImagePixels(image,0,y,image->columns,1);
                if (q == (PixelPacket *) NULL)
                  break;
                indexes=AccessMutableIndexes(image);
                for (x=0; x < image->columns; x++)
                  {
                    index=!PNMInteger(image,2);
                    if (EOFBlob(image))
                      break;
                    VerifyColormapIndex(image,index);
                    indexes[x]=index;
                    *q++=image->colormap[index];
                  }
                if (!SyncImagePixels(image))
                  break;
                if (image->previous == (Image *) NULL)
                  if (QuantumTick(y,image->rows))
                    if (!MagickMonitorFormatted(y,image->rows,exception,
                                                LoadImageText,image->filename,
                                                image->columns,image->rows))
                      break;
                if (EOFBlob(image))
                  break;
              }
            image->is_grayscale=MagickTrue;
            image->is_monochrome=MagickTrue;
            if (EOFBlob(image))
              ThrowException(exception,CorruptImageError,UnexpectedEndOfFile,
                             image->filename);
            break;
          }
        case PGM_ASCII_Format:
          {
            /*
              Convert PGM image to pixel packets.
            */
            register unsigned long
              x;

            register PixelPacket
              *q;

            unsigned long
              intensity;

            MagickBool
              is_grayscale,
              is_monochrome;

            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Reading %s pixel data",PNMSubformatToString(format));
            is_grayscale=MagickTrue;
            is_monochrome=MagickTrue;
            for (y=0; y < (long) image->rows; y++)
              {
                q=SetImagePixels(image,0,y,image->columns,1);
                if (q == (PixelPacket *) NULL)
                  break;
                if (image->storage_class == PseudoClass)
                  {
                    /*
                      PseudoClass
                    */
                    indexes=AccessMutableIndexes(image);
                    for (x=0; x < image->columns; x++)
                      {
                        intensity=PNMInteger(image,10);
                        ValidateScalingIndex(image, intensity, max_value);
                        if (EOFBlob(image))
                          break;
                        index=intensity;
                        VerifyColormapIndex(image,index);
                        indexes[x]=index;
                        *q=image->colormap[index];
                        is_monochrome &= IsMonochrome(*q);
                        q++;
                      }
                  }
                else
                  {
                    /*
                      DirectClass
                    */
                    for (x=0; x < image->columns; x++)
                      {
                        intensity=PNMInteger(image,10);
                        ValidateScalingIndex(image, intensity, max_value);
                        if (EOFBlob(image))
                          break;
                        intensity=ScaleAnyToQuantum(intensity, max_value);
                        q->red=q->green=q->blue=intensity;
                        is_monochrome &= IsMonochrome(*q);
                        q++;
                      }
                  }
                if (EOFBlob(image))
                  break;
                if (!SyncImagePixels(image))
                  break;
                if (image->previous == (Image *) NULL)
                  if (QuantumTick(y,image->rows))
                    if (!MagickMonitorFormatted(y,image->rows,exception,
                                                LoadImageText,image->filename,
                                                image->columns,image->rows))
                      break;
              }
            image->is_monochrome=is_monochrome;
            image->is_grayscale=is_grayscale;
            if (EOFBlob(image))
              ThrowException(exception,CorruptImageError,UnexpectedEndOfFile,
                             image->filename);
            break;
          }
        case PPM_ASCII_Format:
          {
            /*
              Convert PNM image to pixel packets.
            */
            register unsigned long
              x;

            register PixelPacket
              *q;

            MagickBool
              is_grayscale,
              is_monochrome;

            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Reading %s pixel data",PNMSubformatToString(format));
            is_grayscale=MagickTrue;
            is_monochrome=MagickTrue;
            for (y=0; y < (long) image->rows; y++)
              {
                q=SetImagePixels(image,0,y,image->columns,1);
                if (q == (PixelPacket *) NULL)
                  break;
                for (x=0; x < image->columns; x++)
                  {
                    pixel.red=PNMInteger(image,10);
                    pixel.green=PNMInteger(image,10);
                    pixel.blue=PNMInteger(image,10);
                    if (EOFBlob(image))
                      break;
                    ValidateScalingPixel(image, pixel, max_value);
                    pixel.red=ScaleAnyToQuantum(pixel.red, max_value);
                    pixel.green=ScaleAnyToQuantum(pixel.green, max_value);
                    pixel.blue=ScaleAnyToQuantum(pixel.blue, max_value);
                    q->red=(Quantum) pixel.red;
                    q->green=(Quantum) pixel.green;
                    q->blue=(Quantum) pixel.blue;
                    is_monochrome &= IsMonochrome(*q);
                    is_grayscale &= IsGray(*q);
                    q++;
                  }
                if (EOFBlob(image))
                  break;
                if (!SyncImagePixels(image))
                  break;
                if (image->previous == (Image *) NULL)
                  if (QuantumTick(y,image->rows))
                    if (!MagickMonitorFormatted(y,image->rows,exception,
                                                LoadImageText,image->filename,
                                                image->columns,image->rows))
                      break;
              }
            image->is_monochrome=is_monochrome;
            image->is_grayscale=is_grayscale;
            if (EOFBlob(image))
              ThrowException(exception,CorruptImageError,UnexpectedEndOfFile,
                             image->filename);
            break;
          }
        case PBM_RAW_Format:
        case PGM_RAW_Format:
        case PPM_RAW_Format:
        case PAM_Format:
        case XV_332_Format:
          {
            /*
              Convert PBM/PGM/PPM/PAM/XV raw raster image to pixel packets.
            */
            ImportPixelAreaOptions
              import_options;

            QuantumType
              quantum_type;

            size_t
              bytes_per_row;

            MagickBool
              check_pixels,
              is_grayscale,
              is_monochrome,
              use_scaling;

            unsigned long
              max_value_given_bits,
              row_count=0;

            ThreadViewDataSet
              *scanline_set;

            double
              sample_scale;

            unsigned int
              sample_max;

#if defined(HAVE_OPENMP)
            int
              pnm_read_threads;
#endif

            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Reading %s pixel data",PNMSubformatToString(format));

            ImportPixelAreaOptionsInit(&import_options);

            check_pixels=MagickTrue;
            is_grayscale=MagickTrue;
            is_monochrome=MagickTrue;

            /*
              Deduce correct import parameters.
            */
            quantum_type=UndefinedQuantum;
            import_options.grayscale_miniswhite=MagickFalse;
            max_value_given_bits=MaxValueGivenBits(bits_per_sample);
            if (max_value_given_bits == 0UL)
              {
                ThrowException(exception,CorruptImageError,ImproperImageHeader,
                               image->filename);
                break;
              }
            sample_max=RoundDoubleToQuantum((MaxRGBDouble*max_value)/max_value_given_bits);
            if (sample_max == 0U)
              {
                ThrowException(exception,CorruptImageError,ImproperImageHeader,
                               image->filename);
                break;
              }
            sample_scale=MaxRGBDouble/sample_max;
            use_scaling=(MaxRGB != sample_max);
            bytes_per_row=0;

            if (1 == samples_per_pixel)
              {
                if (1 == bits_per_sample)
                  {
                    /* PBM */
                    import_options.grayscale_miniswhite=MagickTrue;
                    quantum_type=GrayQuantum;
                  }
                else
                  {
                    /* PGM & XV_332 */
                    if ((XV_332_Format == format) && (image->storage_class == PseudoClass))
                      {
                        quantum_type=IndexQuantum;
                      }
                    else
                      {
                        quantum_type=GrayQuantum;
                      }
                  }
              }
            else if (2 == samples_per_pixel && image->matte)
              {
                quantum_type=GrayAlphaQuantum;
              }
            else if (3 == samples_per_pixel)
              {
                /* PPM */
                quantum_type=RGBQuantum;
              }
            else if (4 == samples_per_pixel)
              {
                if (CMYKColorspace == image->colorspace)
                  quantum_type=CMYKQuantum;
                else
                  quantum_type=RGBAQuantum;
              }
            else if (5 == samples_per_pixel)
              {
                if (CMYKColorspace == image->colorspace)
                  quantum_type=CMYKAQuantum;
              }

            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Import Quantum Type: %s",
                                    QuantumTypeToString(quantum_type));

            samples_per_pixel=MagickGetQuantumSamplesPerPixel(quantum_type);
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Samples/Pixel: %u", samples_per_pixel);

            if (1 == bits_per_sample)
              {
                /* bytes_per_row=(((size_t) image->columns*samples_per_pixel+7) >> 3); */
                bytes_per_row=MagickArraySize(image->columns,samples_per_pixel);
                if (bytes_per_row)
                  bytes_per_row += 7;
                if (bytes_per_row)
                  bytes_per_row >>= 3;
              }
            else
              {
                bytes_per_row=MagickArraySize((((size_t) bits_per_sample+7)/8)*
                                              samples_per_pixel,image->columns);
              }

            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Bytes/Row: %" MAGICK_SIZE_T_F "u",
                                    (MAGICK_SIZE_T) bytes_per_row);

            if (1 == samples_per_pixel)
              {
                check_pixels=MagickFalse;
              }
            if (GrayQuantum)
              {
                if (1 == bits_per_sample)
                  {
                    is_grayscale=MagickTrue;
                    is_monochrome=MagickTrue;
                  }
                else
                  {
                    is_grayscale=MagickTrue;
                    is_monochrome=MagickFalse;
                  }
              }

            /* Validate file size before allocating memory */
            if (BlobIsSeekable(image))
              {
                const magick_off_t file_size = GetBlobSize(image);
                const magick_off_t current_offset = TellBlob(image);
                if ((file_size > 0) &&
                    (current_offset > 0) &&
                    (file_size > current_offset))
                  {
                    const magick_off_t remaining = file_size-current_offset;
                    const magick_off_t needed = (magick_off_t) image->rows *
                      (magick_off_t) bytes_per_row;
                    if ((remaining < (magick_off_t) bytes_per_row) ||
                        (remaining < needed))
                      {
                        ThrowException(exception,CorruptImageError,UnexpectedEndOfFile,
                                       image->filename);
                        break;
                      }
                  }
              }

#if defined(HAVE_OPENMP)
            pnm_read_threads = PNMReadThreads(image,bytes_per_row);
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Using %d thread%s...", pnm_read_threads,
                                    pnm_read_threads > 1 ? "s" : "");
#endif
            scanline_set=AllocateThreadViewDataArray(image,exception,bytes_per_row,1);
            if (scanline_set == (ThreadViewDataSet *) NULL)
              ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
#if PNMReadUseOpenMP
#  if defined(TUNE_OPENMP)
#    pragma omp parallel for schedule(runtime) shared(is_grayscale,is_monochrome,row_count,status)
#  else
#    pragma omp parallel for num_threads(pnm_read_threads) schedule(static,1) shared(is_grayscale,is_monochrome,row_count,status)
#  endif
#endif
            for (y=0; y < (long) image->rows; y++)
              {
                register unsigned long
                  x;

                register PixelPacket
                  *q = (PixelPacket *) NULL;

                void
                  *pixels;

                MagickBool
                  thread_status;

                MagickBool
                  thread_is_grayscale,
                  thread_is_monochrome;

                unsigned long
                  thread_row_count;

                ImportPixelAreaInfo
                  import_info;

#if PNMReadUseOpenMP
#  pragma omp critical (GM_ReadPNMImage)
#endif
                thread_status=status;
                if (thread_status == MagickFail)
                  continue;

                pixels=AccessThreadViewData(scanline_set);

#if PNMReadUseOpenMP
#  pragma omp critical (GM_ReadPNMImage)
#endif
                {
                  thread_is_grayscale=is_grayscale;
                  thread_is_monochrome=is_monochrome;

                  if (ReadBlobZC(image,bytes_per_row,&pixels) != bytes_per_row)
                    thread_status=MagickFail;

                  thread_row_count=row_count;
                  row_count++;

                  if (image->previous == (Image *) NULL)
                    if (QuantumTick(thread_row_count,image->rows))
                      if (!MagickMonitorFormatted(thread_row_count,image->rows,
                                                  exception,LoadImageText,
                                                  image->filename,
                                                  image->columns,image->rows))
                        thread_status=MagickFail;
                }

                if (thread_status != MagickFail)
                  if ((q=SetImagePixels(image,0,thread_row_count,image->columns,1)) ==
                      (PixelPacket *) NULL)
                    thread_status=MagickFail;

                if (thread_status != MagickFail)
                  if (!ImportImagePixelArea(image,quantum_type,bits_per_sample,pixels,
                                            &import_options,&import_info))
                    thread_status=MagickFail;
                /*
                  Scale sub-ranged pixels up to full range if necessary
                */
                if ((thread_status != MagickFail) && (use_scaling))
                  for (x=0; x < image->columns; x++)
                    {
                      SetRedSample(&q[x],
                                   RoundDoubleToQuantum(sample_scale*
                                                        GetRedSample(&q[x])));
                      SetGreenSample(&q[x],
                                     RoundDoubleToQuantum(sample_scale*
                                                          GetGreenSample(&q[x])));
                      SetBlueSample(&q[x],
                                    RoundDoubleToQuantum(sample_scale*
                                                         GetBlueSample(&q[x])));
                      if (image->matte)
                        SetOpacitySample(&q[x],
                                         MaxRGB-
                                         RoundDoubleToQuantum(sample_scale*
                                                              (MaxRGB-
                                                               GetOpacitySample(&q[x]))));
                    }
                /*
                  For a DirectClass image, check all pixels for
                  gray/monochrome status since this format is often
                  used for input from Ghostscript, which may output
                  bilevel or gray in an RGB format.  It is easier to
                  check now while the pixels are still "hot" in
                  memory.
                */
                if (thread_status != MagickFail)
                  if (check_pixels)
                    if (thread_is_grayscale || thread_is_monochrome)
                      for (x=0; x < image->columns; x++)
                        {
                          thread_is_grayscale = thread_is_grayscale && IsGray(q[x]);
                          thread_is_monochrome = thread_is_monochrome && IsMonochrome(q[x]);
                          if (!thread_is_grayscale && !thread_is_monochrome)
                            break;
                        }

                if (thread_status != MagickFail)
                  if (!SyncImagePixels(image))
                    thread_status=MagickFail;

#if PNMReadUseOpenMP
#  pragma omp critical (GM_ReadPNMImage)
#endif
                {
                  if (thread_status == MagickFail)
                    status=MagickFail;

                  if (!thread_is_grayscale)
                    is_grayscale=thread_is_grayscale;

                  if (!thread_is_monochrome)
                    is_monochrome=thread_is_monochrome;
                }
              }
            DestroyThreadViewDataSet(scanline_set);
            image->is_monochrome=is_monochrome;
            image->is_grayscale=is_grayscale;
            if ((status == MagickFail) && (image->exception.severity))
              CopyException(exception,&image->exception);
            if (EOFBlob(image))
              ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image);
            break;
          }
        default:
          ThrowReaderException(CorruptImageError,ImproperImageHeader,image);
        }
      StopTimer(&image->timer);
      if (status ==MagickFail)
        break;
      /*
        Proceed to next image.
      */
      if (image_info->subrange != 0)
        if (image->scene >= (image_info->subimage+image_info->subrange-1))
          break;
      if ((format == PBM_ASCII_Format) || (format == PGM_ASCII_Format) || (format == PPM_ASCII_Format))
        do
          {
            /*
              Skip to end of line.
            */
            count=ReadBlob(image,1,&c);
            if (count == 0)
              break;
          } while (c != '\n');
      count=ReadBlob(image,1,&c);
      if ((count != 0) && (c == 'P'))
        {
          /*
            Allocate next image structure.
          */
          AllocateNextImage(image_info,image);
          if (image->next == (Image *) NULL)
            {
              DestroyImageList(image);
              return((Image *) NULL);
            }
          image=SyncNextImageInList(image);
          if (!MagickMonitorFormatted(TellBlob(image),GetBlobSize(image),
                                      exception,LoadImagesText,
                                      image->filename))
            break;
        }
    } while ((count != 0) && (c == 'P'));
  while (image->previous != (Image *) NULL)
    image=image->previous;
  CloseBlob(image);
  return(image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r P N M I m a g e                                           %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method RegisterPNMImage adds attributes for the PNM image format to
%  the list of supported formats.  The attributes include the image format
%  tag, a method to read and/or write the format, whether the format
%  supports the saving of more than one frame to the same file or blob,
%  whether the format supports native in-memory I/O, and a brief
%  description of the format.
%
%  The format of the RegisterPNMImage method is:
%
%      RegisterPNMImage(void)
%
*/
ModuleExport void RegisterPNMImage(void)
{
  MagickInfo
    *entry;

  entry=SetMagickInfo("P7");
  entry->decoder=(DecoderHandler) ReadPNMImage;
  entry->encoder=(EncoderHandler) WritePNMImage;
  entry->description="Xv thumbnail format";
  entry->extension_treatment=IgnoreExtensionTreatment;
  entry->module="PNM";
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("PAM");
  entry->decoder=(DecoderHandler) ReadPNMImage;
  entry->encoder=(EncoderHandler) WritePNMImage;
  entry->description="Portable Arbitrary Map format";
  entry->module="PNM";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("PBM");
  entry->decoder=(DecoderHandler) ReadPNMImage;
  entry->encoder=(EncoderHandler) WritePNMImage;
  entry->description="Portable bitmap format (black/white)";
  entry->module="PNM";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("PGM");
  entry->decoder=(DecoderHandler) ReadPNMImage;
  entry->encoder=(EncoderHandler) WritePNMImage;
  entry->description="Portable graymap format (gray scale)";
  entry->module="PNM";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("PNM");
  entry->decoder=(DecoderHandler) ReadPNMImage;
  entry->encoder=(EncoderHandler) WritePNMImage;
  entry->magick=(MagickHandler) IsPNM;
  entry->description="Portable anymap";
  entry->module="PNM";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("PPM");
  entry->decoder=(DecoderHandler) ReadPNMImage;
  entry->encoder=(EncoderHandler) WritePNMImage;
  entry->description="Portable pixmap format (color)";
  entry->module="PNM";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r P N M I m a g e                                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method UnregisterPNMImage removes format registrations made by the
%  PNM module from the list of supported formats.
%
%  The format of the UnregisterPNMImage method is:
%
%      UnregisterPNMImage(void)
%
*/
ModuleExport void UnregisterPNMImage(void)
{
  (void) UnregisterMagickInfo("P7");
  (void) UnregisterMagickInfo("PAM");
  (void) UnregisterMagickInfo("PBM");
  (void) UnregisterMagickInfo("PGM");
  (void) UnregisterMagickInfo("PNM");
  (void) UnregisterMagickInfo("PPM");
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e P N M I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Procedure WritePNMImage writes an image to a file in the PNM rasterfile
%  format.
%
%  The format of the WritePNMImage method is:
%
%      unsigned int WritePNMImage(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows.
%
%    o status: Method WritePNMImage return True if the image is written.
%      False is returned is there is a memory shortage or if the image file
%      fails to write.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o image:  A pointer to an Image structure.
%
%
*/
static const char lut_255[][4] =
{
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14",
  "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27",
  "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40",
  "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53",
  "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66",
  "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
  "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92",
  "93", "94", "95", "96", "97", "98", "99", "100", "101", "102", "103", "104",
  "105", "106", "107", "108", "109", "110", "111", "112", "113", "114", "115",
  "116", "117", "118", "119", "120", "121", "122", "123", "124", "125", "126",
  "127", "128", "129", "130", "131", "132", "133", "134", "135", "136", "137",
  "138", "139", "140", "141", "142", "143", "144", "145", "146", "147", "148",
  "149", "150", "151", "152", "153", "154", "155", "156", "157", "158", "159",
  "160", "161", "162", "163", "164", "165", "166", "167", "168", "169", "170",
  "171", "172", "173", "174", "175", "176", "177", "178", "179", "180", "181",
  "182", "183", "184", "185", "186", "187", "188", "189", "190", "191", "192",
  "193", "194", "195", "196", "197", "198", "199", "200", "201", "202", "203",
  "204", "205", "206", "207", "208", "209", "210", "211", "212", "213", "214",
  "215", "216", "217", "218", "219", "220", "221", "222", "223", "224", "225",
  "226", "227", "228", "229", "230", "231", "232", "233", "234", "235", "236",
  "237", "238", "239", "240", "241", "242", "243", "244", "245", "246", "247",
  "248", "249", "250", "251", "252", "253", "254", "255"
};

#define AppendUnsignedCharValueToString(j,buffer,value) \
{ \
  const char *lut_entry=lut_255[value]; \
  while(*lut_entry != '\0') \
    { \
      buffer[j++]=*lut_entry; \
      lut_entry++; \
    } \
}

static unsigned int WritePNMImage(const ImageInfo *image_info,Image *image)
{
  char
    buffer[MaxTextExtent];

  const ImageAttribute
    *attribute;

  IndexPacket
    index;

  PNMSubformat
    format;

  unsigned int
    depth;

  unsigned long
    y;

  register const PixelPacket
    *p;

  register const IndexPacket
    *indexes;

  register unsigned long
    i,
    x;

  unsigned int
    scene,
    status;

  size_t
    image_list_length;

  /*
    Open output image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  image_list_length=GetImageListLength(image);
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
  if (status == MagickFail)
    ThrowWriterException(FileOpenError,UnableToOpenFile,image);
  scene=0;
  do
    {
      depth=(image->depth <= 8 ? 8 : image->depth <= 16 ? 16 : 32);

      /*
        Write PNM file header.
      */
      format=Undefined_PNM_Format;
      if (LocaleCompare(image_info->magick,"P7") == 0)
        {
          format=XV_332_Format;
        }
      else if (LocaleCompare(image_info->magick,"PPM") == 0)
        {
          format=PPM_RAW_Format;
        }
      else if (LocaleCompare(image_info->magick,"PGM") == 0)
        {
          format=PGM_RAW_Format;
        }
      else if (LocaleCompare(image_info->magick,"PBM") == 0)
        {
          format=PBM_RAW_Format;
        }
      else if (LocaleCompare(image_info->magick,"PAM") == 0)
        {
          format=PAM_Format;
        }
      else /* PNM auto format */
        {
          ImageCharacteristics
            characteristics;

          /*
            Make sure that image is in an RGB type space.
          */
          (void) TransformColorspace(image,RGBColorspace);

          /*
            Analyze image to be written.
          */
          if (!GetImageCharacteristics(image,&characteristics,
                                       (OptimizeType == image_info->type),
                                       &image->exception))
            {
              CloseBlob(image);
              return MagickFail;
            }

          if ((characteristics.monochrome) &&
              (image_info->type != GrayscaleType) &&
              (image_info->type != GrayscaleMatteType) &&
              (image_info->type != TrueColorType) &&
              (image_info->type != TrueColorMatteType))
            {
              /* PBM */
              format=PBM_RAW_Format;
            }
          else if ((characteristics.grayscale) &&
                   (image_info->type != TrueColorType) &&
                   (image_info->type != TrueColorMatteType))
            {
              /* PGM */
              format=PGM_RAW_Format;
            }
          else
            {
              /* PPM */
              format=PPM_RAW_Format;
            }
        }

      /*
        Check if ASCII subformat is requested.
      */
      if ((PBM_RAW_Format == format) || (PGM_RAW_Format == format) | (PPM_RAW_Format == format))
        {
          MagickBool
            ascii = MagickFalse;

          /*
            If quality is set to zero or "pnm:ascii" is defined, then
            select an ASCII subformat.
          */
          if (image_info->quality == 0)
            ascii=MagickTrue;
          else if ((AccessDefinition(image_info,"pnm","ascii")))
            ascii=MagickTrue;

          if (ascii)
            {
              if (PBM_RAW_Format == format)
                format=PBM_ASCII_Format;
              else if (PGM_RAW_Format == format)
                format=PGM_ASCII_Format;
              else if (PPM_RAW_Format == format)
                format=PPM_ASCII_Format;
            }
        }

      {
        const char *header = "";
        switch (format)
          {
          case Undefined_PNM_Format: break;
          case PBM_ASCII_Format: header="P1"; break;
          case PGM_ASCII_Format: header="P2"; break;
          case PPM_ASCII_Format: header="P3"; break;
          case PBM_RAW_Format:   header="P4"; break;
          case PGM_RAW_Format:   header="P5"; break;
          case PPM_RAW_Format:   header="P6"; break;
          case PAM_Format:       header="P7"; break;
          case XV_332_Format:    header="P7 332"; break;
          }
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Format Id: %s",
                              header);
        (void) WriteBlobString(image,header);
        (void) WriteBlobByte(image,'\n');
      }

      attribute=GetImageAttribute(image,"comment");
      if (attribute != (const ImageAttribute *) NULL)
        {
          register char
            *av;

          /*
            Write comments to file.
          */
          (void) WriteBlobByte(image,'#');
          for (av=attribute->value; *av != '\0'; av++)
            {
              (void) WriteBlobByte(image,*av);
              if ((*av == '\n') && (*(av+1) != '\0'))
                (void) WriteBlobByte(image,'#');
            }
          (void) WriteBlobByte(image,'\n');
        }
      if ((PAM_Format != format) && (XV_332_Format != format))
        {
          FormatString(buffer,"%lu %lu\n",image->columns,image->rows);
          (void) WriteBlobString(image,buffer);
        }
      /*
        Write PNM raster pixels.
      */
      switch (format)
        {
        case PBM_ASCII_Format:
          {
            unsigned int
              polarity;

            size_t
              j;

            /*
              Convert image to a PBM ASCII image.
            */
            (void) SetImageType(image,BilevelType);
            polarity=PixelIntensityToQuantum(&image->colormap[0]) < (MaxRGB/2);
            if (image->colors == 2)
              polarity=PixelIntensityToQuantum(&image->colormap[0]) <
                PixelIntensityToQuantum(&image->colormap[1]);
            i=0;
            j=0;
            for (y=0; y < image->rows; y++)
              {
                p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
                if (p == (const PixelPacket *) NULL)
                  {
                    status=MagickFail;
                    break;
                  }
                indexes=AccessImmutableIndexes(image);
                for (x=0; x < image->columns; x++)
                  {
                    buffer[j++] = (indexes[x] == polarity ? '0' : '1');
                    buffer[j++] = ' ';
                    i++;
                    if (i == 36)
                      {
                        buffer[j++] = '\n';
                        i=0;
                      }
                    if (j+4 > sizeof(buffer))
                      {
                        status=(WriteBlob(image,j,buffer) == j);
                        j=0;
                        if (MagickFail == status)
                          break;
                      }
                  }
                if (image->previous == (Image *) NULL)
                  if (QuantumTick(y,image->rows))
                    if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                                SaveImageText,image->filename,
                                                image->columns,image->rows))
                      {
                        status=MagickFail;
                        break;
                      }
                if (MagickFail == status)
                  break;
              }
            if (MagickFail != status)
              {
                if (i != 0)
                  buffer[j++] = '\n';
                if (j > 0)
                  status=(WriteBlob(image,j,buffer) == j);
              }
            break;
          }
        case PGM_ASCII_Format:
          {
            /*
              Convert image to a PGM ASCII image.
            */
            size_t
              j;

            unsigned int
              value;

            /*
              Make sure that image is in an RGB type space.
            */
            (void) TransformColorspace(image,RGBColorspace);

            i=0;
            j=0;

            value=(depth <=8 ? 255U : depth <= 16 ? 65535U : 4294967295U);

            j += snprintf(&buffer[j],(sizeof(buffer)-j),"%u\n",value);
            for (y=0; y < image->rows; y++)
              {
                p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
                if (p == (const PixelPacket *) NULL)
                  {
                    status=MagickFail;
                    break;
                  }
                for (x=0; x < image->columns; x++)
                  {
                    if (image->is_grayscale)
                      index=p->red;
                    else
                      index=PixelIntensityToQuantum(p);
                    if (depth <= 8)
                      {
                        /* Use LUT for speed */
                        value=ScaleQuantumToChar(index);
                        AppendUnsignedCharValueToString(j,buffer,value);
                        buffer[j++] = ' ';
                      }
                    else if (depth <= 16)
                      {
                        value=ScaleQuantumToShort(index);
                        j += snprintf(&buffer[j],(sizeof(buffer)-j)," %u",value);
                      }
                    else
                      {
                        value=ScaleQuantumToLong(index);
                        j += snprintf(&buffer[j],(sizeof(buffer)-j)," %u",value);
                      }

                    i++;
                    if (i == 12)
                      {
                        buffer[j++] = '\n';
                        i=0;
                      }
                    if (j+8 > sizeof(buffer))
                      {
                        status=(WriteBlob(image,j,buffer) == j);
                        j=0;
                        if (MagickFail == status)
                          break;
                      }
                    p++;
                  }
                if (image->previous == (Image *) NULL)
                  if (QuantumTick(y,image->rows))
                    if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                                SaveImageText,image->filename,
                                                image->columns,image->rows))
                      {
                        status=MagickFail;
                        break;
                      }
                if (MagickFail == status)
                  break;
              }
            if (MagickFail != status)
              {
                if (i != 0)
                  buffer[j++] = '\n';
                if (j > 0)
                  status=(WriteBlob(image,j,buffer) == j);
              }
            break;
          }
        case PPM_ASCII_Format:
          {
            /*
              Convert image to a PPM ASCII image.
            */
            size_t
              j;

            unsigned int
              value;

            /*
              Make sure that image is in an RGB type space.
            */
            (void) TransformColorspace(image,RGBColorspace);

            i=0;
            j=0;

            value=(depth <=8 ? 255U : (depth <= 16 ? 65535U : 4294967295U));

            j += snprintf(&buffer[j],(sizeof(buffer)-j),"%u\n",value);
            for (y=0; y < image->rows; y++)
              {
                p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
                if (p == (const PixelPacket *) NULL)
                  {
                    status=MagickFail;
                    break;
                  }
                for (x=0; x < image->columns; x++)
                  {
                    if (depth <= 8)
                      {
                        /* Use LUT for speed */
                        value=ScaleQuantumToChar(p->red);
                        AppendUnsignedCharValueToString(j,buffer,value);
                        buffer[j++] = ' ';
                        value=ScaleQuantumToChar(p->green);
                        AppendUnsignedCharValueToString(j,buffer,value);
                        buffer[j++] = ' ';
                        value=ScaleQuantumToChar(p->blue);
                        AppendUnsignedCharValueToString(j,buffer,value);
                        buffer[j++] = ' ';
                      }
                    else if (depth <= 16)
                      {
                        j += snprintf(&buffer[j],(sizeof(buffer)-j),"%u %u %u ",
                                     ScaleQuantumToShort(p->red),
                                     ScaleQuantumToShort(p->green),
                                     ScaleQuantumToShort(p->blue));
                      }
                    else
                      {
                        j += snprintf(&buffer[j],(sizeof(buffer)-j),"%u %u %u ",
                                     (unsigned int) ScaleQuantumToLong(p->red),
                                     (unsigned int) ScaleQuantumToLong(p->green),
                                     (unsigned int) ScaleQuantumToLong(p->blue));
                      }
                    i++;
                    if (i == 4)
                      {
                        buffer[j++] = '\n';
                        i=0;
                      }
                    if (j+(8*3) > sizeof(buffer))
                      {
                        status=(WriteBlob(image,j,buffer) == j);
                        j=0;
                        if (MagickFail == status)
                          break;
                      }
                    p++;
                  }
                if (image->previous == (Image *) NULL)
                  if (QuantumTick(y,image->rows))
                    if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                                SaveImageText,image->filename,
                                                image->columns,image->rows))
                      {
                        status=MagickFail;
                        break;
                      }
                if (MagickFail == status)
                  break;
              }
            if (MagickFail != status)
              {
                if (i != 0)
                  buffer[j++] = '\n';
                if (j > 0)
                  status=(WriteBlob(image,j,buffer) == j);
              }
            break;
          }
        case PBM_RAW_Format:
        case PGM_RAW_Format:
        case PPM_RAW_Format:
        case PAM_Format:
          {
            ExportPixelAreaOptions
              export_options;

            size_t
              bytes_per_row;

            QuantumType
              quantum_type;

            unsigned int
              bits_per_sample,
              samples_per_pixel;

            MagickBool
              grayscale_miniswhite=MagickFalse;

            unsigned char
              *pixels;

            /*
              Deduce correct export parameters.
            */
            bits_per_sample=(depth <=8 ? 8 : (depth <= 16 ? 16 : 32));
            quantum_type=RGBQuantum;
            if (PBM_RAW_Format == format)
              {
                bits_per_sample=1;
                grayscale_miniswhite=MagickTrue;
                quantum_type=GrayQuantum;
              }
            else if (PGM_RAW_Format == format)
              {
                quantum_type=GrayQuantum;
              }
            else if (PPM_RAW_Format == format)
              {
                quantum_type=RGBQuantum;
              }
            else if (PAM_Format == format)
              {
                ImageCharacteristics
                  characteristics;

                /*
                  Make sure image is of desired type.
                */
                if (UndefinedType != image_info->type)
                  SetImageType(image,image_info->type);

                /*
                  Analyze the image to get its characteristics.
                */
                if (!GetImageCharacteristics(image,&characteristics,
                                             (OptimizeType == image_info->type),
                                             &image->exception))
                  {
                    CloseBlob(image);
                    return MagickFail;
                  }

                /*
                  Choose best encoding based on image characteristics.
                */
                if (characteristics.cmyk)
                  {
                    if (image->matte)
                      quantum_type=CMYKAQuantum;
                    else
                      quantum_type=CMYKQuantum;
                  }
                else if (characteristics.monochrome)
                  {
                    bits_per_sample=1;
                    grayscale_miniswhite=MagickTrue;

                    if (image->matte)
                      quantum_type=GrayAlphaQuantum;
                    else
                      quantum_type=GrayQuantum;
                  }
                else if (characteristics.grayscale)
                  {
                    if (image->matte)
                      quantum_type=GrayAlphaQuantum;
                    else
                      quantum_type=GrayQuantum;
                  }
                else
                  {
                    if (image->matte)
                      quantum_type=RGBAQuantum;
                    else
                      quantum_type=RGBQuantum;
                  }
              }
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Export Quantum Type: %s",
                                    QuantumTypeToString(quantum_type));

            samples_per_pixel=MagickGetQuantumSamplesPerPixel(quantum_type);
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Samples/Pixel: %u", samples_per_pixel);

            if (1 == bits_per_sample)
              {
                /* bytes_per_row=(((size_t) image->columns*samples_per_pixel+7) >> 3); */
                bytes_per_row=MagickArraySize(image->columns,samples_per_pixel);
                if (bytes_per_row)
                  bytes_per_row += 7;
                if (bytes_per_row)
                  bytes_per_row >>= 3;
              }
            else
              {
                bytes_per_row=MagickArraySize((((size_t) bits_per_sample+7)/8)*
                                              samples_per_pixel,image->columns);
              }

            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Bytes/Row: %" MAGICK_SIZE_T_F "u",
                                    (MAGICK_SIZE_T) bytes_per_row);

            ExportPixelAreaOptionsInit(&export_options);
            export_options.grayscale_miniswhite=grayscale_miniswhite;

            /*
              Allocate memory for pixels.
            */
            pixels=MagickAllocateResourceLimitedMemory(unsigned char *,bytes_per_row);
            if (pixels == (unsigned char *) NULL)
              ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,
                                   image);

            /*
              Output header details
            */
            if (PAM_Format == format)
              {
                /*
                  PAM header
                */
                const char *tuple_type=NULL;

                if (GrayQuantum == quantum_type)
                  {
                    if (1 == bits_per_sample)
                      tuple_type="BLACKANDWHITE";
                    else
                      tuple_type="GRAYSCALE";
                  }
                else if (GrayAlphaQuantum == quantum_type)
                  {
                    if (1 == bits_per_sample)
                      tuple_type="BLACKANDWHITE_ALPHA";
                    else
                      tuple_type="GRAYSCALE_ALPHA";
                  }
                else if (RGBQuantum == quantum_type)
                  tuple_type="RGB";
                else if (RGBAQuantum == quantum_type)
                  tuple_type="RGB_ALPHA";
                else if (CMYKQuantum == quantum_type)
                  tuple_type="CMYK";
                else if (CMYKAQuantum == quantum_type)
                  tuple_type="CMYK_ALPHA";

                FormatString(buffer,"WIDTH %lu\nHEIGHT %lu\nDEPTH %u"
                             "\nMAXVAL %lu\nTUPLTYPE %s\n",
                             image->columns,image->rows,samples_per_pixel,
                             MaxValueGivenBits(bits_per_sample),tuple_type);
                if (image->logging)
                  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                        "PAM Header: WIDTH %lu, HEIGHT %lu, "
                                        "DEPTH %u, MAXVAL %lu, TUPLTYPE %s",
                                        image->columns,
                                        image->rows,samples_per_pixel,
                                        MaxValueGivenBits(bits_per_sample),
                                        tuple_type);
                WriteBlobString(image,buffer);

                (void) WriteBlobString(image,"ENDHDR\n");
              }
            else if ((PGM_RAW_Format == format) || (PPM_RAW_Format == format))
              {
                /*
                  PGM, PPM header
                */
                FormatString(buffer,"%lu\n",MaxValueGivenBits(bits_per_sample));
                WriteBlobString(image,buffer);
              }

            /*
              Output pixels
            */
            for (y=0; y < image->rows; y++)
              {
                p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
                if (p == (const PixelPacket *) NULL)
                  break;
                if (ExportImagePixelArea(image,quantum_type,bits_per_sample,pixels,&export_options,0) == MagickFail)
                  break;
                if (WriteBlob(image,bytes_per_row,(char *) pixels) != bytes_per_row)
                  break;
                if (image->previous == (Image *) NULL)
                  if (QuantumTick(y,image->rows))
                    if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                                SaveImageText,image->filename,
                                                image->columns,image->rows))
                      break;
              }
            MagickFreeResourceLimitedMemory(pixels);

            break;
          }
        case XV_332_Format:
          {
            static const short int
              dither_red[2][16]=
              {
                {-16,  4, -1, 11,-14,  6, -3,  9,-15,  5, -2, 10,-13,  7, -4,  8},
                { 15, -5,  0,-12, 13, -7,  2,-10, 14, -6,  1,-11, 12, -8,  3, -9}
              },
              dither_green[2][16]=
                {
                  { 11,-15,  7, -3,  8,-14,  4, -2, 10,-16,  6, -4,  9,-13,  5, -1},
                  {-12, 14, -8,  2, -9, 13, -5,  1,-11, 15, -7,  3,-10, 12, -6,  0}
                },
                dither_blue[2][16]=
                  {
                    { -3,  9,-13,  7, -1, 11,-15,  5, -4,  8,-14,  6, -2, 10,-16,  4},
                    {  2,-10, 12, -8,  0,-12, 14, -6,  3, -9, 13, -7,  1,-11, 15, -5}
                  };

                long
                  value;

                Quantum
                  pixel;

                unsigned short
                  *blue_map[2][16],
                  *green_map[2][16],
                  *red_map[2][16];

                unsigned int
                  j;

                /*
                  Allocate and initialize dither maps.
                */
                memset(blue_map,0,sizeof(blue_map));
                memset(green_map,0,sizeof(green_map));
                memset(red_map,0,sizeof(red_map));
                for (i=0; i < 2; i++)
                  for (j=0; j < 16; j++)
                    {
                      red_map[i][j]=MagickAllocateResourceLimitedMemory(unsigned short *,
                                                         256*sizeof(unsigned short));
                      green_map[i][j]=MagickAllocateResourceLimitedMemory(unsigned short *,
                                                           256*sizeof(unsigned short));
                      blue_map[i][j]=MagickAllocateResourceLimitedMemory(unsigned short *,
                                                          256*sizeof(unsigned short));
                      if ((red_map[i][j] == (unsigned short *) NULL) ||
                          (green_map[i][j] == (unsigned short *) NULL) ||
                          (blue_map[i][j] == (unsigned short *) NULL))
                        {
                          for (i=0; i < 2; i++)
                            for (j=0; j < 16; j++)
                              {
                                MagickFreeResourceLimitedMemory(green_map[i][j]);
                                MagickFreeResourceLimitedMemory(blue_map[i][j]);
                                MagickFreeResourceLimitedMemory(red_map[i][j]);
                              }
                          ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,
                                               image);
                        }
                    }
                /*
                  Initialize dither tables.
                */
                for (i=0; i < 2; i++)
                  for (j=0; j < 16; j++)
                    for (x=0; x < 256; x++)
                      {
                        value=x-16;
                        if (x < 48)
                          value=x/2+8;
                        value+=dither_red[i][j];
                        red_map[i][j][x]=(unsigned short)
                          ((value < 0) ? 0 : (value > 255) ? 255 : value);
                        value=x-16;
                        if (x < 48)
                          value=x/2+8;
                        value+=dither_green[i][j];
                        green_map[i][j][x]=(unsigned short)
                          ((value < 0) ? 0 : (value > 255) ? 255 : value);
                        value=x-32;
                        if (x < 112)
                          value=x/2+24;
                        value+=2*dither_blue[i][j];
                        blue_map[i][j][x]=(unsigned short)
                          ((value < 0) ? 0 : (value > 255) ? 255 : value);
                      }
                /*
                  Write pixels.
                */
                (void) WriteBlobString(image,"#END_OF_COMMENTS\n");
                FormatString(buffer,"%lu %lu 255\n",image->columns,image->rows);
                (void) WriteBlobString(image,buffer);
                i=0;
                j=0;
                for (y=0; y < image->rows; y++)
                  {
                    p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
                    if (p == (const PixelPacket *) NULL)
                      break;
                    for (x=0; x < image->columns; x++)
                      {
                        if (!image_info->dither)
                          pixel=(Quantum) ((ScaleQuantumToChar(p->red) & 0xe0) |
                                           ((ScaleQuantumToChar(p->green) & 0xe0) >> 3) |
                                           ((ScaleQuantumToChar(p->blue) & 0xc0) >> 6));
                        else
                          pixel=(Quantum)
                            ((red_map[i][j][ScaleQuantumToChar(p->red)] & 0xe0) |
                             ((green_map[i][j][ScaleQuantumToChar(p->green)] & 0xe0) >> 3) |
                             ((blue_map[i][j][ScaleQuantumToChar(p->blue)] & 0xc0) >> 6));
                        (void) WriteBlobByte(image,pixel);
                        p++;
                        j++;
                        if (j == 16)
                          j=0;
                      }
                    i++;
                    if (i == 2)
                      i=0;
                    if (QuantumTick(y,image->rows))
                      if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                                  SaveImageText,image->filename,
                                                  image->columns,image->rows))
                        break;
                  }
                /*
                  Free allocated memory.
                */
                for (i=0; i < 2; i++)
                  for (j=0; j < 16; j++)
                    {
                      MagickFreeResourceLimitedMemory(green_map[i][j]);
                      MagickFreeResourceLimitedMemory(blue_map[i][j]);
                      MagickFreeResourceLimitedMemory(red_map[i][j]);
                    }
                break;
          }
        case Undefined_PNM_Format:
          break;
        }
      if (image->next == (Image *) NULL)
        break;
      image=SyncNextImageInList(image);
      if (status != MagickFail)
        status=MagickMonitorFormatted(scene++,image_list_length,
                                      &image->exception,SaveImagesText,
                                      image->filename);
      if (status == MagickFail)
        break;
    } while (image_info->adjoin);
  if (image_info->adjoin)
    while (image->previous != (Image *) NULL)
      image=image->previous;
  status &= CloseBlob(image);
  return(status);
}
