]> granicus.if.org Git - imagemagick/blobdiff - coders/jpeg.c
The PNG encoder sometimes would write indexed PNG when grayscale was reqested.
[imagemagick] / coders / jpeg.c
index 0ba7077b3f45dda96a7c503d891b97ca1014d156..e3fe7a1d343df915a9762c554f7d2cf3957a0a89 100644 (file)
@@ -17,7 +17,7 @@
 %                                 July 1992                                   %
 %                                                                             %
 %                                                                             %
-%  Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization      %
+%  Copyright 1999-2012 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  %
 /*
   Include declarations.
 */
-#include "magick/studio.h"
-#include "magick/blob.h"
-#include "magick/blob-private.h"
-#include "magick/cache.h"
-#include "magick/color.h"
-#include "magick/colormap-private.h"
-#include "magick/color-private.h"
-#include "magick/colormap.h"
-#include "magick/colorspace.h"
-#include "magick/constitute.h"
-#include "magick/exception.h"
-#include "magick/exception-private.h"
-#include "magick/geometry.h"
-#include "magick/image.h"
-#include "magick/image-private.h"
-#include "magick/list.h"
-#include "magick/log.h"
-#include "magick/magick.h"
-#include "magick/memory_.h"
-#include "magick/module.h"
-#include "magick/monitor.h"
-#include "magick/monitor-private.h"
-#include "magick/option.h"
-#include "magick/profile.h"
-#include "magick/property.h"
-#include "magick/quantum-private.h"
-#include "magick/resource_.h"
-#include "magick/splay-tree.h"
-#include "magick/static.h"
-#include "magick/string_.h"
-#include "magick/string-private.h"
-#include "magick/utility.h"
+#include "MagickCore/studio.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/module.h"
+#include "MagickCore/monitor.h"
+#include "MagickCore/monitor-private.h"
+#include "MagickCore/option.h"
+#include "MagickCore/pixel-accessor.h"
+#include "MagickCore/profile.h"
+#include "MagickCore/property.h"
+#include "MagickCore/quantum-private.h"
+#include "MagickCore/resource_.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 <setjmp.h>
 #if defined(MAGICKCORE_JPEG_DELEGATE)
 #define JPEG_INTERNAL_OPTIONS
 #if defined(__MINGW32__)
 # define XMD_H 1  /* Avoid conflicting typedef for INT32 */
-typedef unsigned char boolean;
 #endif
 #undef HAVE_STDLIB_H
 #include "jpeglib.h"
@@ -94,7 +99,7 @@ typedef unsigned char boolean;
 #define ICC_PROFILE  "ICC_PROFILE"
 #define IPTC_MARKER  (JPEG_APP0+13)
 #define XML_MARKER  (JPEG_APP0+1)
-#define MaxBufferExtent  8192
+#define MaxBufferExtent  16384
 \f
 /*
   Typedef declarations.
@@ -114,9 +119,18 @@ typedef struct _DestinationManager
 
 typedef struct _ErrorManager
 {
+  ExceptionInfo
+    *exception;
+
   Image
     *image;
 
+  MagickBooleanType
+    finished;
+
+  StringInfo
+    *profile;
+
   jmp_buf
     error_recovery;
 } ErrorManager;
@@ -136,13 +150,30 @@ typedef struct _SourceManager
     start_of_blob;
 } SourceManager;
 #endif
+
+typedef struct _QuantizationTable
+{
+  char
+    *slot,
+    *description;
+
+  size_t
+    width,
+    height;
+
+  double
+    divisor;
+
+  unsigned int
+    *levels;
+} QuantizationTable;
 \f
 /*
   Forward declarations.
 */
 #if defined(MAGICKCORE_JPEG_DELEGATE)
 static MagickBooleanType
-  WriteJPEGImage(const ImageInfo *,Image *);
+  WriteJPEGImage(const ImageInfo *,Image *,ExceptionInfo *);
 #endif
 \f
 /*
@@ -208,45 +239,17 @@ static MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
 %
 */
 
