/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                        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                                   %
%                                                                             %
%                                                                             %
%  Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization         %
%  dedicated to making software imaging solutions freely available.           %
%                                                                             %
%  You may not use this file except in compliance with the License.  You may  %
%  obtain a copy of the License at                                            %
%                                                                             %
%    https://imagemagick.org/license/                                         %
%                                                                             %
%  Unless required by applicable law or agreed to in writing, software        %
%  distributed under the License is distributed on an "AS IS" BASIS,          %
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
%  See the License for the specific language governing permissions and        %
%  limitations under the License.                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% 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 "MagickCore/studio.h"
#include "MagickCore/artifact.h"
#include "MagickCore/attribute.h"
#include "MagickCore/blob.h"
#include "MagickCore/blob-private.h"
#include "MagickCore/cache.h"
#include "MagickCore/color.h"
#include "MagickCore/colormap-private.h"
#include "MagickCore/color-private.h"
#include "MagickCore/colormap.h"
#include "MagickCore/colorspace.h"
#include "MagickCore/colorspace-private.h"
#include "MagickCore/constitute.h"
#include "MagickCore/exception.h"
#include "MagickCore/exception-private.h"
#include "MagickCore/geometry.h"
#include "MagickCore/image.h"
#include "MagickCore/image-private.h"
#include "MagickCore/list.h"
#include "MagickCore/log.h"
#include "MagickCore/magick.h"
#include "MagickCore/memory_.h"
#include "MagickCore/memory-private.h"
#include "MagickCore/module.h"
#include "MagickCore/monitor.h"
#include "MagickCore/monitor-private.h"
#include "MagickCore/option.h"
#include "MagickCore/option-private.h"
#include "MagickCore/pixel-accessor.h"
#include "MagickCore/profile.h"
#include "MagickCore/profile-private.h"
#include "MagickCore/property.h"
#include "MagickCore/quantum-private.h"
#include "MagickCore/resource_.h"
#include "MagickCore/semaphore.h"
#include "MagickCore/splay-tree.h"
#include "MagickCore/static.h"
#include "MagickCore/string_.h"
#include "MagickCore/string-private.h"
#include "MagickCore/token.h"
#include "MagickCore/utility.h"
#include "MagickCore/xml-tree.h"
#include "MagickCore/xml-tree-private.h"
#include "coders/coders-private.h"
#include <setjmp.h>
#if defined(MAGICKCORE_JPEG_DELEGATE)
#define JPEG_INTERNAL_OPTIONS
#if defined(__MINGW32__)
# define XMD_H 1  /* Avoid conflicting typedef for INT32 */
#endif
#undef HAVE_STDLIB_H
#include "jpeglib.h"
#include "jerror.h"
#endif

/*
  Define declarations.
*/
#define COMMENT_INDEX  0
#define APP_INDEX  0
#define APP_MARKER  (JPEG_APP0+APP_INDEX)
#define ICC_INDEX  2
#define ICC_MARKER  (JPEG_APP0+ICC_INDEX)
#define ICC_PROFILE  "ICC_PROFILE"
#define IPTC_INDEX  13
#define IPTC_MARKER  (JPEG_APP0+IPTC_INDEX)
#define MaxJPEGProfiles  16
#define MaxJPEGScans  1024

/*
  Typedef declarations.
*/
#if defined(MAGICKCORE_JPEG_DELEGATE)
typedef struct _DestinationManager
{
  struct jpeg_destination_mgr
    manager;

  Image
    *image;

  JOCTET
    *buffer;
} DestinationManager;

typedef struct _JPEGClientInfo
{
  jmp_buf
    error_recovery;

  Image
    *image;

  MagickBooleanType
    finished;

  StringInfo
    *profiles[MaxJPEGProfiles+1];

  ExceptionInfo
    *exception;
} JPEGClientInfo;

typedef struct _SourceManager
{
  struct jpeg_source_mgr
    manager;

  Image
    *image;

  JOCTET
    *buffer;

  boolean
    start_of_blob;
} SourceManager;
#endif

typedef struct _QuantizationTable
{
  char
    *slot,
    *description;

  size_t
    width,
    height;

  double
    divisor;

  unsigned int
    *levels;
} QuantizationTable;

/*
  Const declarations.
*/
static const char
  xmp_namespace[] = "http://ns.adobe.com/xap/1.0/";

/*
  Forward declarations.
*/
#if defined(MAGICKCORE_JPEG_DELEGATE)
static MagickBooleanType
  WriteJPEGImage(const ImageInfo *,Image *,ExceptionInfo *);
#endif

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   I s J P E G                                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  IsJPEG() returns MagickTrue if the image format type, identified by the
%  magick string, is JPEG.
%
%  The format of the IsJPEG  method is:
%
%      MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
%
%  A description of each parameter follows:
%
%    o magick: compare image format pattern against these bytes.
%
%    o length: Specifies the length of the magick string.
%
*/
static MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
{
  if (length < 3)
    return(MagickFalse);
  if (memcmp(magick,"\377\330\377",3) == 0)
    return(MagickTrue);
  return(MagickFalse);
}

#if defined(MAGICKCORE_JPEG_DELEGATE)
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d J P E G I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  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_info: the image info.
%
%    o exception: return any errors or warnings in this structure.
%
*/

static inline Quantum JPEGGetQuantum(const struct jpeg_decompress_struct *jpeg_info,
  QuantumAny range,const JSAMPLE *p)
{
  if (jpeg_info->data_precision == 8)
    return ScaleCharToQuantum(*p);
  else if (jpeg_info->data_precision == 16)
    return ScaleShortToQuantum(*(unsigned short *) p);
  else if (jpeg_info->data_precision < 8)
    return ScaleAnyToQuantum(*p,range);
  else
    return ScaleAnyToQuantum(*(unsigned short *) p,range);
}

static boolean FillInputBuffer(const j_decompress_ptr compress_info)
{
  SourceManager
    *source;

  source=(SourceManager *) compress_info->src;
  source->manager.bytes_in_buffer=(size_t) ReadBlob(source->image,
    MagickMinBufferExtent,source->buffer);
  if (source->manager.bytes_in_buffer == 0)
    {
      if (source->start_of_blob != FALSE)
        ERREXIT(compress_info,JERR_INPUT_EMPTY);
      WARNMS(compress_info,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)
    {
      (void) (*jpeg_info->src->fill_input_buffer)(jpeg_info);
      if (jpeg_info->err->msg_code == JWRN_JPEG_EOF)
        return(EOF);
    }
  jpeg_info->src->bytes_in_buffer--;
  return((int) GETJOCTET(*jpeg_info->src->next_input_byte++));
}

static void InitializeSource(j_decompress_ptr compress_info)
{
  SourceManager
    *source;

  source=(SourceManager *) compress_info->src;
  source->start_of_blob=TRUE;
}

static MagickBooleanType IsAspectRatio(const j_decompress_ptr compress_info)
{
  if (compress_info->density_unit != 0)
    return(MagickFalse);
  if ((compress_info->X_density == 1) && (compress_info->Y_density == 1))
    return(MagickTrue);
  if ((compress_info->X_density == 3) && (compress_info->Y_density == 2))
    return(MagickTrue);
  if ((compress_info->X_density == 4) && (compress_info->Y_density == 3))
    return(MagickTrue);
  if ((compress_info->X_density == 16) && (compress_info->Y_density == 9))
    return(MagickTrue);
  if ((compress_info->X_density == 16) && (compress_info->Y_density == 10))
    return(MagickTrue);
  return(MagickFalse);
}

static MagickBooleanType IsITUFaxImage(const Image *image)
{
  const StringInfo
    *profile;

  const unsigned char
    *datum;

  profile=GetImageProfile(image,"8bim");
  if (profile == (const StringInfo *) NULL)
    return(MagickFalse);
  if (GetStringInfoLength(profile) < 5)
    return(MagickFalse);
  datum=GetStringInfoDatum(profile);
  if ((datum[0] == 0x47) && (datum[1] == 0x33) && (datum[2] == 0x46) &&
      (datum[3] == 0x41) && (datum[4] == 0x58))
    return(MagickTrue);
  return(MagickFalse);
}

static void JPEGErrorHandler(j_common_ptr jpeg_info)
{
  char
    message[JMSG_LENGTH_MAX];

  ExceptionInfo
    *exception;

  Image
    *image;

  JPEGClientInfo
    *client_info;

  *message='\0';
  client_info=(JPEGClientInfo *) jpeg_info->client_data;
  image=client_info->image;
  exception=client_info->exception;
  (jpeg_info->err->format_message)(jpeg_info,message);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
      "[%s] JPEG Trace: \"%s\"",image->filename,message);
  if (client_info->finished != MagickFalse)
    (void) ThrowMagickException(exception,GetMagickModule(),
      CorruptImageWarning,(char *) message,"`%s'",image->filename);
  else
    (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
      (char *) message,"`%s'",image->filename);
  longjmp(client_info->error_recovery,1);
}

static void JPEGProgressHandler(j_common_ptr jpeg_info)
{
  ExceptionInfo
    *exception;

  Image
    *image;

  JPEGClientInfo
    *client_info;

  client_info=(JPEGClientInfo *) jpeg_info->client_data;
  image=client_info->image;
  exception=client_info->exception;
  if (jpeg_info->is_decompressor == 0)
    return;
  if (((j_decompress_ptr) jpeg_info)->input_scan_number < MaxJPEGScans)
    return;
  (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
    "too many scans","`%s'",image->filename);
  longjmp(client_info->error_recovery,1);
}

static void JPEGWarningHandler(j_common_ptr jpeg_info,int level)
{
#define JPEGExcessiveWarnings  1000

  char
    message[JMSG_LENGTH_MAX];

  ExceptionInfo
    *exception;

  Image
    *image;

  JPEGClientInfo
    *client_info;

  *message='\0';
  client_info=(JPEGClientInfo *) jpeg_info->client_data;
  exception=client_info->exception;
  image=client_info->image;
  if (level < 0)
    {
      /*
        Process warning message.
      */
      (jpeg_info->err->format_message)(jpeg_info,message);
      if (jpeg_info->err->num_warnings++ < JPEGExcessiveWarnings)
        (void) ThrowMagickException(exception,GetMagickModule(),
          CorruptImageWarning,message,"`%s'",image->filename);
    }
  else
    if (level >= jpeg_info->err->trace_level)
      {
        /*
          Process trace message.
        */
        (jpeg_info->err->format_message)(jpeg_info,message);
        if ((image != (Image *) NULL) && (image->debug != MagickFalse))
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "[%s] JPEG Trace: \"%s\"",image->filename,message);
      }
}

