]> granicus.if.org Git - imagemagick/blobdiff - coders/jpeg.c
https://github.com/ImageMagick/ImageMagick/issues/631
[imagemagick] / coders / jpeg.c
index 0c85022f30358f2d2fc12a9203a739d642835907..c3a813a6e593f8d3f0e9cbeda398089a388369c2 100644 (file)
@@ -23,7 +23,7 @@
 %  You may not use this file except in compliance with the License.  You may  %
 %  obtain a copy of the License at                                            %
 %                                                                             %
-%    http://www.imagemagick.org/script/license.php                            %
+%    https://www.imagemagick.org/script/license.php                           %
 %                                                                             %
 %  Unless required by applicable law or agreed to in writing, software        %
 %  distributed under the License is distributed on an "AS IS" BASIS,          %
 #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/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"
@@ -93,8 +95,7 @@
 #include "jpeglib.h"
 #include "jerror.h"
 #endif
-
-
+\f
 /*
   Define declarations.
 */
 #define IPTC_MARKER  (JPEG_APP0+13)
 #define XML_MARKER  (JPEG_APP0+1)
 #define MaxBufferExtent  16384
-
-
+\f
 /*
   Typedef declarations.
 */
@@ -171,8 +171,7 @@ typedef struct _QuantizationTable
   unsigned int
     *levels;
 } QuantizationTable;
-
-
+\f
 /*
   Forward declarations.
 */
@@ -180,8 +179,7 @@ typedef struct _QuantizationTable
 static MagickBooleanType
   WriteJPEGImage(const ImageInfo *,Image *,ExceptionInfo *);
 #endif
-
-
+\f
 /*
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %                                                                             %
@@ -215,8 +213,7 @@ static MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
     return(MagickTrue);
   return(MagickFalse);
 }
-
-
+\f
 #if defined(MAGICKCORE_JPEG_DELEGATE)
 /*
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -362,10 +359,9 @@ static MagickBooleanType JPEGWarningHandler(j_common_ptr jpeg_info,int level)
         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);
+      if (jpeg_info->err->num_warnings++ < JPEGExcessiveWarnings)
+        ThrowBinaryException(CorruptImageWarning,(char *) message,
+          image->filename);
     }
   else
     if ((image->debug != MagickFalse) &&
@@ -412,9 +408,9 @@ static boolean ReadComment(j_decompress_ptr jpeg_info)
   image=error_manager->image;
   length=(size_t) ((size_t) GetCharacter(jpeg_info) << 8);
   length+=GetCharacter(jpeg_info);
-  length-=2;
-  if (length <= 0)
+  if (length <= 2)
     return(TRUE);
+  length-=2;
   comment=BlobToStringInfo((const void *) NULL,length);
   if (comment == (StringInfo *) NULL)
     {
@@ -519,8 +515,11 @@ static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
       status=SetImageProfile(image,"icc",profile,exception);
       profile=DestroyStringInfo(profile);
       if (status == MagickFalse)
-        ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
-          image->filename);
+        {
+          (void) ThrowMagickException(exception,GetMagickModule(),
+            ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
+          return(FALSE);
+        }
     }
   if (image->debug != MagickFalse)
     (void) LogMagickEvent(CoderEvent,GetMagickModule(),
@@ -531,7 +530,7 @@ static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
 static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
 {
   char
-    magick[MaxTextExtent];
+    magick[MagickPathExtent];
 
   ErrorManager
     *error_manager;
@@ -576,9 +575,9 @@ static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
   for (i=0; i < 10; i++)
     magick[i]=(char) GetCharacter(jpeg_info);
   magick[10]='\0';
+  length-=10;
   if (length <= 10)
     return(TRUE);
-  length-=10;
   if (LocaleCompare(magick,"Photoshop ") != 0)
     {
       /*
@@ -593,11 +592,9 @@ static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
   */
   for (i=0; i < 4; i++)
     (void) GetCharacter(jpeg_info);
-  if (length <= 4)
-    return(MagickTrue);
+  if (length <= 11)
+    return(TRUE);
   length-=4;
-  if (length == 0)
-    return(MagickTrue);
   error_manager=(ErrorManager *) jpeg_info->client_data;
   exception=error_manager->exception;
   image=error_manager->image;
@@ -624,8 +621,11 @@ static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
       status=SetImageProfile(image,"8bim",profile,exception);
       profile=DestroyStringInfo(profile);
       if (status == MagickFalse)