-static MagickBooleanType EmitMessage(j_common_ptr jpeg_info,int level)
-{
-  char
-    message[JMSG_LENGTH_MAX];
-
-  ErrorManager
-    *error_manager;
-
-  Image
-    *image;
-
-  (jpeg_info->err->format_message)(jpeg_info,message);
-  error_manager=(ErrorManager *) jpeg_info->client_data;
-  image=error_manager->image;
-  if (level < 0)
-    {
-      if ((jpeg_info->err->num_warnings == 0) ||
-          (jpeg_info->err->trace_level >= 3))
-        ThrowBinaryException(CorruptImageWarning,(char *) message,
-          image->filename);
-      jpeg_info->err->num_warnings++;
-    }
-  else
-    if (jpeg_info->err->trace_level >= level)
-      ThrowBinaryException(CoderError,(char *) message,image->filename);
-  return(MagickTrue);
-}
-
 static boolean FillInputBuffer(j_decompress_ptr cinfo)
 {
   SourceManager
     *source;
 
   source=(SourceManager *) cinfo->src;
-  source->manager.bytes_in_buffer=(size_t)
-    ReadBlob(source->image,MaxBufferExtent,source->buffer);
+  source->manager.bytes_in_buffer=(size_t) ReadBlob(source->image,
+    MaxBufferExtent,source->buffer);
   if (source->manager.bytes_in_buffer == 0)
     {
-      if (source->start_of_blob != 0)
+      if (source->start_of_blob != FALSE)
         ERREXIT(cinfo,JERR_INPUT_EMPTY);
       WARNMS(cinfo,JWRN_JPEG_EOF);
       source->buffer[0]=(JOCTET) 0xff;
@@ -297,26 +300,92 @@ static MagickBooleanType IsITUFaxImage(const Image *image)
 
 static void JPEGErrorHandler(j_common_ptr jpeg_info)
 {
+  char
+    message[JMSG_LENGTH_MAX];
+
   ErrorManager
     *error_manager;
 
-  (void) EmitMessage(jpeg_info,0);
+  ExceptionInfo
+    *exception;
+
+  Image
+    *image;
+
+  *message='\0';
   error_manager=(ErrorManager *) jpeg_info->client_data;
+  image=error_manager->image;
+  exception=error_manager->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 (error_manager->finished != MagickFalse)
+    (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageWarning,
+      (char *) message,"`%s'",image->filename);
+  else
+    (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
+      (char *) message,"`%s'",image->filename);
   longjmp(error_manager->error_recovery,1);
 }
 
-static boolean ReadComment(j_decompress_ptr jpeg_info)
+static MagickBooleanType JPEGWarningHandler(j_common_ptr jpeg_info,int level)
 {
+#define JPEGExcessiveWarnings  1000
+
   char
-    *comment;
+    message[JMSG_LENGTH_MAX];
 
   ErrorManager
     *error_manager;
 
+  ExceptionInfo
+    *exception;
+
   Image
     *image;
 
-  register char
+  *message='\0';
+  error_manager=(ErrorManager *) jpeg_info->client_data;
+  exception=error_manager->exception;
+  image=error_manager->image;
+  if (level < 0)
+    {
+      /*
+        Process warning message.
+      */
+      (jpeg_info->err->format_message)(jpeg_info,message);
+      if (jpeg_info->err->num_warnings++ > JPEGExcessiveWarnings)
+        JPEGErrorHandler(jpeg_info);
+      ThrowBinaryException(CorruptImageWarning,(char *) message,
+        image->filename);
+    }
+  else
+    if ((image->debug != MagickFalse) &&
+        (level >= jpeg_info->err->trace_level))
+      {
+        /*
+          Process trace message.
+        */
+        (jpeg_info->err->format_message)(jpeg_info,message);
+        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
+          "[%s] JPEG Trace: \"%s\"",image->filename,message);
+      }
+  return(MagickTrue);
+}
+
+static boolean ReadComment(j_decompress_ptr jpeg_info)
+{
+  ErrorManager
+    *error_manager;
+
+  ExceptionInfo
+    *exception;
+
+  Image
+    *image;
+
+  register unsigned char
     *p;
 
   register ssize_t
@@ -325,32 +394,36 @@ static boolean ReadComment(j_decompress_ptr jpeg_info)
   size_t
     length;
 
+  StringInfo
+    *comment;
+
   /*
     Determine length of comment.
   */
   error_manager=(ErrorManager *) jpeg_info->client_data;
+  exception=error_manager->exception;
   image=error_manager->image;
   length=(size_t) ((size_t) GetCharacter(jpeg_info) << 8);
   length+=GetCharacter(jpeg_info);
   length-=2;
   if (length <= 0)
     return(MagickTrue);
-  comment=(char *) NULL;
-  if (~length >= MaxTextExtent)
-    comment=(char *) AcquireQuantumMemory(length+MaxTextExtent,
-      sizeof(*comment));
-  if (comment == (char *) NULL)
+  comment=BlobToStringInfo((const void *) NULL,length);
+  if (comment == (StringInfo *) NULL)
     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
       image->filename);
   /*
     Read comment.
   */
-  i=(ssize_t) length-1;
-  for (p=comment; i-- >= 0; p++)
-    *p=(char) GetCharacter(jpeg_info);
+  error_manager->profile=comment;
+  p=GetStringInfoDatum(comment);
+  for (i=0; i < (ssize_t) GetStringInfoLength(comment); i++)
+    *p++=(unsigned char) GetCharacter(jpeg_info);
   *p='\0';
-  (void) SetImageProperty(image,"comment",comment);
-  comment=DestroyString(comment);
+  error_manager->profile=NULL;
+  p=GetStringInfoDatum(comment);
+  (void) SetImageProperty(image,"comment",(const char *) p,exception);
+  comment=DestroyStringInfo(comment);
   return(MagickTrue);
 }
 
@@ -362,6 +435,9 @@ static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
   ErrorManager
     *error_manager;
 
+  ExceptionInfo
+    *exception;
+
   Image
     *image;
 
@@ -408,14 +484,17 @@ static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
   (void) GetCharacter(jpeg_info);  /* markers */
   length-=14;
   error_manager=(ErrorManager *) jpeg_info->client_data;
+  exception=error_manager->exception;
   image=error_manager->image;
-  profile=AcquireStringInfo(length);
+  profile=BlobToStringInfo((const void *) NULL,length);
   if (profile == (StringInfo *) NULL)
     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
       image->filename);
+  error_manager->profile=profile;
   p=GetStringInfoDatum(profile);
   for (i=(ssize_t) GetStringInfoLength(profile)-1; i >= 0; i--)
     *p++=(unsigned char) GetCharacter(jpeg_info);
+  error_manager->profile=NULL;
   icc_profile=(StringInfo *) GetImageProfile(image,"icc");
   if (icc_profile != (StringInfo *) NULL)
     {
@@ -424,7 +503,7 @@ static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
     }
   else
     {
-      status=SetImageProfile(image,"icc",profile);
+      status=SetImageProfile(image,"icc",profile,exception);
       profile=DestroyStringInfo(profile);
       if (status == MagickFalse)
         ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
@@ -444,6 +523,9 @@ static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
   ErrorManager
     *error_manager;
 
+  ExceptionInfo
+    *exception;
+
   Image
     *image;
 
@@ -504,14 +586,17 @@ static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
   if (length == 0)
     return(MagickTrue);
   error_manager=(ErrorManager *) jpeg_info->client_data;
+  exception=error_manager->exception;
   image=error_manager->image;
-  profile=AcquireStringInfo(length);
+  profile=BlobToStringInfo((const void *) NULL,length);
   if (profile == (StringInfo *) NULL)
     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
       image->filename);
+  error_manager->profile=profile;
   p=GetStringInfoDatum(profile);
   for (i=0;  i < (ssize_t) GetStringInfoLength(profile); i++)
     *p++=(unsigned char) GetCharacter(jpeg_info);
+  error_manager->profile=NULL;
   iptc_profile=(StringInfo *) GetImageProfile(image,"8bim");
   if (iptc_profile != (StringInfo *) NULL)
     {
@@ -520,7 +605,7 @@ static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
     }
   else
     {
-      status=SetImageProfile(image,"8bim",profile);
+      status=SetImageProfile(image,"8bim",profile,exception);
       profile=DestroyStringInfo(profile);
       if (status == MagickFalse)
         ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
@@ -537,9 +622,15 @@ static boolean ReadProfile(j_decompress_ptr jpeg_info)
   char
     name[MaxTextExtent];
 
+  const StringInfo
+    *previous_profile;
+
   ErrorManager
     *error_manager;
 
+  ExceptionInfo
+    *exception;
+
   Image
     *image;
 
@@ -570,16 +661,19 @@ static boolean ReadProfile(j_decompress_ptr jpeg_info)
     return(MagickTrue);
   length-=2;
   marker=jpeg_info->unread_marker-JPEG_APP0;
-  (void) FormatMagickString(name,MaxTextExtent,"APP%d",marker);
+  (void) FormatLocaleString(name,MaxTextExtent,"APP%d",marker);
   error_manager=(ErrorManager *) jpeg_info->client_data;
+  exception=error_manager->exception;
   image=error_manager->image;
-  profile=AcquireStringInfo(length);
+  profile=BlobToStringInfo((const void *) NULL,length);
   if (profile == (StringInfo *) NULL)
     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
       image->filename);
+  error_manager->profile=profile;
   p=GetStringInfoDatum(profile);