static MagickBooleanType ReadProfilePayload(j_decompress_ptr jpeg_info,const int index,
  const size_t length)
{
  ExceptionInfo
    *exception;

  Image
    *image;

  JPEGClientInfo
    *client_info;

  size_t
    offset = 0;

  ssize_t
    i;

  unsigned char
    *p;

  client_info=(JPEGClientInfo *) jpeg_info->client_data;
  exception=client_info->exception;
  image=client_info->image;
  if ((index < 0) || (index > MaxJPEGProfiles))
    {
      (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
        "TooManyProfiles","`%s'",image->filename);
      return(MagickFalse);
    }
  if (client_info->profiles[index] == (StringInfo *) NULL)
    client_info->profiles[index]=AcquireStringInfo(length);
  else
    {
      offset=GetStringInfoLength(client_info->profiles[index]);
      SetStringInfoLength(client_info->profiles[index],offset+length);
    }
  p=GetStringInfoDatum(client_info->profiles[index])+offset;
  for (i=0; i < (ssize_t) length; i++)
  {
    int
      c;

    c=GetCharacter(jpeg_info);
    if (c == EOF)
      break;
    *p++=(unsigned char) c;
  }
  if (i != (ssize_t) length)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),
        CorruptImageError,"InsufficientImageDataInFile","`%s'",image->filename);
      return(MagickFalse);
    }
  *p='\0';
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
      "Profile[%.20g]: %.20g bytes",(double) index,(double) length);
  return(MagickTrue);
}

static boolean ReadComment(j_decompress_ptr jpeg_info)
{
#define GetProfileLength(jpeg_info,length) \
do \
{ \
  int \
    c; \
 \
  if (((c=GetCharacter(jpeg_info)) == EOF) || (c < 0)) \
    length=0; \
  else \
    { \
      length=(size_t) (256*c); \
      if (((c=GetCharacter(jpeg_info)) == EOF) || (c < 0)) \
        length=0; \
      else \
        length+=(unsigned char) c; \
    } \
} while(0)

  ExceptionInfo
    *exception;

  Image
    *image;

  JPEGClientInfo
    *client_info;

  MagickBooleanType
    status;

  size_t
    length;

  /*
    Determine length of comment.
  */
  GetProfileLength(jpeg_info,length);
  if (length <= 2)
    return(TRUE);
  length-=2;
  status=ReadProfilePayload(jpeg_info,COMMENT_INDEX,length);
  if (status == MagickFalse)
    return(FALSE);
  client_info=(JPEGClientInfo *) jpeg_info->client_data;
  image=client_info->image;
  exception=client_info->exception;
  status=SetImageProperty(image,"comment",(const char *) GetStringInfoDatum(
    client_info->profiles[COMMENT_INDEX]),exception);
  return(status == MagickFalse ? FALSE : TRUE);
}

static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
{
  char
    magick[13];

  ExceptionInfo
    *exception;

  Image
    *image;

  JPEGClientInfo
    *client_info;

  MagickBooleanType
    status;

  size_t
    length;

  ssize_t
    i;

  /*
    Read color profile.
  */
  GetProfileLength(jpeg_info,length);
  if (length <= 2)
    return(TRUE);
  length-=2;
  if (length <= 14)
    {
      while (length-- > 0)
        if (GetCharacter(jpeg_info) == EOF)
          break;
      return(TRUE);
    }
  for (i=0; i < 12; i++)
    magick[i]=(char) GetCharacter(jpeg_info);
  magick[i]='\0';
  if (LocaleCompare(magick,ICC_PROFILE) != 0)
    {
      if (LocaleCompare(magick,"MPF") == 0)
        {
          /*
            Multi-picture support (Future).
          */
          status=ReadProfilePayload(jpeg_info,ICC_INDEX,length-12);
          if (status == MagickFalse)
            return(FALSE);
          client_info=(JPEGClientInfo *) jpeg_info->client_data;
          status=SetImageProfile(client_info->image,"MPF",
            client_info->profiles[ICC_INDEX],client_info->exception);
          client_info->profiles[ICC_INDEX]=DestroyStringInfo(
            client_info->profiles[ICC_INDEX]);
          return(TRUE);
        }
      /*
        Not a ICC profile, return.
      */
      for (i=0; i < (ssize_t) (length-12); i++)
        if (GetCharacter(jpeg_info) == EOF)
          break;
      return(TRUE);
    }
  (void) GetCharacter(jpeg_info);  /* id */
  (void) GetCharacter(jpeg_info);  /* markers */
  length-=14;
  status=ReadProfilePayload(jpeg_info,ICC_INDEX,length);
  if (status == MagickFalse)
    return(FALSE);
  client_info=(JPEGClientInfo *) jpeg_info->client_data;
  image=client_info->image;
  exception=client_info->exception;
  status=SetImageProfile(image,"icc",client_info->profiles[ICC_INDEX],
    exception);
  return(status == MagickFalse ? FALSE : TRUE);
}

static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
{
  char
    magick[MagickPathExtent];

  ExceptionInfo
    *exception;

  Image
    *image;

  JPEGClientInfo
    *client_info;

  MagickBooleanType
    status;

  size_t
    length;

  ssize_t
    i;

  /*
    Determine length of binary data stored here.
  */
  GetProfileLength(jpeg_info,length);
  if (length <= 2)
    return(TRUE);
  length-=2;
  if (length <= 14)
    {
      while (length-- > 0)
        if (GetCharacter(jpeg_info) == EOF)
          break;
      return(TRUE);
    }
  /*
    Validate that this was written as a Photoshop resource format slug.
  */
  for (i=0; i < 10; i++)
    magick[i]=(char) GetCharacter(jpeg_info);
  magick[10]='\0';
  length-=10;
  if (length <= 10)
    return(TRUE);
  if (LocaleCompare(magick,"Photoshop ") != 0)
    {
      /*
        Not a IPTC profile, return.
      */
      for (i=0; i < (ssize_t) length; i++)
        if (GetCharacter(jpeg_info) == EOF)
          break;
      return(TRUE);
    }
  /*
    Remove the version number.
  */
  if (length <= 15)
    return(TRUE);
  for (i=0; i < 4; i++)
    if (GetCharacter(jpeg_info) == EOF)
      break;
  if (length <= 11)
    return(TRUE);
  length-=4;
  status=ReadProfilePayload(jpeg_info,IPTC_INDEX,length);
  if (status == MagickFalse)
    return(FALSE);
  client_info=(JPEGClientInfo *) jpeg_info->client_data;
  image=client_info->image;
  exception=client_info->exception;
  status=SetImageProfile(image,"8bim",client_info->profiles[IPTC_INDEX],
    exception);
  return(status == MagickFalse ? FALSE : TRUE);
}

static boolean ReadAPPProfiles(j_decompress_ptr jpeg_info)
{
  ExceptionInfo
    *exception;

  Image
    *image;

  int
    marker;

  JPEGClientInfo
    *client_info;

  MagickBooleanType
    status;

  size_t
    length,
    offset = 0;

  StringInfo
    *profile;

  unsigned char
    *p;

  /*
    Read generic profile.
  */
  GetProfileLength(jpeg_info,length);
  if (length <= 2)
    return(TRUE);
  length-=2;
  marker=jpeg_info->unread_marker-JPEG_APP0;
  client_info=(JPEGClientInfo *) jpeg_info->client_data;
  image=client_info->image;
  exception=client_info->exception;
  if (client_info->profiles[marker] != (StringInfo *) NULL)
    offset=GetStringInfoLength(client_info->profiles[marker]);
  status=ReadProfilePayload(jpeg_info,marker,length);
  if (status == MagickFalse)
    return(FALSE);
  if (marker != 1)
    return(TRUE);
  p=GetStringInfoDatum(client_info->profiles[marker])+offset;
  if ((length > strlen(xmp_namespace)) &&
      (LocaleNCompare((char *) p,xmp_namespace,strlen(xmp_namespace)-1) == 0))
    {
      size_t
        i;

      /*
        Extract XMP profile minus the namespace header.
      */
      for (i=0; i < length; i++)
      {
        if (*p == '\0')
          break;
        p++;
      }
      if (i != length)
        {
          profile=AcquireProfileStringInfo("xmp",length,exception);
          if (profile != (StringInfo*) NULL)
            {
              (void) memcpy(GetStringInfoDatum(profile),p+1,length-i-1);
              SetStringInfoLength(profile,length-i-1);
              status=SetImageProfilePrivate(image,profile,exception);
            }
          client_info->profiles[marker]=DestroyStringInfo(
            client_info->profiles[marker]);
        }
    }
  else
    {
      status=SetImageProfile(image,"app1",client_info->profiles[marker],
        exception);
      client_info->profiles[marker]=DestroyStringInfo(
        client_info->profiles[marker]);
    }
  return(status == MagickFalse ? FALSE : TRUE);
}

static void SkipInputData(j_decompress_ptr compress_info,long number_bytes)
{
  SourceManager
    *source;

  if (number_bytes <= 0)
    return;
  source=(SourceManager *) compress_info->src;
  while (number_bytes > (long) source->manager.bytes_in_buffer)
  {
    number_bytes-=(long) source->manager.bytes_in_buffer;
    (void) FillInputBuffer(compress_info);
  }
  source->manager.next_input_byte+=number_bytes;
  source->manager.bytes_in_buffer-=(size_t) number_bytes;
}

static void TerminateSource(j_decompress_ptr compress_info)
{
  (void) compress_info;
}

static void JPEGSourceManager(j_decompress_ptr compress_info,Image *image)
{
  SourceManager
    *source;

  compress_info->src=(struct jpeg_source_mgr *)
    (*compress_info->mem->alloc_small) ((j_common_ptr) compress_info,
    JPOOL_IMAGE,sizeof(SourceManager));
  source=(SourceManager *) compress_info->src;
  source->buffer=(JOCTET *) (*compress_info->mem->alloc_small) ((j_common_ptr)
    compress_info,JPOOL_IMAGE,MagickMinBufferExtent*sizeof(JOCTET));
  source=(SourceManager *) compress_info->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;
}

static void JPEGSetImageQuality(const struct jpeg_decompress_struct *jpeg_info,
  Image *image)
{
  image->quality=UndefinedCompressionQuality;
#if defined(D_PROGRESSIVE_SUPPORTED)
  if (image->compression == LosslessJPEGCompression)
    {
      image->quality=100;
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "Quality: 100 (lossless)");
    }
  else
#endif
  {
    ssize_t
      i,
      j,
      qvalue,
      sum;

    /*
      Determine the JPEG compression quality from the quantization tables.
    */
    sum=0;
    for (i=0; i < NUM_QUANT_TBLS; i++)
    {
      if (jpeg_info->quant_tbl_ptrs[i] != NULL)
        for (j=0; j < DCTSIZE2; j++)
          sum+=jpeg_info->quant_tbl_ptrs[i]->quantval[j];
    }
    if ((jpeg_info->quant_tbl_ptrs[0] != NULL) &&
        (jpeg_info->quant_tbl_ptrs[1] != NULL))
      {
        ssize_t
          hash[101] =
          {
            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
          },
          sums[101] =
          {
            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
          };

        qvalue=(ssize_t) (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 ((qvalue < hash[i]) && (sum < sums[i]))
            continue;
          if (((qvalue <= hash[i]) && (sum <= sums[i])) || (i >= 50))
            image->quality=(size_t) i+1;
          if (image->debug != MagickFalse)
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
              "Quality: %.20g (%s)",(double) i+1,(qvalue <= hash[i]) &&
              (sum <= sums[i]) ? "exact" : "approximate");
          break;
        }
      }
    else
      if (jpeg_info->quant_tbl_ptrs[0] != NULL)
        {
          ssize_t
            hash[101] =
            {
              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
            },
            sums[101] =
            {
              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
            };

          qvalue=(ssize_t) (jpeg_info->quant_tbl_ptrs[0]->quantval[2]+
            jpeg_info->quant_tbl_ptrs[0]->quantval[53]);
          for (i=0; i < 100; i++)
          {
            if ((qvalue < hash[i]) && (sum < sums[i]))
              continue;
            if (((qvalue <= hash[i]) && (sum <= sums[i])) || (i >= 50))
              image->quality=(size_t) i+1;
            if (image->debug != MagickFalse)
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "Quality: %.20g (%s)",(double) i+1,(qvalue <= hash[i]) &&
                (sum <= sums[i]) ? "exact" : "approximate");
            break;
          }
        }
  }
}

