/*
% Copyright (C) 2003 - 2021 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   SSSSS  33333                              %
%                            P   P  SS        33                              %
%                            PPPP    SSS    333                               %
%                            P         SS     33                              %
%                            P      SSSSS  33333                              %
%                                                                             %
%                                                                             %
%                    Write Postscript Level III 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/pixel_cache.h"
#include "magick/channel.h"
#include "magick/color.h"
#include "magick/compress.h"
#include "magick/constitute.h"
#include "magick/enum_strings.h"
#include "magick/magick.h"
#include "magick/map.h"
#include "magick/monitor.h"
#include "magick/tempfile.h"
#include "magick/utility.h"
#include "magick/version.h"
#if defined(HasTIFF)
#define CCITTParam  "-1"
#else
#define CCITTParam  "0"
#endif

/* For consistent PS and C code usage */
#define PS3_NoCompression "0"
#define PS3_FaxCompression "1"
#define PS3_RLECompression "2"
#define PS3_LZWCompression "3"
#define PS3_ZipCompression "4"
#define PS3_JPEGCompression "5"

#define PS3_RGBColorspace "0"
#define PS3_CMYKColorspace "1"

#define PS3_DirectClass "0"
#define PS3_PseudoClass "1"

/*
  Forward declarations.
*/
static unsigned int
  WritePS3Image(const ImageInfo *,Image *),
  ZLIBEncode2Image(Image *,const size_t,const unsigned long,unsigned char *,
    WriteByteHook,void *);

#if defined(HasTIFF)
#if defined(HAVE_TIFFCONF_H)
#include "tiffconf.h"
#endif
#include "tiffio.h"

#if defined(HAVE_STDINT_H) && (TIFFLIB_VERSION >= 20201219)
#  undef uint16
#  define uint16 uint16_t
#  undef uint32
#  define uint32 uint32_t
#endif /* TIFFLIB_VERSION */

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   H u f f m a n 2 D E n c o d e I m a g e                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method Huffman2DEncodeImage compresses an image via two-dimensional
%  Huffman-coding.
%
%  The format of the Huffman2DEncodeImage method is:
%
%      unsigned int Huffman2DEncodeImage(const ImageInfo *image_info,
%        Image *image)
%
%  A description of each parameter follows:
%
%    o status:  Method Huffman2DEncodeImage returns True if all the pixels are
%      compressed without error, otherwise False.
%
%    o image_info: The image info..
%
%    o image: The image.
%
*/
static unsigned int SerializeHuffman2DImage(const ImageInfo *image_info,
  Image *image, unsigned char **pixels, size_t *length)
{
  char
    filename[MaxTextExtent];

  Image
    *huffman_image;

  ImageInfo
    *clone_info;

  long
    count,
    j;

  register long
    i;

  TIFF
    *tiff;

  uint16
    fillorder;

  unsigned char
    *buffer,
    *p;

  unsigned int
    status;

  unsigned long
    *byte_count,
    strip_size;

  /*
    Write image as CCITTFax4 TIFF image to a temporary file.
  */
  assert(image_info != (ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  if(!AcquireTemporaryFileName(filename))
    {
      ThrowBinaryException(FileOpenError,UnableToCreateTemporaryFile,
        filename)
    }
  huffman_image=CloneImage(image,0,0,True,&image->exception);
  if (huffman_image == (Image *) NULL)
    return(False);
  /*
    TODO: If (image, then) huffman_image->compression is JPEG, huffman_image
    is changed to DirectClass in WriteTIFFImage and the huffman_image ends up
    broken. Change WriteTIFFIMage to not alter bilevel image to directclass
    when clone_info is G4 compression. Until then, set
    huffman_image->compression here:
  */
  huffman_image->compression=Group4Compression;

  (void) SetImageType(huffman_image,BilevelType);
  FormatString(huffman_image->filename,"tiff:%s",filename);
  clone_info=CloneImageInfo(image_info);
  clone_info->compression=Group4Compression;
  clone_info->type=BilevelType;
  (void) AddDefinitions(clone_info,"tiff:fill-order=msb2lsb",
                        &image->exception);
  status=WriteImage(clone_info,huffman_image);
  DestroyImageInfo(clone_info);
  DestroyImage(huffman_image);
  if (status == False)
    {
      (void) LiberateTemporaryFile(filename);
      return(False);
    }
  tiff=TIFFOpen(filename,"rb");
  if (tiff == (TIFF *) NULL)
    {
      (void) LiberateTemporaryFile(filename);
      ThrowBinaryException(FileOpenError,UnableToOpenFile,
        image_info->filename)
    }
  /*
    Allocate raw strip buffer.
  */
  (void) TIFFGetField(tiff,TIFFTAG_STRIPBYTECOUNTS,&byte_count);
  *length=strip_size=byte_count[0];
  for (i=1; i < (long) TIFFNumberOfStrips(tiff); i++)
  {
    if (byte_count[i] > strip_size)
      strip_size=byte_count[i];
    *length+=byte_count[i];
  }
  buffer=MagickAllocateResourceLimitedMemory(unsigned char *,strip_size);
  if (buffer == (unsigned char *) NULL)
    {
      TIFFClose(tiff);
      (void) LiberateTemporaryFile(filename);
      ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
        (char *) NULL)
    }
  *pixels=MagickAllocateResourceLimitedMemory(unsigned char *,*length);
  if (*pixels == (unsigned char *) NULL)
    {
      MagickFreeResourceLimitedMemory(buffer);
      TIFFClose(tiff);
      (void) LiberateTemporaryFile(filename);
      ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
        (char *) NULL)
    }

  (void) TIFFGetFieldDefaulted(tiff,TIFFTAG_FILLORDER,&fillorder);
  p=(*pixels);
  for (i=0; i < (long) TIFFNumberOfStrips(tiff); i++)
  {
    count=TIFFReadRawStrip(tiff,(uint32) i,buffer,(long) byte_count[i]);
    if (fillorder == FILLORDER_LSB2MSB)
      TIFFReverseBits(buffer,count);
    for (j=0; j < count; j++)
      *p++=buffer[j];
  }
  MagickFreeResourceLimitedMemory(buffer);
  TIFFClose(tiff);
  (void) LiberateTemporaryFile(filename);
  return(True);
}