-        ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
-          image->filename);
+        {
+          (void) ThrowMagickException(exception,GetMagickModule(),
+            ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
+          return(FALSE);
+        }
     }
   if (image->debug != MagickFalse)
     (void) LogMagickEvent(CoderEvent,GetMagickModule(),
@@ -636,7 +636,7 @@ static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
 static boolean ReadProfile(j_decompress_ptr jpeg_info)
 {
   char
-    name[MaxTextExtent];
+    name[MagickPathExtent];
 
   const StringInfo
     *previous_profile;
@@ -677,7 +677,7 @@ static boolean ReadProfile(j_decompress_ptr jpeg_info)
     return(TRUE);
   length-=2;
   marker=jpeg_info->unread_marker-JPEG_APP0;
-  (void) FormatLocaleString(name,MaxTextExtent,"APP%d",marker);
+  (void) FormatLocaleString(name,MagickPathExtent,"APP%d",marker);
   error_manager=(ErrorManager *) jpeg_info->client_data;
   exception=error_manager->exception;
   image=error_manager->image;
@@ -697,7 +697,7 @@ static boolean ReadProfile(j_decompress_ptr jpeg_info)
     {
       p=GetStringInfoDatum(profile);
       if ((length > 4) && (LocaleNCompare((char *) p,"exif",4) == 0))
-        (void) CopyMagickString(name,"exif",MaxTextExtent);
+        (void) CopyMagickString(name,"exif",MagickPathExtent);
       if ((length > 5) && (LocaleNCompare((char *) p,"http:",5) == 0))
         {
           ssize_t
@@ -715,21 +715,21 @@ static boolean ReadProfile(j_decompress_ptr jpeg_info)
           }
           if (j < (ssize_t) GetStringInfoLength(profile))
             (void) DestroyStringInfo(SplitStringInfo(profile,(size_t) (j+1)));
-          (void) CopyMagickString(name,"xmp",MaxTextExtent);
+          (void) CopyMagickString(name,"xmp",MagickPathExtent);
         }
     }
   previous_profile=GetImageProfile(image,name);
   if (previous_profile != (const StringInfo *) NULL)
     {
       size_t
-        length;
+        profile_length;
 
-      length=GetStringInfoLength(profile);
+      profile_length=GetStringInfoLength(profile);
       SetStringInfoLength(profile,GetStringInfoLength(profile)+
         GetStringInfoLength(previous_profile));
       (void) memmove(GetStringInfoDatum(profile)+
         GetStringInfoLength(previous_profile),GetStringInfoDatum(profile),
-        length);
+        profile_length);
       (void) memcpy(GetStringInfoDatum(profile),
         GetStringInfoDatum(previous_profile),
         GetStringInfoLength(previous_profile));
@@ -792,7 +792,7 @@ static void JPEGSourceManager(j_decompress_ptr cinfo,Image *image)
 }
 
 static void JPEGSetImageQuality(struct jpeg_decompress_struct *jpeg_info,
-  Image *image, ExceptionInfo *exception)
+  Image *image)
 {
   image->quality=UndefinedCompressionQuality;
 #if defined(D_PROGRESSIVE_SUPPORTED)
@@ -929,14 +929,14 @@ static void JPEGSetImageQuality(struct jpeg_decompress_struct *jpeg_info,
 static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,  Image *image,ExceptionInfo *exception)
 {
   char
-    sampling_factor[MaxTextExtent];
+    sampling_factor[MagickPathExtent];
 
   switch (jpeg_info->out_color_space)
   {
     case JCS_CMYK:
     {
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: CMYK");
-      (void) FormatLocaleString(sampling_factor,MaxTextExtent,
+      (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,
@@ -951,7 +951,7 @@ static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,
     {
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),
         "Colorspace: GRAYSCALE");
-      (void) FormatLocaleString(sampling_factor,MaxTextExtent,"%dx%d",
+      (void) FormatLocaleString(sampling_factor,MagickPathExtent,"%dx%d",
         jpeg_info->comp_info[0].h_samp_factor,
         jpeg_info->comp_info[0].v_samp_factor);
       break;
@@ -959,7 +959,7 @@ static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,
     case JCS_RGB:
     {
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: RGB");
-      (void) FormatLocaleString(sampling_factor,MaxTextExtent,
+      (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,
@@ -972,7 +972,7 @@ static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info,
     {
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: %d",
         jpeg_info->out_color_space);
-      (void) FormatLocaleString(sampling_factor,MaxTextExtent,
+      (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,
@@ -994,7 +994,7 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   ExceptionInfo *exception)
 {
   char
-    value[MaxTextExtent];
+    value[MagickPathExtent];
 
   const char
     *option;
@@ -1046,12 +1046,12 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
     Open image file.
   */
   assert(image_info != (const ImageInfo *) NULL);
-  assert(image_info->signature == MagickSignature);
+  assert(image_info->signature == MagickCoreSignature);
   if (image_info->debug != MagickFalse)
     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
       image_info->filename);
   assert(exception != (ExceptionInfo *) NULL);
-  assert(exception->signature == MagickSignature);
+  assert(exception->signature == MagickCoreSignature);
   debug=IsEventLogging();
   (void) debug;
   image=AcquireImage(image_info,exception);
@@ -1061,6 +1061,11 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       image=DestroyImageList(image);
       return((Image *) NULL);
     }
+  /*
+    Verify that file size large enough to contain a JPEG datastream.
+  */
+  if (GetBlobSize(image) < 107)
+    ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile");
   /*
     Initialize JPEG parameters.
   */
@@ -1088,11 +1093,15 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   jpeg_create_decompress(&jpeg_info);
   JPEGSourceManager(&jpeg_info,image);
   jpeg_set_marker_processor(&jpeg_info,JPEG_COM,ReadComment);
-  jpeg_set_marker_processor(&jpeg_info,ICC_MARKER,ReadICCProfile);
-  jpeg_set_marker_processor(&jpeg_info,IPTC_MARKER,ReadIPTCProfile);
+  option=GetImageOption(image_info,"profile:skip");
+  if (IsOptionMember("ICC",option) == MagickFalse)
+    jpeg_set_marker_processor(&jpeg_info,ICC_MARKER,ReadICCProfile);
+  if (IsOptionMember("IPTC",option) == MagickFalse)
+    jpeg_set_marker_processor(&jpeg_info,IPTC_MARKER,ReadIPTCProfile);
   for (i=1; i < 16; i++)
     if ((i != 2) && (i != 13) && (i != 14))
-      jpeg_set_marker_processor(&jpeg_info,(int) (JPEG_APP0+i),ReadProfile);
+      if (IsOptionMember("APP",option) == MagickFalse)
+        jpeg_set_marker_processor(&jpeg_info,(int) (JPEG_APP0+i),ReadProfile);
   i=(ssize_t) jpeg_read_header(&jpeg_info,TRUE);
   if ((image_info->colorspace == YCbCrColorspace) ||
       (image_info->colorspace == Rec601YCbCrColorspace) ||
@@ -1115,7 +1124,8 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
     image->units=PixelsPerCentimeterResolution;
   number_pixels=(MagickSizeType) image->columns*image->rows;
   option=GetImageOption(image_info,"jpeg:size");
-  if (option != (const char *) NULL)
+  if ((option != (const char *) NULL) &&
+      (jpeg_info.out_color_space != JCS_YCbCr))
     {
       double
         scale_factor;
@@ -1255,8 +1265,7 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
     if (AcquireImageColormap(image,StringToUnsignedLong(option),exception)
          == MagickFalse)
       ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
-  if ((jpeg_info.output_components == 1) &&
-      (jpeg_info.quantize_colors == MagickFalse))
+  if ((jpeg_info.output_components == 1) && (jpeg_info.quantize_colors == 0))
     {
       size_t
         colors;
@@ -1278,9 +1287,9 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Geometry: %dx%d",
         (int) jpeg_info.output_width,(int) jpeg_info.output_height);
     }
-  JPEGSetImageQuality(&jpeg_info,image,exception);
+  JPEGSetImageQuality(&jpeg_info,image);
   JPEGSetImageSamplingFactor(&jpeg_info,image,exception);
-  (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
+  (void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
     jpeg_info.out_color_space);
   (void) SetImageProperty(image,"jpeg:colorspace",value,exception);
   if (image_info->ping != MagickFalse)
@@ -1289,6 +1298,18 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       (void) CloseBlob(image);
       return(GetFirstImageInList(image));
     }
+  status=SetImageExtent(image,image->columns,image->rows,exception);
+  if (status == MagickFalse)
+    {
+      jpeg_destroy_decompress(&jpeg_info);
+      return(DestroyImageList(image));
+    }
+  if ((jpeg_info.output_components != 1) &&
+      (jpeg_info.output_components != 3) && (jpeg_info.output_components != 4))
+    {
+      jpeg_destroy_decompress(&jpeg_info);
+      ThrowReaderException(CorruptImageError,"ImageTypeNotSupported");
+    }
   memory_info=AcquireVirtualMemory((size_t) image->columns,
     jpeg_info.output_components*sizeof(*jpeg_pixels));
   if (memory_info == (MemoryInfo *) NULL)
@@ -1297,6 +1318,8 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
     }
   jpeg_pixels=(JSAMPLE *) GetVirtualMemoryBlob(memory_info);
+  (void) ResetMagickMemory(jpeg_pixels,0,image->columns*
+    jpeg_info.output_components*sizeof(*jpeg_pixels));
   /*
     Convert JPEG pixels to pixel packets.
   */
@@ -1311,7 +1334,7 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
         return(GetFirstImageInList(image));
       return(DestroyImage(image));
     }
-  if (jpeg_info.quantize_colors != MagickFalse)
+  if (jpeg_info.quantize_colors != 0)
     {
       image->colors=(size_t) jpeg_info.actual_number_of_colors;
       if (jpeg_info.out_color_space == JCS_GRAYSCALE)
@@ -1321,7 +1344,7 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
             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=OpaqueAlpha;
+          image->colormap[i].alpha=(MagickRealType) OpaqueAlpha;
         }
       else
         for (i=0; i < (ssize_t) image->colors; i++)
@@ -1332,7 +1355,7 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
             jpeg_info.colormap[1][i]);
           image->colormap[i].blue=(double) ScaleCharToQuantum(
             jpeg_info.colormap[2][i]);
-          image->colormap[i].alpha=OpaqueAlpha;
+          image->colormap[i].alpha=(MagickRealType) OpaqueAlpha;
         }
     }
   scanline[0]=(JSAMPROW) jpeg_pixels;
@@ -1342,7 +1365,7 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       x;
 
     register Quantum
-      *restrict q;
+      *magick_restrict q;
 
     if (jpeg_read_scanlines(&jpeg_info,scanline,1) != 1)
       {
@@ -1359,17 +1382,18 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
         unsigned short
           scale;
 
-        scale=65535U/GetQuantumRange(jpeg_info.data_precision);
+        scale=65535/(unsigned short) GetQuantumRange((size_t)
+          jpeg_info.data_precision);
         if (jpeg_info.output_components == 1)
           for (x=0; x < (ssize_t) image->columns; x++)
           {
-            size_t
+            ssize_t
               pixel;
 
-            pixel=(size_t) (scale*GETJSAMPLE(*p));
-            index=ConstrainColormapIndex(image,pixel,exception);
+            pixel=(ssize_t) (scale*GETJSAMPLE(*p));
+            index=(Quantum) ConstrainColormapIndex(image,pixel,exception);
             SetPixelIndex(image,index,q);
-            SetPixelInfoPixel(image,image->colormap+(ssize_t) index,q);
+            SetPixelViaPixelInfo(image,image->colormap+(ssize_t) index,q);
             p++;
             q+=GetPixelChannels(image);
           }
@@ -1377,26 +1401,26 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
           if (image->colorspace != CMYKColorspace)
             for (x=0; x < (ssize_t) image->columns; x++)
             {
-              SetPixelRed(image,ScaleShortToQuantum(scale*GETJSAMPLE(*p++)),
-                q);
-              SetPixelGreen(image,ScaleShortToQuantum(scale*GETJSAMPLE(*p++)),
-                q);
-              SetPixelBlue(image,ScaleShortToQuantum(scale*GETJSAMPLE(*p++)),
-                q);
+              SetPixelRed(image,ScaleShortToQuantum(
+                (unsigned short) (scale*GETJSAMPLE(*p++))),q);
+              SetPixelGreen(image,ScaleShortToQuantum(
+                (unsigned short) (scale*GETJSAMPLE(*p++))),q);
+              SetPixelBlue(image,ScaleShortToQuantum(
+                (unsigned short) (scale*GETJSAMPLE(*p++))),q);
               SetPixelAlpha(image,OpaqueAlpha,q);
               q+=GetPixelChannels(image);
             }
           else
             for (x=0; x < (ssize_t) image->columns; x++)
             {
-              SetPixelCyan(image,QuantumRange-ScaleShortToQuantum(scale*
-                GETJSAMPLE(*p++)),q);
-              SetPixelMagenta(image,QuantumRange-ScaleShortToQuantum(scale*
-                GETJSAMPLE(*p++)),q);
-              SetPixelYellow(image,QuantumRange-ScaleShortToQuantum(scale*
-                GETJSAMPLE(*p++)),q);
-              SetPixelBlack(image,QuantumRange-ScaleShortToQuantum(scale*
-                GETJSAMPLE(*p++)),q);
+              SetPixelCyan(image,QuantumRange-ScaleShortToQuantum(
+                (unsigned short) (scale*GETJSAMPLE(*p++))),q);
+              SetPixelMagenta(image,QuantumRange-ScaleShortToQuantum(
+                (unsigned short) (scale*GETJSAMPLE(*p++))),q);
+              SetPixelYellow(image,QuantumRange-ScaleShortToQuantum(
+                (unsigned short) (scale*GETJSAMPLE(*p++))),q);
+              SetPixelBlack(image,QuantumRange-ScaleShortToQuantum(
+                (unsigned short) (scale*GETJSAMPLE(*p++))),q);
               SetPixelAlpha(image,OpaqueAlpha,q);
               q+=GetPixelChannels(image);
             }
@@ -1405,9 +1429,10 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
       if (jpeg_info.output_components == 1)
         for (x=0; x < (ssize_t) image->columns; x++)
         {
-          index=ConstrainColormapIndex(image,(size_t) GETJSAMPLE(*p),exception);
+          index=(Quantum) ConstrainColormapIndex(image,(ssize_t) GETJSAMPLE(*p),
+            exception);
           SetPixelIndex(image,index,q);
-          SetPixelInfoPixel(image,image->colormap+(ssize_t) index,q);
+          SetPixelViaPixelInfo(image,image->colormap+(ssize_t) index,q);
           p++;
           q+=GetPixelChannels(image);
         }
@@ -1463,8 +1488,7 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
   return(GetFirstImageInList(image));
 }
 #endif
-
-
+\f
 /*
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %                                                                             %
@@ -1490,63 +1514,97 @@ static Image *ReadJPEGImage(const ImageInfo *image_info,
 */
 ModuleExport size_t RegisterJPEGImage(void)
 {
+#define JPEGDescription "Joint Photographic Experts Group JFIF format"
+
   char
-    version[MaxTextExtent];
+    version[MagickPathExtent];
 
   MagickInfo
     *entry;
 
-  static const char
-    description[] = "Joint Photographic Experts Group JFIF format";
-
   *version='\0';
 #if defined(JPEG_LIB_VERSION)
-  (void) FormatLocaleString(version,MaxTextExtent,"%d",JPEG_LIB_VERSION);
+  (void) FormatLocaleString(version,MagickPathExtent,"%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
-  entry=SetMagickInfo("JPEG");
-  entry->thread_support=NoThreadSupport;
 #if defined(MAGICKCORE_JPEG_DELEGATE)
   entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
   entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
 #endif
   entry->magick=(IsImageFormatHandler *) IsJPEG;
-  entry->adjoin=MagickFalse;
-  entry->description=ConstantString(description);
+  entry->flags|=CoderDecoderSeekableStreamFlag;
+  entry->flags^=CoderAdjoinFlag;
   if (*version != '\0')
     entry->version=ConstantString(version);
   entry->mime_type=ConstantString("image/jpeg");
-  entry->module=ConstantString("JPEG");
   (void) RegisterMagickInfo(entry);
-  entry=SetMagickInfo("JPG");
-  entry->thread_support=NoThreadSupport;
+  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->adjoin=MagickFalse;
-  entry->description=ConstantString(description);
+  entry->flags|=CoderDecoderSeekableStreamFlag;
+  entry->flags^=CoderAdjoinFlag;
+  entry->flags^=CoderUseExtensionFlag;
   if (*version != '\0')
     entry->version=ConstantString(version);
   entry->mime_type=ConstantString("image/jpeg");
-  entry->module=ConstantString("JPEG");
   (void) RegisterMagickInfo(entry);
-  entry=SetMagickInfo("PJPEG");
-  entry->thread_support=NoThreadSupport;
+  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->adjoin=MagickFalse;
-  entry->description=ConstantString(description);
+  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","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");
-  entry->module=ConstantString("JPEG");
   (void) RegisterMagickInfo(entry);
   return(MagickImageCoderSignature);
 }
-
-
+\f
 /*
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %                                                                             %
@@ -1569,11 +1627,12 @@ ModuleExport size_t RegisterJPEGImage(void)
 ModuleExport void UnregisterJPEGImage(void)
 {
   (void) UnregisterMagickInfo("PJPG");
-  (void) UnregisterMagickInfo("JPEG");
+  (void) UnregisterMagickInfo("JPS");
   (void) UnregisterMagickInfo("JPG");
+  (void) UnregisterMagickInfo("JPEG");
+  (void) UnregisterMagickInfo("JPE");
 }
-
-
+\f
 #if defined(MAGICKCORE_JPEG_DELEGATE)
 /*
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1696,7 +1755,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (description == (XMLTreeInfo *) NULL)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-        "XmlMissingElement", "<description>, slot \"%s\"",slot);
+        "XmlMissingElement","<description>, slot \"%s\"",slot);
       quantization_tables=DestroyXMLTree(quantization_tables);
       xml=DestroyString(xml);
       return(table);
@@ -1705,7 +1764,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (levels == (XMLTreeInfo *) NULL)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-        "XmlMissingElement", "<levels>, slot \"%s\"", slot);
+        "XmlMissingElement","<levels>, slot \"%s\"",slot);
       quantization_tables=DestroyXMLTree(quantization_tables);
       xml=DestroyString(xml);
       return(table);
@@ -1727,7 +1786,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (attribute == (char *) NULL)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-        "XmlMissingAttribute", "<levels width>, slot \"%s\"",slot);
+        "XmlMissingAttribute","<levels width>, slot \"%s\"",slot);
       quantization_tables=DestroyXMLTree(quantization_tables);
       table=DestroyQuantizationTable(table);
       xml=DestroyString(xml);
@@ -1737,7 +1796,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (table->width == 0)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-       "XmlInvalidAttribute", "<levels width>, table \"%s\"",slot);
+       "XmlInvalidAttribute","<levels width>, table \"%s\"",slot);
       quantization_tables=DestroyXMLTree(quantization_tables);
       table=DestroyQuantizationTable(table);
       xml=DestroyString(xml);
@@ -1747,7 +1806,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (attribute == (char *) NULL)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-        "XmlMissingAttribute", "<levels height>, table \"%s\"",slot);
+        "XmlMissingAttribute","<levels height>, table \"%s\"",slot);
       quantization_tables=DestroyXMLTree(quantization_tables);
       table=DestroyQuantizationTable(table);
       xml=DestroyString(xml);
@@ -1757,7 +1816,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (table->height == 0)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-        "XmlInvalidAttribute", "<levels height>, table \"%s\"",slot);
+        "XmlInvalidAttribute","<levels height>, table \"%s\"",slot);
       quantization_tables=DestroyXMLTree(quantization_tables);
       table=DestroyQuantizationTable(table);
       xml=DestroyString(xml);
@@ -1767,7 +1826,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (attribute == (char *) NULL)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-        "XmlMissingAttribute", "<levels divisor>, table \"%s\"",slot);
+        "XmlMissingAttribute","<levels divisor>, table \"%s\"",slot);
       quantization_tables=DestroyXMLTree(quantization_tables);
       table=DestroyQuantizationTable(table);
       xml=DestroyString(xml);
@@ -1777,7 +1836,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (table->divisor == 0.0)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-        "XmlInvalidAttribute", "<levels divisor>, table \"%s\"",slot);
+        "XmlInvalidAttribute","<levels divisor>, table \"%s\"",slot);
       quantization_tables=DestroyXMLTree(quantization_tables);
       table=DestroyQuantizationTable(table);
       xml=DestroyString(xml);
@@ -1787,7 +1846,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (content == (char *) NULL)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-        "XmlMissingContent", "<levels>, table \"%s\"",slot);
+        "XmlMissingContent","<levels>, table \"%s\"",slot);
       quantization_tables=DestroyXMLTree(quantization_tables);
       table=DestroyQuantizationTable(table);
       xml=DestroyString(xml);
@@ -1816,7 +1875,7 @@ static QuantizationTable *GetQuantizationTable(const char *filename,
   if (p != content)
     {
       (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
-        "XmlInvalidContent", "<level> too many values, table \"%s\"",slot);
+        "XmlInvalidContent","<level> too many values, table \"%s\"",slot);
      quantization_tables=DestroyXMLTree(quantization_tables);
      table=DestroyQuantizationTable(table);
      xml=DestroyString(xml);
@@ -1841,13 +1900,6 @@ static void InitializeDestination(j_compress_ptr cinfo)
   destination->manager.free_in_buffer=MaxBufferExtent;
 }
 
-static inline size_t MagickMin(const size_t x,const size_t y)
-{
-  if (x < y)
-    return(x);
-  return(y);
-}
-
 static void TerminateDestination(j_compress_ptr cinfo)
 {
   DestinationManager
@@ -1867,7 +1919,8 @@ static void TerminateDestination(j_compress_ptr cinfo)
     }
 }
 
-static void WriteProfile(j_compress_ptr jpeg_info,Image *image)
+static void WriteProfile(j_compress_ptr jpeg_info,Image *image,
+  ExceptionInfo *exception)
 {
   const char
     *name;
@@ -1896,16 +1949,18 @@ static void WriteProfile(j_compress_ptr jpeg_info,Image *image)
   ResetImageProfileIterator(image);
   for (name=GetNextImageProfile(image); name != (const char *) NULL; )
   {
-    register unsigned char
-      *p;
-
     profile=GetImageProfile(image,name);
-    p=GetStringInfoDatum(custom_profile);
     if (LocaleCompare(name,"EXIF") == 0)
-      for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=65533L)
       {
-        length=MagickMin(GetStringInfoLength(profile)-i,65533L);
-        jpeg_write_marker(jpeg_info,XML_MARKER,GetStringInfoDatum(profile)+i,
+        length=GetStringInfoLength(profile);
+        if (length > 65533L)
+          {
+            (void) ThrowMagickException(exception,GetMagickModule(),
+              CoderWarning,"ExifProfileSizeExceedsLimit","`%s'",
+              image->filename);
+            length=65533L;
+          }
+        jpeg_write_marker(jpeg_info,XML_MARKER,GetStringInfoDatum(profile),
           (unsigned int) length);
       }
     if (LocaleCompare(name,"ICC") == 0)
@@ -1931,10 +1986,14 @@ static void WriteProfile(j_compress_ptr jpeg_info,Image *image)
     if (((LocaleCompare(name,"IPTC") == 0) ||
         (LocaleCompare(name,"8BIM") == 0)) && (iptc == MagickFalse))
       {
+        register unsigned char
+          *p;
+
         size_t
           roundup;
 
         iptc=MagickTrue;
+        p=GetStringInfoDatum(custom_profile);
         for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=65500L)
         {
           length=MagickMin(GetStringInfoLength(profile)-i,65500L);
@@ -1968,15 +2027,19 @@ 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/ ");
-        ConcatenateStringInfo(xmp_profile,profile);
-        GetStringInfoDatum(xmp_profile)[28]='\0';
-        for (i=0; i < (ssize_t) GetStringInfoLength(xmp_profile); i+=65533L)
-        {
-          length=MagickMin(GetStringInfoLength(xmp_profile)-i,65533L);
-          jpeg_write_marker(jpeg_info,XML_MARKER,
-            GetStringInfoDatum(xmp_profile)+i,(unsigned int) length);
-        }
-        xmp_profile=DestroyStringInfo(xmp_profile);
+        if (xmp_profile != (StringInfo *) NULL)
+          {
+            if (profile != (StringInfo *) NULL)
+              ConcatenateStringInfo(xmp_profile,profile);
+            GetStringInfoDatum(xmp_profile)[28]='\0';
+            for (i=0; i < (ssize_t) GetStringInfoLength(xmp_profile); i+=65533L)
+            {
+              length=MagickMin(GetStringInfoLength(xmp_profile)-i,65533L);
+              jpeg_write_marker(jpeg_info,XML_MARKER,
+                GetStringInfoDatum(xmp_profile)+i,(unsigned int) length);
+            }
+            xmp_profile=DestroyStringInfo(xmp_profile);
+          }
       }
     (void) LogMagickEvent(CoderEvent,GetMagickModule(),
       "%s profile: %.20g bytes",name,(double) GetStringInfoLength(profile));
@@ -2013,38 +2076,34 @@ static char **SamplingFactorToList(const char *text)
   register ssize_t
     i;
 
-  size_t
-    lines;
-
   if (text == (char *) NULL)
     return((char **) NULL);
   /*
     Convert string to an ASCII list.
   */
-  lines=1;
-  for (p=text; *p != '\0'; p++)
-    if (*p == ',')
-      lines++;
-  textlist=(char **) AcquireQuantumMemory((size_t) lines+MaxTextExtent,
+  textlist=(char **) AcquireQuantumMemory((size_t) MAX_COMPONENTS,
     sizeof(*textlist));
   if (textlist == (char **) NULL)
     ThrowFatalException(ResourceLimitFatalError,"UnableToConvertText");
   p=text;
-  for (i=0; i < (ssize_t) lines; i++)
+  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)+MaxTextExtent,
+    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;
   }
-  textlist[i]=(char *) NULL;
+  for (i++; i < (ssize_t) MAX_COMPONENTS; i++)
+    textlist[i]=ConstantString("1x1");
   return(textlist);
 }
 
@@ -2059,6 +2118,9 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   ErrorManager
     error_manager;
 
+  Image
+    *volatile volatile_image;
+
   int
     colorspace,
     quality;
@@ -2097,13 +2159,16 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
     Open image file.
   */
   assert(image_info != (const ImageInfo *) NULL);
-  assert(image_info->signature == MagickSignature);
+  assert(image_info->signature == MagickCoreSignature);
   assert(image != (Image *) NULL);
-  assert(image->signature == MagickSignature);
+  assert(image->signature == MagickCoreSignature);
   if (image->debug != MagickFalse)
     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
   assert(exception != (ExceptionInfo *) NULL);
-  assert(exception->signature == MagickSignature);
+  assert(exception->signature == MagickCoreSignature);
+  if ((LocaleCompare(image_info->magick,"JPS") == 0) &&
+      (image->next != (Image *) NULL))
+    image=AppendImages(image,MagickFalse,exception);
   status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
   if (status == MagickFalse)
     return(status);
@@ -2113,17 +2178,18 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
   (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;
+  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;
   error_manager.exception=exception;
-  error_manager.image=image;
+  error_manager.image=volatile_image;
   memory_info=(MemoryInfo *) NULL;
   if (setjmp(error_manager.error_recovery) != 0)
     {
       jpeg_destroy_compress(&jpeg_info);
-      (void) CloseBlob(image);
+      (void) CloseBlob(volatile_image);
       return(MagickFalse);
     }
   jpeg_info.client_data=(void *) &error_manager;
@@ -2162,11 +2228,10 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
     }
     default:
     {
-      if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
-        (void) TransformImageColorspace(image,sRGBColorspace,exception);
+      (void) TransformImageColorspace(image,sRGBColorspace,exception);
       if (image_info->type == TrueColorType)
         break;
-      if (IsImageGray(image,exception) != MagickFalse)
+      if (SetImageGray(image,exception) != MagickFalse)
         {
           jpeg_info.input_components=1;
           jpeg_info.in_color_space=JCS_GRAYSCALE;
@@ -2181,23 +2246,20 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
     jpeg_info.data_precision=8;
   else
     jpeg_info.data_precision=BITS_IN_JSAMPLE;
-  jpeg_info.density_unit=(UINT8) 1;
   if (image->debug != MagickFalse)
     (void) LogMagickEvent(CoderEvent,GetMagickModule(),
-      "Image resolution: %.20g,%.20g",floor(image->resolution.x+0.5),
-      floor(image->resolution.y+0.5));
+      "Image resolution: %.20g,%.20g",image->resolution.x,image->resolution.y);
   if ((image->resolution.x != 0.0) && (image->resolution.y != 0.0))
     {
       /*
         Set image resolution.
       */
       jpeg_info.write_JFIF_header=TRUE;
-      jpeg_info.X_density=(UINT16) floor(image->resolution.x+0.5);
-      jpeg_info.Y_density=(UINT16) floor(image->resolution.y+0.5);
+      jpeg_info.X_density=(UINT16) image->resolution.x;
+      jpeg_info.Y_density=(UINT16) image->resolution.y;
       /*
         Set image resolution units.
       */
-      jpeg_info.density_unit=(UINT8) 0;
       if (image->units == PixelsPerInchResolution)
         jpeg_info.density_unit=(UINT8) 1;
       if (image->units == PixelsPerCentimeterResolution)
@@ -2236,7 +2298,8 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
     }
   option=GetImageOption(image_info,"jpeg:optimize-coding");
   if (option != (const char *) NULL)
-    jpeg_info.optimize_coding=IsStringTrue(option) != MagickFalse ? TRUE : FALSE;
+    jpeg_info.optimize_coding=IsStringTrue(option) != MagickFalse ? TRUE :
+      FALSE;
   else
     {
       MagickSizeType
@@ -2251,7 +2314,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
           */
           status=AcquireMagickResource(MemoryResource,length);
           RelinquishMagickResource(MemoryResource,length);
-          jpeg_info.optimize_coding=status;
+          jpeg_info.optimize_coding=status == MagickFalse ? FALSE : TRUE;
         }
     }
 #if (JPEG_LIB_VERSION >= 61) && defined(C_PROGRESSIVE_SUPPORTED)
@@ -2273,51 +2336,6 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
       "Interlace: nonprogressive");
 #endif
   quality=92;
-  option=GetImageOption(image_info,"jpeg:extent");
-  if (option != (const char *) NULL)
-    {
-      Image
-        *jpeg_image;
-
-      ImageInfo
-        *jpeg_info;
-
-      jpeg_info=CloneImageInfo(image_info);
-      jpeg_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.
-          */
-          jpeg_info->quality=0;
-          extent=(MagickSizeType) SiPrefixToDoubleInterval(option,100.0);
-          (void) DeleteImageOption(jpeg_info,"jpeg:extent");
-          (void) DeleteImageArtifact(jpeg_image,"jpeg:extent");
-          (void) AcquireUniqueFilename(jpeg_image->filename);
-          maximum=101;
-          for (minimum=2; minimum < maximum; )
-          {
-            jpeg_info->quality=minimum+(maximum-minimum+1)/2;
-            status=WriteJPEGImage(jpeg_info,jpeg_image,exception);
-            if (GetBlobSize(jpeg_image) <= extent)
-              minimum=jpeg_info->quality+1;
-            else
-              maximum=jpeg_info->quality-1;
-          }
-          (void) RelinquishUniqueFileResource(jpeg_image->filename);
-          quality=minimum-1;
-          jpeg_image=DestroyImage(jpeg_image);
-        }
-      jpeg_info=DestroyImageInfo(jpeg_info);
-    }
   if ((image_info->compression != LosslessJPEGCompression) &&
       (image->quality <= 100))
     {
@@ -2336,7 +2354,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
 #else
       if (image->quality < 100)
         (void) ThrowMagickException(exception,GetMagickModule(),CoderWarning,
-          "LosslessToLossyJPEGConversion",image->filename);
+          "LosslessToLossyJPEGConversion","`%s'",image->filename);
       else
         {
           int
@@ -2358,7 +2376,54 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
         }
 #endif
     }
-  jpeg_set_quality(&jpeg_info,quality,MagickTrue);
+  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);
+            if (GetBlobSize(jpeg_image) <= extent)
+              minimum=jpeg_image->quality+1;
+            else
+              maximum=jpeg_image->quality-1;
+            (void) RelinquishUniqueFileResource(jpeg_image->filename);
+          }
+          quality=(int) minimum-1;
+          jpeg_image=DestroyImage(jpeg_image);
+        }
+      extent_info=DestroyImageInfo(extent_info);
+    }
+  jpeg_set_quality(&jpeg_info,quality,TRUE);
 #if (JPEG_LIB_VERSION >= 70)
   option=GetImageOption(image_info,"quality");
   if (option != (const char *) NULL)