static void JPEGSetImageSamplingFactor(const struct jpeg_decompress_struct *jpeg_info,
  Image *image,ExceptionInfo *exception)
{
  char
    sampling_factor[MagickPathExtent];

  switch (jpeg_info->out_color_space)
  {
    case JCS_CMYK:
    {
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: CMYK");
      (void) FormatLocaleString(sampling_factor,MagickPathExtent,
        "%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) LogMagickEvent(CoderEvent,GetMagickModule(),
        "Colorspace: GRAYSCALE");
      (void) FormatLocaleString(sampling_factor,MagickPathExtent,"%dx%d",
        jpeg_info->comp_info[0].h_samp_factor,
        jpeg_info->comp_info[0].v_samp_factor);
      break;
    }
    case JCS_RGB:
    {
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: RGB");
      (void) FormatLocaleString(sampling_factor,MagickPathExtent,
        "%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) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: %d",
        jpeg_info->out_color_space);
      (void) FormatLocaleString(sampling_factor,MagickPathExtent,
        "%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;
    }
  }
  (void) SetImageProperty(image,"jpeg:sampling-factor",sampling_factor,
    exception);
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Sampling Factors: %s",
    sampling_factor);
}

static JPEGClientInfo *JPEGCleanup(struct jpeg_decompress_struct *jpeg_info,
  JPEGClientInfo *client_info)
{
  size_t
    i;

  if (client_info != (JPEGClientInfo *) NULL)
    {
      for (i=0; i < MaxJPEGProfiles; i++)
      {
        if (client_info->profiles[i] != (StringInfo *) NULL)
          client_info->profiles[i]=DestroyStringInfo(client_info->profiles[i]);
      }
      client_info=(JPEGClientInfo *) RelinquishMagickMemory(client_info);
    }
  jpeg_destroy_decompress(jpeg_info);
  return(client_info);
}

static void JPEGDestroyImageProfiles(JPEGClientInfo *client_info)
{
  ssize_t
    i;

  StringInfo
    *profile;

  for (i=0; i < MaxJPEGProfiles; i++)
  {
    profile=client_info->profiles[i];
    if (profile == (StringInfo *) NULL)
      continue;
    client_info->profiles[i]=DestroyStringInfo(client_info->profiles[i]);
  }
}

static Image *ReadOneJPEGImage(const ImageInfo *image_info,
  struct jpeg_decompress_struct *jpeg_info,MagickOffsetType *offset,
  ExceptionInfo *exception)
{
  char
    value[MagickPathExtent];

  const char
    *dct_method,
    *option;

  Image
    *image;

  JPEGClientInfo
    *client_info = (JPEGClientInfo *) NULL;

  JSAMPLE
    *volatile jpeg_pixels,
    *p;

  MagickBooleanType
    status;

  MagickSizeType
    number_pixels;

  MemoryInfo
    *memory_info;

  QuantumAny
    range;

  size_t
    bytes_per_pixel,
    max_memory_to_use;

  ssize_t
    i,
    y;

  struct jpeg_error_mgr
    jpeg_error;

  struct jpeg_progress_mgr
    jpeg_progress;

  /*
    Open image file.
  */
  image=AcquireImage(image_info,exception);
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == MagickFalse)
    {
      image=DestroyImageList(image);
      return((Image *) NULL);
    }
  if (*offset != 0)
    (void) SeekBlob(image,*offset,SEEK_SET);
  /*
    Verify that file size large enough to contain a JPEG datastream.
  */
  if (GetBlobSize(image) < 107)
    ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile");
  /*
    Initialize JPEG parameters.
  */
  client_info=(JPEGClientInfo *) AcquireMagickMemory(sizeof(*client_info));
  if (client_info == (JPEGClientInfo *) NULL)
    ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
  (void) memset(client_info,0,sizeof(*client_info));
  (void) memset(jpeg_info,0,sizeof(*jpeg_info));
  (void) memset(&jpeg_error,0,sizeof(jpeg_error));
  (void) memset(&jpeg_progress,0,sizeof(jpeg_progress));
  jpeg_info->err=jpeg_std_error(&jpeg_error);
  jpeg_info->err->emit_message=(void (*)(j_common_ptr,int)) JPEGWarningHandler;
  jpeg_info->err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
  memory_info=(MemoryInfo *) NULL;
  client_info->exception=exception;
  client_info->image=image;
  if (setjmp(client_info->error_recovery) != 0)
    {
      client_info=JPEGCleanup(jpeg_info,client_info);
      (void) CloseBlob(image);
      if (exception->severity < ErrorException)
        return(GetFirstImageInList(image));
      return(DestroyImage(image));
    }
  jpeg_info->client_data=(void *) client_info;
  jpeg_create_decompress(jpeg_info);
  max_memory_to_use=GetMaxMemoryRequest();
  if (max_memory_to_use < (size_t) LONG_MAX)
    jpeg_info->mem->max_memory_to_use=(long) max_memory_to_use;
  jpeg_progress.progress_monitor=(void (*)(j_common_ptr)) JPEGProgressHandler;
  jpeg_info->progress=(&jpeg_progress);
  JPEGSourceManager(jpeg_info,image);
  jpeg_set_marker_processor(jpeg_info,JPEG_COM,ReadComment);
  option=GetImageOption(image_info,"profile:skip");
  for (i=1; i < MaxJPEGProfiles; i++)
  {
    if (i == ICC_INDEX)
      {
        if (IsOptionMember("ICC",option) == MagickFalse)
          jpeg_set_marker_processor(jpeg_info,ICC_MARKER,ReadICCProfile);
      }
    else if (i == IPTC_INDEX)
      {
        if (IsOptionMember("IPTC",option) == MagickFalse)
          jpeg_set_marker_processor(jpeg_info,IPTC_MARKER,ReadIPTCProfile);
      }
    else if (i != 14)
      {
        /*
          Ignore APP14 as this will change the colors of the image.
        */
        if (IsOptionMember("APP",option) == MagickFalse)
          jpeg_set_marker_processor(jpeg_info,(int) (JPEG_APP0+i),
            ReadAPPProfiles);
      }
  }
  (void) jpeg_read_header(jpeg_info,TRUE);
  if (IsYCbCrCompatibleColorspace(image_info->colorspace) != MagickFalse)
    jpeg_info->out_color_space=JCS_YCbCr;
  /*
    Set image resolution.
  */
  if (jpeg_info->saw_JFIF_marker != 0)
    {
      if (jpeg_info->density_unit == 1)
        image->units=PixelsPerInchResolution;
      else if (jpeg_info->density_unit == 2)
        image->units = PixelsPerCentimeterResolution;
      if (IsAspectRatio(jpeg_info) == MagickFalse)
        {
          if (jpeg_info->X_density != 0)
            image->resolution.x=(double) jpeg_info->X_density;
          if (jpeg_info->Y_density != 0)
            image->resolution.y=(double) jpeg_info->Y_density;
        }
    }
  number_pixels=(MagickSizeType) image->columns*image->rows;
  option=GetImageOption(image_info,"jpeg:size");
  if ((option != (const char *) NULL) &&
      (jpeg_info->out_color_space != JCS_YCbCr))
    {
      double
        scale_factor;

      GeometryInfo
        geometry_info;

      MagickStatusType
        flags;

      /*
        Scale the image.
      */
      flags=ParseGeometry(option,&geometry_info);
      if ((flags & SigmaValue) == 0)
        geometry_info.sigma=geometry_info.rho;
      jpeg_calc_output_dimensions(jpeg_info);
      image->magick_columns=jpeg_info->output_width;
      image->magick_rows=jpeg_info->output_height;
      scale_factor=1.0;
      if (geometry_info.rho != 0.0)
        scale_factor=jpeg_info->output_width/geometry_info.rho;
      if ((geometry_info.sigma != 0.0) &&
          (scale_factor > (jpeg_info->output_height/geometry_info.sigma)))
        scale_factor=jpeg_info->output_height/geometry_info.sigma;
#if defined(LIBJPEG_TURBO_VERSION_NUMBER) || (JPEG_LIB_VERSION >= 70)
      jpeg_info->scale_num=(unsigned int) (8.0/scale_factor+0.5);
      if (jpeg_info->scale_num > 16U)
        jpeg_info->scale_num=16U;
      if (jpeg_info->scale_num < 1U)
        jpeg_info->scale_num=1U;
      jpeg_info->scale_denom=8U;
#else
      jpeg_info->scale_num=1U;
      jpeg_info->scale_denom=(unsigned int) scale_factor;
#endif
      jpeg_calc_output_dimensions(jpeg_info);
      if (image->debug != MagickFalse)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "Scale factor: %.20g",(double) scale_factor);
    }
#if (JPEG_LIB_VERSION >= 61) && defined(D_PROGRESSIVE_SUPPORTED)
#if !defined(LIBJPEG_TURBO_VERSION_NUMBER) && defined(D_LOSSLESS_SUPPORTED)
  image->interlace=jpeg_info->process == JPROC_PROGRESSIVE ?
    JPEGInterlace : NoInterlace;
  image->compression=jpeg_info->process == JPROC_LOSSLESS ?
    LosslessJPEGCompression : JPEGCompression;
  if (jpeg_info->data_precision > 8)
    (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
      "12-bit JPEG not supported. Reducing pixel data to 8 bits","`%s'",
      image->filename);
  if (jpeg_info->data_precision == 16)
    jpeg_info->data_precision=12;
#else
  image->interlace=jpeg_info->progressive_mode != 0 ? JPEGInterlace :
    NoInterlace;
  image->compression=JPEGCompression;
#endif
#else
  image->compression=JPEGCompression;
  image->interlace=JPEGInterlace;