static unsigned int Huffman2DEncodeImage(const ImageInfo *image_info,
  Image *image)
{
  size_t
    length;

  register unsigned long
    i;

  unsigned char
    *pixels;

  unsigned int
    status;

  assert(image_info != (ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);

  status=SerializeHuffman2DImage(image_info,image,&pixels,&length);
  if (!status)
    return False;

  Ascii85Initialize(image);
  for (i=0; i < length; i++)
    Ascii85Encode(image,(unsigned long) pixels[i]);
  Ascii85Flush(image);
  MagickFreeResourceLimitedMemory(pixels);
  return(True);
}

#else

static unsigned int SerializeHuffman2DImage(const ImageInfo *image_info,
  Image *image, unsigned char **pixels, size_t *length)
{
  ARG_NOT_USED(image_info);
  ARG_NOT_USED(pixels);
  ARG_NOT_USED(length);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  ThrowBinaryException(MissingDelegateError,TIFFLibraryIsNotAvailable,image->filename)
}
static unsigned int Huffman2DEncodeImage(const ImageInfo *image_info,
  Image *image)
{
  ARG_NOT_USED(image_info);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  ThrowBinaryException(MissingDelegateError,TIFFLibraryIsNotAvailable,image->filename)
}
#endif

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   J P E G E n c o d e I m a g e                                             %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method JPEGEncodeImage compresses an image via JPEG compression.
%
%  The format of the JPEGEncodeImage method is:
%
%      unsigned int JPEGEncodeImage(const ImageInfo *image_info,
%        Image *image)
%
%  A description of each parameter follows:
%
%    o status:  Method JPEGEncodeImage returns True if all the pixels are
%      compressed without error, otherwise False.
%
%    o image_info: The image info.
%
%    o image: The image.
%
*/
static MagickPassFail JPEGEncodeImage(const ImageInfo *image_info,
  Image *image)
{
  unsigned char
    *blob;

  size_t
    length;

  MagickPassFail
    status=MagickFail;

  blob=ImageToJPEGBlob(image,image_info,&length,&image->exception);
  if (blob != (unsigned char *) NULL)
    {
      register const unsigned char
        *p;

      register size_t
        i;

        Ascii85Initialize(image);
      for (p=(const unsigned char*) blob,i=0; i < length; i++)
        Ascii85Encode(image,(unsigned long) p[i]);
      Ascii85Flush(image);
      MagickFreeMemory(blob);
      status=MagickPass;
    }
  return status;
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r P S 3 I m a g e                                           %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method RegisterPS3Image adds attributes for the PS3 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 RegisterPS3Image method is:
%
%      RegisterPS3Image(void)
%
*/
ModuleExport void RegisterPS3Image(void)
{
  MagickInfo
    *entry;

  entry=SetMagickInfo("EPS3");
  entry->encoder=(EncoderHandler) WritePS3Image;
  entry->description="Adobe Level III Encapsulated PostScript";
  entry->seekable_stream=MagickTrue;
  entry->module="PS3";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("PS3");
  entry->encoder=(EncoderHandler) WritePS3Image;
  entry->description="Adobe Level III PostScript";
  entry->seekable_stream=MagickTrue;
  entry->module="PS3";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   S e r i a l i z e P s e u d o C l a s s I m a g e                         %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Convert the indexes of a color mapped image to a stream of bytes.
%
%  The format of the SerializePseudoClassImage method is:
%
%       unsigned int SerializePseudoClassImage(const ImageInfo *image_info,
%         Image *image, unsigned char **pixels, size_t *length)
%
%  A description of each parameter follows:
%
%    o image_info: Specifies a pointer to a ImageInfo structure. required by
%                  exception handlers.
%
%    o image: The image for which the color map indexes should be serialized.
%
%    o pixels: the serialized indexes.
%
%    o length: the length of the pixels mamory area.
%
*/
static unsigned int SerializePseudoClassImage(const ImageInfo *image_info,
  Image *image, unsigned char **pixels, size_t *length)
{
  long
    x,
    y;

  register unsigned char
    *q;

  const PixelPacket
    *p;

  int
    status;

  register const IndexPacket
    *indexes;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  status=True;
  *length=MagickArraySize(image->columns,image->rows);
  *pixels=MagickAllocateResourceLimitedMemory(unsigned char *, *length);
  if (*pixels == (unsigned char *) NULL)
    ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
  q=(*pixels);
  for (y=0; y < (long) image->rows; y++)
  {
    p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
    if (p == (const PixelPacket *) NULL)
      break;
    indexes=AccessImmutableIndexes(image);
    for (x=0; x < (long) image->columns; x++)
      *q++=indexes[x];
    if (image->previous == (Image *) NULL)
      if (QuantumTick(y,image->rows))
        {
          status=MagickMonitorFormatted(y,image->rows,&image->exception,
                                        SaveImageText,image->filename,
                                        image->columns,image->rows);
          if (status == False)
            break;
        }
  }
  if (status == False)
    MagickFreeResourceLimitedMemory(*pixels);
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   S e r i a l i z e M u l t i C h a n n e l I m a g e                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Convert three and four channel images to a stream of bytes.
%
%  The format of the SerializeMultiChannelImage method is:
%
%       unsigned int SerializeMultiChannelImage(const ImageInfo *image_info,
%         Image *image, unsigned char **pixels, size_t *length)
%
%  A description of each parameter follows:
%
%    o image_info: Specifies a pointer to a ImageInfo structure. required by
%                  exception handlers.
%
%    o image: The image for which the RGB or CMYK channels should be
%             serialized.
%
%    o pixels: the serialized image channels.
%
%    o length: the length of the pixels mamory area.
%
*/
static unsigned int SerializeMultiChannelImage(const ImageInfo *image_info,
  Image *image, unsigned char **pixels, size_t *length)
{
  long
    x,
    y;

  register unsigned char
    *q;

  const PixelPacket
    *p;

  int
    status;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  status=True;
  *length=(size_t) (image->colorspace == CMYKColorspace ? 4U : 3U)*MagickArraySize(image->columns,image->rows);
  *pixels=MagickAllocateResourceLimitedMemory(unsigned char *, *length);
  if (*pixels == (unsigned char *) NULL)
    ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);

  q=(*pixels);
  for (y=0; y < (long) image->rows; y++)
  {
    p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
    if (p == (const PixelPacket *) NULL)
      break;
    if (image->colorspace == CMYKColorspace)
      for (x=0; x < (long) image->columns; x++)
      {
        *q++=ScaleQuantumToChar(p->red);
        *q++=ScaleQuantumToChar(p->green);
        *q++=ScaleQuantumToChar(p->blue);
        *q++=ScaleQuantumToChar(p->opacity);
        p++;
      }
    else
      for (x=0; x < (long) image->columns; x++)
      {
        *q++=ScaleQuantumToChar(p->red);
        *q++=ScaleQuantumToChar(p->green);
        *q++=ScaleQuantumToChar(p->blue);
        p++;
      }
    if (image->previous == (Image *) NULL)
      if (QuantumTick(y,image->rows))
        {
          status=MagickMonitorFormatted(y,image->rows,&image->exception,
                                        SaveImageText,image->filename,
                                        image->columns,image->rows);
          if (status == False)
            break;
        }
  }
  if (status == False)
    MagickFreeResourceLimitedMemory(*pixels);
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   S e r i a l i z e S i n g l e C h a n n e l I m a g e                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Convert 1 and 8 bit single channel images to a stream of bytes. Bilevel
%  image pixels are packed 8 pixels in a byte.
%
%  The format of the SerializeSingleChannelImage method is:
%
%       unsigned int SerializeSingleChannelImage(const ImageInfo *image_info,
%         Image *image, unsigned char **pixels, size_t *length)
%
%  A description of each parameter follows:
%
%    o image_info: Specifies a pointer to an ImageInfo structure. Required by
%                  exception handlers.
%
%    o image: The image for which the RGB or CMYK channels should be
%             serialized.
%
%    o characteristics: Already populated image characteristics.
%
%    o pixels: the serialized image channels.
%
%    o length: the length of the pixels mamory area.
%
*/
static unsigned int SerializeSingleChannelImage(const ImageInfo *image_info,
  Image *image,unsigned char **pixels, size_t *length)
{
  unsigned long
    x,
    y;

  register unsigned char
    *q;

  const PixelPacket
    *p;

  unsigned long
    pack,
    padded_columns;

  unsigned char
    code,
    bit;

  int
    status;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  status=True;
  pack=IsMonochromeImage(image,&image->exception) ? 8 : 1;
  /* Padded columns are padded to byte boundary */
  padded_columns=((image->columns+pack-1)/pack)*pack;
  *length=padded_columns*image->rows/pack;
  *pixels=MagickAllocateResourceLimitedMemory(unsigned char *, *length);
  if (*pixels == (unsigned char *) NULL)
    ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,
      image);

  q=(*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 (pack == 1)
    {
      for (x=0; x < image->columns; x++)
      {
        *q++=ScaleQuantumToChar(PixelIntensityToQuantum(p));
        p++;
      }
    }
    else
    {
      code=0;
      for (x=0; x < padded_columns; x++)
      {
        bit=0x00;
        if (x < image->columns)
          bit=PixelIntensityToQuantum(p) == TransparentOpacity ? 0x01 : 0x00;
        code=(code << 1)+bit;
        if ((x+1) % pack == 0)
        {
          *q++=code;
          code=0;
        }
        p++;
      }
    }
    if (image->previous == (Image *) NULL)
      if (QuantumTick(y,image->rows))
        {
          status=MagickMonitorFormatted(y,image->rows,
                                        &image->exception,SaveImageText,
                                        image->filename,
                                        image->columns,image->rows);
          if (status == False)
            break;
        }
  }
  if (status == False)
    MagickFreeResourceLimitedMemory(*pixels);
  return(status);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r P S 3 I m a g e                                       %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method UnregisterPS3Image removes format registrations made by the
%  PS3 module from the list of supported formats.
%
%  The format of the UnregisterPS3Image method is:
%
%      UnregisterPS3Image(void)
%
*/
ModuleExport void UnregisterPS3Image(void)
{
  (void) UnregisterMagickInfo("EPS3");
  (void) UnregisterMagickInfo("PS3");
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e P S 3 M a s k I m a g e                                         %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method WritePS3ImageMask writes the alpha channel of an image as an
%  Encapsulated Postscript Level III image mask. The mask is written as a
%  bilevel image, converted from the grayscale alpha channel using dithering
%  options supplied in image_info.
%
%  The format of the WritePS3MaskImage method is:
%
%      unsigned int WritePS3MaskImage(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows:
%
%    o status: Method WritePS3MaskImage return True if the mask is printed.
%      False is returned on errors like out-of-memory.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o image: The address of a structure of type Image.
%
%
*/
typedef struct WriteByteHookInfo_
{
  Image
    *image;
} WriteByteHookInfo;

static unsigned int MaskWriteByteHook(Image *mask_image,
  const magick_uint8_t code, void *info)
{
  ARG_NOT_USED(mask_image);
  Ascii85Encode(((WriteByteHookInfo *)info)->image, (unsigned long)code);
  return(True);
}

static MagickPassFail WritePS3MaskImage(const ImageInfo *image_info,Image *image)
{
  char
    buffer[MaxTextExtent];

  Image
    *mask_image;

  size_t
    i,
    length;

  CompressionType
    compression;

  unsigned char
    *pixels;

  MagickPassFail
    status;

  magick_off_t
    here,
    start,
    stop;

  assert(image_info != (ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  assert(image->matte);
  status=MagickPass;
  compression=image->compression;
  if (image_info->compression != UndefinedCompression)
    compression=image_info->compression;

  /* Keep position of BeginData DSC comment for update later */
  start=TellBlob(image);
  if (start < 0)
    return MagickFail;
  FormatString(buffer,"%%%%BeginData:%13ld ASCII Bytes\n",0L);
  (void) WriteBlobString(image,buffer);
  stop=TellBlob(image);
  if (stop < 0)
    return MagickFail;

  /* Only lossless compressions for the mask */
  switch (compression)
  {
    case NoCompression:
      FormatString(buffer,
        "currentfile %lu %lu "PS3_NoCompression" ByteStreamDecodeFilter\n",
        image->columns, image->rows);
      break;
    case FaxCompression:
    default:
      FormatString(buffer,
        "currentfile %lu %lu "PS3_FaxCompression" ByteStreamDecodeFilter\n",
        image->columns, image->rows);
      break;
    case RLECompression:
      FormatString(buffer,
        "currentfile %lu %lu "PS3_RLECompression" ByteStreamDecodeFilter\n",
        image->columns, image->rows);
      break;
    case LZWCompression:
      FormatString(buffer,
        "currentfile %lu %lu "PS3_LZWCompression" ByteStreamDecodeFilter\n",
        image->columns, image->rows);
      break;
    case ZipCompression:
      FormatString(buffer,
        "currentfile %lu %lu "PS3_ZipCompression" ByteStreamDecodeFilter\n",
        image->columns, image->rows);
      break;
  }
  (void)WriteBlobString(image, buffer);
  (void)WriteBlobString(image, "/ReusableStreamDecode filter\n");

  mask_image=CloneImage(image,0,0,True,&image->exception);
  if (mask_image == (Image *) NULL)
    return MagickFail;
  status=ChannelImage(mask_image, OpacityChannel);
  if (!status)
  {
    CopyException(&image->exception,&mask_image->exception);
    DestroyImage(mask_image);
    return(MagickFail);
  }
  (void) SetImageType(mask_image, BilevelType);
  mask_image->matte=False;

  /* Only lossless compressions for the mask */
  switch (compression)
  {
    case NoCompression:
      status=SerializeSingleChannelImage(image_info,mask_image,&pixels,&length);
      if (status)
      {
        Ascii85Initialize(image);
        for (i=0; i < length; i++)
          Ascii85Encode(image, (unsigned long)pixels[i]);
        Ascii85Flush(image);
        MagickFreeResourceLimitedMemory(pixels);
      }
      break;
    case FaxCompression:
    default:
      if (LocaleCompare(CCITTParam,"0") == 0)
      {
        WriteByteHookInfo
          info;
        info.image=image;
        /* Read bytes from mask and write them ASCII85 encoded to image */
        Ascii85Initialize(image);
        status=HuffmanEncode2Image(image_info,mask_image,MaskWriteByteHook,(void *)&info);
        Ascii85Flush(image);
      }
      else
      {
        status=SerializeHuffman2DImage(image_info,mask_image,&pixels,&length);
        if (status)
        {
          Ascii85Initialize(image);
          for (i=0; i < length; i++)
            Ascii85Encode(image,(unsigned long) pixels[i]);
          Ascii85Flush(image);
          MagickFreeResourceLimitedMemory(pixels);
        }
      }
      break;
    case RLECompression:
      status=SerializeSingleChannelImage(image_info,mask_image,&pixels,&length);
      if (status)
      {
        Ascii85Initialize(image);
        status=PackbitsEncode2Image(image,length,pixels,
          Ascii85WriteByteHook,(void *)NULL);
        Ascii85Flush(image);
        MagickFreeResourceLimitedMemory(pixels);
      }
      break;
    case LZWCompression:
      status=SerializeSingleChannelImage(image_info,mask_image,&pixels,&length);
      if (status)
      {
        Ascii85Initialize(image);
        status=LZWEncode2Image(image,length,pixels,Ascii85WriteByteHook,(void*)NULL);
        Ascii85Flush(image);
        MagickFreeResourceLimitedMemory(pixels);
      }
      break;
    case ZipCompression:
      status=SerializeSingleChannelImage(image_info,mask_image,&pixels,&length);
      if (status)
      {
        Ascii85Initialize(image);
        status=ZLIBEncode2Image(image,length,image_info->quality,pixels,
          Ascii85WriteByteHook,(void*)NULL);
        Ascii85Flush(image);
        MagickFreeResourceLimitedMemory(pixels);
      }
      break;
  }
  DestroyImage(mask_image);
  here=TellBlob(image);
  if (here < 0)
    return MagickFail;
  length=here-stop;
  stop=TellBlob(image);
  if (stop < 0)
    return MagickFail;
  if (SeekBlob(image,start,SEEK_SET) != start)
    ThrowWriterException(BlobError,UnableToSeekToOffset,image);
  FormatString(buffer,"%%%%BeginData:%13ld ASCII Bytes\n",(long) length);
  (void) WriteBlobString(image,buffer);
  if (SeekBlob(image,stop,SEEK_SET) != stop)
    ThrowWriterException(BlobError,UnableToSeekToOffset,image);
  (void) WriteBlobString(image,"%%EndData\n");
  (void)WriteBlobString(image, "/mask_stream exch def\n");
  return(status);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e P S 3 I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method WritePS3Image translates an image to encapsulated Postscript
%  Level III for printing.  If the supplied geometry is null, the image is
%  centered on the Postscript page.  Otherwise, the image is positioned as
%  specified by the geometry.
%
%  The format of the WritePS3Image method is:
%
%      MagickPassFail WritePS3Image(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows:
%
%    o status: Method WritePS3Image return MagickPass if the image is printed.
%      MagickFail is returned if the image file cannot be opened for printing.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o image: The address of a structure of type Image;  returned from
%      ReadImage.
%
%
*/
static MagickPassFail WritePS3Image(const ImageInfo *image_info,Image *image)
{
  static const char PostscriptProlog[]=
    "/ByteStreamDecodeFilter\n"
    "{\n"
    "  /z exch def\n"
    "  /r exch def\n"
    "  /c exch def\n"
    "  /ASCII85Decode filter\n"
    "  z "PS3_FaxCompression" eq\n"
    "  {\n"
    "    <<\n"
    "      /K "CCITTParam"\n"
    "      /Columns c\n"
    "      /Rows r\n"
    "    >>\n"
    "    /CCITTFaxDecode filter\n"
    "  } if\n"
    "  z "PS3_RLECompression" eq {/RunLengthDecode filter} if\n"
    "  z "PS3_LZWCompression" eq {/LZWDecode filter} if\n"
    "  z "PS3_ZipCompression" eq {/FlateDecode filter} if\n"
    "  z "PS3_JPEGCompression" eq {/DCTDecode filter} if\n"
    "} bind def\n"
    "\n"
    "/DirectClassImageDict\n"
    "{\n"
    "  colorspace "PS3_RGBColorspace" eq\n"
    "  {\n"
    "    /DeviceRGB setcolorspace\n"
    "    <<\n"
    "      /ImageType 1\n"
    "      /Width columns\n"
    "      /Height rows\n"
    "      /BitsPerComponent 8\n"
    "      /DataSource pixel_stream\n"
    "      /MultipleDataSources false\n"
    "      /ImageMatrix [columns 0 0 rows neg 0 rows]\n"
    "      /Decode [0 1 0 1 0 1]\n"
    "    >>\n"
    "  }\n"
    "  {\n"
    "    /DeviceCMYK setcolorspace\n"
    "    <<\n"
    "      /ImageType 1\n"
    "      /Width columns\n"
    "      /Height rows\n"
    "      /BitsPerComponent 8\n"
    "      /DataSource pixel_stream\n"
    "      /MultipleDataSources false\n"
    "      /ImageMatrix [columns 0 0 rows neg 0 rows]\n"
    "      /Decode\n"
    /*
      JPEG coder is used for JPEG compression. It compensates
      for inverted CMYK, so use an inverted decode matrix for
      JPEG compressed CMYK images.
    */
    "        compression "PS3_JPEGCompression" eq\n"
    "        {[1 0 1 0 1 0 1 0]}\n"
    "        {[0 1 0 1 0 1 0 1]}\n"
    "        ifelse\n"
    "    >>\n"
    "  }\n"
    "  ifelse\n"
    "} bind def\n"
    "\n"
    "/PseudoClassImageDict\n"
    "{\n"
    "  % COLORS IN PSEUDO CLASS IMAGE\n"
    "  currentfile buffer readline pop\n"
    "  token pop /colors exch def pop\n"
    "  colors 0 eq\n"
    "  {\n"
    "    % DEPTH OF GRAYSCALE\n"
    "    currentfile buffer readline pop\n"
    "    token pop /bits exch def pop\n"
    "    /DeviceGray setcolorspace\n"
    "    <<\n"
    "      /ImageType 1\n"
    "      /Width columns\n"
    "      /Height rows\n"
    "      /BitsPerComponent bits\n"
    "      /Decode [0 1]\n"
    "      /ImageMatrix [columns 0 0 rows neg 0 rows]\n"
    "      /DataSource pixel_stream\n"
    "    >>\n"
    "  }\n"
    "  {\n"
    "    % RGB COLORMAP\n"
    "    /colormap colors 3 mul string def\n"
    "    % INDEXES\n"
    "    currentfile /ASCII85Decode filter colormap readstring pop pop\n"
    "    [ /Indexed /DeviceRGB colors 1 sub colormap ] setcolorspace\n"
    "    <<\n"
    "      /ImageType 1\n"
    "      /Width columns\n"
    "      /Height rows\n"
    "      /BitsPerComponent 8\n"
    "      /Decode [0 255]\n"
    "      /ImageMatrix [columns 0 0 rows neg 0 rows]\n"
    "      /DataSource pixel_stream\n"
    "    >>\n"
    "  }\n"
    "  ifelse\n"
    "} bind def\n"
    "\n"
    "/NonMaskedImageDict\n"
    "{\n"
    "  class "PS3_PseudoClass" eq\n"
    "  { PseudoClassImageDict }\n"
    "  { DirectClassImageDict }\n"
    "  ifelse\n"
    "} bind def\n"
    "\n"
    "/MaskedImageDict\n"
    "{\n"
    "  <<\n"
    "    /ImageType 3\n"
    "    /InterleaveType 3\n"
    "    /DataDict NonMaskedImageDict\n"
    "    /MaskDict\n"
    "    <<\n"
    "      /ImageType 1\n"
    "      /Width columns\n"
    "      /Height rows\n"
    "      /BitsPerComponent 1\n"
    "      /DataSource mask_stream\n"
    "      /MultipleDataSources false\n"
    "      /ImageMatrix [ columns 0 0 rows neg 0 rows]\n"
    "      /Decode [ 0 1 ]\n"
    "    >>\n"
    "  >>\n"
    "} bind def\n"
    "\n"
    /*
      Default procedure for image clipping does nothing. Image
      will provide overriding ClipPath procdure if relevant.
    */
    "/ClipImage\n"
    "{} def\n"
    "\n"
    "/DisplayImage\n"
    "{\n"
    "  /buffer 512 string def\n"
    "\n"
    "  % TRANSLATION\n"
    "  currentfile buffer readline pop\n"
    "  token pop /x exch def\n"
    "  token pop /y exch def pop\n"
    "  x y translate\n"
    "\n"
    "  % IMAGE SIZE AND FONT SIZE\n"
    "  currentfile buffer readline pop\n"
    "  token pop /x exch def\n"
    "  token pop /y exch def pop\n"
    "  currentfile buffer readline pop\n"
    "  token pop /pointsize exch def pop\n";

  /*
    This hole in the PS prolog is for labels.
  */
  static const char PostscriptEpilog[]=
    "  x y scale\n"
    "\n"
    "  % CLIPPING PATH\n"
    "  currentfile buffer readline pop\n"
    "  token pop /clipped exch def pop\n"
    "\n"
    "  % EPS\n"
    "  currentfile buffer readline pop\n"
    "  token pop /sp exch def pop\n"
    "\n"
    "  % IMAGE PIXEL SIZE\n"
    "  currentfile buffer readline pop\n"
    "  token pop /columns exch def\n"
    "  token pop /rows exch def pop\n"
    "\n"
    "  % COLORSPACE (RGB/CMYK)\n"
    "  currentfile buffer readline pop\n"
    "  token pop /colorspace exch def pop\n"
    "\n"
    "  % TRANSPARENCY\n"
    "  currentfile buffer readline pop\n"
    "  token pop /alpha exch def pop\n"
    "\n"
    "  % STENCIL MASK?\n"
    "  currentfile buffer readline pop\n"
    "  token pop /stencil exch def pop\n"
    "\n"
    "  % IMAGE CLASS (DIRECT/PSEUDO)\n"
    "  currentfile buffer readline pop\n"
    "  token pop /class exch def pop\n"
    "\n"
    "  % COMPRESSION\n"
    "  currentfile buffer readline pop\n"
    "  token pop /compression exch def pop\n"
    "\n"
    "  % CLIP AND RENDER\n"
    "  /pixel_stream currentfile columns rows compression\n"
    "    ByteStreamDecodeFilter def\n"
    "  clipped {ClipImage} if\n"
    "  alpha stencil not and\n"
    "  { MaskedImageDict mask_stream resetfile }\n"
    "  { NonMaskedImageDict }\n"
    "  ifelse\n"
    "  stencil {0 setgray imagemask} {image} ifelse\n"
    "  grestore\n"
    "  sp {showpage} if\n"
    "} bind def\n";

  char
    buffer[MaxTextExtent],
    date[MaxTextExtent],
    density[MaxTextExtent],
    page_geometry[MaxTextExtent],
    **labels;

  CompressionType
    compression;

  const ImageAttribute
    *attribute;

  double
    dx_resolution,
    dy_resolution,
    x_resolution,
    x_scale,
    y_resolution,
    y_scale;

  magick_off_t
    current,
    start,
    stop;

  int
    count,
    status;

  RectangleInfo
    geometry;

  register unsigned int
    i,
    j;

  SegmentInfo
    bounds={0.0,0.0,0.0,0.0};

  size_t
    length;

  time_t
    timer;

  unsigned char
    *pixels;

  unsigned long
    page,
    scene,
    text_size;

  size_t
    image_list_length;

  Image
    *clip_mask;

  /*
    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 == False)
    ThrowWriterException(FileOpenError,UnableToOpenFile,image);
  compression=image->compression;
  if (image_info->compression != UndefinedCompression)
    compression=image_info->compression;
  switch (compression)
  {
#if !defined(HasJPEG)
    case JPEGCompression:
    {
      compression=RLECompression;
      ThrowException(&image->exception,MissingDelegateError,JPEGLibraryIsNotAvailable,image->filename);
      break;
    }
#endif
#if !defined(HasZLIB)
    case ZipCompression:
    {
      compression=RLECompression;
      ThrowException(&image->exception,MissingDelegateError,ZipLibraryIsNotAvailable,image->filename);
      break;
    }
#endif
    default:
      break;
  }
  page=0;
  scene=0;
  clip_mask = *ImageGetClipMask(image);
  do
  {
    page++;
    /*
      Scale image to size of Postscript page.
    */
    text_size=0;
    attribute=GetImageAttribute(image,"label");
    if (attribute != (const ImageAttribute *) NULL)
      text_size=(unsigned int)(MultilineCensus(attribute->value)*
        image_info->pointsize+12);
    SetGeometry(image,&geometry);
    geometry.y=(long) text_size;
    FormatString(page_geometry,"%lux%lu",image->columns,image->rows);
    if (image_info->page != (char *) NULL)
      {
        (void) strlcpy(page_geometry,image_info->page,MaxTextExtent);
      }
    else
      {
        if ((image->page.width != 0) && (image->page.height != 0))
          (void) FormatString(page_geometry,"%lux%lu%+ld%+ld",
            image->page.width,image->page.height,image->page.x,
              image->page.y);
        else
          if (LocaleCompare(image_info->magick,"PS3") == 0)
            (void) strlcpy(page_geometry,PSPageGeometry,sizeof(page_geometry));
      }
    (void) GetMagickGeometry(page_geometry,&geometry.x,&geometry.y,
       &geometry.width,&geometry.height);
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                          "Image Resolution: %gx%g %s",
                          image->x_resolution,
                          image->y_resolution,
                          ResolutionTypeToString(image->units));
    /*
      Scale relative to dots-per-inch. First whatever resolution passed
      in image_info. Then resolution from the image. Last default PS
      resolution.
    */
    dx_resolution=72.0;
    dy_resolution=72.0;
    x_resolution=72.0;
    (void) strlcpy(density,PSDensityGeometry,sizeof(density));
    count=GetMagickDimension(density,&x_resolution,&y_resolution,NULL,NULL);
    if (count != 2)
      y_resolution=x_resolution;
    /*
      Use override resolution information if it appears to be valid.
    */
    if ((image_info->density != (char *) NULL) &&
        ((image_info->units == PixelsPerInchResolution) ||
         (image_info->units == PixelsPerCentimeterResolution)))
      {
        count=GetMagickDimension(image_info->density,&x_resolution,
                                 &y_resolution,NULL,NULL);
        if (count != 2)
          y_resolution=x_resolution;
        if (image_info->units == PixelsPerCentimeterResolution)
          {
            x_resolution *= 2.54;
            y_resolution *= 2.54;
          }
      }
    /*
      Use image resolution information if it appears to be valid.
    */
    else if ((image->x_resolution > 0.0) && (image->y_resolution > 0.0) &&
             ((image->units == PixelsPerInchResolution) ||
              (image->units == PixelsPerCentimeterResolution)))
      {
        x_resolution = image->x_resolution;
        y_resolution = image->y_resolution;
        if (image->units == PixelsPerCentimeterResolution)
          {
            x_resolution *= 2.54;
            y_resolution *= 2.54;
          }
      }
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                          "Postscript Resolution: %gx%g DPI",
                          x_resolution,y_resolution);
    x_scale=(geometry.width*dx_resolution)/x_resolution;
    geometry.width=(unsigned long) (x_scale+0.5);
    y_scale=(geometry.height*dy_resolution)/y_resolution;
    geometry.height=(unsigned long) (y_scale+0.5);

    /*
      Postscript header on the first page
    */
    if (page == 1)
      {
#if defined(HAVE_CTIME_R)
        char time_buf[26];
#endif /* defined(HAVE_CTIME_R) */
        /* Postscript magic */
        if (LocaleCompare(image_info->magick,"PS3") == 0)
          (void) strlcpy(buffer,"%!PS-Adobe-3.0\n", sizeof(buffer));
        else
          (void) strlcpy(buffer,"%!PS-Adobe-3.0 EPSF-3.0\n", sizeof(buffer));
        (void) WriteBlobString(image,buffer);

        /* Creator */
        FormatString(buffer,"%%%%Creator: GraphicsMagick %s\n",
          MagickLibVersionText);
        (void) WriteBlobString(image,buffer);

        /* Title */
        FormatString(buffer,"%%%%Title: %.1024s\n",image->filename);
        (void) WriteBlobString(image,buffer);

        /* File creation timestamp */
        timer=time((time_t *) NULL);
#if defined(HAVE_CTIME_R)
        (void) strlcpy(date,ctime_r(&timer,time_buf),MaxTextExtent);
#else
        (void) strlcpy(date,ctime(&timer),MaxTextExtent); /* Thread-unsafe version */
#endif /* defined(HAVE_CTIME_R) */
        date[strlen(date)-1]='\0';
        FormatString(buffer,"%%%%CreationDate: %.1024s\n",date);
        (void) WriteBlobString(image,buffer);

        /* Bounding box and process colors if not RGB */
        bounds.x1=geometry.x;
        bounds.y1=geometry.y;
        bounds.x2=geometry.x+x_scale;
        bounds.y2=geometry.y+y_scale+text_size;
        if (image_info->adjoin && (image->next != (Image *) NULL))
          {
            (void) WriteBlobString(image,"%%BoundingBox: (atend)\n");
            (void) WriteBlobString(image,"%%HiResBoundingBox: (atend)\n");
            /* No document process colors if there's more than one page */
          }
        else
          {
            FormatString(buffer,"%%%%BoundingBox: %g %g %g %g\n",
              floor(bounds.x1+0.5),floor(bounds.y1+0.5),ceil(bounds.x2-0.5),
              ceil(bounds.y2-0.5));
            (void) WriteBlobString(image,buffer);
            FormatString(buffer,"%%%%HiResBoundingBox: %.7g %.7g %.7g %.7g\n",
              bounds.x1,bounds.y1,bounds.x2,bounds.y2);
            (void) WriteBlobString(image,buffer);

            if (image->colorspace == CMYKColorspace)
              (void) WriteBlobString(image,
                "%%DocumentProcessColors: Cyan Magenta Yellow Black\n");
            else
              if (IsGrayImage(image,&image->exception))
                (void) WriteBlobString(image,
                  "%%DocumentProcessColors: Black\n");
          }

        /* Font resources */
        attribute=GetImageAttribute(image,"label");
        if (attribute != (const ImageAttribute *) NULL)
          (void) WriteBlobString(image,
            "%%DocumentNeededResources: font Helvetica\n");

        /* Language level */
        (void) WriteBlobString(image,"%%LanguageLevel: 3\n");

        /* Pages, orientation, and page order */
        if (LocaleCompare(image_info->magick,"PS3") != 0)
          {
            (void) WriteBlobString(image,"%%Pages: 1\n");
          }
        else
          {
            (void) WriteBlobString(image,"%%Orientation: Portrait\n");
            (void) WriteBlobString(image,"%%PageOrder: Ascend\n");
            if (!image_info->adjoin)
              (void) strlcpy(buffer,"%%Pages: 1\n",sizeof(buffer));
            else
              FormatString(buffer,"%%%%Pages: %lu\n",(unsigned long)
                           image_list_length);
            (void) WriteBlobString(image,buffer);
          }
        (void) WriteBlobString(image,"%%EndComments\n");

        /* The static postscript procedures prolog. */
        (void)WriteBlobString(image,"%%BeginProlog\n");
        (void) WriteBlob(image,sizeof(PostscriptProlog)-1,PostscriptProlog);

        /* One label line for each line in label string */
        attribute=GetImageAttribute(image,"label");
        if (attribute != (const ImageAttribute *) NULL)
          {
            long
              si;

            (void) WriteBlobString(image,"\n  % LABELS\n  /Helvetica "
              "findfont pointsize scalefont setfont\n");
            for (si=(long) MultilineCensus(attribute->value)-1; si >= 0; si--)
              {
                (void) WriteBlobString(image,
                  "  currentfile buffer readline pop token pop\n");
                FormatString(buffer,"  0 y %g add moveto show pop\n",
                  si*image_info->pointsize+12);
                (void) WriteBlobString(image,buffer);
              }
          }

        /* The static postscript procedures epilog. */
        (void) WriteBlob(image,sizeof(PostscriptEpilog)-1,PostscriptEpilog);
        (void)WriteBlobString(image,"%%EndProlog\n");
      }

    /* Page number */
    FormatString(buffer,"%%%%Page: 1 %lu\n",page);
    (void) WriteBlobString(image,buffer);

    /* Page bounding box */
    FormatString(buffer,"%%%%PageBoundingBox: %ld %ld %ld %ld\n",geometry.x,
      geometry.y,geometry.x+(long) geometry.width,geometry.y+(long)
      (geometry.height+text_size));
    (void) WriteBlobString(image,buffer);

    /* Page process colors if not RGB */
    if (image->colorspace == CMYKColorspace)
      (void) WriteBlobString(image,
        "%%PageProcessColors: Cyan Magenta Yellow Black\n");
    else
      if (IsGrayImage(image,&image->exception))
        (void) WriteBlobString(image,"%%PageProcessColors: Black\n");

    /*
      Adjust document bounding box to bound page bounding
      box if page bounding box is the larger
    */
    if (geometry.x < bounds.x1)
      bounds.x1=geometry.x;
    if (geometry.y < bounds.y1)
      bounds.y1=geometry.y;
    if ((geometry.x+x_scale) > bounds.x2)
      bounds.x2=geometry.x+x_scale;
    if ((geometry.y+y_scale+text_size) > bounds.y2)
      bounds.y2=geometry.y+y_scale+text_size;

    /* Page font resource if there's a label */
    attribute=GetImageAttribute(image,"label");
    if (attribute != (const ImageAttribute *) NULL)
      (void) WriteBlobString(image,"%%PageResources: font Helvetica\n");

    /* PS clipping path from Photoshop clipping path */
    if ((clip_mask != (Image *) NULL) &&
      (LocaleNCompare("8BIM:",clip_mask->magick_filename,5) == 0))
      {
        const ImageAttribute
          *attribute;

        attribute=GetImageAttribute(image,clip_mask->magick_filename);
        if (attribute == (const ImageAttribute *) NULL)
          return(False);
        (void) WriteBlobString(image,attribute->value);
        (void) WriteBlobByte(image,'\n');
      }
    else
      {
        /* Reset definition in case previous image had a clipping path.*/
        (void)WriteBlobString(image,"/ClipImage {} def\n");
      }

    /* Push a dictionary for our own def's if this is an EPS */
    if (LocaleCompare(image_info->magick,"PS3") != 0)
      (void) WriteBlobString(image,"userdict begin\n");

    /* Image mask */
    if (image->matte)
      if (!WritePS3MaskImage(image_info, image))
      {
        CloseBlob(image);
        return(False);
      }

    /*
      Remember position of BeginData comment so we can update
      it when we know how many ascii85 encoded bytes we have
      in the blob.
    */
    start=TellBlob(image);
    if (start < 0)
      ThrowWriterException(BlobError,UnableToObtainOffset,image);
    FormatString(buffer,"%%%%BeginData:%13ld ASCII Bytes\n",0L);
    (void) WriteBlobString(image,buffer);
    stop=TellBlob(image);
    if (stop < 0)
      ThrowWriterException(BlobError,UnableToObtainOffset,image);

    /* Call to image display procedure */
    (void) WriteBlobString(image,"DisplayImage\n");

    /* Translate, scale, and font point size */
    FormatString(buffer,"%ld %ld\n%.7g %.7g\n%f\n",geometry.x,geometry.y,
      x_scale,y_scale,image_info->pointsize);
    (void) WriteBlobString(image,buffer);

    /* Output labels */
    labels=(char **) NULL;
    attribute=GetImageAttribute(image,"label");
    if (attribute != (const ImageAttribute *) NULL)
      labels=StringToList(attribute->value);
    if (labels != (char **) NULL)
      {
        for (i=0; labels[i] != (char *) NULL; i++)
          {
            Ascii85Initialize(image);
            (void) WriteBlobString(image,"<~");
            for (j=0; labels[i][j] != '\0'; j++)
              Ascii85Encode(image, (unsigned long)labels[i][j]);
            Ascii85Flush(image);
            MagickFreeMemory(labels[i]);
          }
        MagickFreeMemory(labels);
      }

    /* Photoshop clipping path active? */
    if ((clip_mask != (Image *) NULL) &&
        (LocaleNCompare("8BIM:",clip_mask->magick_filename,5) == 0))
      (void) WriteBlobString(image,"true\n");
    else
      (void) WriteBlobString(image,"false\n");

    /* Compression seems to take precedence over anyting */
    if (compression == FaxCompression)
      (void) SetImageType(image, BilevelType);

    /* Showpage for non-EPS. */
    (void) WriteBlobString(image,
      LocaleCompare(image_info->magick,"PS3") == 0 ? "true\n" : "false\n");

    /* Image columns and rows; color space */
    FormatString(buffer,"%lu %lu\n%.1024s\n",
      image->columns,image->rows,image->colorspace == CMYKColorspace ?
        PS3_CMYKColorspace : PS3_RGBColorspace);
    (void) WriteBlobString(image,buffer);

    /* Masked image? */
    (void) WriteBlobString(image,image->matte ? "true\n" : "false\n");

    /* Render with imagemask operator? */
    if (((AccessDefinition(image_info,"ps","imagemask")) != 0) &&
        IsMonochromeImage(image,&image->exception))
      (void) WriteBlobString(image,"true\n");
    else
      (void) WriteBlobString(image,"false\n");

    /*
      Output data in one of three ways:
      1) 1 bit and 8 bit single channel direct color image data.
      2) 3 and 4 channel direct color image data
      3) 8 bit color mapped image data
    */

    /*
      Output 1 bit and 8 bit single channel image data. IsGray and IsMono
      may return true for both direct class RGB and CMYK colors, so we need
      to test that these were not requested explicitly.
    */
    if ((image_info->type != TrueColorType) &&
         (image_info->type != TrueColorMatteType) &&
         (image_info->type != ColorSeparationType) &&
         (image_info->type != ColorSeparationMatteType) &&
         (image->colorspace != CMYKColorspace) &&
         (IsGrayImage(image,&image->exception) ||
          IsMonochromeImage(image,&image->exception)))
    {
      /* Image class */
      (void) WriteBlobString(image,PS3_PseudoClass"\n");

      /* Compression scheme - any */
      switch (compression)
      {
        case NoCompression:
        default:
          (void) WriteBlobString(image,PS3_NoCompression"\n");
          break;
        case FaxCompression:
          (void) WriteBlobString(image,PS3_FaxCompression"\n");
          break;
        case RLECompression:
          (void) WriteBlobString(image,PS3_RLECompression"\n");
          break;
        case LZWCompression:
          (void) WriteBlobString(image,PS3_LZWCompression"\n");
          break;
        case ZipCompression:
          (void) WriteBlobString(image,PS3_ZipCompression"\n");
          break;
        case JPEGCompression:
          (void) WriteBlobString(image,PS3_JPEGCompression"\n");
          break;
      }

      /* Number of colors -- 0 for single component non-color mapped data */
      (void) WriteBlobString(image,"0\n");

      /* 1 bit or 8 bit components? */
      FormatString(buffer,"%d\n",
        IsMonochromeImage(image,&image->exception) ? 1 : 8);
      (void) WriteBlobString(image,buffer);

      /* Image data. Always ASCII85 encoded. */
      if (compression == JPEGCompression)
        {
          status=JPEGEncodeImage(image_info,image);
        }
      else
        if (compression == FaxCompression)
          {
            if (LocaleCompare(CCITTParam,"0") == 0)
              status=HuffmanEncodeImage(image_info,image);
            else
              status=Huffman2DEncodeImage(image_info,image);
          }
        else
          {
            status=SerializeSingleChannelImage(image_info,image,&pixels,
              &length);
            if (!status)
              {
                CloseBlob(image);
                return(False);
              }
            Ascii85Initialize(image);
            switch (compression)
            {
              case NoCompression:
              default:
                for (i=0; i < length; i++)
                  Ascii85Encode(image, (unsigned long)pixels[i]);
                break;
              case RLECompression:
                status=PackbitsEncode2Image(image,length,pixels,
                  Ascii85WriteByteHook,(void*)NULL);
                break;
              case LZWCompression:
                status=LZWEncode2Image(image,length,pixels,
                  Ascii85WriteByteHook,(void*)NULL);
                break;
              case ZipCompression:
                status=ZLIBEncode2Image(image,length,image_info->quality,
                  pixels,Ascii85WriteByteHook,(void*)NULL);
                break;
            }
            Ascii85Flush(image);
            MagickFreeResourceLimitedMemory(pixels);
          }
    }
    else
      if ((image->storage_class == DirectClass) || (image->colors > 256) ||
          (compression == JPEGCompression))
        {
          /* Image class */
          (void) WriteBlobString(image,PS3_DirectClass"\n");

          /* Compression scheme - fax is only for bilevel images */
          switch (compression)
          {
            case NoCompression:
            default:
              (void) WriteBlobString(image,PS3_NoCompression"\n");
              break;
            case RLECompression:
              (void) WriteBlobString(image,PS3_RLECompression"\n");
              break;
            case LZWCompression:
              (void) WriteBlobString(image,PS3_LZWCompression"\n");
              break;
            case ZipCompression:
              (void) WriteBlobString(image,PS3_ZipCompression"\n");
              break;
            case JPEGCompression:
              (void) WriteBlobString(image,PS3_JPEGCompression"\n");
              break;
          }

          /* Image data. Always ASCII85 encoded. */
          if (compression == JPEGCompression)
            {
                status=JPEGEncodeImage(image_info,image);
            }
          else
            {
              /* Stream based compressions */
              status=SerializeMultiChannelImage(image_info,image,&pixels,
                &length);
              if (!status)
                {
                  CloseBlob(image);
                  return(False);
                }
              Ascii85Initialize(image);
              switch (compression)
              {
                case NoCompression:
                default:
                  for (i=0; i < length; i++)
                    Ascii85Encode(image, (unsigned long)pixels[i]);
                  status=True;
                  break;
                case RLECompression:
                  status=PackbitsEncode2Image(image,length,pixels,
                    Ascii85WriteByteHook,(void*)NULL);
                  break;
                case LZWCompression:
                  status=LZWEncode2Image(image,length,pixels,
                    Ascii85WriteByteHook,(void*)NULL);
                  break;
                case ZipCompression:
                  status=ZLIBEncode2Image(image,length,image_info->quality,
                    pixels,Ascii85WriteByteHook,(void*)NULL);
                  break;
              }
              Ascii85Flush(image);
              MagickFreeResourceLimitedMemory(pixels);
            }
        }
      else
        {
          /*
            Color mapped images.

            Image class.
          */
          (void) WriteBlobString(image,PS3_PseudoClass"\n");

          /*
            Compression scheme - fax is only for bilevel images,
            JPEG for true color (single channel) images.
          */
          switch (compression)
          {
            case NoCompression:
            default:
              (void) WriteBlobString(image,PS3_NoCompression"\n");
              break;
            case RLECompression:
              (void) WriteBlobString(image,PS3_RLECompression"\n");
              break;
            case LZWCompression:
              (void) WriteBlobString(image,PS3_LZWCompression"\n");
              break;
            case ZipCompression:
              (void) WriteBlobString(image,PS3_ZipCompression"\n");
              break;
          }

          /* Number of colors in color map */
          FormatString(buffer,"%u\n",image->colors);
          (void) WriteBlobString(image,buffer);

          /* Color map - uncompressed, ascii85 encoded */
          Ascii85Initialize(image);
          for (i=0; i < image->colors; i++)
          {
            Ascii85Encode(image, (unsigned long)image->colormap[i].red);
            Ascii85Encode(image, (unsigned long)image->colormap[i].green);
            Ascii85Encode(image, (unsigned long)image->colormap[i].blue);
          }
          Ascii85Flush(image);

          status=SerializePseudoClassImage(image_info,image,&pixels,&length);
          if (!status)
            {
              CloseBlob(image);
              return(False);
            }
          Ascii85Initialize(image);
          switch (compression)
          {
            case NoCompression:
            default:
              for (i=0; i < length; i++)
                Ascii85Encode(image, (unsigned long)pixels[i]);
              status=True;
              break;
            case RLECompression:
              status=PackbitsEncode2Image(image,length,pixels,
                Ascii85WriteByteHook,(void *)NULL);
              break;
            case LZWCompression:
              status=LZWEncode2Image(image,length,pixels,Ascii85WriteByteHook,
                (void *)NULL);
              break;
            case ZipCompression:
              status=ZLIBEncode2Image(image,length,image_info->quality,pixels,
                Ascii85WriteByteHook,(void *)NULL);
              break;
          }
          Ascii85Flush(image);
          MagickFreeResourceLimitedMemory(pixels);
        }

    if (!status)
      {
        CloseBlob(image);
        return(False);
      }

    /* Update BeginData now that we know the data size */
    current=TellBlob(image);
    if (current < 0)
      ThrowWriterException(BlobError,UnableToObtainOffset,image);
    length=current-stop;
    stop=TellBlob(image);
    if (stop < 0)
      ThrowWriterException(BlobError,UnableToObtainOffset,image);
    if (SeekBlob(image,start,SEEK_SET) != start)
      ThrowWriterException(BlobError,UnableToSeekToOffset,image);
    FormatString(buffer,"%%%%BeginData:%13ld ASCII Bytes\n",(long) length);
    (void) WriteBlobString(image,buffer);
    if (SeekBlob(image,stop,SEEK_SET) != stop)
      ThrowWriterException(BlobError,UnableToSeekToOffset,image);
    (void) WriteBlobString(image,"%%EndData\n");

    /* End private dictionary if this is an EPS */
    if (LocaleCompare(image_info->magick,"PS3") != 0)
      (void) WriteBlobString(image,"end\n");

    (void) WriteBlobString(image,"%%PageTrailer\n");
    if (image->next == (Image *) NULL)
      break;
    image=SyncNextImageInList(image);
    status=MagickMonitorFormatted(scene,image_list_length,
                                  &image->exception,SaveImagesText,
                                  image->filename);
    if (status == False)
      break;
    scene++;
  } while (image_info->adjoin);
  if (image_info->adjoin)
    while (image->previous != (Image *) NULL)
      image=image->previous;
  (void) WriteBlobString(image,"%%Trailer\n");
  if (page > 1)
    {
      FormatString(buffer,"%%%%BoundingBox: %g %g %g %g\n",
        floor(bounds.x1+0.5),floor(bounds.y1+0.5),ceil(bounds.x2-0.5),
        ceil(bounds.y2-0.5));
      (void) WriteBlobString(image,buffer);
      FormatString(buffer,"%%%%HiResBoundingBox: %.7g %.7g %.7g %.7g\n",
        bounds.x1,bounds.y1,bounds.x2,bounds.y2);
      (void) WriteBlobString(image,buffer);
    }
  (void) WriteBlobString(image,"%%EOF\n");
  CloseBlob(image);
  return(True);
}

#if defined(HasZLIB)
#include "zlib.h"
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   Z L I B E n c o d e 2 I m a g e                                           %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method ZLIBEncode2Image compresses an image via ZLIB-coding specific to
%  Postscript Level II or Portable Document Format.  To ensure portability, the
%  binary ZLIB bytes are encoded as ASCII base-85.
%
%  The format of the ZLIBEncode2Image method is:
%
%      unsigned int ZLIBEncode2Image(Image *image,const size_t length,
%        const unsigned long quality,unsigned char *pixels,
%        WriteByteHook write_byte,void *info)
%
%  A description of each parameter follows:
%
%    o image: The address of a structure of type Image;  returned from
%      ReadImage.
%
%    o length:  A value that specifies the number of pixels to compress.
%
%    o quality: the compression level (0-100).
%
%    o pixels: The address of an unsigned array of characters containing the
%      pixels to compress.
%
%    o write_byte: function (hook) to call for writing each byte of compressed
%      data.
%
%    o info: information block to pass along when calling write_byte
%      function.
%
%
*/

static voidpf ZLIBAllocFunc(voidpf opaque, uInt items, uInt size) MAGICK_FUNC_MALLOC;
static voidpf ZLIBAllocFunc(voidpf opaque, uInt items, uInt size)
{
  ARG_NOT_USED(opaque);
  return MagickMallocCleared(MagickArraySize(items,size));
}
static void ZLIBFreeFunc(voidpf opaque, voidpf address)
{
  ARG_NOT_USED(opaque);
  MagickFree(address);
}

static unsigned int ZLIBEncode2Image(Image *image,const size_t length,
  const unsigned long quality,unsigned char *pixels,WriteByteHook write_byte,
  void *info)
{
  int
    status;

  register long
    i;

  unsigned char
    *compressed_pixels;

  unsigned long
    compressed_packets;

  z_stream
    stream;

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  compressed_packets=(unsigned long) (1.001*length+12);
  compressed_pixels=MagickAllocateResourceLimitedMemory(unsigned char *,compressed_packets);
  if (compressed_pixels == (unsigned char *) NULL)
    ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
      (char *) NULL);
  stream.next_in=pixels;
  stream.avail_in=(unsigned int) length;
  stream.next_out=compressed_pixels;
  stream.avail_out=(unsigned int) compressed_packets;
  stream.zalloc=ZLIBAllocFunc;
  stream.zfree=ZLIBFreeFunc;
  stream.opaque=(voidpf) NULL;
  status=deflateInit(&stream,(int) Min(quality/10,9));
  if (status == Z_OK)
    {
      status=deflate(&stream,Z_FINISH);
      if (status == Z_STREAM_END)
        status=deflateEnd(&stream);
      else
        (void) deflateEnd(&stream);
      compressed_packets=stream.total_out;
    }
  if (status)
    ThrowBinaryException(CoderError,UnableToZipCompressImage,(char *) NULL)
  else
    for (i=0; i < (long) compressed_packets; i++)
      (void) (*write_byte)(image,(magick_uint8_t)compressed_pixels[i],info);
  MagickFreeResourceLimitedMemory(compressed_pixels);
  return(!status);
}
#else
static unsigned int ZLIBEncode2Image(Image *image,const size_t length,
  const unsigned long quality,unsigned char *pixels,WriteByteHook write_byte,
  void *info)
{
  ARG_NOT_USED(length);
  ARG_NOT_USED(quality);
  ARG_NOT_USED(pixels);
  ARG_NOT_USED(write_byte);
  ARG_NOT_USED(info);

  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  ThrowBinaryException(MissingDelegateError,ZipLibraryIsNotAvailable,image->filename);
  return(False);
}
#endif
