text: /*
  % 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.
  %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %                        JJJJJ  PPPP   EEEEE   GGGG                           %
  %                          J    P   P  E      G                               %
  %                          J    PPPP   EEE    G  GG                           %
  %                        J J    P      E      G   G                           %
  %                        JJJ    P      EEEEE   GGG                            %
  %                                                                             %
  %                                                                             %
  %                       Read/Write JPEG Image Format.                         %
  %                                                                             %
  %                                                                             %
  %                              Software Design                                %
  %                                John Cristy                                  %
  %                                 July 1992                                   %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  % This software is based in part on the work of the Independent JPEG Group.
  % See ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz for copyright and
  % licensing restrictions.  Blob support contributed by Glenn Randers-Pehrson.
  %
  %
  */
  
  /*
    Include declarations.
  */
  #include "magick/studio.h"
  #include "magick/analyze.h"
  #include "magick/attribute.h"
  #include "magick/blob.h"
  #include "magick/colormap.h"
  #include "magick/enum_strings.h"
  #include "magick/log.h"
  #include "magick/magick.h"
  #include "magick/monitor.h"
  #include "magick/pixel_cache.h"
  #include "magick/profile.h"
  #include "magick/resource.h"
  #include "magick/utility.h"
  
  /*
    Forward declarations.
  */
  static unsigned int
    WriteJPEGImage(const ImageInfo *,Image *);
  
  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %   I s J P E G                                                               %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  Method IsJPEG returns True if the image format type, identified by the
  %  magick string, is JPEG.
  %
  %  The format of the IsJPEG  method is:
  %
  %      unsigned int IsJPEG(const unsigned char *magick,const size_t length)
  %
  %  A description of each parameter follows:
  %
  %    o status:  Method IsJPEG returns True if the image format type is JPEG.
  %
  %    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 IsJPEG(const unsigned char *magick,const size_t length)
  {
    if (length < 3)
      return(False);
    if (memcmp(magick,"\377\330\377",3) == 0)
      return(True);
    return(False);
  }
  
  #if defined(HasJPEG)
  #define JPEG_INTERNAL_OPTIONS
  /*
    Avoid conflicting typedef for INT32
  */
  #if defined(__MINGW32__)
  # define XMD_H 1
  #endif
  /*
    The JPEG headers have the annoying problem that they define
    HAVE_STDLIB_H and we do too.  The define isn't actually used
    so just undef it.
  */
  #undef HAVE_STDLIB_H
  #include <setjmp.h>
  #include "jpeglib.h"
  #include "jerror.h"
  
  /*
    Define declarations.
  */
  #define ICC_MARKER  (JPEG_APP0+2)
  #define IPTC_MARKER  (JPEG_APP0+13)
  #define XML_MARKER  (JPEG_APP0+1)
  #define MaxBufferExtent  8192
  #define JPEG_MARKER_MAX_SIZE 65533
  #define MaxWarningCount 3
  
  /*
    Set to 1 to use libjpeg callback for progress indication.  This is
    not enabled by default since it outputs multiple progress
    indications, which may be confusing for the user.  However, the
    libjpeg method provides more detailed progress.
  */
  #define USE_LIBJPEG_PROGRESS 0 /* Use libjpeg callback for progress */
  
  static const char xmp_std_header[]="http://ns.adobe.com/xap/1.0/";
  
  
  typedef struct _DestinationManager
  {
    struct jpeg_destination_mgr
      manager;
  
    Image
      *image;
  
    JOCTET
      *buffer;
  
  } DestinationManager;
  
  typedef struct _MagickClientData
  {
    Image
      *image;
  
    MagickBool
      ping;
  
    MagickBool
      completed;
  
    jmp_buf
      error_recovery;
  
    unsigned int
      max_warning_count;
  
    magick_uint16_t
      warning_counts[JMSG_LASTMSGCODE];
  
    int
      max_scan_number;
  
    unsigned char
      buffer[65537+200];
  
  } MagickClientData;
  
  typedef struct _SourceManager
  {
    struct jpeg_source_mgr
      manager;
  
    Image
      *image;
  
    JOCTET
      *buffer;
  
    boolean
      start_of_blob;
  } SourceManager;
  
  static MagickClientData *AllocateMagickClientData(void)
  {
    MagickClientData
      *client_data;
  
    client_data = MagickAllocateClearedMemory(MagickClientData *, sizeof(MagickClientData));
  
    return client_data;
  }
  
  static MagickClientData *FreeMagickClientData(MagickClientData *client_data)
  {
    MagickFreeMemory(client_data);
    return client_data;
  }
  
  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %   R e a d J P E G I m a g e                                                 %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  Method ReadJPEGImage reads a JPEG 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 ReadJPEGImage method is:
  %
  %      Image *ReadJPEGImage(const ImageInfo *image_info,
  %        ExceptionInfo *exception)
  %
  %  A description of each parameter follows:
  %
  %    o image:  Method ReadJPEGImage 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.
  %
  %
  */
  
  
  /*
    Format a libjpeg warning or trace event while decoding.  Warnings
    are converted to GraphicsMagick warning exceptions while traces are
    optionally logged.
  
    JPEG message codes range from 0 to JMSG_LASTMSGCODE
  */
  static void JPEGDecodeMessageHandler(j_common_ptr jpeg_info,int msg_level)
  {
    char
      message[JMSG_LENGTH_MAX];
  
    struct jpeg_error_mgr
      *err;
  
    MagickClientData
      *client_data;
  
    Image
      *image;
  
    message[0]='\0';
    err=jpeg_info->err;
    client_data=(MagickClientData *) jpeg_info->client_data;
    image=client_data->image;
    /* msg_level is -1 for warnings, 0 and up for trace messages. */
    if (msg_level < 0)
      {
        unsigned int strikes = 0;
        /* A warning */
        (err->format_message)(jpeg_info,message);
  
        if ((err->msg_code >= 0) &&
            ((size_t) err->msg_code < ArraySize(client_data->warning_counts)))
          {
            client_data->warning_counts[err->msg_code]++;
            strikes=client_data->warning_counts[err->msg_code];
          }
  
        if (image->logging)
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "[%s] JPEG Warning[%u]: \"%s\""
                                  " (code=%d "
                                  "parms=0x%02x,0x%02x,"
                                  "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)",
                                  image->filename,
                                  strikes,
                                  message,err->msg_code,
                                  err->msg_parm.i[0], err->msg_parm.i[1],
                                  err->msg_parm.i[2], err->msg_parm.i[3],
                                  err->msg_parm.i[4], err->msg_parm.i[5],
                                  err->msg_parm.i[6], err->msg_parm.i[7]);
          }
        if (strikes > client_data->max_warning_count)
          {
            ThrowException2(&image->exception,CorruptImageError,(char *) message,
                            image->filename);
            longjmp(client_data->error_recovery,1);
          }
  
        if ((err->num_warnings == 0) ||
            (err->trace_level >= 3))
          ThrowException2(&image->exception,CorruptImageWarning,message,
                          image->filename);
        /* JWRN_JPEG_EOF - "Premature end of JPEG file" */
        err->num_warnings++;
        return /* False */;
      }
    else
      {
        /* A trace message */
        if ((image->logging) && (msg_level >= err->trace_level))
          {
            (err->format_message)(jpeg_info,message);
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "[%s] JPEG Trace: \"%s\"",image->filename,
                                  message);
          }
      }
    return /* True */;
  }
  
  static void JPEGDecodeProgressMonitor(j_common_ptr cinfo)
  {
    MagickClientData *client_data = (MagickClientData *) cinfo->client_data;
    Image *image = client_data->image;
    const int max_scan_number = client_data->max_scan_number;
  
  #if USE_LIBJPEG_PROGRESS
    {
      struct jpeg_progress_mgr *p = cinfo->progress;
  
  #if 0
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Progress: pass_counter=%ld, pass_limit=%ld,"
                            " completed_passes=%d, total_passes=%d, filename=%s",
                            p->pass_counter, p->pass_limit,
                            p->completed_passes, p->total_passes, image->filename);
  #endif
  
      if (QuantumTick(p->pass_counter,p->pass_limit))
        if (!MagickMonitorFormatted(p->pass_counter,p->pass_limit,&image->exception,
                                    "[%s] Loading image: %lux%lu (pass %d of %d)...  ",
                                    image->filename,
                                    image->columns,image->rows,
                                    p->completed_passes+1, p->total_passes))
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Quitting due to progress monitor");
            longjmp(client_data->error_recovery,1);
          }
    }
  #endif /* USE_LIBJPEG_PROGRESS */
  
    if (cinfo->is_decompressor)
      {
        int scan_no = ((j_decompress_ptr) cinfo)->input_scan_number;
  
        if (scan_no > max_scan_number)
          {
            char message[MaxTextExtent];
            FormatString(message,"Scan number %d exceeds maximum scans (%d)",
                         scan_no, max_scan_number);
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),"%s", message);
            ThrowException2(&image->exception,CorruptImageError,(char *) message,
                            image->filename);
            longjmp(client_data->error_recovery,1);
          }
      }
  }
  
  static boolean FillInputBuffer(j_decompress_ptr cinfo)
  {
    SourceManager
      *source;
  
    source=(SourceManager *) cinfo->src;
    source->manager.bytes_in_buffer=
      ReadBlob(source->image,MaxBufferExtent,(char *) source->buffer);
    if (source->manager.bytes_in_buffer == 0)
      {
        if (source->start_of_blob)
          ERREXIT(cinfo,JERR_INPUT_EMPTY);
        WARNMS(cinfo,JWRN_JPEG_EOF);
        source->buffer[0]=(JOCTET) 0xff;
        source->buffer[1]=(JOCTET) JPEG_EOI;
        source->manager.bytes_in_buffer=2;
      }
    source->manager.next_input_byte=source->buffer;
    source->start_of_blob=FALSE;
    return(TRUE);
  }
  
  static int GetCharacter(j_decompress_ptr jpeg_info)
  {
    if (jpeg_info->src->bytes_in_buffer == 0)
      if ((!((*jpeg_info->src->fill_input_buffer)(jpeg_info))) ||
          (jpeg_info->src->bytes_in_buffer == 0))
        return EOF;
    jpeg_info->src->bytes_in_buffer--;
    return(GETJOCTET(*jpeg_info->src->next_input_byte++));
  }
  
  static void InitializeSource(j_decompress_ptr cinfo)
  {
    SourceManager
      *source;
  
    source=(SourceManager *) cinfo->src;
    source->start_of_blob=TRUE;
  }
  
  /*
    Format and report a libjpeg error event.  Errors are reported via a
    GraphicsMagick error exception. The function terminates with
    longjmp() so it never returns to the caller.
  */
  static void JPEGErrorHandler(j_common_ptr jpeg_info) MAGICK_FUNC_NORETURN;
  
  static void JPEGErrorHandler(j_common_ptr jpeg_info)
  {
    char
      message[JMSG_LENGTH_MAX];
  
    struct jpeg_error_mgr
      *err;
  
    MagickClientData
      *client_data;
  
    Image
      *image;
  
    message[0]='\0';
    err=jpeg_info->err;
    client_data=(MagickClientData *) jpeg_info->client_data;
    image=client_data->image;
    (err->format_message)(jpeg_info,message);
    if (image->logging)
      {
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "[%s] JPEG Error: \"%s\" (code=%d, "
                              "parms=0x%02x,0x%02x,"
                              "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)",
                              image->filename,message,err->msg_code,
                              err->msg_parm.i[0], err->msg_parm.i[1],
                              err->msg_parm.i[2], err->msg_parm.i[3],
                              err->msg_parm.i[4], err->msg_parm.i[5],
                              err->msg_parm.i[6], err->msg_parm.i[7]);
      }
    if (client_data->completed)
      ThrowException2(&image->exception,CoderWarning,(char *) message,
                      image->filename);
    else
      ThrowException2(&image->exception,CoderError,(char *) message,
                      image->filename);
    longjmp(client_data->error_recovery,1);
  }
  
  #define GetProfileLength(jpeg_info, length)                             \
    do {                                                                  \
      int                                                                 \
        _c;                                                               \
                                                                          \
      if (((_c = GetCharacter(jpeg_info)) != EOF) && (_c >= 0))           \
        {                                                                 \
          length=_c*256;                                                  \
          if (((_c = GetCharacter(jpeg_info)) != EOF) && (_c >= 0))       \
            length+=_c;                                                   \
          else                                                            \
            length=0;                                                     \
        }                                                                 \
      else                                                                \
        {                                                                 \
          length=0;                                                       \
        }                                                                 \
    } while(0)
  
  static boolean ReadComment(j_decompress_ptr jpeg_info)
  {
    char
      *comment;
  
    MagickClientData
      *client_data;
  
    Image
      *image;
  
    register char
      *p;
  
    size_t
      i,
      length;
  
    int
      c;
  
    /*
      Determine length of comment.
    */
    client_data=(MagickClientData *) jpeg_info->client_data;
    image=client_data->image;
    GetProfileLength(jpeg_info, length);
    if (length <= 2)
      return(True);
    length-=2;
    comment=(char *) client_data->buffer;
    /*
      Read comment.
    */
    p=comment;
    for (i=0; i < length; i++)
      {
        if ((c=GetCharacter(jpeg_info)) == EOF)
          break;
        *p=c;
        p++;
      }
    *p='\0';
    (void) SetImageAttribute(image,"comment",comment);
    return(True);
  }
  
  static boolean ReadGenericProfile(j_decompress_ptr jpeg_info)
  {
    MagickClientData
      *client_data;
  
    Image
      *image;
  
    size_t
      header_length=0,
      length;
  
    register size_t
      i;
  
    char
      profile_name[MaxTextExtent];
  
    unsigned char
      *profile;
  
    int
      c,
      marker;
  
    /*
      Determine length of generic profile.
    */
    GetProfileLength(jpeg_info, length);
    if (length <= 2)
      return(True);
    length-=2;
  
    /*
      jpeg_info->unread_marker ('int') is either zero or the code of a
      JPEG marker that has been read from the data source, but has not
      yet been processed.  The underlying type for a marker appears to
      be UINT8 (JPEG_COM, or JPEG_APP0+n).
  
      Unexpected markers are prevented due to registering for specific
      markers we are interested in via jpeg_set_marker_processor().
  
      #define JPEG_APP0 0xE0
      #define JPEG_COM 0xFE
  
      #define ICC_MARKER  (JPEG_APP0+2)
      #define IPTC_MARKER  (JPEG_APP0+13)
      #define XML_MARKER  (JPEG_APP0+1)
     */
    marker=jpeg_info->unread_marker-JPEG_APP0;
  
    /*
      Compute generic profile name.
    */
    FormatString(profile_name,"APP%d",marker);
  
    /*
      Obtain Image.
    */
    client_data=(MagickClientData *) jpeg_info->client_data;
    image=client_data->image;
  
    /*
      Copy profile from JPEG to allocated memory.
    */
    profile=client_data->buffer;
  
    for (i=0 ; i < length ; i++)
      {
        if ((c=GetCharacter(jpeg_info)) != EOF)
          profile[i]=c;
        else
          break;
      }
    if (i != length)
      return True;
  
    /*
      Detect EXIF and XMP profiles.
    */
    if ((marker == 1) && (length > 4) &&
        (strncmp((char *) profile,"Exif",4) == 0))
      {
        FormatString(profile_name,"EXIF");
      }
    else if (((marker == 1) && (length > strlen(xmp_std_header)+1)) &&
             (memcmp(profile, xmp_std_header, strlen(xmp_std_header)+1) == 0))
      {
        /*
          XMP is required to fit in one 64KB chunk.  Strip off its JPEG
           namespace header.
        */
        header_length=strlen(xmp_std_header)+1;
        FormatString((char *) profile_name,"XMP");
      }
  
    /*
      Store profile in Image.
    */
    (void) AppendImageProfile(image,profile_name,profile+header_length,
                              length-header_length);
  
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                          "Profile: %s, header %" MAGICK_SIZE_T_F "u bytes, "
                          "data %" MAGICK_SIZE_T_F "u bytes",
                          profile_name, (MAGICK_SIZE_T) header_length,
                          (MAGICK_SIZE_T) length-header_length);
  
    return (True);
  }
  
  static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
  {
    char
      magick[12];
  
    MagickClientData
      *client_data;
  
    Image
      *image;
  
    unsigned char
      *profile;
  
    long
      length;
  
    register long
      i;
  
    int
      c;
  
    /*
      Determine length of color profile.
    */
    GetProfileLength(jpeg_info, length);
    length-=2;
    if (length <= 14)
      {
        while (--length >= 0)
          (void) GetCharacter(jpeg_info);
        return(True);
      }
    for (i=0; i < 12; i++)
      magick[i]=GetCharacter(jpeg_info);
    if (LocaleCompare(magick,"ICC_PROFILE") != 0)
      {
        /*
          Not a ICC profile, return.
        */
        for (i=0; i < length-12; i++)
          (void) GetCharacter(jpeg_info);
        return(True);
      }
    (void) GetCharacter(jpeg_info);  /* id */
    (void) GetCharacter(jpeg_info);  /* markers */
    length-=14;
    client_data=(MagickClientData *) jpeg_info->client_data;
    image=client_data->image;
  
    /*
      Read color profile.
    */
    profile=client_data->buffer;
  
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                          "ICC profile chunk: %ld bytes",
      length);
  
    for (i=0 ; i < length; i++)
      {
        if ((c=GetCharacter(jpeg_info)) != EOF)
          profile[i]=c;
        else
          break;
      }
    if (i == length)
      (void) AppendImageProfile(image,"ICM",profile,length);
  
    return(True);
  }
  
  static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
  {
    MagickClientData
      *client_data;
  
    Image
      *image;
  
    long
      length,
      tag_length;
  
    unsigned char
      *profile;
  
    register long
      i;
  
  #ifdef GET_ONLY_IPTC_DATA
    unsigned char
      tag[MaxTextExtent];
  #endif
  
    int
      c;
  
    /*
      Determine length of binary data stored here.
    */
    GetProfileLength(jpeg_info, length);
    length-=2;
    if (length <= 0)
      return(True);
    tag_length=0;
  #ifdef GET_ONLY_IPTC_DATA
    *tag='\0';
  #endif
    client_data=(MagickClientData *) jpeg_info->client_data;
    image=client_data->image;
  #ifdef GET_ONLY_IPTC_DATA
    /*
      Find the beginning of the IPTC portion of the binary data.
    */
    for (*tag='\0'; length > 0; )
      {
        *tag=GetCharacter(jpeg_info);
        *(tag+1)=GetCharacter(jpeg_info);
        length-=2;
        if ((*tag == 0x1c) && (*(tag+1) == 0x02))
          break;
      }
    tag_length=2;
  #else
    /*
      Validate that this was written as a Photoshop resource format slug.
    */
    {
      char
        magick[MaxTextExtent];
  
      for (i=0; i < 10; i++)
        magick[i]=GetCharacter(jpeg_info);
      magick[10]='\0';
      length-=10;
      if (LocaleCompare(magick,"Photoshop ") != 0)
        {
          /*
            Not a ICC profile, return.
          */
          for (i=0; i < length; i++)
            (void) GetCharacter(jpeg_info);
          return(True);
        }
    }
    /*
      Remove the version number.
    */
    for (i=0; i < 4; i++)
      (void) GetCharacter(jpeg_info);
    length-=4;
    tag_length=0;
  #endif
    if (length <= 0)
      return(True);
  
    if ((size_t) length+tag_length > sizeof(client_data->buffer))
      ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
        (char *) NULL);
    profile=client_data->buffer;
    /*
      Read the payload of this binary data.
    */
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                          "Profile: IPTC, %ld bytes",
      length);
  
    for (i=0; i < length; i++)
      {
        if ((c=GetCharacter(jpeg_info)) != EOF)
          profile[i]=c;
        else
          break;
      }
    if (i == length)
      (void) AppendImageProfile(image,"IPTC",profile,length);
  
    return(True);
  }
  
  static void SkipInputData(j_decompress_ptr cinfo,long number_bytes)
  {
    SourceManager
      *source;
  
    if (number_bytes <= 0)
      return;
    source=(SourceManager *) cinfo->src;
    while (number_bytes > (long) source->manager.bytes_in_buffer)
    {
      number_bytes-=(long) source->manager.bytes_in_buffer;
      (void) FillInputBuffer(cinfo);
    }
    source->manager.next_input_byte+=(size_t) number_bytes;
    source->manager.bytes_in_buffer-=(size_t) number_bytes;
  }
  
  static void TerminateSource(j_decompress_ptr cinfo)
  {
    (void) cinfo;
  }
  
  static void JPEGSourceManager(j_decompress_ptr cinfo,Image *image)
  {
    SourceManager
      *source;
  
    cinfo->src=(struct jpeg_source_mgr *) (*cinfo->mem->alloc_small)
      ((j_common_ptr) cinfo,JPOOL_IMAGE,sizeof(SourceManager));
    source=(SourceManager *) cinfo->src;
    source->buffer=(JOCTET *) (*cinfo->mem->alloc_small)
      ((j_common_ptr) cinfo,JPOOL_IMAGE,MaxBufferExtent*sizeof(JOCTET));
    source=(SourceManager *) cinfo->src;
    source->manager.init_source=InitializeSource;
    source->manager.fill_input_buffer=FillInputBuffer;
    source->manager.skip_input_data=SkipInputData;
    source->manager.resync_to_restart=jpeg_resync_to_restart;
    source->manager.term_source=TerminateSource;
    source->manager.bytes_in_buffer=0;
    source->manager.next_input_byte=NULL;
    source->image=image;
  }
  
  /*
    Estimate the IJG quality factor used when saving the file.
  */
  static int
  EstimateJPEGQuality(const struct jpeg_decompress_struct *jpeg_info,
                      Image *image)
  {
    int
      save_quality;
  
    register long
      i;
  
    save_quality=0;
  #ifdef D_LOSSLESS_SUPPORTED
    if (image->compression==LosslessJPEGCompression)
      {
        save_quality=100;
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Quality: 100 (lossless)");
      }
    else
  #endif
  
      {
        int
          hashval,
          sum;
  
        /*
          Log the JPEG quality that was used for compression.
        */
        sum=0;
        for (i=0; i < NUM_QUANT_TBLS; i++)
          {
            int
              j;
  
            if (jpeg_info->quant_tbl_ptrs[i] != NULL)
              for (j=0; j < DCTSIZE2; j++)
                {
                  UINT16 *c;
                  c=jpeg_info->quant_tbl_ptrs[i]->quantval;
                  sum+=c[j];
                }
          }
        if ((jpeg_info->quant_tbl_ptrs[0] != NULL) &&
            (jpeg_info->quant_tbl_ptrs[1] != NULL))
          {
            int
              hash[] =
              {
                1020, 1015,  932,  848,  780,  735,  702,  679,  660,  645,
                632,  623,  613,  607,  600,  594,  589,  585,  581,  571,
                555,  542,  529,  514,  494,  474,  457,  439,  424,  410,
                397,  386,  373,  364,  351,  341,  334,  324,  317,  309,
                299,  294,  287,  279,  274,  267,  262,  257,  251,  247,
                243,  237,  232,  227,  222,  217,  213,  207,  202,  198,
                192,  188,  183,  177,  173,  168,  163,  157,  153,  148,
                143,  139,  132,  128,  125,  119,  115,  108,  104,   99,
                94,   90,   84,   79,   74,   70,   64,   59,   55,   49,
                45,   40,   34,   30,   25,   20,   15,   11,    6,    4,
                0
              };
  
            int
              sums[] =
              {
                32640,32635,32266,31495,30665,29804,29146,28599,28104,27670,
                27225,26725,26210,25716,25240,24789,24373,23946,23572,22846,
                21801,20842,19949,19121,18386,17651,16998,16349,15800,15247,
                14783,14321,13859,13535,13081,12702,12423,12056,11779,11513,
                11135,10955,10676,10392,10208, 9928, 9747, 9564, 9369, 9193,
                9017, 8822, 8639, 8458, 8270, 8084, 7896, 7710, 7527, 7347,
                7156, 6977, 6788, 6607, 6422, 6236, 6054, 5867, 5684, 5495,
                5305, 5128, 4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698,
                3509, 3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
                1666, 1483, 1297, 1109,  927,  735,  554,  375,  201,  128,
                0
              };
  
            hashval=(jpeg_info->quant_tbl_ptrs[0]->quantval[2]+
                     jpeg_info->quant_tbl_ptrs[0]->quantval[53]+
                     jpeg_info->quant_tbl_ptrs[1]->quantval[0]+
                     jpeg_info->quant_tbl_ptrs[1]->quantval[DCTSIZE2-1]);
            for (i=0; i < 100; i++)
              {
                if ((hashval >= hash[i]) || (sum >= sums[i]))
                  {
                    save_quality=i+1;
                    if (image->logging)
                      {
                        if ((hashval > hash[i]) || (sum > sums[i]))
                          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                                "Quality: %d (approximate)",
                                                save_quality);
                        else
                          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                                "Quality: %d",save_quality);
                      }
                    break;
                  }
              }
          }
        else
          if (jpeg_info->quant_tbl_ptrs[0] != NULL)
            {
              int
                bwhash[] =
                {
                  510,  505,  422,  380,  355,  338,  326,  318,  311,  305,
                  300,  297,  293,  291,  288,  286,  284,  283,  281,  280,
                  279,  278,  277,  273,  262,  251,  243,  233,  225,  218,
                  211,  205,  198,  193,  186,  181,  177,  172,  168,  164,
                  158,  156,  152,  148,  145,  142,  139,  136,  133,  131,
                  129,  126,  123,  120,  118,  115,  113,  110,  107,  105,
                  102,  100,   97,   94,   92,   89,   87,   83,   81,   79,
                  76,   74,   70,   68,   66,   63,   61,   57,   55,   52,
                  50,   48,   44,   42,   39,   37,   34,   31,   29,   26,
                  24,   21,   18,   16,   13,   11,    8,    6,    3,    2,
                  0
                };
  
              int
                bwsum[] =
                {
                  16320,16315,15946,15277,14655,14073,13623,13230,12859,12560,
                  12240,11861,11456,11081,10714,10360,10027, 9679, 9368, 9056,
                  8680, 8331, 7995, 7668, 7376, 7084, 6823, 6562, 6345, 6125,
                  5939, 5756, 5571, 5421, 5240, 5086, 4976, 4829, 4719, 4616,
                  4463, 4393, 4280, 4166, 4092, 3980, 3909, 3835, 3755, 3688,
                  3621, 3541, 3467, 3396, 3323, 3247, 3170, 3096, 3021, 2952,
                  2874, 2804, 2727, 2657, 2583, 2509, 2437, 2362, 2290, 2211,
                  2136, 2068, 1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477,
                  1398, 1326, 1251, 1179, 1109, 1031,  961,  884,  814,  736,
                  667,  592,  518,  441,  369,  292,  221,  151,   86,   64,
                  0
                };
  
              hashval=(jpeg_info->quant_tbl_ptrs[0]->quantval[2]+
                       jpeg_info->quant_tbl_ptrs[0]->quantval[53]);
              for (i=0; i < 100; i++)
                {
                  if ((hashval >= bwhash[i]) || (sum >= bwsum[i]))
                    {
                      save_quality=i+1;
                      if (image->logging)
                        {
                          if ((hashval > bwhash[i]) || (sum > bwsum[i]))
                            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                                  "Quality: %ld (approximate)",
                                                  i+1);
                          else
                            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                                  "Quality: %ld",i+1);
                        }
                      break;
                    }
                }
            }
      }
  
    return save_quality;
  }
  
  /*
    Format JPEG color space to a string.
  */
  static void
  FormatJPEGColorSpace(const J_COLOR_SPACE colorspace,
                       char *colorspace_name)
  {
    const char
      *s = NULL;
  
    switch (colorspace)
      {
      default:
      case JCS_UNKNOWN:
        s = "UNKNOWN";
        break;
      case JCS_GRAYSCALE:
        s = "GRAYSCALE";
        break;
      case JCS_RGB:
        s = "RGB";
        break;
      case JCS_YCbCr:
        s = "YCbCr";
        break;
      case JCS_CMYK:
        s = "CMYK";
        break;
      case JCS_YCCK:
        s = "YCCK";
        break;
      }
    (void) strlcpy(colorspace_name,s,MaxTextExtent);
  }
  
  /*
    Format JPEG sampling factors to a string.
  */
  static void
  FormatJPEGSamplingFactors(const struct jpeg_decompress_struct *jpeg_info,
                            char *sampling_factors)
  {
    switch (jpeg_info->out_color_space)
      {
      case JCS_CMYK:
        {
          (void) FormatString(sampling_factors,"%dx%d,%dx%d,%dx%d,%dx%d",
                              jpeg_info->comp_info[0].h_samp_factor,
                              jpeg_info->comp_info[0].v_samp_factor,
                              jpeg_info->comp_info[1].h_samp_factor,
                              jpeg_info->comp_info[1].v_samp_factor,
                              jpeg_info->comp_info[2].h_samp_factor,
                              jpeg_info->comp_info[2].v_samp_factor,
                              jpeg_info->comp_info[3].h_samp_factor,
                              jpeg_info->comp_info[3].v_samp_factor);
          break;
        }
      case JCS_GRAYSCALE:
        {
          (void) FormatString(sampling_factors,"%dx%d",
                              jpeg_info->comp_info[0].h_samp_factor,
                              jpeg_info->comp_info[0].v_samp_factor);
          break;
        }
      case JCS_RGB:
        {
          (void) FormatString(sampling_factors,"%dx%d,%dx%d,%dx%d",
                              jpeg_info->comp_info[0].h_samp_factor,
                              jpeg_info->comp_info[0].v_samp_factor,
                              jpeg_info->comp_info[1].h_samp_factor,
                              jpeg_info->comp_info[1].v_samp_factor,
                              jpeg_info->comp_info[2].h_samp_factor,
                              jpeg_info->comp_info[2].v_samp_factor);
          break;
        }
      default:
        {
          (void) FormatString(sampling_factors,"%dx%d,%dx%d,%dx%d,%dx%d",
                              jpeg_info->comp_info[0].h_samp_factor,
                              jpeg_info->comp_info[0].v_samp_factor,
                              jpeg_info->comp_info[1].h_samp_factor,
                              jpeg_info->comp_info[1].v_samp_factor,
                              jpeg_info->comp_info[2].h_samp_factor,
                              jpeg_info->comp_info[2].v_samp_factor,
                              jpeg_info->comp_info[3].h_samp_factor,
                              jpeg_info->comp_info[3].v_samp_factor);
          break;
        }
      }
  }
  
  static MagickBool
  IsITUFax(const Image* image)
  {
    size_t
      profile_length;
  
    const unsigned char
      *profile;
  
    MagickBool
      status;
  
    status=MagickFalse;
    if ((profile=GetImageProfile(image,"APP1",&profile_length)) &&
        (profile_length >= 5))
      {
        if (profile[0] == 0x47 &&
            profile[1] == 0x33 &&
            profile[2] == 0x46 &&
            profile[3] == 0x41 &&
            profile[4] == 0x58)
        status=MagickTrue;
      }
  
      return status;
  }
  
  #define ThrowJPEGReaderException(code_,reason_,image_)  \
    {                                                     \
      client_data=FreeMagickClientData(client_data);      \
      ThrowReaderException(code_,reason_,image_);         \
    }
  
  static Image *ReadJPEGImage(const ImageInfo *image_info,
                              ExceptionInfo *exception)
  {
    Image
      *image;
  
    MagickClientData
      *client_data = (MagickClientData *) NULL;
  
    IndexPacket
      index;
  
    long
      y;
  
    JSAMPLE
      *jpeg_pixels;
  
    JSAMPROW
      scanline[1];
  
    const char
      *value;
  
    register long
      i;
  
    struct jpeg_error_mgr
      jpeg_error;
  
    struct jpeg_progress_mgr
      jpeg_progress;
  
    struct jpeg_decompress_struct
      jpeg_info;
  
    register JSAMPLE
      *p;
  
    MagickPassFail
      status;
  
    unsigned long
      number_pixels;
  
    /*
      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);
    if (image == (Image *) NULL)
      ThrowJPEGReaderException(ResourceLimitError,MemoryAllocationFailed,image);
    client_data=AllocateMagickClientData();
    if (client_data == (MagickClientData *) NULL)
      ThrowJPEGReaderException(ResourceLimitError,MemoryAllocationFailed,image);
    status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
    if (status == MagickFail)
      ThrowJPEGReaderException(FileOpenError,UnableToOpenFile,image);
    if (BlobIsSeekable(image) && GetBlobSize(image) < 107)
      ThrowJPEGReaderException(CorruptImageError,InsufficientImageDataInFile,image);
    /*
      Initialize structures.
    */
    (void) memset(&jpeg_progress,0,sizeof(jpeg_progress));
    (void) memset(&jpeg_info,0,sizeof(jpeg_info));
    (void) memset(&jpeg_error,0,sizeof(jpeg_error));
    jpeg_info.err=jpeg_std_error(&jpeg_error);
    jpeg_info.err->emit_message=/*(void (*)(j_common_ptr,int))*/ JPEGDecodeMessageHandler;
    jpeg_info.err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
    jpeg_pixels=(JSAMPLE *) NULL;
    client_data->image=image;
    client_data->ping=image_info->ping;
    client_data->max_scan_number=100;
    client_data->max_warning_count=MaxWarningCount;
  
    /*
      Allow the user to set how many warnings of any given type are
      allowed before promotion of the warning to a hard error.
    */
    if ((value=AccessDefinition(image_info,"jpeg","max-warnings")))
      client_data->max_warning_count=strtol(value,(char **) NULL, 10);
  
    /*
      Set initial longjmp based error handler.
    */
    if (setjmp(client_data->error_recovery) != 0)
      {
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Setjmp return from longjmp!");
        jpeg_destroy_decompress(&jpeg_info);
        GetImageException(image,exception);
        client_data=FreeMagickClientData(client_data);
        CloseBlob(image);
        if (exception->severity < ErrorException)
          return(image);
        DestroyImage(image);
        return((Image *) NULL);
      }
    jpeg_info.client_data=(void *) client_data;
  
    jpeg_create_decompress(&jpeg_info);
    /*
      Specify a memory limit for libjpeg which is 1/5th the absolute
      limit.  Don't actually consume the resource since we don't know
      how much libjpeg will actually consume.
    */
    jpeg_info.mem->max_memory_to_use=(long) (GetMagickResourceLimit(MemoryResource) -
                                             GetMagickResource(MemoryResource))/5U;
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Memory capped to %ld bytes", jpeg_info.mem->max_memory_to_use);
    /*
      Register our progress monitor
    */
    jpeg_progress.progress_monitor=(void (*)(j_common_ptr)) JPEGDecodeProgressMonitor;
    jpeg_info.progress=&jpeg_progress;
  
    JPEGSourceManager(&jpeg_info,image);
    jpeg_set_marker_processor(&jpeg_info,JPEG_COM,ReadComment);
    jpeg_set_marker_processor(&jpeg_info,ICC_MARKER,ReadICCProfile);
    jpeg_set_marker_processor(&jpeg_info,IPTC_MARKER,ReadIPTCProfile);
    for (i=1; i < 16; i++)
      if ((i != 2) && (i != 13) && (i != 14))
        jpeg_set_marker_processor(&jpeg_info,JPEG_APP0+i,ReadGenericProfile);
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Reading JPEG header...");
    i=jpeg_read_header(&jpeg_info,True);
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Done with reading JPEG header");
    if (IsITUFax(image))
      {
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Image colorspace set to LAB");
        image->colorspace=LABColorspace;
        jpeg_info.out_color_space = JCS_YCbCr;
      }
    else if (jpeg_info.out_color_space == JCS_CMYK)
      {
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Image colorspace set to CMYK");
        image->colorspace=CMYKColorspace;
      }
    if (jpeg_info.saw_JFIF_marker)
      {
        if ((jpeg_info.X_density != 1U) && (jpeg_info.Y_density != 1U))
          {
            /*
              Set image resolution.
            */
            image->x_resolution=jpeg_info.X_density;
            image->y_resolution=jpeg_info.Y_density;
            if (jpeg_info.density_unit == 1)
              image->units=PixelsPerInchResolution;
            if (jpeg_info.density_unit == 2)
              image->units=PixelsPerCentimeterResolution;
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Image resolution set to %gx%g %s",
                                    image->x_resolution,
                                    image->y_resolution,
                                    ResolutionTypeToString(image->units));
          }
      }
  
    /*
      If the desired image size is pre-set (e.g. by using -size), then
      let the JPEG library subsample for us.
    */
    number_pixels=image->columns*image->rows;
    if (number_pixels != 0)
      {
        double
          scale_factor;
  
  
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Requested Geometry: %lux%lu",
                                image->columns,image->rows);
        jpeg_calc_output_dimensions(&jpeg_info);
        image->magick_columns=jpeg_info.output_width;
        image->magick_rows=jpeg_info.output_height;
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "magick_geometry=%lux%lu",
                                image->magick_columns, image->magick_rows);
        scale_factor=(double) jpeg_info.output_width/image->columns;
        if (scale_factor > ((double) jpeg_info.output_height/image->rows))
          scale_factor=(double) jpeg_info.output_height/image->rows;
        jpeg_info.scale_denom *=(unsigned int) scale_factor;
        jpeg_calc_output_dimensions(&jpeg_info);
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Original Geometry: %lux%lu,"
                                " Scale_factor: %ld (scale_num=%d,"
                                " scale_denom=%d)",
                                image->magick_columns, image->magick_rows,
                                (long) scale_factor,
                                jpeg_info.scale_num,jpeg_info.scale_denom);
      }
  #if 0
    /*
      The subrange parameter is set by the filename array syntax similar
      to the way an image is requested from a list (e.g. myfile.jpg[2]).
      Argument values other than zero are used to scale the image down
      by that factor.  IJG JPEG 62 (6b) supports values of 1,2,4, or 8
      while IJG JPEG 70 supports all values in the range 1-16.  This
      feature is useful in case you want to view all of the images with
      a consistent ratio.  Unfortunately, it uses the same syntax as
      list member access.
    */
    else if (image_info->subrange != 0)
      {
        jpeg_info.scale_denom *=(int) image_info->subrange;
        jpeg_calc_output_dimensions(&jpeg_info);
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Requested Scaling Denominator: %d "
                                "(scale_num=%d, scale_denom=%d)",
                                (int) image_info->subrange,
                                jpeg_info.scale_num,jpeg_info.scale_denom);
  
      }
  #endif
  #if (JPEG_LIB_VERSION >= 61) && defined(D_PROGRESSIVE_SUPPORTED)
  #ifdef D_LOSSLESS_SUPPORTED
    image->interlace=
      jpeg_info.process == JPROC_PROGRESSIVE ? LineInterlace : NoInterlace;
    image->compression=jpeg_info.process == JPROC_LOSSLESS ?
      LosslessJPEGCompression : JPEGCompression;
    if (jpeg_info.data_precision > 8)
      MagickError2(OptionError,
                   "12-bit JPEG not supported. Reducing pixel data to 8 bits",
                   (char *) NULL);
  #else
    image->interlace=jpeg_info.progressive_mode ? LineInterlace : NoInterlace;
    image->compression=JPEGCompression;
  #endif
  #else
    image->compression=JPEGCompression;
    image->interlace=LineInterlace;
  #endif
  
    /*
      Allow the user to enable/disable block smoothing.
    */
    if ((value=AccessDefinition(image_info,"jpeg","block-smoothing")))
      {
        if (LocaleCompare(value,"FALSE") == 0)
          jpeg_info.do_block_smoothing=False;
        else
          jpeg_info.do_block_smoothing=True;
      }
  
    /*
      Allow the user to select the DCT decoding algorithm.
    */
    if ((value=AccessDefinition(image_info,"jpeg","dct-method")))
      {
        if (LocaleCompare(value,"ISLOW") == 0)
          jpeg_info.dct_method=JDCT_ISLOW;
        else if (LocaleCompare(value,"IFAST") == 0)
          jpeg_info.dct_method=JDCT_IFAST;
        else if (LocaleCompare(value,"FLOAT") == 0)
          jpeg_info.dct_method=JDCT_FLOAT;
        else if (LocaleCompare(value,"DEFAULT") == 0)
          jpeg_info.dct_method=JDCT_DEFAULT;
        else if (LocaleCompare(value,"FASTEST") == 0)
          jpeg_info.dct_method=JDCT_FASTEST;
      }
  
    /*
      Allow the user to enable/disable fancy upsampling.
    */
    if ((value=AccessDefinition(image_info,"jpeg","fancy-upsampling")))
      {
        if (LocaleCompare(value,"FALSE") == 0)
          jpeg_info.do_fancy_upsampling=False;
        else
          jpeg_info.do_fancy_upsampling=True;
      }
  
    /*
      Allow the user to adjust the maximum JPEG scan number
    */
    if ((value=AccessDefinition(image_info,"jpeg","max-scan-number")))
      {
        client_data->max_scan_number=strtol(value,(char **) NULL, 10);
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "JPEG max-scan-number set to %d",
                              client_data->max_scan_number);
      }
  
    jpeg_calc_output_dimensions(&jpeg_info);
    image->columns=jpeg_info.output_width;
    image->rows=jpeg_info.output_height;
    image->storage_class = jpeg_info.output_components == 1 ? PseudoClass : DirectClass;
    image->depth=Min(jpeg_info.data_precision,QuantumDepth);
  
    if (image->logging)
      {
        if (image->interlace == LineInterlace)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Interlace: progressive");
        else
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Interlace: nonprogressive");
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Data precision: %d",
                              (int) jpeg_info.data_precision);
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Components: %d",
                              (int) jpeg_info.output_components);
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Geometry: %dx%d",
                              (int) jpeg_info.output_width,
                              (int) jpeg_info.output_height);
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"DCT Method: %d",
                              jpeg_info.dct_method);
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Fancy Upsampling: %s",
                              (jpeg_info.do_fancy_upsampling ? "true" : "false"));
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Block Smoothing: %s",
                              (jpeg_info.do_block_smoothing ? "true" : "false"));
      }
  
    if (CheckImagePixelLimits(image, exception) != MagickPass)
      {
        jpeg_destroy_decompress(&jpeg_info);
        ThrowJPEGReaderException(ResourceLimitError,ImagePixelLimitExceeded,image);
      }
  
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Starting JPEG decompression...");
    (void) jpeg_start_decompress(&jpeg_info);
    image->columns=jpeg_info.output_width;
    image->rows=jpeg_info.output_height;
    {
      char
        attribute[MaxTextExtent];
  
      /*
        Estimate and retain JPEG properties as attributes.
      */
      FormatString(attribute,"%d",EstimateJPEGQuality(&jpeg_info,image));
      (void) SetImageAttribute(image,"JPEG-Quality",attribute);
  
      FormatString(attribute,"%ld",(long)jpeg_info.out_color_space);
      (void) SetImageAttribute(image,"JPEG-Colorspace",attribute);
  
      FormatJPEGColorSpace(jpeg_info.out_color_space,attribute);
      (void) SetImageAttribute(image,"JPEG-Colorspace-Name",attribute);
      if (image->logging)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Colorspace: %s (%d)", attribute,
                              jpeg_info.out_color_space);
  
      FormatJPEGSamplingFactors(&jpeg_info,attribute);
      (void) SetImageAttribute(image,"JPEG-Sampling-factors",attribute);
      if (image->logging)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Sampling Factors: %s", attribute);
    }
  
    image->depth=Min(jpeg_info.data_precision,QuantumDepth);
    if (jpeg_info.out_color_space == JCS_GRAYSCALE)
      if (!AllocateImageColormap(image,1 << image->depth))
        {
          jpeg_destroy_decompress(&jpeg_info);
          ThrowJPEGReaderException(ResourceLimitError,MemoryAllocationFailed,image);
        }
    if (image_info->ping)
      {
        jpeg_destroy_decompress(&jpeg_info);
        client_data=FreeMagickClientData(client_data);
        CloseBlob(image);
        return(image);
      }
    if (CheckImagePixelLimits(image, exception) != MagickPass)
      {
        jpeg_destroy_decompress(&jpeg_info);
        ThrowJPEGReaderException(ResourceLimitError,ImagePixelLimitExceeded,image);
      }
  
    /*
      Verify that we support the number of output components.
    */
    if ((jpeg_info.output_components != 1) &&
        (jpeg_info.output_components != 3) &&
        (jpeg_info.output_components != 4))
      {
        jpeg_destroy_decompress(&jpeg_info);
        ThrowJPEGReaderException(CoderError,ImageTypeNotSupported,image);
      }
    /*
      Verify that file size is reasonable (if we can)
    */
    if (BlobIsSeekable(image))
      {
        magick_off_t
          blob_size;
  
        double
          ratio = 0;
  
        blob_size = GetBlobSize(image);
  
        if (blob_size != 0)
          {
            /* magick columns/rows are only set if size was specified! */
            if (image->magick_columns && image->magick_rows)
              ratio = ((double) image->magick_columns*image->magick_rows*
                       jpeg_info.output_components/blob_size);
            else
              ratio = ((double) image->columns*image->rows*
                       jpeg_info.output_components/blob_size);
          }
  
        /* All-black JPEG can produce tremendous compression ratios.
           Allow for it. */
        if ((blob_size == 0) || (ratio > 2500.0))
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Unreasonable dimensions: "
                                  "geometry=%lux%lu,"
                                  " magick_geometry=%lux%lu,"
                                  " components=%d, "
                                  "blob size=%" MAGICK_OFF_F "d bytes, "
                                  "compression ratio %g",
                                  image->columns, image->rows,
                                  image->magick_columns, image->magick_rows,
                                  jpeg_info.output_components, blob_size, ratio);
  
            jpeg_destroy_decompress(&jpeg_info);
            ThrowJPEGReaderException(CorruptImageError,InsufficientImageDataInFile,image);
          }
      }
  
    jpeg_pixels=MagickAllocateResourceLimitedArray(JSAMPLE *,
                                    jpeg_info.output_components,
                                    MagickArraySize(image->columns,
                                                    sizeof(JSAMPLE)));
    if (jpeg_pixels == (JSAMPLE *) NULL)
      {
        jpeg_destroy_decompress(&jpeg_info);
        ThrowJPEGReaderException(ResourceLimitError,MemoryAllocationFailed,image);
      }
    (void) memset(jpeg_pixels,0,MagickArraySize(jpeg_info.output_components,
                                                MagickArraySize(image->columns,
                                                                sizeof(JSAMPLE))));
  
    /*
      Extended longjmp-based error handler (with jpeg_pixels)
    */
    if (setjmp(client_data->error_recovery) != 0)
      {
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Setjmp return from longjmp!");
        /* Error handling code executed if longjmp was invoked */
        MagickFreeResourceLimitedMemory(jpeg_pixels);
        jpeg_destroy_decompress(&jpeg_info);
        if (image->exception.severity > exception->severity)
          CopyException(exception,&image->exception);
        client_data=FreeMagickClientData(client_data);
        CloseBlob(image);
        number_pixels=image->columns*image->rows;
        if (number_pixels != 0)
          return(image);
        DestroyImage(image);
        return((Image *) NULL);
      }
  
    /*
      Convert JPEG pixels to pixel packets.
    */
    scanline[0]=(JSAMPROW) jpeg_pixels;
    for (y=0; y < (long) image->rows; y++)
      {
        register IndexPacket
          *indexes;
  
        register long
          x;
  
        register PixelPacket
          *q;
  
        /*
          Read scanlines. Stop at first serious error.
         */
        if ((jpeg_read_scanlines(&jpeg_info,scanline,1) != 1) ||
            (image->exception.severity >= ErrorException))
          {
            status=MagickFail;
            break;
          }
  
        q=SetImagePixels(image,0,y,image->columns,1);
        if (q == (PixelPacket *) NULL)
          {
            status=MagickFail;
            break;
          }
        indexes=AccessMutableIndexes(image);
  
        p=jpeg_pixels;
  
        if (jpeg_info.output_components == 1)
          {
            for (x=0; x < (long) image->columns; x++)
              {
                index=(IndexPacket) (GETJSAMPLE(*p++));
                VerifyColormapIndex(image,index);
                indexes[x]=index;
                *q++=image->colormap[index];
              }
          }
        else if ((jpeg_info.output_components == 3) ||
                 (jpeg_info.output_components == 4))
          {
            if (jpeg_info.data_precision > 8)
              {
                unsigned int
                  scale_short;
  
                scale_short=65535U/MaxValueGivenBits(jpeg_info.data_precision);
                for (x=0; x < (long) image->columns; x++)
                  {
                    q->red=ScaleShortToQuantum(scale_short*GETJSAMPLE(*p++));
                    q->green=ScaleShortToQuantum(scale_short*GETJSAMPLE(*p++));
                    q->blue=ScaleShortToQuantum(scale_short*GETJSAMPLE(*p++));
                    if (jpeg_info.output_components > 3)
                      q->opacity=ScaleShortToQuantum(scale_short*GETJSAMPLE(*p++));
                    else
                      q->opacity=OpaqueOpacity;
                    q++;
                  }
              }
            else
              {
                for (x=0; x < (long) image->columns; x++)
                  {
                    q->red=ScaleCharToQuantum(GETJSAMPLE(*p++));
                    q->green=ScaleCharToQuantum(GETJSAMPLE(*p++));
                    q->blue=ScaleCharToQuantum(GETJSAMPLE(*p++));
                    if (jpeg_info.output_components > 3)
                      q->opacity=ScaleCharToQuantum(GETJSAMPLE(*p++));
                    else
                      q->opacity=OpaqueOpacity;
                    q++;
                  }
              }
            if (image->colorspace == CMYKColorspace)
              {
                /*
                  CMYK pixels are inverted.
                */
                q=AccessMutablePixels(image);
                for (x=0; x < (long) image->columns; x++)
                  {
                    q->red=MaxRGB-q->red;
                    q->green=MaxRGB-q->green;
                    q->blue=MaxRGB-q->blue;
                    q->opacity=MaxRGB-q->opacity;
                    q++;
                  }
              }
          }
        if (!SyncImagePixels(image))
          {
            status=MagickFail;
            break;
          }
  #if !USE_LIBJPEG_PROGRESS
        if (QuantumTick(y,image->rows))
          if (!MagickMonitorFormatted(y,image->rows,exception,LoadImageText,
                                      image->filename,
                                      image->columns,image->rows))
            {
              status=MagickFail;
              jpeg_abort_decompress(&jpeg_info);
              break;
            }
  #endif /* !USE_LIBJPEG_PROGRESS */
      }
    /*
      Free jpeg resources.
    */
    if (status == MagickPass)
      {
        /*
          jpeg_finish_decompress() may throw an exception while it is
          finishing the remainder of the JPEG file.  At this point we
          have already decoded the image so we handle exceptions from
          jpeg_finish_decompress() specially, mapping reported
          exceptions as warnings rather than errors.  We try using
          jpeg_finish_decompress() and if it results in a longjmp(),
          then we skip over it again.
        */
        client_data->completed=MagickTrue;
        if (setjmp(client_data->error_recovery) != 0)
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Setjmp return from longjmp!");
          }
        else
          {
            (void) jpeg_finish_decompress(&jpeg_info);
          }
      }
    jpeg_destroy_decompress(&jpeg_info);
    MagickFreeResourceLimitedMemory(jpeg_pixels);
    client_data=FreeMagickClientData(client_data);
    CloseBlob(image);
  
    /*
      Retrieve image orientation from EXIF (if present) and store in
      image.
  
      EXIF orienation enumerations match TIFF enumerations, which happen
      to match the enumeration values used by GraphicsMagick.
    */
    if (status == MagickPass)
      {
        const ImageAttribute
          *attribute;
  
        attribute = GetImageAttribute(image,"EXIF:Orientation");
        if ((attribute != (const ImageAttribute *) NULL) &&
            (attribute->value != (char *) NULL))
          {
            int
              orientation;
  
            orientation=MagickAtoI(attribute->value);
            if ((orientation > UndefinedOrientation) &&
                (orientation <= LeftBottomOrientation))
              image->orientation=(OrientationType) orientation;
          }
      }
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"return");
    GetImageException(image,exception);
    StopTimer(&image->timer);
    return(image);
  }
  #endif
  
  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %   R e g i s t e r J P E G I m a g e                                         %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  Method RegisterJPEGImage adds attributes for the JPEG 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 RegisterJPEGImage method is:
  %
  %      RegisterJPEGImage(void)
  %
  */
  ModuleExport void RegisterJPEGImage(void)
  {
    static const char
      description[]="Joint Photographic Experts Group JFIF format";
  
  #if defined(HasJPEG) && defined(JPEG_LIB_VERSION)
    static const char
      version[] = "IJG JPEG " DefineValueToString(JPEG_LIB_VERSION);
  #define HAVE_JPEG_VERSION
  #endif
  
    MagickInfo
      *entry;
  
    MagickBool
      thread_support;
  
  #if defined(SETJMP_IS_THREAD_SAFE) && (SETJMP_IS_THREAD_SAFE)
    thread_support=MagickTrue;  /* libjpeg is thread safe */
  #else
    thread_support=MagickFalse; /* libjpeg is not thread safe */
  #endif
  
    entry=SetMagickInfo("JPEG");
    entry->thread_support=thread_support;
  #if defined(HasJPEG)
    entry->decoder=(DecoderHandler) ReadJPEGImage;
    entry->encoder=(EncoderHandler) WriteJPEGImage;
  #endif
    entry->magick=(MagickHandler) IsJPEG;
    entry->adjoin=False;
    entry->description=description;
  #if defined(HAVE_JPEG_VERSION)
      entry->version=version;
  #endif
    entry->module="JPEG";
    entry->coder_class=PrimaryCoderClass;
    (void) RegisterMagickInfo(entry);
  
    entry=SetMagickInfo("JPG");
    entry->thread_support=thread_support;
  #if defined(HasJPEG)
    entry->decoder=(DecoderHandler) ReadJPEGImage;
    entry->encoder=(EncoderHandler) WriteJPEGImage;
  #endif
    entry->adjoin=False;
    entry->description=description;
  #if defined(HAVE_JPEG_VERSION)
      entry->version=version;
  #endif
    entry->module="JPEG";
    entry->coder_class=PrimaryCoderClass;
    (void) RegisterMagickInfo(entry);
  }
  
  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %   U n r e g i s t e r J P E G I m a g e                                     %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  Method UnregisterJPEGImage removes format registrations made by the
  %  JPEG module from the list of supported formats.
  %
  %  The format of the UnregisterJPEGImage method is:
  %
  %      UnregisterJPEGImage(void)
  %
  */
  ModuleExport void UnregisterJPEGImage(void)
  {
    (void) UnregisterMagickInfo("JPEG");
    (void) UnregisterMagickInfo("JPG");
  }
  
  #if defined(HasJPEG)
  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %  W r i t e J P E G I m a g e                                                %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  Method WriteJPEGImage writes a JPEG 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 WriteJPEGImage method is:
  %
  %      unsigned int WriteJPEGImage(const ImageInfo *image_info,Image *image)
  %
  %  A description of each parameter follows:
  %
  %    o status:  Method WriteJPEGImage return True if the image is written.
  %      False is returned is there is of a memory shortage or if the image
  %      file cannot be opened for writing.
  %
  %    o image_info: Specifies a pointer to a ImageInfo structure.
  %
  %    o jpeg_image:  A pointer to an Image structure.
  %
  %
  */
  
  static boolean EmptyOutputBuffer(j_compress_ptr cinfo)
  {
    DestinationManager
      *destination;
  
    destination=(DestinationManager *) cinfo->dest;
    destination->manager.free_in_buffer=WriteBlob(destination->image,
      MaxBufferExtent,(char *) destination->buffer);
    if (destination->manager.free_in_buffer != MaxBufferExtent)
      ERREXIT(cinfo,JERR_FILE_WRITE);
    destination->manager.next_output_byte=destination->buffer;
    return(TRUE);
  }
  
  static void InitializeDestination(j_compress_ptr cinfo)
  {
    DestinationManager
      *destination;
  
    destination=(DestinationManager *) cinfo->dest;
    destination->buffer=(JOCTET *) (*cinfo->mem->alloc_small)
      ((j_common_ptr) cinfo,JPOOL_IMAGE,MaxBufferExtent*sizeof(JOCTET));
    destination->manager.next_output_byte=destination->buffer;
    destination->manager.free_in_buffer=MaxBufferExtent;
  }
  
  static void TerminateDestination(j_compress_ptr cinfo)
  {
    DestinationManager
      *destination;
  
    destination=(DestinationManager *) cinfo->dest;
    if ((MaxBufferExtent-(int) destination->manager.free_in_buffer) > 0)
      {
        size_t
          number_bytes;
  
        number_bytes=WriteBlob(destination->image,MaxBufferExtent-
          destination->manager.free_in_buffer,(char *) destination->buffer);
        if (number_bytes != (MaxBufferExtent-destination->manager.free_in_buffer))
          ERREXIT(cinfo,JERR_FILE_WRITE);
      }
  }
  
  /*
    Output generic APPN profile
  */
  static void WriteAPPNProfile(j_compress_ptr jpeg_info,
                                const unsigned char *profile,
                                const size_t profile_length,
                                const char * profile_name)
  {
    size_t
      j;
  
    int
      marker_id;
  
    marker_id=JPEG_APP0+(int) MagickAtoL(profile_name+3);
    for (j=0; j < profile_length; j+=65533L)
      jpeg_write_marker(jpeg_info,marker_id,
                        profile+j,(int)
                        Min(profile_length-j,65533L));
  }
  
  /*
    Output EXIF profile
  */
  static void WriteEXIFProfile(j_compress_ptr jpeg_info,
                                const unsigned char *profile,
                                const size_t profile_length)
  {
    size_t
      j;
  
    for (j=0; j < profile_length; j+=65533L)
      jpeg_write_marker(jpeg_info,JPEG_APP0+1,
                        profile+j,(int)
                        Min(profile_length-j,65533L));
  }
  
  /*
    Output ICC color profile as a APP marker.
  */
  static void WriteICCProfile(j_compress_ptr jpeg_info,
                              const unsigned char *color_profile,
                              const size_t profile_length)
  {
    register long
      i,
      j;
  
    for (i=0; i < (long) profile_length; i+=65519)
    {
      unsigned char
        *profile;
  
      size_t
        length=0;
  
  
      length=Min(profile_length-i,65519);
      profile=MagickAllocateResourceLimitedMemory(unsigned char *,length+14);
      if (profile == (unsigned char *) NULL)
        break;
      (void) strcpy((char *) profile,"ICC_PROFILE");
      profile[12]=(unsigned char) ((i/65519)+1);
      profile[13]=(unsigned char) ((profile_length/65519)+1);
      for (j=0; j < (long) length; j++)
        profile[j+14]=color_profile[i+j];
      jpeg_write_marker(jpeg_info,ICC_MARKER,profile,(unsigned int) length+14);
      MagickFreeResourceLimitedMemory(profile);
    }
  }
  
  /*
    Output binary Photoshop resource data using an APP marker.
  */
  static void WriteIPTCProfile(j_compress_ptr jpeg_info,
                              const unsigned char *iptc_profile,
                              const size_t profile_length)
  {
    register long
      i;
  
    unsigned long
      roundup,
      tag_length;
  
  #ifdef GET_ONLY_IPTC_DATA
    tag_length=26;
  #else
    tag_length=14;
  #endif
    for (i=0; i < (long) profile_length; i+=65500)
    {
      size_t
        length;
  
      unsigned char
        *profile;
  
      length=Min(profile_length-i,65500);
      roundup=(length & 0x01); /* round up for Photoshop */
      profile=MagickAllocateResourceLimitedMemory(unsigned char *,length+roundup+tag_length);
      if (profile == (unsigned char *) NULL)
        break;
  #ifdef GET_ONLY_IPTC_DATA
      (void) memcpy(profile,"Photoshop 3.0 8BIM\04\04\0\0\0\0",24);
      profile[13]=0x00;
      profile[24]=length >> 8;
      profile[25]=length & 0xff;
  #else
      (void) memcpy(profile,"Photoshop 3.0 ",14);
      profile[13]=0x00;
  #endif
      (void) memcpy(&(profile[tag_length]),&(iptc_profile[i]),length);
      if (roundup)
        profile[length+tag_length]=0;
      jpeg_write_marker(jpeg_info,IPTC_MARKER,profile,(unsigned int)
        (length+roundup+tag_length));
      MagickFreeResourceLimitedMemory(profile);
    }
  }
  
  /*
    Output Adobe XMP XML profile.
  */
  static void WriteXMPProfile(j_compress_ptr jpeg_info,
                              const unsigned char *profile,
                              const size_t profile_length)
  {
    size_t
      count,
      index,
          header_length,
          total_length;
  
    unsigned int
          marker_length,
          remaining;
  
    header_length=strlen(xmp_std_header)+1; /* Include terminating null */
    total_length=header_length+profile_length;
    /* XMP profile must be no larger than range of 'unsigned int' */
    remaining=(unsigned int) Min(UINT_MAX,total_length);
  
    marker_length=Min(remaining,JPEG_MARKER_MAX_SIZE);
    jpeg_write_m_header(jpeg_info,XML_MARKER,marker_length);
    count=marker_length;
    {
      for (index=0 ; index < header_length; index++)
        {
          jpeg_write_m_byte(jpeg_info,xmp_std_header[index]);
          --remaining;
          --count;
        }
    }
  
    for (index=0; remaining > 0; --remaining)
      {
        if (count == 0)
          {
            marker_length=Min(remaining,JPEG_MARKER_MAX_SIZE);
            jpeg_write_m_header(jpeg_info,XML_MARKER,marker_length);
            count=marker_length;
          }
        jpeg_write_m_byte(jpeg_info,profile[index]);
        index++;
        count--;
      }
  }
  
  /*
    Output profiles to JPEG stream.
  */
  static void WriteProfiles(j_compress_ptr jpeg_info,Image *image)
  {
    const char
      *profile_name;
  
    const unsigned char *
      profile;
  
    ImageProfileIterator
      profile_iterator;
  
    size_t
      profile_length=0;
  
    profile_iterator=AllocateImageProfileIterator(image);
    while(NextImageProfile(profile_iterator,&profile_name,&profile,
                           &profile_length) != MagickFail)
      {
        if (LocaleNCompare(profile_name,"APP",3) == 0)
          {
            WriteAPPNProfile(jpeg_info, profile, profile_length, profile_name);
          }
        else if (LocaleCompare(profile_name,"EXIF") == 0)
          {
            WriteEXIFProfile(jpeg_info, profile, profile_length);
          }
        else if ((LocaleCompare(profile_name,"ICM") == 0) ||
                 (LocaleCompare(profile_name,"ICC") == 0))
          {
            WriteICCProfile(jpeg_info, profile, profile_length);
          }
        else if ((LocaleCompare(profile_name,"IPTC") == 0) ||
                 (LocaleCompare(profile_name,"8BIM") == 0))
          {
            WriteIPTCProfile(jpeg_info, profile, profile_length);
          }
        else if (LocaleCompare(profile_name,"XMP") == 0)
          {
            WriteXMPProfile(jpeg_info, profile, profile_length);
          }
        else
          {
            /*
              Skip unknown profile type
            */
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Skipped Profile: %s, %"
                                    MAGICK_SIZE_T_F "u bytes",
                                    profile_name,
                                    (MAGICK_SIZE_T) profile_length);
            continue;
          }
  
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Wrote Profile: %s, %"
                                MAGICK_SIZE_T_F "u bytes",profile_name,
                                (MAGICK_SIZE_T) profile_length);
      }
    DeallocateImageProfileIterator(profile_iterator);
  }
  
  static void JPEGDestinationManager(j_compress_ptr cinfo,Image * image)
  {
    DestinationManager
      *destination;
  
    cinfo->dest=(struct jpeg_destination_mgr *) (*cinfo->mem->alloc_small)
      ((j_common_ptr) cinfo,JPOOL_IMAGE,sizeof(DestinationManager));
    destination=(DestinationManager *) cinfo->dest;
    destination->manager.init_destination=InitializeDestination;
    destination->manager.empty_output_buffer=EmptyOutputBuffer;
    destination->manager.term_destination=TerminateDestination;
    destination->image=image;
  }
  
  /*
    Format a libjpeg warning or trace event while encoding.  Warnings
    are converted to GraphicsMagick warning exceptions while traces are
    optionally logged.
  
    JPEG message codes range from 0 to JMSG_LASTMSGCODE
  */
  static void JPEGEncodeMessageHandler(j_common_ptr jpeg_info,int msg_level)
  {
    char
      message[JMSG_LENGTH_MAX];
  
    struct jpeg_error_mgr
      *err;
  
    MagickClientData
      *client_data;
  
    Image
      *image;
  
    message[0]='\0';
    err=jpeg_info->err;
    client_data=(MagickClientData *) jpeg_info->client_data;
    image=client_data->image;
    /* msg_level is -1 for warnings, 0 and up for trace messages. */
    if (msg_level < 0)
      {
        unsigned int strikes = 0;
        /* A warning */
        (err->format_message)(jpeg_info,message);
  
        if ((err->msg_code >= 0) &&
            ((size_t) err->msg_code < ArraySize(client_data->warning_counts)))
          {
            client_data->warning_counts[err->msg_code]++;
            strikes=client_data->warning_counts[err->msg_code];
          }
  
        if (image->logging)
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "[%s] JPEG Warning[%u]: \"%s\""
                                  " (code=%d "
                                  "parms=0x%02x,0x%02x,"
                                  "0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x)",
                                  image->filename,
                                  strikes,
                                  message,err->msg_code,
                                  err->msg_parm.i[0], err->msg_parm.i[1],
                                  err->msg_parm.i[2], err->msg_parm.i[3],
                                  err->msg_parm.i[4], err->msg_parm.i[5],
                                  err->msg_parm.i[6], err->msg_parm.i[7]);
          }
        /*
        if (strikes > client_data->max_warning_count)
          {
            ThrowException2(&image->exception,CorruptImageError,(char *) message,
                            image->filename);
            longjmp(client_data->error_recovery,1);
          }
  
        if ((err->num_warnings == 0) ||
            (err->trace_level >= 3))
          ThrowException2(&image->exception,CorruptImageWarning,message,
                          image->filename);
        */
        /* JWRN_JPEG_EOF - "Premature end of JPEG file" */
        err->num_warnings++;
        return /* False */;
      }
    else
      {
        /* A trace message */
        if ((image->logging) && (msg_level >= err->trace_level))
          {
            (err->format_message)(jpeg_info,message);
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "[%s] JPEG Trace: \"%s\"",image->filename,
                                  message);
          }
      }
    return /* True */;
  }
  
  
  static void JPEGEncodeProgressMonitor(j_common_ptr cinfo)
  {
  #if USE_LIBJPEG_PROGRESS
    struct jpeg_progress_mgr *p = cinfo->progress;
    MagickClientData *client_data = (MagickClientData *) cinfo->client_data;
    Image *image = client_data->image;
  
  #if 0
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                          "Progress: pass_counter=%ld, pass_limit=%ld,"
                          " completed_passes=%d, total_passes=%d, filename=%s",
                          p->pass_counter, p->pass_limit,
                          p->completed_passes, p->total_passes, image->filename);
  #endif
  
    if (QuantumTick(p->pass_counter,p->pass_limit))
      if (!MagickMonitorFormatted(p->pass_counter,p->pass_limit,&image->exception,
                                  "[%s] Saving image: %lux%lu (pass %d of %d)...  ",
                                  image->filename,
                                  image->columns,image->rows,
                                  p->completed_passes+1, p->total_passes))
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Quitting due to progress monitor");
          longjmp(client_data->error_recovery,1);
        }
  #else
    (void) cinfo;
  #endif /* USE_LIBJPEG_PROGRESS */
  }
  
  
  #define ThrowJPEGWriterException(code_,reason_,image_)  \
    {                                                     \
      client_data=FreeMagickClientData(client_data);      \
      ThrowWriterException(code_,reason_,image_);         \
    }
  
  static MagickPassFail WriteJPEGImage(const ImageInfo *image_info,Image *imagep)
  {
    Image
      * volatile image = imagep;  /* volatile to avoid "clobber" */
  
    MagickClientData
      *client_data = (MagickClientData *) NULL;
  
    const ImageAttribute
      *attribute;
  
    JSAMPLE
      *jpeg_pixels;
  
    JSAMPROW
      scanline[1];
  
    char
      *sampling_factors,
      *preserve_settings;
  
    const char
      *value;
  
    long
      y;
  
    register const PixelPacket
      *p;
  
    register JSAMPLE
      *q;
  
    register long
      i,
      x;
  
    struct jpeg_error_mgr
      jpeg_error;
  
    struct jpeg_progress_mgr
      jpeg_progress;
  
    struct jpeg_compress_struct
      jpeg_info;
  
    MagickPassFail
      status;
  
    unsigned long
      input_colorspace;
  
    unsigned long
      quality;
  
    magick_int64_t
      huffman_memory;
  
    ImageCharacteristics
      characteristics;
  
    /*
      Open image file.
    */
    assert(image_info != (const ImageInfo *) NULL);
    assert(image_info->signature == MagickSignature);
    assert(imagep != (Image *) NULL);
    assert(imagep->signature == MagickSignature);
    client_data=AllocateMagickClientData();
    if (client_data == (MagickClientData *) NULL)
      ThrowJPEGWriterException(ResourceLimitError,MemoryAllocationFailed,image);
    status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
    if (status == False)
      ThrowJPEGWriterException(FileOpenError,UnableToOpenFile,image);
  
    (void) memset(&jpeg_progress,0,sizeof(jpeg_progress));
    (void) memset(&jpeg_info,0,sizeof(jpeg_info));
    (void) memset(&jpeg_error,0,sizeof(jpeg_error));
  
    /*
      Set initial longjmp based error handler.
    */
    jpeg_info.client_data=(void *) image;
    jpeg_info.err=jpeg_std_error(&jpeg_error);
    jpeg_info.err->emit_message=/*(void (*)(j_common_ptr,int))*/ JPEGEncodeMessageHandler;
    jpeg_info.err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
    client_data->image=image;
    client_data->max_warning_count=MaxWarningCount;
    /*
      Allow the user to set how many warnings of any given type are
      allowed before promotion of the warning to a hard error.
    */
    if ((value=AccessDefinition(image_info,"jpeg","max-warnings")))
      client_data->max_warning_count=strtol(value,(char **) NULL, 10);
    jpeg_info.client_data=(void *) client_data;
    if (setjmp(client_data->error_recovery) != 0)
      {
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Setjmp return from longjmp!");
        jpeg_destroy_compress(&jpeg_info);
        client_data=FreeMagickClientData(client_data);
        CloseBlob(image);
        return MagickFail ;
      }
  
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
       "  Write JPEG Image: image->orientation = %d",image->orientation);
  
    /*
      Transform image to user-requested colorspace.
    */
    if (UndefinedColorspace != image_info->colorspace)
      {
        (void) TransformColorspace(image,image_info->colorspace);
      }
    /*
      Convert RGB-compatible colorspaces (e.g. CineonLog) to RGB by
      default.  User can still override it by explicitly specifying the
      desired colorspace.
    */
    else if (IsRGBCompatibleColorspace(image->colorspace) &&
             !IsRGBColorspace(image->colorspace))
      {
        (void) TransformColorspace(image,RGBColorspace);
      }
  
    /*
      Analyze image to be written.
    */
    if (!GetImageCharacteristics(image,&characteristics,
                                 (OptimizeType == image_info->type),
                                 &image->exception))
      {
        client_data=FreeMagickClientData(client_data);
        CloseBlob(image);
        return MagickFail;
      }
  
    jpeg_create_compress(&jpeg_info);
    JPEGDestinationManager(&jpeg_info,image);
    jpeg_info.image_width=(unsigned int) image->columns;
    jpeg_info.image_height=(unsigned int) image->rows;
    jpeg_info.input_components=3;
    jpeg_info.in_color_space=JCS_RGB;
  
    /*
      Register our progress monitor
    */
    jpeg_progress.progress_monitor=(void (*)(j_common_ptr)) JPEGEncodeProgressMonitor;
    jpeg_info.progress=&jpeg_progress;
  
    /*
      Set JPEG colorspace as per user request.
    */
    {
      MagickBool
        colorspace_set=MagickFalse;
  
      if (IsCMYKColorspace(image_info->colorspace))
        {
          jpeg_info.input_components=4;
          jpeg_info.in_color_space=JCS_CMYK;
          colorspace_set=MagickTrue;
        }
      else if (IsYCbCrColorspace(image_info->colorspace))
        {
          jpeg_info.input_components=3;
          jpeg_info.in_color_space=JCS_YCbCr;
          colorspace_set=MagickTrue;
        }
      else if (IsGrayColorspace(image_info->colorspace))
        {
          jpeg_info.input_components=1;
          jpeg_info.in_color_space=JCS_GRAYSCALE;
          colorspace_set=MagickTrue;
        }
  
      if (!colorspace_set)
        {
          if (IsCMYKColorspace(image->colorspace))
            {
              jpeg_info.input_components=4;
              jpeg_info.in_color_space=JCS_CMYK;
            }
          else if (IsYCbCrColorspace(image->colorspace))
            {
              jpeg_info.input_components=3;
              jpeg_info.in_color_space=JCS_YCbCr;
            }
          else if ((IsGrayColorspace(image->colorspace) ||
                    (characteristics.grayscale)))
            {
              jpeg_info.input_components=1;
              jpeg_info.in_color_space=JCS_GRAYSCALE;
            }
          else
            {
              jpeg_info.input_components=3;
              jpeg_info.in_color_space=JCS_RGB;
            }
        }
    }
  
    input_colorspace=UndefinedColorspace;
    quality=image_info->quality;
    /* Check for -define jpeg:preserve-settings */
    /* ImageMagick:
      GetImageOption();
    */
    /* GraphicsMagick */
    preserve_settings=(char *) AccessDefinition(image_info,"jpeg",
       "preserve-settings");
  
    sampling_factors=image_info->sampling_factor;
  
    if (preserve_settings)
      {
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
              "  JPEG:preserve-settings flag is defined.");
  
        /* Retrieve input file quality */
        attribute=GetImageAttribute(image,"JPEG-Quality");
        if ((attribute != (const ImageAttribute *) NULL) &&
            (attribute->value != (char *) NULL))
          {
            (void) sscanf(attribute->value,"%lu",&quality);
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                  "  Input quality=%lu",quality);
          }
  
        /* Retrieve input file colorspace */
        attribute=GetImageAttribute(image,"JPEG-Colorspace");
        if ((attribute != (const ImageAttribute *) NULL) &&
            (attribute->value != (char *) NULL))
          {
            (void) sscanf(attribute->value,"%lu",&input_colorspace);
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                  "  Input colorspace=%lu",input_colorspace);
          }
  
        if (input_colorspace == (unsigned long) jpeg_info.in_color_space)
          {
            /* Retrieve input sampling factors */
            attribute=GetImageAttribute(image,"JPEG-Sampling-factors");
            if ((attribute != (const ImageAttribute *) NULL) &&
                (attribute->value != (char *) NULL))
              {
                sampling_factors=attribute->value;
                if (image->logging)
                  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                      "  Input sampling-factors=%s",sampling_factors);
              }
          }
        else
          {
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                  "  Input colorspace (%lu) != Output colorspace (%d)",
                  input_colorspace,jpeg_info.in_color_space);
          }
      }
  
    jpeg_set_defaults(&jpeg_info);
  
    /*
      Determine bit depth.
    */
    {
      int
        sample_size;
  
      sample_size=sizeof(JSAMPLE)*8;
      if (sample_size > 8)
        sample_size=12;
      if ((jpeg_info.data_precision != 12) &&
          (image->depth <= 8))
        sample_size=8;
      jpeg_info.data_precision=sample_size;
    }
    if ((image->x_resolution == 0) || (image->y_resolution == 0))
      {
        image->x_resolution=72.0;
        image->y_resolution=72.0;
        image->units=PixelsPerInchResolution;
      }
    if (image_info->density != (char *) NULL)
      {
        int
          count;
  
        /* FIXME: density should not be set via image_info->density
           but removing this support may break some applications. */
        count=GetMagickDimension(image_info->density,&image->x_resolution,
          &image->y_resolution,NULL,NULL);
        if (count == 1 )
          image->y_resolution=image->x_resolution;
      }
    jpeg_info.density_unit=1;  /* default to DPI */
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "Image resolution: %ld,%ld",(long) image->x_resolution,
        (long) image->y_resolution);
    if ((image->x_resolution != 0) && (image->y_resolution != 0))
      {
        /*
          Set image resolution.
        */
        jpeg_info.write_JFIF_header=True;
        jpeg_info.X_density=(short) image->x_resolution;
        jpeg_info.Y_density=(short) image->y_resolution;
        if (image->units == PixelsPerInchResolution)
          jpeg_info.density_unit=1;
        if (image->units == PixelsPerCentimeterResolution)
          jpeg_info.density_unit=2;
      }
  
    {
      const char
        *value;
  
      /*
        Allow the user to select the DCT encoding algorithm.
      */
      if ((value=AccessDefinition(image_info,"jpeg","dct-method")))
        {
          if (LocaleCompare(value,"ISLOW") == 0)
            jpeg_info.dct_method=JDCT_ISLOW;
          else if (LocaleCompare(value,"IFAST") == 0)
            jpeg_info.dct_method=JDCT_IFAST;
          else if (LocaleCompare(value,"FLOAT") == 0)
            jpeg_info.dct_method=JDCT_FLOAT;
          else if (LocaleCompare(value,"DEFAULT") == 0)
            jpeg_info.dct_method=JDCT_DEFAULT;
          else if (LocaleCompare(value,"FASTEST") == 0)
            jpeg_info.dct_method=JDCT_FASTEST;
        }
    }
  
  {
    const char
      *value;
  
    huffman_memory=0;
    if ((value=AccessDefinition(image_info,"jpeg","optimize-coding")))
      {
        if (LocaleCompare(value,"FALSE") == 0)
          jpeg_info.optimize_coding=MagickFalse;
        else
          jpeg_info.optimize_coding=MagickTrue;
      }
    else
      {
        /*
          Huffman optimization requires that the whole image be buffered in
          memory.  Since this is such a large consumer, obtain a memory
          resource for the memory to be consumed.  If the memory resource
          fails to be acquired, then don't enable huffman optimization.
        */
        huffman_memory=(magick_int64_t) jpeg_info.input_components*
          image->columns*image->rows*sizeof(JSAMPLE);
        jpeg_info.optimize_coding=AcquireMagickResource(MemoryResource,
                                                        huffman_memory);
      }
    if (!jpeg_info.optimize_coding)
      huffman_memory=0;
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Huffman optimization is %s",
                            (jpeg_info.optimize_coding ? "enabled" : "disabled"));
   }
  
  #if (JPEG_LIB_VERSION >= 61) && defined(C_PROGRESSIVE_SUPPORTED)
   if (image_info->interlace == LineInterlace)
     jpeg_simple_progression(&jpeg_info);
   if (image->logging)
     {
       if (image_info->interlace == LineInterlace)
         (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                               "Interlace: progressive");
       else
         (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                               "Interlace: nonprogressive");
     }
  #else
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "Interlace:  nonprogressive");
  #endif
    if ((image->compression == LosslessJPEGCompression) ||
        (quality > 100))
      {
  #if defined(C_LOSSLESS_SUPPORTED)
        if (quality < 100)
          ThrowException(&image->exception,CoderWarning,
                         LosslessToLossyJPEGConversion,(char *) NULL);
        else
          {
            int
              point_transform,
              predictor;
  
            predictor=quality/100;  /* range 1-7 */
            point_transform=quality % 20;  /* range 0-15 */
            jpeg_simple_lossless(&jpeg_info,predictor,point_transform);
            if (image->logging)
              {
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                  "Compression: lossless");
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                  "Predictor: %d",predictor);
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                  "Point Transform: %d",point_transform);
              }
          }
  #else
          {
            jpeg_set_quality(&jpeg_info,100,True);
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: 100");
          }
  #endif
      }
    else
      {
        jpeg_set_quality(&jpeg_info,(int) quality,True);
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: %lu",
            quality);
      }
    if (sampling_factors != (char *) NULL)
      {
        double
          hs[4]={1.0, 1.0, 1.0, 1.0},
          vs[4]={1.0, 1.0, 1.0, 1.0};
  
        long
          count;
  
        /*
          Set sampling factors.
        */
        count=sscanf(sampling_factors,"%lfx%lf,%lfx%lf,%lfx%lf,%lfx%lf",
          &hs[0],&vs[0],&hs[1],&vs[1],&hs[2],&vs[2],&hs[3],&vs[3]);
  
        if (count%2 == 1)
          vs[count/2]=hs[count/2];
  
        for (i=0; i < 4; i++)
        {
          jpeg_info.comp_info[i].h_samp_factor=(int) hs[i];
          jpeg_info.comp_info[i].v_samp_factor=(int) vs[i];
        }
        for (; i < MAX_COMPONENTS; i++)
        {
          jpeg_info.comp_info[i].h_samp_factor=1;
          jpeg_info.comp_info[i].v_samp_factor=1;
        }
      }
     else
      {
        if (quality >= 90)
          for (i=0; i < MAX_COMPONENTS; i++)
          {
            jpeg_info.comp_info[i].h_samp_factor=1;
            jpeg_info.comp_info[i].v_samp_factor=1;
          }
      }
  
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Starting JPEG compression");
    jpeg_start_compress(&jpeg_info,True);
    if (image->logging)
      {
        if (image->storage_class == PseudoClass)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Storage class: PseudoClass");
        else
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Storage class: DirectClass");
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Depth: %u",
          image->depth);
        if (image->colors)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Number of colors: %u",image->colors);
        else
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Number of colors: unspecified");
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "JPEG data precision: %d",(int) jpeg_info.data_precision);
        if (IsCMYKColorspace(image_info->colorspace))
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Storage class: DirectClass");
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Colorspace: CMYK");
          }
        else if (IsYCbCrColorspace(image_info->colorspace))
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Colorspace: YCbCr");
          }
        if (IsCMYKColorspace(image->colorspace))
          {
            /* A CMYK space */
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Colorspace: CMYK");
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Sampling factors: %dx%d,%dx%d,%dx%d,%dx%d",
                                  jpeg_info.comp_info[0].h_samp_factor,
                                  jpeg_info.comp_info[0].v_samp_factor,
                                  jpeg_info.comp_info[1].h_samp_factor,
                                  jpeg_info.comp_info[1].v_samp_factor,
                                  jpeg_info.comp_info[2].h_samp_factor,
                                  jpeg_info.comp_info[2].v_samp_factor,
                                  jpeg_info.comp_info[3].h_samp_factor,
                                  jpeg_info.comp_info[3].v_samp_factor);
          }
        else if (IsGrayColorspace(image->colorspace))
          {
            /* A gray space */
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Colorspace: GRAY");
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Sampling factors: %dx%d",
                                  jpeg_info.comp_info[0].h_samp_factor,
                                  jpeg_info.comp_info[0].v_samp_factor);
          }
        else if (IsRGBCompatibleColorspace(image->colorspace))
          {
            /* An RGB space */
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  " Image colorspace is RGB");
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Sampling factors: %dx%d,%dx%d,%dx%d",
                                  jpeg_info.comp_info[0].h_samp_factor,
                                  jpeg_info.comp_info[0].v_samp_factor,
                                  jpeg_info.comp_info[1].h_samp_factor,
                                  jpeg_info.comp_info[1].v_samp_factor,
                                  jpeg_info.comp_info[2].h_samp_factor,
                                  jpeg_info.comp_info[2].v_samp_factor);
          }
        else if (IsYCbCrColorspace(image->colorspace))
          {
            /* A YCbCr space */
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Colorspace: YCbCr");
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Sampling factors: %dx%d,%dx%d,%dx%d",
                                  jpeg_info.comp_info[0].h_samp_factor,
                                  jpeg_info.comp_info[0].v_samp_factor,
                                  jpeg_info.comp_info[1].h_samp_factor,
                                  jpeg_info.comp_info[1].v_samp_factor,
                                  jpeg_info.comp_info[2].h_samp_factor,
                                  jpeg_info.comp_info[2].v_samp_factor);
          }
        else
          {
            /* Some other color space */
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: %d",
                                  image->colorspace);
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Sampling factors: %dx%d,%dx%d,%dx%d,%dx%d",
                                  jpeg_info.comp_info[0].h_samp_factor,
                                  jpeg_info.comp_info[0].v_samp_factor,
                                  jpeg_info.comp_info[1].h_samp_factor,
                                  jpeg_info.comp_info[1].v_samp_factor,
                                  jpeg_info.comp_info[2].h_samp_factor,
                                  jpeg_info.comp_info[2].v_samp_factor,
                                  jpeg_info.comp_info[3].h_samp_factor,
                                  jpeg_info.comp_info[3].v_samp_factor);
          }
      }
    /*
      Write JPEG profiles.
    */
    attribute=GetImageAttribute(image,"comment");
    if ((attribute != (const ImageAttribute *) NULL) &&
      (attribute->value != (char *) NULL))
      for (i=0; i < (long) strlen(attribute->value); i+=65533L)
        jpeg_write_marker(&jpeg_info,JPEG_COM,(unsigned char *) attribute->value+
          i,(int) Min(strlen(attribute->value+i),65533L));
  #if 0
    WriteICCProfile(&jpeg_info,image);
    WriteIPTCProfile(&jpeg_info,image);
    WriteXMPProfile(&jpeg_info,image);
  #endif
    WriteProfiles(&jpeg_info,image);
    /*
      Convert MIFF to JPEG raster pixels.
    */
    jpeg_pixels=MagickAllocateResourceLimitedArray(JSAMPLE *,
      jpeg_info.input_components*image->columns,sizeof(JSAMPLE));
    if (jpeg_pixels == (JSAMPLE *) NULL)
      {
        if (huffman_memory)
          LiberateMagickResource(MemoryResource,huffman_memory);
        ThrowJPEGWriterException(ResourceLimitError,MemoryAllocationFailed,image);
      }
    scanline[0]=(JSAMPROW) jpeg_pixels;
    if (jpeg_info.data_precision > 8)
      {
        unsigned int
          scale_short;
  
        scale_short=65535U/MaxValueGivenBits(jpeg_info.data_precision);
  
        if (jpeg_info.in_color_space == JCS_GRAYSCALE)
          {
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Writing %d bit JCS_GRAYSCALE samples",
                                    jpeg_info.data_precision);
            for (y=0; y < (long) image->rows; y++)
              {
                p=AcquireImagePixels(image,0,y,image->columns,1,
                                     &image->exception);
                if (p == (const PixelPacket *) NULL)
                  break;
                q=jpeg_pixels;
                if (image->is_grayscale)
                  {
                    for (x=0; x < (long) image->columns; x++)
                      {
                        *q++=(JSAMPLE) (ScaleQuantumToShort(GetGraySample(p))/
                                        scale_short);
                        p++;
                      }
                  }
                else
                  {
                    for (x=0; x < (long) image->columns; x++)
                      {
                        *q++=(JSAMPLE)
                          (ScaleQuantumToShort(PixelIntensityToQuantum(p))/scale_short);
                        p++;
                      }
                  }
                (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
  #if !USE_LIBJPEG_PROGRESS
                if (QuantumTick(y,image->rows))
                  if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                              SaveImageText,image->filename,
                                              image->columns,image->rows))
                    break;
  #endif /* !USE_LIBJPEG_PROGRESS */
              }
          }
        else
          if ((jpeg_info.in_color_space == JCS_RGB) ||
              (jpeg_info.in_color_space == JCS_YCbCr))
            {
              if (image->logging)
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                      "Writing %d bit JCS_RGB or "
                                      "JCS_YCbCr samples",
                                      jpeg_info.data_precision);
              for (y=0; y < (long) image->rows; y++)
                {
                  p=AcquireImagePixels(image,0,y,image->columns,1,
                                       &image->exception);
                  if (p == (const PixelPacket *) NULL)
                    break;
                  q=jpeg_pixels;
                  for (x=0; x < (long) image->columns; x++)
                    {
                      *q++=(JSAMPLE) (ScaleQuantumToShort(p->red)/16);
                      *q++=(JSAMPLE) (ScaleQuantumToShort(p->green)/16);
                      *q++=(JSAMPLE) (ScaleQuantumToShort(p->blue)/16);
                      p++;
                    }
                  (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
  #if !USE_LIBJPEG_PROGRESS
                  if (QuantumTick(y,image->rows))
                    if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                                SaveImageText,image->filename,
                                                image->columns,image->rows))
                      break;
  #endif /* !USE_LIBJPEG_PROGRESS */
                }
            }
          else
            {
              if (image->logging)
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                      "Writing %d bit JCS_CMYK samples",
                                      jpeg_info.data_precision);
              for (y=0; y < (long) image->rows; y++)
                {
                  p=AcquireImagePixels(image,0,y,image->columns,1,
                                       &image->exception);
                  if (p == (const PixelPacket *) NULL)
                    break;
                  q=jpeg_pixels;
                  for (x=0; x < (long) image->columns; x++)
                    {
                      /*
                        Convert DirectClass packets to contiguous CMYK scanlines.
                      */
                      *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->red)/16);
                      *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->green)/16);
                      *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->blue)/16);
                      *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->opacity)/16);
                      p++;
                    }
                  (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
  #if !USE_LIBJPEG_PROGRESS
                  if (QuantumTick(y,image->rows))
                    if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                                SaveImageText,image->filename,
                                                image->columns,image->rows))
                      break;
  #endif /* !USE_LIBJPEG_PROGRESS */
                }
            }
      }
    else
      if (jpeg_info.in_color_space == JCS_GRAYSCALE)
        {
          if (image->logging)
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Writing %d bit JCS_GRAYSCALE samples",
                                  jpeg_info.data_precision);
          for (y=0; y < (long) image->rows; y++)
            {
              p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
              if (p == (const PixelPacket *) NULL)
                break;
              q=jpeg_pixels;
              if (image->is_grayscale)
                  {
                    for (x=0; x < (long) image->columns; x++)
                    {
                      *q++=(JSAMPLE) ScaleQuantumToChar(GetGraySample(p));
                      p++;
                    }
                  }
              else
                {
                  for (x=0; x < (long) image->columns; x++)
                    {
                      *q++=(JSAMPLE)
                        ScaleQuantumToChar(PixelIntensityToQuantum(p));
                      p++;
                    }
                }
              (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
  #if !USE_LIBJPEG_PROGRESS
              if (QuantumTick(y,image->rows))
                if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                            SaveImageText,image->filename,
                                            image->columns,image->rows))
                  break;
  #endif /* !USE_LIBJPEG_PROGRESS */
            }
        }
      else
        if ((jpeg_info.in_color_space == JCS_RGB) ||
            (jpeg_info.in_color_space == JCS_YCbCr))
          {
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Writing %d bit JCS_RGB or JCS_YCbCr samples",
                                    jpeg_info.data_precision);
            for (y=0; y < (long) image->rows; y++)
              {
                p=AcquireImagePixels(image,0,y,image->columns,1,
                                     &image->exception);
                if (p == (const PixelPacket *) NULL)
                  break;
                q=jpeg_pixels;
                for (x=0; x < (long) image->columns; x++)
                  {
                    *q++=(JSAMPLE) ScaleQuantumToChar(p->red);
                    *q++=(JSAMPLE) ScaleQuantumToChar(p->green);
                    *q++=(JSAMPLE) ScaleQuantumToChar(p->blue);
                    p++;
                  }
                (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
  #if !USE_LIBJPEG_PROGRESS
                if (QuantumTick(y,image->rows))
                  if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                              SaveImageText,image->filename,
                                              image->columns,image->rows))
                    break;
  #endif /* !USE_LIBJPEG_PROGRESS */
              }
          }
        else
          {
            if (image->logging)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                    "Writing %d bit JCS_CMYK samples",
                                    jpeg_info.data_precision);
            for (y=0; y < (long) image->rows; y++)
              {
                p=AcquireImagePixels(image,0,y,image->columns,1,
                                     &image->exception);
                if (p == (const PixelPacket *) NULL)
                  break;
                q=jpeg_pixels;
                for (x=0; x < (long) image->columns; x++)
                  {
                    /*
                      Convert DirectClass packets to contiguous CMYK scanlines.
                    */
                    *q++=(JSAMPLE) (ScaleQuantumToChar(MaxRGB-p->red));
                    *q++=(JSAMPLE) (ScaleQuantumToChar(MaxRGB-p->green));
                    *q++=(JSAMPLE) (ScaleQuantumToChar(MaxRGB-p->blue));
                    *q++=(JSAMPLE) (ScaleQuantumToChar(MaxRGB-p->opacity));
                    p++;
                  }
                (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
  #if !USE_LIBJPEG_PROGRESS
                if (QuantumTick(y,image->rows))
                  if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                              SaveImageText,image->filename,
                                              image->columns,image->rows))
                    break;
  #endif /* !USE_LIBJPEG_PROGRESS */
              }
          }
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Finishing JPEG compression");
    jpeg_finish_compress(&jpeg_info);
    /*
      Free memory.
    */
    if (huffman_memory)
      LiberateMagickResource(MemoryResource,huffman_memory);
    client_data=FreeMagickClientData(client_data);
    jpeg_destroy_compress(&jpeg_info);
    MagickFreeResourceLimitedMemory(jpeg_pixels);
    CloseBlob(image);
    return(True);
  }
  #endif
  