#endif
  option=GetImageOption(image_info,"jpeg:colors");
  if (option != (const char *) NULL)
    {
      /*
        Let the JPEG library quantize the image.
      */
      jpeg_info->quantize_colors=TRUE;
      jpeg_info->desired_number_of_colors=StringToInteger(option);
    }
  option=GetImageOption(image_info,"jpeg:block-smoothing");
  if (option != (const char *) NULL)
    jpeg_info->do_block_smoothing=IsStringTrue(option) != MagickFalse ? TRUE :
      FALSE;
  dct_method=GetImageOption(image_info,"jpeg:dct-method");
  if (dct_method != (const char *) NULL)
    switch (*dct_method)
    {
      case 'D':
      case 'd':
      {
        if (LocaleCompare(dct_method,"default") == 0)
          jpeg_info->dct_method=JDCT_DEFAULT;
        break;
      }
      case 'F':
      case 'f':
      {
        if (LocaleCompare(dct_method,"fastest") == 0)
          jpeg_info->dct_method=JDCT_FASTEST;
        if (LocaleCompare(dct_method,"float") == 0)
          jpeg_info->dct_method=JDCT_FLOAT;
        break;
      }
      case 'I':
      case 'i':
      {
        if (LocaleCompare(dct_method,"ifast") == 0)
          jpeg_info->dct_method=JDCT_IFAST;
        if (LocaleCompare(dct_method,"islow") == 0)
          jpeg_info->dct_method=JDCT_ISLOW;
        break;
      }
    }
  option=GetImageOption(image_info,"jpeg:fancy-upsampling");
  if (option != (const char *) NULL)
    jpeg_info->do_fancy_upsampling=IsStringTrue(option) != MagickFalse ? TRUE :
      FALSE;
  jpeg_calc_output_dimensions(jpeg_info);
  image->columns=jpeg_info->output_width;
  image->rows=jpeg_info->output_height;
  image->depth=(size_t) jpeg_info->data_precision;
  if (IsITUFaxImage(image) != MagickFalse)
    {
      (void) SetImageColorspace(image,LabColorspace,exception);
      jpeg_info->out_color_space=JCS_YCbCr;
    }
  else
    {
      switch (jpeg_info->out_color_space)
      {
        case JCS_RGB:
        default:
        {
          (void) SetImageColorspace(image,sRGBColorspace,exception);
          break;
        }
        case JCS_GRAYSCALE:
        {
          (void) SetImageColorspace(image,GRAYColorspace,exception);
          break;
        }
        case JCS_YCbCr:
        {
          (void) SetImageColorspace(image,YCbCrColorspace,exception);
          break;
        }
        case JCS_CMYK:
        {
          (void) SetImageColorspace(image,CMYKColorspace,exception);
          break;
        }
      }
    }
  option=GetImageOption(image_info,"jpeg:colors");
  if (option != (const char *) NULL)
    if (AcquireImageColormap(image,StringToUnsignedLong(option),exception) == MagickFalse)
      {
        client_info=JPEGCleanup(jpeg_info,client_info);
        ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
      }
  if ((jpeg_info->output_components == 1) && (jpeg_info->quantize_colors == 0))
    {
      size_t
        colors;

      colors=(size_t) GetQuantumRange((size_t) jpeg_info->data_precision)+1;
      if (AcquireImageColormap(image,colors,exception) == MagickFalse)
        {
          client_info=JPEGCleanup(jpeg_info,client_info);
          ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
        }
    }
  if (image->debug != MagickFalse)
    {
      if (image->interlace != NoInterlace)
        (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(),"Geometry: %dx%d",
        (int) jpeg_info->output_width,(int) jpeg_info->output_height);
    }
  JPEGSetImageQuality(jpeg_info,image);
  JPEGSetImageSamplingFactor(jpeg_info,image,exception);
  (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
    jpeg_info->out_color_space);
  (void) SetImageProperty(image,"jpeg:colorspace",value,exception);
#if defined(D_ARITH_CODING_SUPPORTED)
  if (jpeg_info->arith_code == TRUE)
    (void) SetImageProperty(image,"jpeg:arithmetic-coding","true",exception);
#endif
  JPEGDestroyImageProfiles(client_info);
  *offset=TellBlob(image);
  if (image_info->ping != MagickFalse)
    {
      client_info=JPEGCleanup(jpeg_info,client_info);
      (void) CloseBlob(image);
      return(GetFirstImageInList(image));
    }
  status=SetImageExtent(image,image->columns,image->rows,exception);
  if (status == MagickFalse)
    {
      client_info=JPEGCleanup(jpeg_info,client_info);
      return(DestroyImageList(image));
    }
  (void) jpeg_start_decompress(jpeg_info);
  if ((jpeg_info->output_components != 1) &&
      (jpeg_info->output_components != 3) &&
      (jpeg_info->output_components != 4))
    {
      client_info=JPEGCleanup(jpeg_info,client_info);
      ThrowReaderException(CorruptImageError,"ImageTypeNotSupported");
    }
  bytes_per_pixel=((size_t) jpeg_info->data_precision+7)/8;
  memory_info=AcquireVirtualMemory((size_t) image->columns,
    (size_t) jpeg_info->output_components*bytes_per_pixel);
  if (memory_info == (MemoryInfo *) NULL)
    {
      client_info=JPEGCleanup(jpeg_info,client_info);
      ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
    }
  jpeg_pixels=(JSAMPLE *) GetVirtualMemoryBlob(memory_info);
  (void) memset(jpeg_pixels,0,(size_t) (image->columns*
    (size_t) jpeg_info->output_components*sizeof(*jpeg_pixels)));
  /*
    Convert JPEG pixels to pixel packets.
  */
  if (setjmp(client_info->error_recovery) != 0)
    {
      if (memory_info != (MemoryInfo *) NULL)
        memory_info=RelinquishVirtualMemory(memory_info);
      client_info=JPEGCleanup(jpeg_info,client_info);
      (void) CloseBlob(image);
      number_pixels=(MagickSizeType) image->columns*image->rows;
      if (number_pixels != 0)
        return(GetFirstImageInList(image));
      return(DestroyImage(image));
    }
  if (jpeg_info->quantize_colors != 0)
    {
      image->colors=(size_t) jpeg_info->actual_number_of_colors;
      if (jpeg_info->out_color_space == JCS_GRAYSCALE)
        for (i=0; i < (ssize_t) image->colors; i++)
        {
          image->colormap[i].red=(double) ScaleCharToQuantum(
            jpeg_info->colormap[0][i]);
          image->colormap[i].green=image->colormap[i].red;
          image->colormap[i].blue=image->colormap[i].red;
          image->colormap[i].alpha=(MagickRealType) OpaqueAlpha;
        }
      else
        for (i=0; i < (ssize_t) image->colors; i++)
        {
          image->colormap[i].red=(double) ScaleCharToQuantum(
            jpeg_info->colormap[0][i]);
          image->colormap[i].green=(double) ScaleCharToQuantum(
            jpeg_info->colormap[1][i]);
          image->colormap[i].blue=(double) ScaleCharToQuantum(
            jpeg_info->colormap[2][i]);
          image->colormap[i].alpha=(MagickRealType) OpaqueAlpha;
        }
    }
  range=GetQuantumRange(jpeg_info->data_precision);
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    JDIMENSION
      number_scanlines = 0;

    Quantum
      *magick_restrict q;

    ssize_t
      x;

    /*
      Uncompress one JPEG scanline.
    */
    if (jpeg_info->data_precision > 12)
      {
#if defined(MAGICKCORE_HAVE_JPEG16_READ_SCANLINES)
        number_scanlines=jpeg16_read_scanlines(jpeg_info,(J16SAMPROW *)
          &jpeg_pixels,1);
#endif
      }
    else if (jpeg_info->data_precision > 8)
      {
#if defined(MAGICKCORE_HAVE_JPEG12_READ_SCANLINES)
        number_scanlines=jpeg12_read_scanlines(jpeg_info,(J12SAMPROW *)
          &jpeg_pixels,1);
#endif
      }
    else
      {
        number_scanlines=jpeg_read_scanlines(jpeg_info,(JSAMPROW *)
          &jpeg_pixels,1);
      }
    if (number_scanlines != 1)
      (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
        "AnErrorHasOccurredReadingFromFile","`%s'",image->filename);
    /*
      Transfer image pixels from JPEG buffer.
    */
    q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
    if (q == (Quantum *) NULL)
      break;
    p=jpeg_pixels;
    switch (jpeg_info->output_components)
    {
      case 1:
      {
        /*
          Grayscale.
        */
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          Quantum
            index;

          unsigned short
            pixel;

          if (jpeg_info->data_precision > 8)
            pixel=(*(unsigned short *) p);
          else
            pixel=(*(JSAMPLE *) p);

          index=(Quantum) ConstrainColormapIndex(image,pixel,exception);
          SetPixelViaPixelInfo(image,image->colormap+(ssize_t) index,q);
          SetPixelIndex(image,index,q);
          p+=(ptrdiff_t) bytes_per_pixel;
          q+=(ptrdiff_t) GetPixelChannels(image);
        }
        break;
      }
      case 4:
      {
        /*
          CMYK.
        */
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          SetPixelCyan(image,QuantumRange-JPEGGetQuantum(jpeg_info,range,p),q);
          p+=(ptrdiff_t) bytes_per_pixel;
          SetPixelMagenta(image,QuantumRange-JPEGGetQuantum(jpeg_info,range,p),q);
          p+=(ptrdiff_t) bytes_per_pixel;
          SetPixelYellow(image,QuantumRange-JPEGGetQuantum(jpeg_info,range,p),q);
          p+=(ptrdiff_t) bytes_per_pixel;
          SetPixelBlack(image,QuantumRange-JPEGGetQuantum(jpeg_info,range,p),q);
          p+=(ptrdiff_t) bytes_per_pixel;
          SetPixelAlpha(image,OpaqueAlpha,q);
          q+=(ptrdiff_t) GetPixelChannels(image);
        }
        break;
      }
      default:
      {
        /*
          RGB || YCC.
        */
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          SetPixelRed(image,JPEGGetQuantum(jpeg_info,range,p),q);
          p+=(ptrdiff_t) bytes_per_pixel;
          SetPixelGreen(image,JPEGGetQuantum(jpeg_info,range,p),q);
          p+=(ptrdiff_t) bytes_per_pixel;
          SetPixelBlue(image,JPEGGetQuantum(jpeg_info,range,p),q);
          p+=(ptrdiff_t) bytes_per_pixel;
          SetPixelAlpha(image,OpaqueAlpha,q);
          q+=(ptrdiff_t) GetPixelChannels(image);
        }
        break;
      }
    }
    if (SyncAuthenticPixels(image,exception) == MagickFalse)
      break;
    status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
      image->rows);
    if (status == MagickFalse)
      {
        jpeg_abort_decompress(jpeg_info);
        break;
      }
  }
  if (status != MagickFalse)
    {
      client_info->finished=MagickTrue;
      if (setjmp(client_info->error_recovery) == 0)
        (void) jpeg_finish_decompress(jpeg_info);
    }
  /*
    Free jpeg resources.
  */
  client_info=JPEGCleanup(jpeg_info,client_info);
  memory_info=RelinquishVirtualMemory(memory_info);
  if (CloseBlob(image) == MagickFalse)
    status=MagickFalse;
  if (status == MagickFalse)
    return(DestroyImageList(image));
  return(GetFirstImageInList(image));
}