-  for (i=(ssize_t) GetStringInfoLength(profile)-1; i >= 0; i--)
+  for (i=0; i < (ssize_t) GetStringInfoLength(profile); i++)
     *p++=(unsigned char) GetCharacter(jpeg_info);
+  error_manager->profile=NULL;
   if (marker == 1)
     {
       p=GetStringInfoDatum(profile);
@@ -605,7 +699,23 @@ static boolean ReadProfile(j_decompress_ptr jpeg_info)
           (void) CopyMagickString(name,"xmp",MaxTextExtent);
         }
     }
-  status=SetImageProfile(image,name,profile);
+  previous_profile=GetImageProfile(image,name);
+  if (previous_profile != (const StringInfo *) NULL)
+    {
+      size_t
+        length;
+
+      length=GetStringInfoLength(profile);
+      SetStringInfoLength(profile,GetStringInfoLength(profile)+
+        GetStringInfoLength(previous_profile));
+      (void) memmove(GetStringInfoDatum(profile)+
+        GetStringInfoLength(previous_profile),GetStringInfoDatum(profile),
+        length);
+      (void) memcpy(GetStringInfoDatum(profile),
+        GetStringInfoDatum(previous_profile),
+        GetStringInfoLength(previous_profile));
+    }
+  status=SetImageProfile(image,name,profile,exception);
   profile=DestroyStringInfo(profile);
   if (status == MagickFalse)
     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
@@ -635,7 +745,7 @@ static void SkipInputData(j_decompress_ptr cinfo,long number_bytes)
 
 static void TerminateSource(j_decompress_ptr cinfo)
 {
-  cinfo=cinfo;
+  (void) cinfo;
 }
 
 static void JPEGSourceManager(j_decompress_ptr cinfo,Image *image)
@@ -794,7 +904,7 @@ static void JPEGSetImageQuality(struct jpeg_decompress_struct *jpeg_info,
   }
 }
 
-static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,  Image *image)
+static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,  Image *image,ExceptionInfo *exception)
 {
   char
     sampling_factor[MaxTextExtent];
@@ -804,7 +914,7 @@ static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,
     case JCS_CMYK:
     {
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: CMYK");
-      (void) FormatMagickString(sampling_factor,MaxTextExtent,
+      (void) FormatLocaleString(sampling_factor,MaxTextExtent,
         "%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,
@@ -819,7 +929,7 @@ static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,
     {
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),
         "Colorspace: GRAYSCALE");
-      (void) FormatMagickString(sampling_factor,MaxTextExtent,"%dx%d",
+      (void) FormatLocaleString(sampling_factor,MaxTextExtent,"%dx%d",
         jpeg_info->comp_info[0].h_samp_factor,
         jpeg_info->comp_info[0].v_samp_factor);
       break;
@@ -827,7 +937,7 @@ static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,
     case JCS_RGB:
     {
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: RGB");
-      (void) FormatMagickString(sampling_factor,MaxTextExtent,
+      (void) FormatLocaleString(sampling_factor,MaxTextExtent,
         "%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,
@@ -840,7 +950,7 @@ static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,
     {
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: %d",
         jpeg_info->out_color_space);
-      (void) FormatMagickString(sampling_factor,MaxTextExtent,
+      (void) FormatLocaleString(sampling_factor,MaxTextExtent,
         "%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,
@@ -852,7 +962,8 @@ static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,
       break;
     }
   }
-  (void) SetImageProperty(image,"jpeg:sampling-factor",sampling_factor);
+  (void) SetImageProperty(image,"jpeg:sampling-factor",sampling_factor,
+    exception);
   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Sampling Factors: %s",
     sampling_factor);
 }
@@ -872,14 +983,8 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   Image
     *image;
 
-  IndexPacket
-    index;
-
-  ssize_t
-    y;
-
   JSAMPLE
-    *jpeg_pixels;
+    *volatile jpeg_pixels;
 
   JSAMPROW
     scanline[1];
@@ -891,6 +996,9 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   MagickSizeType
     number_pixels;
 
+  Quantum
+    index;
+
   register ssize_t
     i;
 
@@ -907,6 +1015,9 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
     precision,
     units;
 
+  ssize_t
+    y;
+
   /*
     Open image file.
   */
@@ -919,7 +1030,7 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   assert(exception->signature == MagickSignature);
   debug=IsEventLogging();
   (void) debug;
-  image=AcquireImage(image_info);
+  image=AcquireImage(image_info,exception);
   status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
   if (status == MagickFalse)
     {
@@ -929,21 +1040,24 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   /*
     Initialize JPEG parameters.
   */
+  (void) ResetMagickMemory(&error_manager,0,sizeof(error_manager));
   (void) ResetMagickMemory(&jpeg_info,0,sizeof(jpeg_info));
   (void) ResetMagickMemory(&jpeg_error,0,sizeof(jpeg_error));
   jpeg_info.err=jpeg_std_error(&jpeg_error);
-  jpeg_info.err->emit_message=(void (*)(j_common_ptr,int)) EmitMessage;
+  jpeg_info.err->emit_message=(void (*)(j_common_ptr,int)) JPEGWarningHandler;
   jpeg_info.err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
   jpeg_pixels=(JSAMPLE *) NULL;
+  error_manager.exception=exception;
   error_manager.image=image;
   if (setjmp(error_manager.error_recovery) != 0)
     {
       jpeg_destroy_decompress(&jpeg_info);
+      if (error_manager.profile != (StringInfo *) NULL)
+        error_manager.profile=DestroyStringInfo(error_manager.profile);
       (void) CloseBlob(image);
       number_pixels=(MagickSizeType) image->columns*image->rows;
       if (number_pixels != 0)
         return(GetFirstImageInList(image));
-      InheritException(exception,&image->exception);
       return(DestroyImage(image));
     }
   jpeg_info.client_data=(void *) &error_manager;
@@ -960,14 +1074,6 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       (image_info->colorspace == Rec601YCbCrColorspace) ||
       (image_info->colorspace == Rec709YCbCrColorspace))
     jpeg_info.out_color_space=JCS_YCbCr;
-  if (IsITUFaxImage(image) != MagickFalse)
-    {
-      image->colorspace=LabColorspace;
-      jpeg_info.out_color_space=JCS_YCbCr;
-    }
-  else
-    if (jpeg_info.out_color_space == JCS_CMYK)
-      image->colorspace=CMYKColorspace;
   /*
     Set image resolution.
   */
@@ -975,8 +1081,8 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   if ((jpeg_info.saw_JFIF_marker != 0) && (jpeg_info.X_density != 1) &&
       (jpeg_info.Y_density != 1))
     {
-      image->x_resolution=(double) jpeg_info.X_density;
-      image->y_resolution=(double) jpeg_info.Y_density;
+      image->resolution.x=(double) jpeg_info.X_density;
+      image->resolution.y=(double) jpeg_info.Y_density;
       units=(size_t) jpeg_info.density_unit;
     }
   if (units == 1)
@@ -1040,21 +1146,18 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   image->compression=JPEGCompression;
   image->interlace=JPEGInterlace;
 #endif
-  if ((image_info->colors > 8) && (image_info->colors <= 256))
+  option=GetImageOption(image_info,"jpeg:colors");
+  if (option != (const char *) NULL)
     {
       /*
-        Let the JPEG library quantize for us.
+        Let the JPEG library quantize the image.
       */
       jpeg_info.quantize_colors=MagickTrue;
-      jpeg_info.desired_number_of_colors=(int) image_info->colors;
+      jpeg_info.desired_number_of_colors=(int) StringToUnsignedLong(option);
     }
   option=GetImageOption(image_info,"jpeg:block-smoothing");
-  if (option != (const char *) NULL)
-    {
-      jpeg_info.do_block_smoothing=MagickFalse;
-      if (IsMagickTrue(option) != MagickFalse)
-        jpeg_info.do_block_smoothing=MagickTrue;
-    }
+  jpeg_info.do_block_smoothing=IsStringTrue(option);
+  jpeg_info.dct_method=JDCT_FLOAT;
   option=GetImageOption(image_info,"jpeg:dct-method");
   if (option != (const char *) NULL)
     switch (*option)
@@ -1086,22 +1189,44 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       }
     }
   option=GetImageOption(image_info,"jpeg:fancy-upsampling");
-  if (option != (const char *) NULL)
-    {
-      jpeg_info.do_fancy_upsampling=MagickFalse;
-      if (IsMagickTrue(option) != MagickFalse)
-        jpeg_info.do_fancy_upsampling=MagickTrue;
-    }
+  jpeg_info.do_fancy_upsampling=IsStringTrue(option);
   (void) jpeg_start_decompress(&jpeg_info);
   image->columns=jpeg_info.output_width;
   image->rows=jpeg_info.output_height;
   image->depth=(size_t) jpeg_info.data_precision;
-  if (jpeg_info.out_color_space == JCS_YCbCr)
-    image->colorspace=YCbCrColorspace;
-  if (jpeg_info.out_color_space == JCS_CMYK)
-    image->colorspace=CMYKColorspace;
-  if ((image_info->colors != 0) && (image_info->colors <= 256))
-    if (AcquireImageColormap(image,image_info->colors) == MagickFalse)
+  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;
+    }
+  }
+  if (IsITUFaxImage(image) != MagickFalse)
+    {
+      (void) SetImageColorspace(image,LabColorspace,exception);
+      jpeg_info.out_color_space=JCS_YCbCr;
+    }
+  option=GetImageOption(image_info,"jpeg:colors");
+  if (option != (const char *) NULL)
+    if (AcquireImageColormap(image,StringToUnsignedLong(option),exception)
+         == MagickFalse)
       ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
   if ((jpeg_info.output_components == 1) &&
       (jpeg_info.quantize_colors == MagickFalse))
@@ -1110,7 +1235,7 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
         colors;
 
       colors=(size_t) GetQuantumRange(image->depth)+1;
-      if (AcquireImageColormap(image,colors) == MagickFalse)
+      if (AcquireImageColormap(image,colors,exception) == MagickFalse)
         ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
     }
   if (image->debug != MagickFalse)