@@ -2379,7 +2444,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
             (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);
+          jpeg_default_qtables(&jpeg_info,TRUE);
         }
     }
 #endif
@@ -2408,7 +2473,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
     sampling_factor=image_info->sampling_factor;
   if (sampling_factor == (const char *) NULL)
     {
-      if (image->quality >= 90)
+      if (quality >= 90)
         for (i=0; i < MAX_COMPONENTS; i++)
         {
           jpeg_info.comp_info[i].h_samp_factor=1;
@@ -2498,7 +2563,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
           table=DestroyQuantizationTable(table);
         }
     }
-  jpeg_start_compress(&jpeg_info,MagickTrue);
+  jpeg_start_compress(&jpeg_info,TRUE);
   if (image->debug != MagickFalse)
     {
       if (image->storage_class == PseudoClass)
@@ -2623,7 +2688,7 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
       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)
-    WriteProfile(&jpeg_info,image);
+    WriteProfile(&jpeg_info,image,exception);
   /*
     Convert MIFF to JPEG raster pixels.
   */
@@ -2641,9 +2706,10 @@ static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
       return(MagickFalse);
     }
   scanline[0]=(JSAMPROW) jpeg_pixels;
-  scale=65535U/GetQuantumRange(jpeg_info.data_precision);
+  scale=65535/(unsigned short) GetQuantumRange((size_t)
+    jpeg_info.data_precision);
   if (scale == 0)
-    scale=1; 
+    scale=1;
   if (jpeg_info.data_precision <= 8)
     {
       if ((jpeg_info.in_color_space == JCS_RGB) ||