static MagickBooleanType ReadMPOImages(const ImageInfo *image_info,
  struct jpeg_decompress_struct *jpeg_info,Image *images,
  const MagickOffsetType start_offset,ExceptionInfo *exception)
{
#define BUFFER_SIZE  8192
#define SIGNATURE_SIZE 4

  Image
    *image;

  MagickBooleanType
    status;

  MagickOffsetType
    offset = start_offset;

  ssize_t
    count,
    j = 0;

  unsigned char
    alt_signature[SIGNATURE_SIZE] = {0xff, 0xd8, 0xff, 0xe1},
    buffer[BUFFER_SIZE],
    signature[SIGNATURE_SIZE] = {0xff, 0xd8, 0xff, 0xe0};

  /*
    Read multi-picture object images.
  */
  image=AcquireImage(image_info,exception);
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == MagickFalse)
    {
      image=DestroyImageList(image);
      return(MagickFalse);
    }
  (void) SeekBlob(image,offset,SEEK_SET);
  while ((count=ReadBlob(image,BUFFER_SIZE,buffer)) != 0)
  {
    ssize_t
      i;

    for (i=0; i < count; i++)
    {
      Image
        *jpeg_image;

      MagickOffsetType
        old_offset;

      if ((buffer[i] != signature[j]) && (buffer[i] != alt_signature[j]))
        {
          j=0;
          continue;
        }
      if (++j != SIGNATURE_SIZE)
        continue;
      offset+=i-SIGNATURE_SIZE+1;
      old_offset=offset;
      (void) CloseBlob(image);
      jpeg_image=ReadOneJPEGImage(image_info,jpeg_info,&offset,exception);
      if (jpeg_image != (Image *) NULL)
        AppendImageToList(&images,jpeg_image);
      if (offset <= old_offset)
        break;
      status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
      if (status == MagickFalse)
        break;
      (void) SeekBlob(image,offset,SEEK_SET);
      count=0;
      j=0;
      break;
    }
    offset+=count;
  }
  (void) CloseBlob(image);
  image=DestroyImageList(image);
  return(status);
}

static Image *ReadJPEGImage(const ImageInfo *image_info,
  ExceptionInfo *exception)
{
  Image
    *images;

  struct jpeg_decompress_struct
    jpeg_info;

  MagickOffsetType
    offset;

  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  if (IsEventLogging() != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
      image_info->filename);
  offset=0;
  images=ReadOneJPEGImage(image_info,&jpeg_info,&offset,exception);
  if ((images != (Image *) NULL) &&
      (LocaleCompare(image_info->magick,"MPO") == 0))
    {
      const StringInfo
        *profile;

      /*
        Check for multi-picture object.
      */
      profile=GetImageProfile(images,"MPF");
      if (profile != (const StringInfo *) NULL)
        (void) ReadMPOImages(image_info,&jpeg_info,images,offset,exception);
    }
  return(images);
}
#endif

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r J P E G I m a g e                                         %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  RegisterJPEGImage() adds properties for the JPEG image format to
%  the list of supported formats.  The properties 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:
%
%      size_t RegisterJPEGImage(void)
%
*/
ModuleExport size_t RegisterJPEGImage(void)
{
#define JPEGDescription "Joint Photographic Experts Group JFIF format"
#define JPEGStringify(macro_or_string)  JPEGStringifyArg(macro_or_string)
#define JPEGStringifyArg(contents)  #contents

  char
    version[MagickPathExtent];

  MagickInfo
    *entry;

  *version='\0';
#if defined(LIBJPEG_TURBO_VERSION)
  (void) CopyMagickString(version,"libjpeg-turbo " JPEGStringify(
    LIBJPEG_TURBO_VERSION),MagickPathExtent);
#elif defined(JPEG_LIB_VERSION)
  (void) FormatLocaleString(version,MagickPathExtent,"libjpeg %d",
    JPEG_LIB_VERSION);
#endif
  entry=AcquireMagickInfo("JPEG","JPE",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
  entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
  entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
  entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
  entry->magick=(IsImageFormatHandler *) IsJPEG;
  entry->flags|=CoderDecoderSeekableStreamFlag;
  entry->flags^=CoderAdjoinFlag;
  entry->flags^=CoderUseExtensionFlag;
  if (*version != '\0')
    entry->version=ConstantString(version);
  entry->mime_type=ConstantString("image/jpeg");
  (void) RegisterMagickInfo(entry);
  entry=AcquireMagickInfo("JPEG","JPEG",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
  entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
  entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
  entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
  entry->magick=(IsImageFormatHandler *) IsJPEG;
  entry->flags|=CoderDecoderSeekableStreamFlag;
  entry->flags^=CoderAdjoinFlag;
  if (*version != '\0')
    entry->version=ConstantString(version);
  entry->mime_type=ConstantString("image/jpeg");
  (void) RegisterMagickInfo(entry);
  entry=AcquireMagickInfo("JPEG","JPG",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
  entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
  entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
  entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
  entry->flags|=CoderDecoderSeekableStreamFlag;
  entry->flags^=CoderAdjoinFlag;
  entry->flags^=CoderUseExtensionFlag;
  if (*version != '\0')
    entry->version=ConstantString(version);
  entry->mime_type=ConstantString("image/jpeg");
  (void) RegisterMagickInfo(entry);
  entry=AcquireMagickInfo("JPEG","JPS",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
  entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
  entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
  entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
  entry->flags|=CoderDecoderSeekableStreamFlag;
  entry->flags^=CoderAdjoinFlag;
  entry->flags^=CoderUseExtensionFlag;
  if (*version != '\0')
    entry->version=ConstantString(version);
  entry->mime_type=ConstantString("image/jpeg");
  (void) RegisterMagickInfo(entry);
  entry=AcquireMagickInfo("JPEG","MPO",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
  entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
  entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
#endif
  entry->magick=(IsImageFormatHandler *) IsJPEG;
  entry->flags|=CoderDecoderSeekableStreamFlag;
  if (*version != '\0')
    entry->version=ConstantString(version);
  entry->mime_type=ConstantString("image/jpeg");
  (void) RegisterMagickInfo(entry);
  entry=AcquireMagickInfo("JPEG","PJPEG",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
  entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
  entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
  entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
  entry->flags|=CoderDecoderSeekableStreamFlag;
  entry->flags^=CoderAdjoinFlag;
  entry->flags^=CoderUseExtensionFlag;
  if (*version != '\0')
    entry->version=ConstantString(version);
  entry->mime_type=ConstantString("image/jpeg");
  (void) RegisterMagickInfo(entry);
  return(MagickImageCoderSignature);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r J P E G I m a g e                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  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("PJPG");
  (void) UnregisterMagickInfo("MPO");
  (void) UnregisterMagickInfo("JPS");
  (void) UnregisterMagickInfo("JPG");
  (void) UnregisterMagickInfo("JPEG");
  (void) UnregisterMagickInfo("JPE");
}

#if defined(MAGICKCORE_JPEG_DELEGATE)
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%  W r i t e J P E G I m a g e                                                %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  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:
%
%      MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
%        Image *image,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image_info: the image info.
%
%    o jpeg_image:  The image.
%
%    o exception: return any errors or warnings in this structure.
%
*/

static QuantizationTable *DestroyQuantizationTable(QuantizationTable *table)
{
  assert(table != (QuantizationTable *) NULL);
  if (table->slot != (char *) NULL)
    table->slot=DestroyString(table->slot);
  if (table->description != (char *) NULL)
    table->description=DestroyString(table->description);
  if (table->levels != (unsigned int *) NULL)
    table->levels=(unsigned int *) RelinquishMagickMemory(table->levels);
  table=(QuantizationTable *) RelinquishMagickMemory(table);
  return(table);
}

static boolean EmptyOutputBuffer(j_compress_ptr compress_info)
{
  DestinationManager
    *destination;

  destination=(DestinationManager *) compress_info->dest;
  destination->manager.free_in_buffer=(size_t) WriteBlob(destination->image,
    MagickMinBufferExtent,destination->buffer);
  if (destination->manager.free_in_buffer != MagickMinBufferExtent)
    ERREXIT(compress_info,JERR_FILE_WRITE);
  destination->manager.next_output_byte=destination->buffer;
  return(TRUE);
}

static QuantizationTable *GetQuantizationTable(const char *filename,
  const char *slot,ExceptionInfo *exception)
{
  char
    *p,
    *xml;

  const char
    *attribute,
    *content;

  double
    value;

  QuantizationTable
    *table;

  size_t
    length;

  ssize_t
    i,
    j;

  XMLTreeInfo
    *description,
    *levels,
    *quantization_tables,
    *table_iterator;

  (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
    "Loading quantization tables \"%s\" ...",filename);
  table=(QuantizationTable *) NULL;
  xml=FileToString(filename,~0UL,exception);
  if (xml == (char *) NULL)
    return(table);
  quantization_tables=NewXMLTree(xml,exception);
  if (quantization_tables == (XMLTreeInfo *) NULL)
    {
      xml=DestroyString(xml);
      return(table);
    }
  for (table_iterator=GetXMLTreeChild(quantization_tables,"table");
       table_iterator != (XMLTreeInfo *) NULL;
       table_iterator=GetNextXMLTreeTag(table_iterator))
  {
    attribute=GetXMLTreeAttribute(table_iterator,"slot");
    if ((attribute != (char *) NULL) && (LocaleCompare(slot,attribute) == 0))
      break;
    attribute=GetXMLTreeAttribute(table_iterator,"alias");
    if ((attribute != (char *) NULL) && (LocaleCompare(slot,attribute) == 0))
      break;
  }
  if (table_iterator == (XMLTreeInfo *) NULL)
    {
      xml=DestroyString(xml);
      return(table);
    }
  description=GetXMLTreeChild(table_iterator,"description");
  if (description == (XMLTreeInfo *) NULL)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
        "XmlMissingElement","<description>, slot \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      xml=DestroyString(xml);
      return(table);
    }
  levels=GetXMLTreeChild(table_iterator,"levels");
  if (levels == (XMLTreeInfo *) NULL)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
        "XmlMissingElement","<levels>, slot \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      xml=DestroyString(xml);
      return(table);
    }
  table=(QuantizationTable *) AcquireCriticalMemory(sizeof(*table));
  table->slot=(char *) NULL;
  table->description=(char *) NULL;
  table->levels=(unsigned int *) NULL;
  attribute=GetXMLTreeAttribute(table_iterator,"slot");
  if (attribute != (char *) NULL)
    table->slot=ConstantString(attribute);
  content=GetXMLTreeContent(description);
  if (content != (char *) NULL)
    table->description=ConstantString(content);
  attribute=GetXMLTreeAttribute(levels,"width");
  if (attribute == (char *) NULL)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
        "XmlMissingAttribute","<levels width>, slot \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      table=DestroyQuantizationTable(table);
      xml=DestroyString(xml);
      return(table);
    }
  table->width=StringToUnsignedLong(attribute);
  if (table->width == 0)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
       "XmlInvalidAttribute","<levels width>, table \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      table=DestroyQuantizationTable(table);
      xml=DestroyString(xml);
      return(table);
    }
  attribute=GetXMLTreeAttribute(levels,"height");
  if (attribute == (char *) NULL)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
        "XmlMissingAttribute","<levels height>, table \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      table=DestroyQuantizationTable(table);
      xml=DestroyString(xml);
      return(table);
    }
  table->height=StringToUnsignedLong(attribute);
  if (table->height == 0)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
        "XmlInvalidAttribute","<levels height>, table \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      table=DestroyQuantizationTable(table);
      xml=DestroyString(xml);
      return(table);
    }
  attribute=GetXMLTreeAttribute(levels,"divisor");
  if (attribute == (char *) NULL)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
        "XmlMissingAttribute","<levels divisor>, table \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      table=DestroyQuantizationTable(table);
      xml=DestroyString(xml);
      return(table);
    }
  table->divisor=InterpretLocaleValue(attribute,(char **) NULL);
  if (table->divisor == 0.0)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
        "XmlInvalidAttribute","<levels divisor>, table \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      table=DestroyQuantizationTable(table);
      xml=DestroyString(xml);
      return(table);
    }
  content=GetXMLTreeContent(levels);
  if (content == (char *) NULL)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
        "XmlMissingContent","<levels>, table \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      table=DestroyQuantizationTable(table);
      xml=DestroyString(xml);
      return(table);
    }
  length=(size_t) table->width*table->height;
  if (length < 64)
    length=64;
  table->levels=(unsigned int *) AcquireQuantumMemory(length,
    sizeof(*table->levels));
  if (table->levels == (unsigned int *) NULL)
    ThrowFatalException(ResourceLimitFatalError,
      "UnableToAcquireQuantizationTable");
  for (i=0; i < (ssize_t) (table->width*table->height); i++)
  {
    table->levels[i]=(unsigned int) (InterpretLocaleValue(content,&p)/
      table->divisor+0.5);
    while (isspace((int) ((unsigned char) *p)) != 0)
      p++;
    if (*p == ',')
      p++;
    content=p;
  }
  value=InterpretLocaleValue(content,&p);
  (void) value;
  if (p != content)
    {
      (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
        "XmlInvalidContent","<level> too many values, table \"%s\"",slot);
     quantization_tables=DestroyXMLTree(quantization_tables);
     table=DestroyQuantizationTable(table);
     xml=DestroyString(xml);
     return(table);
   }
  for (j=i; j < 64; j++)
    table->levels[j]=table->levels[j-1];
  quantization_tables=DestroyXMLTree(quantization_tables);
  xml=DestroyString(xml);
  return(table);
}