@@ -1127,10 +1252,10 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
         (int) jpeg_info.output_width,(int) jpeg_info.output_height);
     }
   JPEGSetImageQuality(&jpeg_info,image);
-  JPEGSetImageSamplingFactor(&jpeg_info,image);
-  (void) FormatMagickString(value,MaxTextExtent,"%.20g",(double)
+  JPEGSetImageSamplingFactor(&jpeg_info,image,exception);
+  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
     jpeg_info.out_color_space);
-  (void) SetImageProperty(image,"jpeg:colorspace",value);
+  (void) SetImageProperty(image,"jpeg:colorspace",value,exception);
   if (image_info->ping != MagickFalse)
     {
       jpeg_destroy_decompress(&jpeg_info);
@@ -1146,8 +1271,8 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   */
   if (setjmp(error_manager.error_recovery) != 0)
     {
-      if (jpeg_pixels != (unsigned char *) NULL)
-        jpeg_pixels=(unsigned char *) RelinquishMagickMemory(jpeg_pixels);
+      if (jpeg_pixels != (JSAMPLE *) NULL)
+        jpeg_pixels=(JSAMPLE *) RelinquishMagickMemory(jpeg_pixels);
       jpeg_destroy_decompress(&jpeg_info);
       (void) CloseBlob(image);
       number_pixels=(MagickSizeType) image->columns*image->rows;
@@ -1161,30 +1286,31 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       if (jpeg_info.out_color_space == JCS_GRAYSCALE)
         for (i=0; i < (ssize_t) image->colors; i++)
         {
-          image->colormap[i].red=ScaleCharToQuantum(jpeg_info.colormap[0][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].opacity=OpaqueOpacity;
+          image->colormap[i].alpha=OpaqueAlpha;
         }
       else
         for (i=0; i < (ssize_t) image->colors; i++)
         {
-          image->colormap[i].red=ScaleCharToQuantum(jpeg_info.colormap[0][i]);
-          image->colormap[i].green=ScaleCharToQuantum(jpeg_info.colormap[1][i]);
-          image->colormap[i].blue=ScaleCharToQuantum(jpeg_info.colormap[2][i]);
-          image->colormap[i].opacity=OpaqueOpacity;
+          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=OpaqueAlpha;
         }
     }
   scanline[0]=(JSAMPROW) jpeg_pixels;
   for (y=0; y < (ssize_t) image->rows; y++)
   {
-    register IndexPacket
-      *restrict indexes;
-
     register ssize_t
       x;
 
-    register PixelPacket
+    register Quantum
       *restrict q;
 
     if (jpeg_read_scanlines(&jpeg_info,scanline,1) != 1)
@@ -1195,9 +1321,8 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       }
     p=jpeg_pixels;
     q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
-    if (q == (PixelPacket *) NULL)
+    if (q == (Quantum *) NULL)
       break;
-    indexes=GetAuthenticIndexQueue(image);
     if (jpeg_info.data_precision > 8)
       {
         if (jpeg_info.output_components == 1)
@@ -1210,83 +1335,98 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
               pixel=(size_t) GETJSAMPLE(*p);
             else
               pixel=(size_t) ((GETJSAMPLE(*p) ^ 0x80) << 4);
-            index=ConstrainColormapIndex(image,pixel);
-            indexes[x]=index;
-            *q++=image->colormap[(int) index];
+            index=ConstrainColormapIndex(image,pixel,exception);
+            SetPixelIndex(image,index,q);
+            SetPixelInfoPixel(image,image->colormap+(ssize_t) index,q);
             p++;
+            q+=GetPixelChannels(image);
           }
         else
           if (image->colorspace != CMYKColorspace)
             for (x=0; x < (ssize_t) image->columns; x++)
             {
-              q->red=ScaleShortToQuantum((unsigned char)
-                (GETJSAMPLE(*p++) << 4));
-              q->green=ScaleShortToQuantum((unsigned char)
-                (GETJSAMPLE(*p++) << 4));
-              q->blue=ScaleShortToQuantum((unsigned char)
-                (GETJSAMPLE(*p++) << 4));
-              SetOpacityPixelComponent(q,OpaqueOpacity);
-              q++;
+              SetPixelRed(image,ScaleShortToQuantum((unsigned char)
+                (GETJSAMPLE(*p++) << 4)),q);
+              SetPixelGreen(image,ScaleShortToQuantum((unsigned char)
+                (GETJSAMPLE(*p++) << 4)),q);
+              SetPixelBlue(image,ScaleShortToQuantum((unsigned char)
+                (GETJSAMPLE(*p++) << 4)),q);
+              SetPixelAlpha(image,OpaqueAlpha,q);
+              q+=GetPixelChannels(image);
             }
           else
             for (x=0; x < (ssize_t) image->columns; x++)
             {
-              q->red=(Quantum) QuantumRange-ScaleShortToQuantum((unsigned char)
-                (GETJSAMPLE(*p++) << 4));
-              q->green=(Quantum) QuantumRange-ScaleShortToQuantum(
-                (unsigned char) (GETJSAMPLE(*p++) << 4));
-              q->blue=(Quantum) QuantumRange-ScaleShortToQuantum((unsigned char)
-                (GETJSAMPLE(*p++) << 4));
-              SetOpacityPixelComponent(q,OpaqueOpacity);
-              indexes[x]=(IndexPacket) QuantumRange-ScaleShortToQuantum(
-                (unsigned char) (GETJSAMPLE(*p++) << 4));
-              q++;
+              SetPixelCyan(image,QuantumRange-ScaleShortToQuantum(
+                (unsigned char) (GETJSAMPLE(*p++) << 4)),q);
+              SetPixelMagenta(image,QuantumRange-ScaleShortToQuantum(
+                (unsigned char) (GETJSAMPLE(*p++) << 4)),q);
+              SetPixelYellow(image,QuantumRange-ScaleShortToQuantum(
+                (unsigned char) (GETJSAMPLE(*p++) << 4)),q);
+              SetPixelBlack(image,QuantumRange-ScaleShortToQuantum(
+                (unsigned char) (GETJSAMPLE(*p++) << 4)),q);
+              SetPixelAlpha(image,OpaqueAlpha,q);
+              q+=GetPixelChannels(image);
             }
       }
     else
       if (jpeg_info.output_components == 1)
         for (x=0; x < (ssize_t) image->columns; x++)
         {
-          index=ConstrainColormapIndex(image,(size_t) GETJSAMPLE(*p));
-          indexes[x]=(IndexPacket) index;
-          *q++=image->colormap[(int) index];
+          index=ConstrainColormapIndex(image,(size_t) GETJSAMPLE(*p),exception);
+          SetPixelIndex(image,index,q);
+          SetPixelInfoPixel(image,image->colormap+(ssize_t) index,q);
           p++;
+          q+=GetPixelChannels(image);
         }
       else
         if (image->colorspace != CMYKColorspace)
           for (x=0; x < (ssize_t) image->columns; x++)
           {
-            q->red=ScaleCharToQuantum((unsigned char) GETJSAMPLE(*p++));
-            q->green=ScaleCharToQuantum((unsigned char) GETJSAMPLE(*p++));
-            q->blue=ScaleCharToQuantum((unsigned char) GETJSAMPLE(*p++));
-            SetOpacityPixelComponent(q,OpaqueOpacity);
-            q++;
+            SetPixelRed(image,ScaleCharToQuantum((unsigned char)
+              GETJSAMPLE(*p++)),q);
+            SetPixelGreen(image,ScaleCharToQuantum((unsigned char)
+              GETJSAMPLE(*p++)),q);
+            SetPixelBlue(image,ScaleCharToQuantum((unsigned char)
+              GETJSAMPLE(*p++)),q);
+            SetPixelAlpha(image,OpaqueAlpha,q);
+            q+=GetPixelChannels(image);
           }
         else
           for (x=0; x < (ssize_t) image->columns; x++)
           {
-            q->red=(Quantum) QuantumRange-ScaleCharToQuantum((unsigned char)
-              GETJSAMPLE(*p++));
-            q->green=(Quantum) QuantumRange-ScaleCharToQuantum((unsigned char)
-              GETJSAMPLE(*p++));
-            q->blue=(Quantum) QuantumRange-ScaleCharToQuantum((unsigned char)
-              GETJSAMPLE(*p++));
-            SetOpacityPixelComponent(q,OpaqueOpacity);
-            indexes[x]=(IndexPacket) QuantumRange-ScaleCharToQuantum(
-              (unsigned char) GETJSAMPLE(*p++));
-            q++;
+            SetPixelCyan(image,QuantumRange-ScaleCharToQuantum(
+              (unsigned char) GETJSAMPLE(*p++)),q);
+            SetPixelMagenta(image,QuantumRange-ScaleCharToQuantum(
+              (unsigned char) GETJSAMPLE(*p++)),q);
+            SetPixelYellow(image,QuantumRange-ScaleCharToQuantum(
+              (unsigned char) GETJSAMPLE(*p++)),q);
+            SetPixelBlack(image,QuantumRange-ScaleCharToQuantum(
+              (unsigned char) GETJSAMPLE(*p++)),q);
+            SetPixelAlpha(image,OpaqueAlpha,q);
+            q+=GetPixelChannels(image);
           }
     if (SyncAuthenticPixels(image,exception) == MagickFalse)
       break;
-    if (SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,image->rows) == MagickFalse)
-      break;
+    status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
+      image->rows);
+    if (status == MagickFalse)
+      {
+        jpeg_abort_decompress(&jpeg_info);
+        break;
+      }
   }