static void InitializeDestination(j_compress_ptr compress_info)
{
  DestinationManager
    *destination;

  destination=(DestinationManager *) compress_info->dest;
  destination->buffer=(JOCTET *) (*compress_info->mem->alloc_small) (
    (j_common_ptr) compress_info,JPOOL_IMAGE,MagickMinBufferExtent*
    sizeof(JOCTET));
  destination->manager.next_output_byte=destination->buffer;
  destination->manager.free_in_buffer=MagickMinBufferExtent;
}

static void TerminateDestination(j_compress_ptr compress_info)
{
  DestinationManager
    *destination;

  destination=(DestinationManager *) compress_info->dest;
  if ((MagickMinBufferExtent-(int) destination->manager.free_in_buffer) > 0)
    {
      ssize_t
        count;

      count=WriteBlob(destination->image,MagickMinBufferExtent-
        destination->manager.free_in_buffer,destination->buffer);
      if (count != (ssize_t)
          (MagickMinBufferExtent-destination->manager.free_in_buffer))
        ERREXIT(compress_info,JERR_FILE_WRITE);
    }
}

static void WriteProfiles(j_compress_ptr jpeg_info,Image *image,
  ExceptionInfo *exception)
{
  const char
    *name;

  const StringInfo
    *profile;

  MagickBooleanType
    iptc;

  ssize_t
    i;

  size_t
    length,
    tag_length;

  StringInfo
    *custom_profile;

  /*
    Save image profile as a APP marker.
  */
  iptc=MagickFalse;
  custom_profile=AcquireStringInfo(65535L);
  ResetImageProfileIterator(image);
  for (name=GetNextImageProfile(image); name != (const char *) NULL; )
  {
    profile=GetImageProfile(image,name);
    length=GetStringInfoLength(profile);
    if (LocaleNCompare(name,"APP",3) == 0)
      {
        int
          marker;

        marker=APP_MARKER+StringToInteger(name+3);
        for (i=0; i < (ssize_t) length; i+=65533L)
           jpeg_write_marker(jpeg_info,marker,GetStringInfoDatum(profile)+i,
             MagickMin((unsigned int) (length-i),65533));
      }
    else if (LocaleCompare(name,"EXIF") == 0)
      {
        if (length > 65533L)
          (void) ThrowMagickException(exception,GetMagickModule(),CoderWarning,
            "ExifProfileSizeExceedsLimit","`%s'",image->filename);
        else
          jpeg_write_marker(jpeg_info,APP_MARKER+1,GetStringInfoDatum(profile),
            (unsigned int) length);
      }
    else if (LocaleCompare(name,"ICC") == 0)
      {
        unsigned char
          *p;

        tag_length=strlen(ICC_PROFILE);
        p=GetStringInfoDatum(custom_profile);
        (void) memcpy(p,ICC_PROFILE,tag_length);
        p[tag_length]='\0';
        for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=65519L)
        {
          length=(size_t) MagickMin((ssize_t) GetStringInfoLength(profile)-i,
            65519L);
          p[12]=(unsigned char) ((i/65519L)+1);
          p[13]=(unsigned char) (GetStringInfoLength(profile)/65519L+1);
          (void) memcpy(p+tag_length+3,GetStringInfoDatum(profile)+i,
            length);
          jpeg_write_marker(jpeg_info,ICC_MARKER,GetStringInfoDatum(
            custom_profile),(unsigned int) (length+tag_length+3));
        }
      }
    else if (((LocaleCompare(name,"IPTC") == 0) ||
        (LocaleCompare(name,"8BIM") == 0)) && (iptc == MagickFalse))
      {
        size_t
          roundup;

        unsigned char
          *p;

        iptc=MagickTrue;
        p=GetStringInfoDatum(custom_profile);
        for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=65500L)
        {
          length=(size_t) MagickMin((ssize_t) GetStringInfoLength(profile)-i,
            65500L);
          roundup=(size_t) (length & 0x01);
          if (LocaleNCompare((char *) GetStringInfoDatum(profile),"8BIM",4) == 0)
            {
              (void) memcpy(p,"Photoshop 3.0 ",14);
              tag_length=14;
            }
          else
            {
              (void) memcpy(p,"Photoshop 3.0 8BIM\04\04\0\0\0\0",24);
              tag_length=26;
              p[24]=(unsigned char) (length >> 8);
              p[25]=(unsigned char) (length & 0xff);
            }
          p[13]=0x00;
          (void) memcpy(p+tag_length,GetStringInfoDatum(profile)+i,length);
          if (roundup != 0)
            p[length+tag_length]='\0';
          jpeg_write_marker(jpeg_info,IPTC_MARKER,GetStringInfoDatum(
            custom_profile),(unsigned int) (length+tag_length+roundup));
        }
      }
    else if (LocaleCompare(name,"XMP") == 0)
      {
        StringInfo
          *xmp_profile;

        /*
          Add namespace to XMP profile.
        */
        xmp_profile=StringToStringInfo(xmp_namespace);
        SetStringInfoLength(xmp_profile,strlen(xmp_namespace)+1);
        ConcatenateStringInfo(xmp_profile,profile);
        GetStringInfoDatum(xmp_profile)[strlen(xmp_namespace)]='\0';
        length=GetStringInfoLength(xmp_profile);
        for (i=0; i < (ssize_t) length; i+=65533L)
          jpeg_write_marker(jpeg_info,APP_MARKER+1,GetStringInfoDatum(
            xmp_profile)+i,MagickMin((unsigned int) (length-i),65533));
        xmp_profile=DestroyStringInfo(xmp_profile);
      }
    if (image->debug != MagickFalse)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "%s profile: %.20g bytes",name,(double) GetStringInfoLength(profile));
    name=GetNextImageProfile(image);
  }
  custom_profile=DestroyStringInfo(custom_profile);
}

static void JPEGDestinationManager(j_compress_ptr compress_info,Image * image)
{
  DestinationManager
    *destination;

  compress_info->dest=(struct jpeg_destination_mgr *) (*
    compress_info->mem->alloc_small) ((j_common_ptr) compress_info,JPOOL_IMAGE,
    sizeof(DestinationManager));
  destination=(DestinationManager *) compress_info->dest;
  destination->manager.init_destination=InitializeDestination;
  destination->manager.empty_output_buffer=EmptyOutputBuffer;
  destination->manager.term_destination=TerminateDestination;
  destination->image=image;
}

static char **SamplingFactorToList(const char *text)
{
  char
    *q,
    **textlist;

  const char
    *p;

  ssize_t
    i;

  /*
    Convert string to an ASCII list.
  */
  if (text == (char *) NULL)
    return((char **) NULL);
  textlist=(char **) AcquireQuantumMemory((size_t) MAX_COMPONENTS,
    sizeof(*textlist));
  if (textlist == (char **) NULL)
    ThrowFatalException(ResourceLimitFatalError,"UnableToConvertText");
  p=text;
  for (i=0; i < (ssize_t) MAX_COMPONENTS; i++)
  {
    for (q=(char *) p; *q != '\0'; q++)
      if (*q == ',')
        break;
    textlist[i]=(char *) AcquireQuantumMemory((size_t) (q-p)+MagickPathExtent,
      sizeof(*textlist[i]));
    if (textlist[i] == (char *) NULL)
      ThrowFatalException(ResourceLimitFatalError,"UnableToConvertText");
    (void) CopyMagickString(textlist[i],p,(size_t) (q-p+1));
    if (*q == '\r')
      q++;
    if (*q == '\0')
      break;
    p=q+1;
  }
  for (i++; i < (ssize_t) MAX_COMPONENTS; i++)
    textlist[i]=ConstantString("1x1");
  return(textlist);
}

static inline void JPEGSetSample(const struct jpeg_compress_struct *jpeg_info,
  const Quantum pixel,QuantumAny range,JSAMPLE *q)
{
  if (jpeg_info->data_precision == 8)
    *q=(JSAMPLE) ScaleQuantumToChar(pixel);
  else if (jpeg_info->data_precision == 16)
    (*(unsigned short *) q)=(unsigned short) ScaleQuantumToShort(pixel);
  else if (jpeg_info->data_precision < 8)
    *q=(JSAMPLE) ScaleQuantumToAny(pixel,range);
  else
    (*(unsigned short *) q)=(unsigned short) ScaleQuantumToAny(pixel,range);
}

static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
  Image *myImage,struct jpeg_compress_struct *jpeg_info,
  ExceptionInfo *exception)
{
#define ThrowJPEGWriterException(exception,message) \
{ \
  if (client_info != (JPEGClientInfo *) NULL) \
    client_info=(JPEGClientInfo *) RelinquishMagickMemory(client_info); \
  ThrowWriterException((exception),(message)); \
}

  const char
    *dct_method,
    *option,
    *sampling_factor,
    *value;

  Image
    *volatile image = (Image *) NULL,
    *volatile jps_image = (Image *) NULL,
    *volatile volatile_image = (Image *) NULL;

  int
    colorspace,
    quality;

  JPEGClientInfo
    *client_info = (JPEGClientInfo *) NULL;

  JSAMPLE
    *volatile jpeg_pixels,
    *q;

  MagickBooleanType
    status;

  MemoryInfo
    *memory_info;

  QuantumAny
    range;

  ssize_t
    bytes_per_pixel,
    i,
    y;

  struct jpeg_error_mgr
    jpeg_error;

  /*
    Open image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickCoreSignature);
  assert(myImage != (Image *) NULL);
  assert(myImage->signature == MagickCoreSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  image=myImage;
  if (IsEventLogging() != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
  if ((LocaleCompare(image_info->magick,"JPS") == 0) &&
      (image->next != (Image *) NULL))
    {
      jps_image=AppendImages(image,MagickFalse,exception);
      if (jps_image != (Image *) NULL)
        image=jps_image;
    }
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
  if (status == MagickFalse)
    {
      if (jps_image != (Image *) NULL)
        jps_image=DestroyImage(jps_image);
      return(status);
    }
  /*
    Initialize JPEG parameters.
  */
  client_info=(JPEGClientInfo *) AcquireMagickMemory(sizeof(*client_info));
  if (client_info == (JPEGClientInfo *) NULL)
    ThrowJPEGWriterException(ResourceLimitError,"MemoryAllocationFailed");
  (void) memset(client_info,0,sizeof(*client_info));
  (void) memset(jpeg_info,0,sizeof(*jpeg_info));
  (void) memset(&jpeg_error,0,sizeof(jpeg_error));
  volatile_image=image;
  jpeg_info->client_data=(void *) volatile_image;
  jpeg_info->err=jpeg_std_error(&jpeg_error);
  jpeg_info->err->emit_message=(void (*)(j_common_ptr,int)) JPEGWarningHandler;
  jpeg_info->err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
  client_info->exception=exception;
  client_info->image=volatile_image;
  memory_info=(MemoryInfo *) NULL;
  if (setjmp(client_info->error_recovery) != 0)
    {
      jpeg_destroy_compress(jpeg_info);
      client_info=(JPEGClientInfo *) RelinquishMagickMemory(client_info);
      (void) CloseBlob(image);
      if (jps_image != (Image *) NULL)
        jps_image=DestroyImage(jps_image);
      return(MagickFalse);
    }
  jpeg_info->client_data=(void *) client_info;
  jpeg_create_compress(jpeg_info);
  JPEGDestinationManager(jpeg_info,image);
  if ((image->columns != (unsigned int) image->columns) ||
      (image->rows != (unsigned int) image->rows))
    ThrowJPEGWriterException(ImageError,"WidthOrHeightExceedsLimit");
  jpeg_info->image_width=(unsigned int) image->columns;
  jpeg_info->image_height=(unsigned int) image->rows;
  jpeg_info->input_components=3;
  jpeg_info->data_precision=8;
  option=GetImageOption(image_info,"jpeg:high-bit-depth");
  if (IsStringTrue(option) != MagickFalse)
    {
#if defined(C_LOSSLESS_SUPPORTED)
      if (image_info->compression == LosslessJPEGCompression)
        {
#if defined(LIBJPEG_TURBO_VERSION_NUMBER) && LIBJPEG_TURBO_VERSION_NUMBER >= 3000090
          if (image_info->quality >= 100)
            jpeg_info->data_precision=(int) image->depth;
          else
#endif
          if (image->depth > 12)
            jpeg_info->data_precision=16;
          else if (image->depth > 8)
            jpeg_info->data_precision=12;
        }
      else
#endif
        {
#if defined(MAGICKCORE_HAVE_JPEG12_WRITE_SCANLINES)
          if (image->depth > 8)
            jpeg_info->data_precision=12;
#endif
        }
    }
  jpeg_info->in_color_space=JCS_RGB;
  switch (image->colorspace)
  {
    case CMYKColorspace:
    {
      jpeg_info->input_components=4;
      jpeg_info->in_color_space=JCS_CMYK;
      break;
    }
    case YCbCrColorspace:
    case Rec601YCbCrColorspace:
    case Rec709YCbCrColorspace:
    {
      jpeg_info->in_color_space=JCS_YCbCr;
      break;
    }
    case LinearGRAYColorspace:
    case GRAYColorspace:
    {
      if (image_info->type == TrueColorType)
        break;
      jpeg_info->input_components=1;
      jpeg_info->in_color_space=JCS_GRAYSCALE;
      break;
    }
    default:
    {
      if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
        (void) TransformImageColorspace(image,sRGBColorspace,exception);
      if (image_info->type == TrueColorType)
        break;
      if (IdentifyImageCoderGray(image,exception) != MagickFalse)
        {
          jpeg_info->input_components=1;
          jpeg_info->in_color_space=JCS_GRAYSCALE;
        }
      break;
    }
  }
  jpeg_set_defaults(jpeg_info);
  option=GetImageOption(image_info,"jpeg:restart-interval");
  if (option != (const char *) NULL)
    jpeg_info->restart_interval=StringToInteger(option);
  if (jpeg_info->in_color_space == JCS_CMYK)
    jpeg_set_colorspace(jpeg_info,JCS_YCCK);
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
      "Image resolution: %.20g,%.20g",image->resolution.x,image->resolution.y);
  if ((image->resolution.x > 0) && (image->resolution.x < (double) SHRT_MAX) &&
      (image->resolution.y > 0) && (image->resolution.y < (double) SHRT_MAX))
    {
      UINT16
        x_density=(UINT16) image->resolution.x,
        y_density=(UINT16) image->resolution.y;

      if ((x_density > 0) && (y_density > 0))
        {
          /*
            Set image resolution.
          */
          jpeg_info->write_JFIF_header=TRUE;
          jpeg_info->X_density=x_density;
          jpeg_info->Y_density=y_density;
          /*
            Set image resolution units.
          */
          if (image->units == PixelsPerInchResolution)
            jpeg_info->density_unit=(UINT8) 1;
          else if (image->units == PixelsPerCentimeterResolution)
            jpeg_info->density_unit=(UINT8) 2;
        }
    }
  dct_method=GetImageOption(image_info,"jpeg:dct-method");
  if (dct_method != (const char *) NULL)
    switch (*dct_method)
    {
      case 'D':
      case 'd':
      {
        if (LocaleCompare(dct_method,"default") == 0)
          jpeg_info->dct_method=JDCT_DEFAULT;
        break;
      }
      case 'F':
      case 'f':
      {
        if (LocaleCompare(dct_method,"fastest") == 0)
          jpeg_info->dct_method=JDCT_FASTEST;
        if (LocaleCompare(dct_method,"float") == 0)
          jpeg_info->dct_method=JDCT_FLOAT;
        break;
      }
      case 'I':
      case 'i':
      {
        if (LocaleCompare(dct_method,"ifast") == 0)
          jpeg_info->dct_method=JDCT_IFAST;
        if (LocaleCompare(dct_method,"islow") == 0)
          jpeg_info->dct_method=JDCT_ISLOW;
        break;
      }
    }
  option=GetImageOption(image_info,"jpeg:optimize-coding");
  if (option != (const char *) NULL)
    jpeg_info->optimize_coding=IsStringTrue(option) != MagickFalse ? TRUE :
      FALSE;
  else
    {
      MagickSizeType
        length;

      length=(MagickSizeType) jpeg_info->input_components*image->columns*
        image->rows*sizeof(JSAMPLE);
      if (length == (MagickSizeType) ((size_t) length))
        {
          /*
            Perform optimization only if available memory resources permit it.
          */
          status=AcquireMagickResource(MemoryResource,length);
          if (status != MagickFalse)
            RelinquishMagickResource(MemoryResource,length);
          jpeg_info->optimize_coding=status == MagickFalse ? FALSE : TRUE;
        }
    }
#if defined(C_ARITH_CODING_SUPPORTED)
  option=GetImageOption(image_info,"jpeg:arithmetic-coding");
  if (IsStringTrue(option) != MagickFalse)
    {
      jpeg_info->arith_code=TRUE;
      jpeg_info->optimize_coding=FALSE;  /* not supported */
    }
#endif
#if (JPEG_LIB_VERSION >= 61) && defined(C_PROGRESSIVE_SUPPORTED)
  if ((LocaleCompare(image_info->magick,"PJPEG") == 0) ||
      (image_info->interlace != NoInterlace))
    {
      if (image->debug != MagickFalse)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "Interlace: progressive");
      jpeg_simple_progression(jpeg_info);
    }
  else
    if (image->debug != MagickFalse)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "Interlace: non-progressive");
#else
  if (image->debug != MagickFalse)
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
      "Interlace: nonprogressive");
#endif
  quality=92;
  if ((image_info->compression != LosslessJPEGCompression) &&
      (image->quality <= 100))
    {
      if (image->quality != UndefinedCompressionQuality)
        quality=(int) image->quality;
      if (image->debug != MagickFalse)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: %.20g",
          (double) image->quality);
    }
  else
    {
#if defined(C_LOSSLESS_SUPPORTED)
      if (image->quality < 100)
        (void) ThrowMagickException(exception,GetMagickModule(),CoderWarning,
          "LosslessToLossyJPEGConversion","`%s'",image->filename);
      else
        {
          int
            point_transform,
            predictor;

          predictor=(int) image->quality/100;  /* range 1-7 */
          point_transform=image->quality % 20;  /* range 0-15 */
#if defined(MAGICKCORE_HAVE_JPEG_SIMPLE_LOSSLESS)
          jpeg_simple_lossless(jpeg_info,predictor,point_transform);
#endif
#if defined(MAGICKCORE_HAVE_JPEG_ENABLE_LOSSLESS)
          jpeg_enable_lossless(jpeg_info,predictor,point_transform);
#endif
          if (image->debug != MagickFalse)
            {
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "Compression: lossless");
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "Predictor: %d",predictor);
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "Point Transform: %d",point_transform);
            }
        }
#else
      quality=100;
      if (image->debug != MagickFalse)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: 100");
#endif
    }
  option=GetImageOption(image_info,"jpeg:extent");
  if (option != (const char *) NULL)
    {
      Image
        *jpeg_image;

      ImageInfo
        *extent_info;

      extent_info=CloneImageInfo(image_info);
      extent_info->blob=NULL;
      jpeg_image=CloneImage(image,0,0,MagickTrue,exception);
      if (jpeg_image != (Image *) NULL)
        {
          MagickSizeType
            extent;

          size_t
            maximum,
            minimum;

          /*
            Search for compression quality that does not exceed image extent.
          */
          extent_info->quality=0;
          extent=(MagickSizeType) SiPrefixToDoubleInterval(option,100.0);
          (void) DeleteImageOption(extent_info,"jpeg:extent");
          (void) DeleteImageArtifact(jpeg_image,"jpeg:extent");
          maximum=image_info->quality;
          if (maximum < 2)
            maximum=101;
          for (minimum=2; minimum < maximum; )
          {
            (void) AcquireUniqueFilename(jpeg_image->filename);
            jpeg_image->quality=minimum+(maximum-minimum+1)/2;
            status=WriteJPEGImage(extent_info,jpeg_image,exception);
            (void) RelinquishUniqueFileResource(jpeg_image->filename);
            if (status == MagickFalse)
              break;
            if (GetBlobSize(jpeg_image) <= extent)
              minimum=jpeg_image->quality+1;
            else
              maximum=jpeg_image->quality-1;
          }
          while (minimum > 2)
          {
            (void) AcquireUniqueFilename(jpeg_image->filename);
            jpeg_image->quality=minimum--;
            status=WriteJPEGImage(extent_info,jpeg_image,exception);
            (void) RelinquishUniqueFileResource(jpeg_image->filename);
            if (status == MagickFalse)
              continue;
            if (GetBlobSize(jpeg_image) <= extent)
              break;
          }
          quality=(int) minimum;
          jpeg_image=DestroyImage(jpeg_image);
        }
      extent_info=DestroyImageInfo(extent_info);
    }
  jpeg_set_quality(jpeg_info,quality,TRUE);
  if ((dct_method == (const char *) NULL) && (quality <= 90))
    jpeg_info->dct_method=JDCT_IFAST;