+  if (status != MagickFalse)
+    {
+      error_manager.finished=MagickTrue;
+      if (setjmp(error_manager.error_recovery) == 0)
+        (void) jpeg_finish_decompress(&jpeg_info);
+    }
   /*
     Free jpeg resources.
   */
-  (void) jpeg_finish_decompress(&jpeg_info);
   jpeg_destroy_decompress(&jpeg_info);
-  jpeg_pixels=(unsigned char *) RelinquishMagickMemory(jpeg_pixels);
+  jpeg_pixels=(JSAMPLE *) RelinquishMagickMemory(jpeg_pixels);
   (void) CloseBlob(image);
   return(GetFirstImageInList(image));
 }
@@ -1328,7 +1468,7 @@ ModuleExport size_t RegisterJPEGImage(void)
 
   *version='\0';
 #if defined(JPEG_LIB_VERSION)
-  (void) FormatMagickString(version,MaxTextExtent,"%d",JPEG_LIB_VERSION);
+  (void) FormatLocaleString(version,MaxTextExtent,"%d",JPEG_LIB_VERSION);
 #endif
   entry=SetMagickInfo("JPEG");
   entry->thread_support=NoThreadSupport;
@@ -1414,7 +1554,8 @@ ModuleExport void UnregisterJPEGImage(void)
 %
 %  The format of the WriteJPEGImage method is:
 %
-%      MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,Image *image)
+%      MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
+%        Image *image,ExceptionInfo *exception)
 %
 %  A description of each parameter follows:
 %
@@ -1422,9 +1563,23 @@ ModuleExport void UnregisterJPEGImage(void)
 %
 %    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 cinfo)
 {
   DestinationManager
@@ -1439,6 +1594,203 @@ static boolean EmptyOutputBuffer(j_compress_ptr cinfo)
   return(TRUE);
 }
 
+static QuantizationTable *GetQuantizationTable(const char *filename,
+  const char *slot,ExceptionInfo *exception)
+{
+  char
+    *p,
+    *xml;
+
+  const char
+    *attribute,
+    *content;
+
+  double
+    value;
+
+  register ssize_t
+    i;
+
+  ssize_t
+    j;
+
+  QuantizationTable
+    *table;
+
+  size_t
+    length;
+
+  XMLTreeInfo
+    *description,
+    *levels,
+    *quantization_tables,
+    *table_iterator;
+
+  (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
+    "Loading quantization tables \"%s\" ...",filename);
+  table=(QuantizationTable *) NULL;
+  xml=FileToString(filename,~0,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 *) AcquireMagickMemory(sizeof(*table));
+  if (table == (QuantizationTable *) NULL)
+    ThrowFatalException(ResourceLimitFatalError,
+      "UnableToAcquireQuantizationTable");
+  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 cinfo)
 {
   DestinationManager
@@ -1492,14 +1844,12 @@ static void WriteProfile(j_compress_ptr jpeg_info,Image *image)
     i;
 
   size_t
-    length;
+    length,
+    tag_length;
 
   StringInfo
     *custom_profile;
 
-  size_t
-    tag_length;
-
   /*
     Save image profile as a APP marker.
   */
@@ -1578,7 +1928,7 @@ static void WriteProfile(j_compress_ptr jpeg_info,Image *image)
         /*
           Add namespace to XMP profile.
         */
-        xmp_profile=StringToStringInfo("http://ns.adobe.com/xap/1.0/");
+        xmp_profile=StringToStringInfo("http://ns.adobe.com/xap/1.0/ ");
         ConcatenateStringInfo(xmp_profile,profile);
         GetStringInfoDatum(xmp_profile)[28]='\0';
         for (i=0; i < (ssize_t) GetStringInfoLength(xmp_profile); i+=65533L)
@@ -1660,7 +2010,7 @@ static char **SamplingFactorToList(const char *text)
 }
 
 static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
-  Image *image)
+  Image *image,ExceptionInfo *exception)
 {
   const char
     *option,
@@ -1670,15 +2020,15 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   ErrorManager
     error_manager;
 
+  int
+    quality;
+
   JSAMPLE
-    *jpeg_pixels;
+    *volatile jpeg_pixels;
 
   JSAMPROW
     scanline[1];
 
-  ssize_t
-    y;
-
   MagickBooleanType
     status;
 
@@ -1688,6 +2038,9 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   register ssize_t
     i;
 
+  ssize_t
+    y;
+
   struct jpeg_compress_struct
     jpeg_info;
 
@@ -1703,17 +2056,22 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   assert(image->signature == MagickSignature);
   if (image->debug != MagickFalse)
     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
-  status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
+  assert(exception != (ExceptionInfo *) NULL);
+  assert(exception->signature == MagickSignature);
+  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
   if (status == MagickFalse)
     return(status);
   /*
     Initialize JPEG parameters.
   */
+  (void) ResetMagickMemory(&error_manager,0,sizeof(error_manager));
   (void) ResetMagickMemory(&jpeg_info,0,sizeof(jpeg_info));
   (void) ResetMagickMemory(&jpeg_error,0,sizeof(jpeg_error));
   jpeg_info.client_data=(void *) image;
   jpeg_info.err=jpeg_std_error(&jpeg_error);
-  jpeg_info.err->emit_message=(void (*)(j_common_ptr,int)) EmitMessage;
+  jpeg_info.err->emit_message=(void (*)(j_common_ptr,int)) JPEGWarningHandler;
+  jpeg_info.err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
+  error_manager.exception=exception;
   error_manager.image=image;
   jpeg_pixels=(JSAMPLE *) NULL;
   if (setjmp(error_manager.error_recovery) != 0)
@@ -1758,13 +2116,13 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
     }
     default:
     {
-      if (image->colorspace != RGBColorspace)
-        (void) TransformImageColorspace(image,RGBColorspace);
+      if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
+        (void) TransformImageColorspace(image,sRGBColorspace,exception);
       break;
     }
   }
   if ((image_info->type != TrueColorType) &&
-      (IsGrayImage(image,&image->exception) != MagickFalse))
+      (IsImageGray(image,exception) != MagickFalse))
     {
       jpeg_info.input_components=1;
       jpeg_info.in_color_space=JCS_GRAYSCALE;
@@ -1778,21 +2136,22 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   jpeg_info.density_unit=(UINT8) 1;
   if (image->debug != MagickFalse)
     (void) LogMagickEvent(CoderEvent,GetMagickModule(),
-      "Image resolution: %.20g,%.20g",floor(image->x_resolution+0.5),
-      floor(image->y_resolution+0.5));
-  if ((image->x_resolution != 0.0) && (image->y_resolution != 0.0))
+      "Image resolution: %.20g,%.20g",floor(image->resolution.x+0.5),
+      floor(image->resolution.y+0.5));
+  if ((image->resolution.x != 0.0) && (image->resolution.y != 0.0))
     {
       /*
         Set image resolution.
       */
       jpeg_info.write_JFIF_header=MagickTrue;
-      jpeg_info.X_density=(UINT16) floor(image->x_resolution+0.5);
-      jpeg_info.Y_density=(UINT16) floor(image->y_resolution+0.5);
+      jpeg_info.X_density=(UINT16) floor(image->resolution.x+0.5);
+      jpeg_info.Y_density=(UINT16) floor(image->resolution.y+0.5);
       if (image->units == PixelsPerInchResolution)
         jpeg_info.density_unit=(UINT8) 1;
       if (image->units == PixelsPerCentimeterResolution)
         jpeg_info.density_unit=(UINT8) 2;
     }