#if (JPEG_LIB_VERSION >= 70)
  option=GetImageOption(image_info,"quality");
  if (option != (const char *) NULL)
    {
      GeometryInfo
        geometry_info;

      int
        flags;

      /*
        Set quality scaling for luminance and chrominance separately.
      */
      flags=ParseGeometry(option,&geometry_info);
      if (((flags & RhoValue) != 0) && ((flags & SigmaValue) != 0))
        {
          jpeg_info->q_scale_factor[0]=jpeg_quality_scaling((int)
            (geometry_info.rho+0.5));
          jpeg_info->q_scale_factor[1]=jpeg_quality_scaling((int)
            (geometry_info.sigma+0.5));
          jpeg_default_qtables(jpeg_info,TRUE);
        }
    }
#endif
  colorspace=(int) jpeg_info->in_color_space;
  value=GetImageOption(image_info,"jpeg:colorspace");
  if (value == (char *) NULL)
    value=GetImageProperty(image,"jpeg:colorspace",exception);
  if (value != (char *) NULL)
    colorspace=StringToInteger(value);
  sampling_factor=(const char *) NULL;
  if ((J_COLOR_SPACE) colorspace == jpeg_info->in_color_space)
    {
      value=GetImageOption(image_info,"jpeg:sampling-factor");
      if (value == (char *) NULL)
        value=GetImageProperty(image,"jpeg:sampling-factor",exception);
      if (value != (char *) NULL)
        {
          sampling_factor=value;
          if (image->debug != MagickFalse)
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
              "  Input sampling-factors=%s",sampling_factor);
        }
    }
  value=GetImageOption(image_info,"jpeg:sampling-factor");
  if (image_info->sampling_factor != (char *) NULL)
    sampling_factor=image_info->sampling_factor;
  if (sampling_factor == (const char *) NULL)
    {
      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;
        }
    }
  else
    {
      char
        **factors;

      GeometryInfo
        geometry_info;

      MagickStatusType
        flags;

      /*
        Set sampling factor.
      */
      i=0;
      factors=SamplingFactorToList(sampling_factor);
      if (factors != (char **) NULL)
        {
          for (i=0; i < MAX_COMPONENTS; i++)
          {
            if (factors[i] == (char *) NULL)
              break;
            flags=ParseGeometry(factors[i],&geometry_info);
            if ((flags & SigmaValue) == 0)
              geometry_info.sigma=geometry_info.rho;
            jpeg_info->comp_info[i].h_samp_factor=(int) geometry_info.rho;
            jpeg_info->comp_info[i].v_samp_factor=(int) geometry_info.sigma;
            factors[i]=(char *) RelinquishMagickMemory(factors[i]);
          }
          factors=(char **) RelinquishMagickMemory(factors);
        }
      for ( ; i < MAX_COMPONENTS; i++)
      {
        jpeg_info->comp_info[i].h_samp_factor=1;
        jpeg_info->comp_info[i].v_samp_factor=1;
      }
    }
  option=GetImageOption(image_info,"jpeg:q-table");
  if (option != (const char *) NULL)
    {
      QuantizationTable
        *table;

      /*
        Custom quantization tables.
      */
      table=GetQuantizationTable(option,"0",exception);
      if (table != (QuantizationTable *) NULL)
        {
          for (i=0; i < MAX_COMPONENTS; i++)
            jpeg_info->comp_info[i].quant_tbl_no=0;
          jpeg_add_quant_table(jpeg_info,0,table->levels,
            jpeg_quality_scaling(quality),0);
          table=DestroyQuantizationTable(table);
        }
      table=GetQuantizationTable(option,"1",exception);
      if (table != (QuantizationTable *) NULL)
        {
          for (i=1; i < MAX_COMPONENTS; i++)
            jpeg_info->comp_info[i].quant_tbl_no=1;
          jpeg_add_quant_table(jpeg_info,1,table->levels,
            jpeg_quality_scaling(quality),0);
          table=DestroyQuantizationTable(table);
        }
      table=GetQuantizationTable(option,"2",exception);
      if (table != (QuantizationTable *) NULL)
        {
          for (i=2; i < MAX_COMPONENTS; i++)
            jpeg_info->comp_info[i].quant_tbl_no=2;
          jpeg_add_quant_table(jpeg_info,2,table->levels,
            jpeg_quality_scaling(quality),0);
          table=DestroyQuantizationTable(table);
        }
      table=GetQuantizationTable(option,"3",exception);
      if (table != (QuantizationTable *) NULL)
        {
          for (i=3; i < MAX_COMPONENTS; i++)
            jpeg_info->comp_info[i].quant_tbl_no=3;
          jpeg_add_quant_table(jpeg_info,3,table->levels,
            jpeg_quality_scaling(quality),0);
          table=DestroyQuantizationTable(table);
        }
    }
  jpeg_start_compress(jpeg_info,TRUE);
  if (image->debug != MagickFalse)
    {
      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: %.20g",
        (double) image->depth);
      if (image->colors != 0)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "Number of colors: %.20g",(double) image->colors);
      else
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "Number of colors: unspecified");
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "JPEG data precision: %d",(int) jpeg_info->data_precision);
      switch (jpeg_info->in_color_space)
      {
        case JCS_CMYK:
        {
          (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);
          break;
        }
        case JCS_GRAYSCALE:
        {
          (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);
          break;
        }
        case JCS_UNKNOWN:
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Colorspace: 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);
          break;
        }
        case JCS_YCbCr:
        {
          (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);
          break;
        }
        default: break;
      }
    }
  /*
    Write JPEG profiles.
  */
  value=GetImageProperty(image,"comment",exception);
  if (value != (char *) NULL)
    {
      size_t
        length;

      length=strlen(value);
      for (i=0; i < (ssize_t) length; i+=65533L)
        jpeg_write_marker(jpeg_info,JPEG_COM,(unsigned char *) value+i,
          (unsigned int) MagickMin((size_t) strlen(value+i),65533L));
    }
  if (image->profiles != (void *) NULL)
    WriteProfiles(jpeg_info,image,exception);
  /*
    Convert MIFF to JPEG raster pixels.
  */
  bytes_per_pixel=(jpeg_info->data_precision+7)/8;
  memory_info=AcquireVirtualMemory((size_t) image->columns,
    (size_t) (jpeg_info->input_components*bytes_per_pixel));
  if (memory_info == (MemoryInfo *) NULL)
    ThrowJPEGWriterException(ResourceLimitError,"MemoryAllocationFailed");
  jpeg_pixels=(JSAMPLE *) GetVirtualMemoryBlob(memory_info);
  if (setjmp(client_info->error_recovery) != 0)
    {
      jpeg_destroy_compress(jpeg_info);
      client_info=(JPEGClientInfo *) RelinquishMagickMemory(client_info);
      if (memory_info != (MemoryInfo *) NULL)
        memory_info=RelinquishVirtualMemory(memory_info);
      (void) CloseBlob(image);
      if (jps_image != (Image *) NULL)
        jps_image=DestroyImage(jps_image);
      return(MagickFalse);
    }
  range=GetQuantumRange(jpeg_info->data_precision);
  for (y=0; y < (ssize_t) image->rows; y++)
  {
    const Quantum
      *p;

    JDIMENSION
      number_scanlines = 0;

    ssize_t
      x;

    /*
      Transfer image pixel to JPEG buffer.
    */
    p=GetVirtualPixels(image,0,y,image->columns,1,exception);
    if (p == (const Quantum *) NULL)
      break;
    q=jpeg_pixels;
    switch (jpeg_info->input_components)
    {
      case 1:
      {
        /*
          Grayscale.
        */
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          JPEGSetSample(jpeg_info,ClampToQuantum(GetPixelLuma(image,p)),range,q);
          q+=(ptrdiff_t) bytes_per_pixel;
          p+=(ptrdiff_t) GetPixelChannels(image);
        }
        break;
      }
      case 4:
      {
        /*
          CMYK.
        */
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          /*
            Convert DirectClass packets to contiguous CMYK scanlines.
          */
          JPEGSetSample(jpeg_info,(Quantum) ((QuantumRange-
            GetPixelCyan(image,p))),range,q);
          q+=(ptrdiff_t) bytes_per_pixel;
          JPEGSetSample(jpeg_info,(Quantum) ((QuantumRange-
            GetPixelMagenta(image,p))),range,q);
          q+=(ptrdiff_t) bytes_per_pixel;
          JPEGSetSample(jpeg_info,(Quantum) ((QuantumRange-
            GetPixelYellow(image,p))),range,q);
          q+=(ptrdiff_t) bytes_per_pixel;
          JPEGSetSample(jpeg_info,(Quantum) ((QuantumRange-
            GetPixelBlack(image,p))),range,q);
          q+=(ptrdiff_t) bytes_per_pixel;
          p+=(ptrdiff_t) GetPixelChannels(image);
        }
        break;
      }
      default:
      {
        /*
          RGB || YCC.
        */
        for (x=0; x < (ssize_t) image->columns; x++)
        {
          JPEGSetSample(jpeg_info,GetPixelRed(image,p),range,q);
          q+=(ptrdiff_t) bytes_per_pixel;
          JPEGSetSample(jpeg_info,GetPixelGreen(image,p),range,q);
          q+=(ptrdiff_t) bytes_per_pixel;
          JPEGSetSample(jpeg_info,GetPixelBlue(image,p),range,q);
          q+=(ptrdiff_t) bytes_per_pixel;
          p+=(ptrdiff_t) GetPixelChannels(image);
        }
        break;
      }
    }
    /*
      Compress one JPEG scanline.
    */
    if (jpeg_info->data_precision > 12)
      {
#if defined(MAGICKCORE_HAVE_JPEG16_WRITE_SCANLINES)
        number_scanlines=jpeg16_write_scanlines(jpeg_info,(J16SAMPROW *)
          &jpeg_pixels,1);
#endif
      }
    else if (jpeg_info->data_precision > 8)
      {
#if defined(MAGICKCORE_HAVE_JPEG12_WRITE_SCANLINES)
        number_scanlines=jpeg12_write_scanlines(jpeg_info,(J12SAMPROW *)
          &jpeg_pixels,1);
#endif
      }
    else
      {
        number_scanlines=jpeg_write_scanlines(jpeg_info,(JSAMPROW *)
          &jpeg_pixels,1);
      }
    if (number_scanlines != 1)
      (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
        "AnErrorHasOccurredWritingToFile","`%s'",image->filename);
    status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
      image->rows);
    if (status == MagickFalse)
      break;
  }
  /*
    Relinquish resources.
  */
  jpeg_finish_compress(jpeg_info);
  jpeg_destroy_compress(jpeg_info);
  client_info=(JPEGClientInfo *) RelinquishMagickMemory(client_info);
  memory_info=RelinquishVirtualMemory(memory_info);
  if (CloseBlob(image) == MagickFalse)
    status=MagickFalse;
  if (jps_image != (Image *) NULL)
    jps_image=DestroyImage(jps_image);
  return(status);
}

static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
  Image *image,ExceptionInfo *exception)
{
  struct jpeg_compress_struct
    jpeg_info;

  return(WriteJPEGImage_(image_info,image,&jpeg_info,exception));
}
#endif