+  jpeg_info.dct_method=JDCT_FLOAT;
   option=GetImageOption(image_info,"jpeg:dct-method");
   if (option != (const char *) NULL)
     switch (*option)
@@ -1825,11 +2184,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
     }
   option=GetImageOption(image_info,"jpeg:optimize-coding");
   if (option != (const char *) NULL)
-    {
-      jpeg_info.optimize_coding=MagickFalse;
-      if (IsMagickTrue(option) != MagickFalse)
-        jpeg_info.optimize_coding=MagickTrue;
-    }
+    jpeg_info.optimize_coding=IsStringTrue(option);
   else
     {
       MagickSizeType
@@ -1843,9 +2198,8 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
             Perform optimization only if available memory resources permit it.
           */
           status=AcquireMagickResource(MemoryResource,length);
-          if (status != MagickFalse)
-            jpeg_info.optimize_coding=MagickTrue;
           RelinquishMagickResource(MemoryResource,length);
+          jpeg_info.optimize_coding=status;
         }
     }
 #if (JPEG_LIB_VERSION >= 61) && defined(C_PROGRESSIVE_SUPPORTED)
@@ -1876,7 +2230,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
         *jpeg_info;
 
       jpeg_info=CloneImageInfo(image_info);
-      jpeg_image=CloneImage(image,0,0,MagickTrue,&image->exception);
+      jpeg_image=CloneImage(image,0,0,MagickTrue,exception);
       if (jpeg_image != (Image *) NULL)
         {
           MagickSizeType
@@ -1890,14 +2244,14 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
             Search for compression quality that does not exceed image extent.
           */
           jpeg_info->quality=0;
-          extent=(MagickSizeType) SiPrefixToDouble(option,100.0);
+          extent=(MagickSizeType) SiPrefixToDoubleInterval(option,100.0);
           (void) DeleteImageOption(jpeg_info,"jpeg:extent");
           (void) AcquireUniqueFilename(jpeg_image->filename);
           maximum=101;
           for (minimum=0; minimum != maximum; )
           {
             jpeg_image->quality=minimum+(maximum-minimum)/2;
-            status=WriteJPEGImage(jpeg_info,jpeg_image);
+            status=WriteJPEGImage(jpeg_info,jpeg_image,exception);
             if (GetBlobSize(jpeg_image) <= extent)
               minimum=jpeg_image->quality+1;
             else
@@ -1909,13 +2263,12 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
         }
       jpeg_info=DestroyImageInfo(jpeg_info);
     }
+  quality=92;
   if ((image_info->compression != LosslessJPEGCompression) &&
       (image->quality <= 100))
     {
-      if (image->quality == UndefinedCompressionQuality)
-        jpeg_set_quality(&jpeg_info,92,MagickTrue);
-      else
-        jpeg_set_quality(&jpeg_info,(int) image->quality,MagickTrue);
+      if (image->quality != UndefinedCompressionQuality)
+        quality=(int) image->quality;
       if (image->debug != MagickFalse)
         (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: %.20g",
           (double) image->quality);
@@ -1923,13 +2276,13 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   else
     {
 #if !defined(C_LOSSLESS_SUPPORTED)
-      jpeg_set_quality(&jpeg_info,100,MagickTrue);
+      quality=100;
       if (image->debug != MagickFalse)
         (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: 100");
 #else
       if (image->quality < 100)
-        (void) ThrowMagickException(&image->exception,GetMagickModule(),
-          CoderWarning,"LosslessToLossyJPEGConversion",image->filename);
+        (void) ThrowMagickException(exception,GetMagickModule(),CoderWarning,
+          "LosslessToLossyJPEGConversion",image->filename);
       else
         {
           int
@@ -1951,8 +2304,35 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
         }
 #endif
     }
+  jpeg_set_quality(&jpeg_info,quality,MagickTrue);
+#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,MagickTrue);
+        }
+    }
+#endif
   sampling_factor=(const char *) NULL;
-  value=GetImageProperty(image,"jpeg:sampling-factor");
+  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;
@@ -2014,6 +2394,44 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
       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)
+        {
+          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)
+        {
+          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)
+        {
+          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)
+        {
+          jpeg_add_quant_table(&jpeg_info,3,table->levels,jpeg_quality_scaling(
+            quality),0);
+          table=DestroyQuantizationTable(table);
+        }
+    }
   jpeg_start_compress(&jpeg_info,MagickTrue);
   if (image->debug != MagickFalse)
     {
@@ -2134,7 +2552,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   /*
     Write JPEG profiles.
   */
-  value=GetImageProperty(image,"comment");
+  value=GetImageProperty(image,"comment",exception);
   if (value != (char *) NULL)
     for (i=0; i < (ssize_t) strlen(value); i+=65533L)
       jpeg_write_marker(&jpeg_info,JPEG_COM,(unsigned char *) value+i,
@@ -2151,8 +2569,8 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   if (setjmp(error_manager.error_recovery) != 0)
     {
       jpeg_destroy_compress(&jpeg_info);
-      if (jpeg_pixels != (unsigned char *) NULL)
-        jpeg_pixels=(unsigned char *) RelinquishMagickMemory(jpeg_pixels);
+      if (jpeg_pixels != (JSAMPLE *) NULL)
+        jpeg_pixels=(JSAMPLE *) RelinquishMagickMemory(jpeg_pixels);
       (void) CloseBlob(image);
       return(MagickFalse);
     }
@@ -2163,84 +2581,86 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
           (jpeg_info.in_color_space == JCS_YCbCr))
         for (y=0; y < (ssize_t) image->rows; y++)
         {
-          register const PixelPacket
+          register const Quantum
             *p;
 
           register ssize_t
             x;
 
-          p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception);
-          if (p == (const PixelPacket *) NULL)
+          p=GetVirtualPixels(image,0,y,image->columns,1,exception);
+          if (p == (const Quantum *) NULL)
             break;
           q=jpeg_pixels;
           for (x=0; x < (ssize_t) image->columns; x++)
           {
-            *q++=(JSAMPLE) ScaleQuantumToChar(GetRedPixelComponent(p));
-            *q++=(JSAMPLE) ScaleQuantumToChar(GetGreenPixelComponent(p));
-            *q++=(JSAMPLE) ScaleQuantumToChar(GetBluePixelComponent(p));
-            p++;
+            *q++=(JSAMPLE) ScaleQuantumToChar(GetPixelRed(image,p));
+            *q++=(JSAMPLE) ScaleQuantumToChar(GetPixelGreen(image,p));
+            *q++=(JSAMPLE) ScaleQuantumToChar(GetPixelBlue(image,p));
+            p+=GetPixelChannels(image);
           }
           (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
-          if (SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,image->rows) == MagickFalse)
+          status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
+            image->rows);
+          if (status == MagickFalse)
             break;
         }
       else
         if (jpeg_info.in_color_space == JCS_GRAYSCALE)
           for (y=0; y < (ssize_t) image->rows; y++)
           {
-            register const PixelPacket
+            register const Quantum
               *p;
 
             register ssize_t
               x;
 
-            p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception);
-            if (p == (const PixelPacket *) NULL)
+            p=GetVirtualPixels(image,0,y,image->columns,1,exception);
+            if (p == (const Quantum *) NULL)
               break;
             q=jpeg_pixels;
             for (x=0; x < (ssize_t) image->columns; x++)
             {
-              *q++=(JSAMPLE) ScaleQuantumToChar(PixelIntensityToQuantum(p));
-              p++;
+              *q++=(JSAMPLE) ScaleQuantumToChar(GetPixelIntensity(image,p));
+              p+=GetPixelChannels(image);
             }
             (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
-            if (SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,image->rows) == MagickFalse)
+            status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
+              image->rows);
+            if (status == MagickFalse)
               break;
           }
         else
           for (y=0; y < (ssize_t) image->rows; y++)
           {
-            register const IndexPacket
-              *indexes;
-
-            register const PixelPacket
+            register const Quantum
               *p;
 
             register ssize_t
               x;
 
-            p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception);
-            if (p == (const PixelPacket *) NULL)
+            p=GetVirtualPixels(image,0,y,image->columns,1,exception);
+            if (p == (const Quantum *) NULL)
               break;
             q=jpeg_pixels;
-            indexes=GetVirtualIndexQueue(image);
             for (x=0; x < (ssize_t) image->columns; x++)
             {
               /*
                 Convert DirectClass packets to contiguous CMYK scanlines.
               */
               *q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
-                GetRedPixelComponent(p))));
+                GetPixelRed(image,p))));
               *q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
-                GetGreenPixelComponent(p))));
+                GetPixelGreen(image,p))));
               *q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
-                GetBluePixelComponent(p))));
+                GetPixelBlue(image,p))));
               *q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
-                indexes[x])));
-              p++;
+                GetPixelBlack(image,p))));
+              p+=GetPixelChannels(image);
             }
             (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
-            if (SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,image->rows) == MagickFalse)
+            status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
+              image->rows);
+            if (status == MagickFalse)
               break;
           }
     }
@@ -2248,24 +2668,25 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
     if (jpeg_info.in_color_space == JCS_GRAYSCALE)
       for (y=0; y < (ssize_t) image->rows; y++)
       {
-        register const PixelPacket
+        register const Quantum
           *p;
 
         register ssize_t
           x;
 
-        p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception);
-        if (p == (const PixelPacket *) NULL)
+        p=GetVirtualPixels(image,0,y,image->columns,1,exception);
+        if (p == (const Quantum *) NULL)
           break;
         q=jpeg_pixels;
         for (x=0; x < (ssize_t) image->columns; x++)
         {
-          *q++=(JSAMPLE) (ScaleQuantumToShort(PixelIntensityToQuantum(p)) >>
-            4);
-          p++;
+          *q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelIntensity(image,p)) >> 4);
+          p+=GetPixelChannels(image);
         }
         (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
-        if (SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,image->rows) == MagickFalse)
+        status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
+          image->rows);
+        if (status == MagickFalse)
           break;
       }
     else
@@ -2273,66 +2694,60 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
           (jpeg_info.in_color_space == JCS_YCbCr))
         for (y=0; y < (ssize_t) image->rows; y++)
         {
-          register const PixelPacket
+          register const Quantum
             *p;
 
           register ssize_t
             x;
 
-          p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception);
-          if (p == (const PixelPacket *) NULL)
+          p=GetVirtualPixels(image,0,y,image->columns,1,exception);
+          if (p == (const Quantum *) NULL)
             break;
           q=jpeg_pixels;
           for (x=0; x < (ssize_t) image->columns; x++)
           {
-            *q++=(JSAMPLE) (ScaleQuantumToShort(GetRedPixelComponent(p)) >>
-              4);
-            *q++=(JSAMPLE) (ScaleQuantumToShort(GetGreenPixelComponent(p)) >>
-              4);
-            *q++=(JSAMPLE) (ScaleQuantumToShort(GetBluePixelComponent(p)) >>
-              4);
-            p++;
+            *q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelRed(image,p)) >> 4);
+            *q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelGreen(image,p)) >> 4);
+            *q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelBlue(image,p)) >> 4);
+            p+=GetPixelChannels(image);
           }
           (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
           status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
-                image->rows);
+            image->rows);
           if (status == MagickFalse)
             break;
         }
       else
         for (y=0; y < (ssize_t) image->rows; y++)
         {
-          register const IndexPacket
-            *indexes;
-
-          register const PixelPacket
+          register const Quantum
             *p;
 
           register ssize_t
             x;
 
-          p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception);
-          if (p == (const PixelPacket *) NULL)
+          p=GetVirtualPixels(image,0,y,image->columns,1,exception);
+          if (p == (const Quantum *) NULL)
             break;
           q=jpeg_pixels;
-          indexes=GetVirtualIndexQueue(image);
           for (x=0; x < (ssize_t) image->columns; x++)
           {
             /*
               Convert DirectClass packets to contiguous CMYK scanlines.
             */
             *q++=(JSAMPLE) (4095-(ScaleQuantumToShort(
-              GetRedPixelComponent(p)) >> 4));
+              GetPixelRed(image,p)) >> 4));
             *q++=(JSAMPLE) (4095-(ScaleQuantumToShort(
-              GetGreenPixelComponent(p)) >> 4));
+              GetPixelGreen(image,p)) >> 4));
             *q++=(JSAMPLE) (4095-(ScaleQuantumToShort(
-              GetBluePixelComponent(p)) >> 4));
-            *q++=(JSAMPLE) (4095-(ScaleQuantumToShort(indexes[x]) >> 4));
-            p++;
+              GetPixelBlue(image,p)) >> 4));
+            *q++=(JSAMPLE) (4095-(ScaleQuantumToShort(
+              GetPixelBlack(image,p)) >> 4));
+            p+=GetPixelChannels(image);
           }
           (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
           status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
-                image->rows);
+            image->rows);
           if (status == MagickFalse)
             break;
         }
@@ -2342,7 +2757,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
     Relinquish resources.
   */
   jpeg_destroy_compress(&jpeg_info);
-  jpeg_pixels=(unsigned char *) RelinquishMagickMemory(jpeg_pixels);
+  jpeg_pixels=(JSAMPLE *) RelinquishMagickMemory(jpeg_pixels);
   (void) CloseBlob(image);
   return(MagickTrue);
 }