root/coders/jpeg.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. IsJPEG
  2. EmitMessage
  3. FillInputBuffer
  4. GetCharacter
  5. InitializeSource
  6. JPEGErrorHandler
  7. ReadComment
  8. ReadGenericProfile
  9. ReadICCProfile
  10. ReadIPTCProfile
  11. SkipInputData
  12. TerminateSource
  13. JPEGSourceManager
  14. EstimateJPEGQuality
  15. FormatJPEGColorSpace
  16. FormatJPEGSamplingFactors
  17. IsITUFax
  18. ReadJPEGImage
  19. RegisterJPEGImage
  20. UnregisterJPEGImage
  21. EmptyOutputBuffer
  22. InitializeDestination
  23. JPEGWarningHandler
  24. TerminateDestination
  25. WriteICCProfile
  26. WriteIPTCProfile
  27. JPEGDestinationManager
  28. WriteJPEGImage

/*
% Copyright (C) 2003-2009 GraphicsMagick Group
% Copyright (C) 2002 ImageMagick Studio
% Copyright 1991-1999 E. I. du Pont de Nemours and Company
%
% This program is covered by multiple licenses, which are described in
% Copyright.txt. You should have received a copy of Copyright.txt with this
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                        JJJJJ  PPPP   EEEEE   GGGG                           %
%                          J    P   P  E      G                               %
%                          J    PPPP   EEE    G  GG                           %
%                        J J    P      E      G   G                           %
%                        JJJ    P      EEEEE   GGG                            %
%                                                                             %
%                                                                             %
%                       Read/Write JPEG Image Format.                         %
%                                                                             %
%                                                                             %
%                              Software Design                                %
%                                John Cristy                                  %
%                                 July 1992                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% This software is based in part on the work of the Independent JPEG Group.
% See ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz for copyright and
% licensing restrictions.  Blob support contributed by Glenn Randers-Pehrson.
%
%
*/

/*
  Include declarations.
*/
#include "magick/studio.h"
#include "magick/analyze.h"
#include "magick/attribute.h"
#include "magick/blob.h"
#include "magick/colormap.h"
#include "magick/log.h"
#include "magick/magick.h"
#include "magick/monitor.h"
#include "magick/pixel_cache.h"
#include "magick/profile.h"
#include "magick/resource.h"
#include "magick/utility.h"

/*
  Forward declarations.
*/
static unsigned int
  WriteJPEGImage(const ImageInfo *,Image *);

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   I s J P E G                                                               %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method IsJPEG returns True if the image format type, identified by the
%  magick string, is JPEG.
%
%  The format of the IsJPEG  method is:
%
%      unsigned int IsJPEG(const unsigned char *magick,const size_t length)
%
%  A description of each parameter follows:
%
%    o status:  Method IsJPEG returns True if the image format type is JPEG.
%
%    o magick: This string is generally the first few bytes of an image file
%      or blob.
%
%    o length: Specifies the length of the magick string.
%
%
*/
static unsigned int IsJPEG(const unsigned char *magick,const size_t length)
{
  if (length < 3)
    return(False);
  if (memcmp(magick,"\377\330\377",3) == 0)
    return(True);
  return(False);
}

#if defined(HasJPEG)
#define JPEG_INTERNAL_OPTIONS
/*
  Avoid conflicting typedef for INT32
*/
#if defined(__MINGW32__)
# define XMD_H 1
#endif
/*
  The JPEG headers have the annoying problem that they define
  HAVE_STDLIB_H and we do too.  The define isn't actually used
  so just undef it.
*/
#undef HAVE_STDLIB_H
#include <setjmp.h>
#include "jpeglib.h"
#include "jerror.h"

/*
  Define declarations.
*/
#define ICC_MARKER  (JPEG_APP0+2)
#define IPTC_MARKER  (JPEG_APP0+13)
#define MaxBufferExtent  8192

typedef struct _DestinationManager
{
  struct jpeg_destination_mgr
    manager;

  Image
    *image;

  JOCTET
    *buffer;
} DestinationManager;

typedef struct _ErrorManager
{
  Image
    *image;

  jmp_buf
    error_recovery;
} ErrorManager;

typedef struct _SourceManager
{
  struct jpeg_source_mgr
    manager;

  Image
    *image;

  JOCTET
    *buffer;

  boolean
    start_of_blob;
} SourceManager;

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d J P E G I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method ReadJPEGImage reads a JPEG image file and returns it.  It allocates
%  the memory necessary for the new Image structure and returns a pointer to
%  the new image.
%
%  The format of the ReadJPEGImage method is:
%
%      Image *ReadJPEGImage(const ImageInfo *image_info,
%        ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image:  Method ReadJPEGImage returns a pointer to the image after
%      reading.  A null image is returned if there is a memory shortage or
%      if the image cannot be read.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o exception: return any errors or warnings in this structure.
%
%
*/

static unsigned int 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))
        ThrowBinaryException2(CorruptImageWarning,(char *) message,
          image->filename);
      jpeg_info->err->num_warnings++;
    }
  else
    if (jpeg_info->err->trace_level >= level)
      ThrowBinaryException2(CoderError,(char *) message,image->filename);
  return(True);
}

static boolean FillInputBuffer(j_decompress_ptr cinfo)
{
  SourceManager
    *source;

  source=(SourceManager *) cinfo->src;
  source->manager.bytes_in_buffer=
    ReadBlob(source->image,MaxBufferExtent,(char *) source->buffer);
  if (source->manager.bytes_in_buffer == 0)
    {
      if (source->start_of_blob)
        ERREXIT(cinfo,JERR_INPUT_EMPTY);
      WARNMS(cinfo,JWRN_JPEG_EOF);
      source->buffer[0]=(JOCTET) 0xff;
      source->buffer[1]=(JOCTET) JPEG_EOI;
      source->manager.bytes_in_buffer=2;
    }
  source->manager.next_input_byte=source->buffer;
  source->start_of_blob=FALSE;
  return(TRUE);
}

static unsigned int GetCharacter(j_decompress_ptr jpeg_info)
{
  if (jpeg_info->src->bytes_in_buffer == 0)
    (void) (*jpeg_info->src->fill_input_buffer)(jpeg_info);
  jpeg_info->src->bytes_in_buffer--;
  return(GETJOCTET(*jpeg_info->src->next_input_byte++));
}

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

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

static void JPEGErrorHandler(j_common_ptr jpeg_info) MAGICK_FUNC_NORETURN;

static void JPEGErrorHandler(j_common_ptr jpeg_info)
{
  ErrorManager
    *error_manager;

  (void) EmitMessage(jpeg_info,0);
  error_manager=( ErrorManager *) jpeg_info->client_data;
  longjmp(error_manager->error_recovery,1);
}

static boolean ReadComment(j_decompress_ptr jpeg_info)
{
  char
    *comment;

  ErrorManager
    *error_manager;

  Image
    *image;

  size_t
    i,
    length;

  register char
    *p;

  /*
    Determine length of comment.
  */
  error_manager=(ErrorManager *) jpeg_info->client_data;
  image=error_manager->image;
  length=GetCharacter(jpeg_info) << 8;
  length+=GetCharacter(jpeg_info);
  if (length <= 2)
    return(True);
  length-=2;
  comment=MagickAllocateMemory(char *,length+1);
  if (comment == (char *) NULL)
    ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
      (char *) NULL);
  /*
    Read comment.
  */
  p=comment;
  for (i=length; i != 0; i--)
    {
      *p=GetCharacter(jpeg_info);
      p++;
    }
  *p='\0';
  (void) SetImageAttribute(image,"comment",comment);
  MagickFreeMemory(comment);
  return(True);
}

static boolean ReadGenericProfile(j_decompress_ptr jpeg_info)
{
  ErrorManager
    *error_manager;

  Image
    *image;

  size_t
    length;

  register size_t
    i;

  char
    profile_name[MaxTextExtent];

  unsigned char
    *profile;

  int
    marker;

  boolean
    status = True;

  /*
    Determine length of generic profile.
  */
  length=GetCharacter(jpeg_info) << 8;
  length+=GetCharacter(jpeg_info);
  if (length <= 2)
    return(status);
  length-=2;

  marker=jpeg_info->unread_marker-JPEG_APP0;

  /*
    Compute generic profile name.
  */
  FormatString(profile_name,"APP%d",marker);

  /*
    Obtain Image.
  */
  error_manager=(ErrorManager *) jpeg_info->client_data;
  image=error_manager->image;

  /*
    Copy profile from JPEG to allocated memory.
  */
  profile=MagickAllocateMemory(unsigned char *,length);
  if (profile == (unsigned char *) NULL)
    ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
                         (char *) NULL);

  for (i=0 ; i < length ; i++)
    profile[i]=GetCharacter(jpeg_info);

  /*
    Detect EXIF and XMP profiles.
  */
  if ((marker==1) && (length > 4) &&
      (strncmp((char *) profile,"Exif",4) == 0))
    FormatString(profile_name,"EXIF");
  else if (((marker==1) && length > 5) &&
           (strncmp((char *) profile,"http:",5) == 0))
    FormatString((char *) profile,"XMP");

  /*
    Store profile in Image.
  */
  status=AppendImageProfile(image,profile_name,profile,length);
  MagickFreeMemory(profile);

  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Profile: %s, %lu bytes",
                        profile_name,(unsigned long) length);

  return (status);
}

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

  ErrorManager
    *error_manager;

  Image
    *image;

  long
    length;

  register long
    i;

  unsigned char
    *profile;

  /*
    Determine length of color profile.
  */
  length=(long) GetCharacter(jpeg_info) << 8;
  length+=(long) GetCharacter(jpeg_info);
  length-=2;
  if (length <= 14)
    {
      while (--length >= 0)
        (void) GetCharacter(jpeg_info);
      return(True);
    }
  for (i=0; i < 12; i++)
    magick[i]=GetCharacter(jpeg_info);
  if (LocaleCompare(magick,"ICC_PROFILE") != 0)
    {
      /*
        Not a ICC profile, return.
      */
      for (i=0; i < length-12; i++)
        (void) GetCharacter(jpeg_info);
      return(True);
    }
  (void) GetCharacter(jpeg_info);  /* id */
  (void) GetCharacter(jpeg_info);  /* markers */
  length-=14;
  error_manager=(ErrorManager *) jpeg_info->client_data;
  image=error_manager->image;

  /*
    Read color profile.
  */
  profile=MagickAllocateMemory(unsigned char *,(size_t) length);
  if (profile == (unsigned char *) NULL)
    ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
      (char *) NULL);

  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                        "ICC profile chunk: %ld bytes",
    length);

  for (i=0 ; i < length; i++)
   profile[i]=GetCharacter(jpeg_info);

  (void) AppendImageProfile(image,"ICM",profile,length);

  MagickFreeMemory(profile);

  return(True);
}

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

  ErrorManager
    *error_manager;

  Image
    *image;

  long
    length,
    tag_length;

  unsigned char
    *profile;

  register long
    i;

#ifdef GET_ONLY_IPTC_DATA
  unsigned char
    tag[MaxTextExtent];
#endif

  /*
    Determine length of binary data stored here.
  */
  length=(long) GetCharacter(jpeg_info) << 8;
  length+=(long) GetCharacter(jpeg_info);
  length-=2;
  if (length <= 0)
    return(True);
  tag_length=0;
#ifdef GET_ONLY_IPTC_DATA
  *tag='\0';
#endif
  error_manager=(ErrorManager *) jpeg_info->client_data;
  image=error_manager->image;
  if (GetImageProfile(image,"IPTC",(size_t *) NULL) == 0)
    {
#ifdef GET_ONLY_IPTC_DATA
      /*
        Find the beginning of the IPTC portion of the binary data.
      */
      for (*tag='\0'; length > 0; )
      {
        *tag=GetCharacter(jpeg_info);
        *(tag+1)=GetCharacter(jpeg_info);
        length-=2;
        if ((*tag == 0x1c) && (*(tag+1) == 0x02))
          break;
      }
      tag_length=2;
#else
      /*
        Validate that this was written as a Photoshop resource format slug.
      */
      for (i=0; i < 10; i++)
        magick[i]=GetCharacter(jpeg_info);
      magick[10]='\0';
      length-=10;
      if (LocaleCompare(magick,"Photoshop ") != 0)
        {
          /*
            Not a ICC profile, return.
          */
          for (i=0; i < length; i++)
            (void) GetCharacter(jpeg_info);
          return(True);
        }
      /*
        Remove the version number.
      */
      for (i=0; i < 4; i++)
        (void) GetCharacter(jpeg_info);
      length-=4;
      tag_length=0;
#endif
    }
  if (length <= 0)
    return(True);

  profile=MagickAllocateMemory(unsigned char *,(size_t) length+tag_length);
  if (profile == (unsigned char *) NULL)
    ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
      (char *) NULL);
  /*
    Read the payload of this binary data.
  */
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                        "Profile: IPTC, %ld bytes",
    length);

  for (i=0; i<length; i++)
    profile[i]=GetCharacter(jpeg_info);

  (void) AppendImageProfile(image,"IPTC",profile,length);

  MagickFreeMemory(profile);
  return(True);
}

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

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

static void TerminateSource(j_decompress_ptr cinfo)
{
  cinfo=cinfo;
}

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

  cinfo->src=(struct jpeg_source_mgr *) (*cinfo->mem->alloc_small)
    ((j_common_ptr) cinfo,JPOOL_IMAGE,sizeof(SourceManager));
  source=(SourceManager *) cinfo->src;
  source->buffer=(JOCTET *) (*cinfo->mem->alloc_small)
    ((j_common_ptr) cinfo,JPOOL_IMAGE,MaxBufferExtent*sizeof(JOCTET));
  source=(SourceManager *) cinfo->src;
  source->manager.init_source=InitializeSource;
  source->manager.fill_input_buffer=FillInputBuffer;
  source->manager.skip_input_data=SkipInputData;
  source->manager.resync_to_restart=jpeg_resync_to_restart;
  source->manager.term_source=TerminateSource;
  source->manager.bytes_in_buffer=0;
  source->manager.next_input_byte=NULL;
  source->image=image;
}

/*
  Estimate the IJG quality factor used when saving the file.
*/
static int
EstimateJPEGQuality(const struct jpeg_decompress_struct *jpeg_info,
                    Image *image)
{
  int
    save_quality;

  register long
    i;

  save_quality=0;
#ifdef D_LOSSLESS_SUPPORTED
  if (image->compression==LosslessJPEGCompression)
    {
      save_quality=100;
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Quality: 100 (lossless)");
    }
  else
#endif

    {
      int
        hashval,
        sum;

      /*
        Log the JPEG quality that was used for compression.
      */
      sum=0;
      for (i=0; i < NUM_QUANT_TBLS; i++)
        {
          int
            j;

          if (jpeg_info->quant_tbl_ptrs[i] != NULL)
            for (j=0; j < DCTSIZE2; j++)
              {
                UINT16 *c;
                c=jpeg_info->quant_tbl_ptrs[i]->quantval;
                sum+=c[j];
              }
        }
      if ((jpeg_info->quant_tbl_ptrs[0] != NULL) &&
          (jpeg_info->quant_tbl_ptrs[1] != NULL))
        {
          int
            hash[] =
            {
              1020, 1015,  932,  848,  780,  735,  702,  679,  660,  645,
              632,  623,  613,  607,  600,  594,  589,  585,  581,  571,
              555,  542,  529,  514,  494,  474,  457,  439,  424,  410,
              397,  386,  373,  364,  351,  341,  334,  324,  317,  309,
              299,  294,  287,  279,  274,  267,  262,  257,  251,  247,
              243,  237,  232,  227,  222,  217,  213,  207,  202,  198,
              192,  188,  183,  177,  173,  168,  163,  157,  153,  148,
              143,  139,  132,  128,  125,  119,  115,  108,  104,   99,
              94,   90,   84,   79,   74,   70,   64,   59,   55,   49,
              45,   40,   34,   30,   25,   20,   15,   11,    6,    4,
              0
            };

          int
            sums[] =
            {
              32640,32635,32266,31495,30665,29804,29146,28599,28104,27670,
              27225,26725,26210,25716,25240,24789,24373,23946,23572,22846,
              21801,20842,19949,19121,18386,17651,16998,16349,15800,15247,
              14783,14321,13859,13535,13081,12702,12423,12056,11779,11513,
              11135,10955,10676,10392,10208, 9928, 9747, 9564, 9369, 9193,
              9017, 8822, 8639, 8458, 8270, 8084, 7896, 7710, 7527, 7347,
              7156, 6977, 6788, 6607, 6422, 6236, 6054, 5867, 5684, 5495,
              5305, 5128, 4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698,
              3509, 3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
              1666, 1483, 1297, 1109,  927,  735,  554,  375,  201,  128,
              0
            };

          hashval=(jpeg_info->quant_tbl_ptrs[0]->quantval[2]+
                   jpeg_info->quant_tbl_ptrs[0]->quantval[53]+
                   jpeg_info->quant_tbl_ptrs[1]->quantval[0]+
                   jpeg_info->quant_tbl_ptrs[1]->quantval[DCTSIZE2-1]);
          for (i=0; i < 100; i++)
            {
              if ((hashval >= hash[i]) || (sum >= sums[i]))
                {
                  save_quality=i+1;
                  if (image->logging)
                    {
                      if ((hashval > hash[i]) || (sum > sums[i]))
                        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                              "Quality: %d (approximate)",
                                              save_quality);
                      else
                        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                              "Quality: %d",save_quality);
                    }
                  break;
                }
            }
        }
      else
        if (jpeg_info->quant_tbl_ptrs[0] != NULL)
          {
            int
              bwhash[] =
              {
                510,  505,  422,  380,  355,  338,  326,  318,  311,  305,
                300,  297,  293,  291,  288,  286,  284,  283,  281,  280,
                279,  278,  277,  273,  262,  251,  243,  233,  225,  218,
                211,  205,  198,  193,  186,  181,  177,  172,  168,  164,
                158,  156,  152,  148,  145,  142,  139,  136,  133,  131,
                129,  126,  123,  120,  118,  115,  113,  110,  107,  105,
                102,  100,   97,   94,   92,   89,   87,   83,   81,   79,
                76,   74,   70,   68,   66,   63,   61,   57,   55,   52,
                50,   48,   44,   42,   39,   37,   34,   31,   29,   26,
                24,   21,   18,   16,   13,   11,    8,    6,    3,    2,
                0
              };

            int
              bwsum[] =
              {
                16320,16315,15946,15277,14655,14073,13623,13230,12859,12560,
                12240,11861,11456,11081,10714,10360,10027, 9679, 9368, 9056,
                8680, 8331, 7995, 7668, 7376, 7084, 6823, 6562, 6345, 6125,
                5939, 5756, 5571, 5421, 5240, 5086, 4976, 4829, 4719, 4616, 
                4463, 4393, 4280, 4166, 4092, 3980, 3909, 3835, 3755, 3688,
                3621, 3541, 3467, 3396, 3323, 3247, 3170, 3096, 3021, 2952,
                2874, 2804, 2727, 2657, 2583, 2509, 2437, 2362, 2290, 2211,
                2136, 2068, 1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477,
                1398, 1326, 1251, 1179, 1109, 1031,  961,  884,  814,  736,
                667,  592,  518,  441,  369,  292,  221,  151,   86,   64,
                0
              };

            hashval=(jpeg_info->quant_tbl_ptrs[0]->quantval[2]+
                     jpeg_info->quant_tbl_ptrs[0]->quantval[53]);
            for (i=0; i < 100; i++)
              {
                if ((hashval >= bwhash[i]) || (sum >= bwsum[i]))
                  {
                    save_quality=i+1;
                    if (image->logging)
                      {
                        if ((hashval > bwhash[i]) || (sum > bwsum[i]))
                          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                                "Quality: %ld (approximate)",
                                                i+1);
                        else
                          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                                "Quality: %ld",i+1);
                      }
                    break;
                  }
              }
          }
    }

  return save_quality;
}

/*
  Format JPEG color space to a string.
*/
static void
FormatJPEGColorSpace(const J_COLOR_SPACE colorspace,
                     char *colorspace_name)
{
  const char
    *s = NULL;

  switch (colorspace)
    {
    default:
    case JCS_UNKNOWN:
      s = "UNKNOWN";
      break;
    case JCS_GRAYSCALE:
      s = "GRAYSCALE";
      break;
    case JCS_RGB:
      s = "RGB";
      break;
    case JCS_YCbCr:
      s = "YCbCr";
      break;
    case JCS_CMYK:
      s = "CMYK";
      break;
    case JCS_YCCK:
      s = "YCCK";
      break;
    }
  (void) strlcpy(colorspace_name,s,MaxTextExtent);
}

/*
  Format JPEG sampling factors to a string.
*/
static void
FormatJPEGSamplingFactors(const struct jpeg_decompress_struct *jpeg_info,
                          char *sampling_factors)
{
  switch (jpeg_info->out_color_space)
    {
    case JCS_CMYK:
      {
        (void) FormatString(sampling_factors,"%dx%d,%dx%d,%dx%d,%dx%d",
                            jpeg_info->comp_info[0].h_samp_factor,
                            jpeg_info->comp_info[0].v_samp_factor,
                            jpeg_info->comp_info[1].h_samp_factor,
                            jpeg_info->comp_info[1].v_samp_factor,
                            jpeg_info->comp_info[2].h_samp_factor,
                            jpeg_info->comp_info[2].v_samp_factor,
                            jpeg_info->comp_info[3].h_samp_factor,
                            jpeg_info->comp_info[3].v_samp_factor);
        break;
      }
    case JCS_GRAYSCALE:
      {
        (void) FormatString(sampling_factors,"%dx%d",
                            jpeg_info->comp_info[0].h_samp_factor,
                            jpeg_info->comp_info[0].v_samp_factor);
        break;
      }
    case JCS_RGB:
      {
        (void) FormatString(sampling_factors,"%dx%d,%dx%d,%dx%d",
                            jpeg_info->comp_info[0].h_samp_factor,
                            jpeg_info->comp_info[0].v_samp_factor,
                            jpeg_info->comp_info[1].h_samp_factor,
                            jpeg_info->comp_info[1].v_samp_factor,
                            jpeg_info->comp_info[2].h_samp_factor,
                            jpeg_info->comp_info[2].v_samp_factor);
        break;
      }
    default:
      {
        (void) FormatString(sampling_factors,"%dx%d,%dx%d,%dx%d,%dx%d",
                            jpeg_info->comp_info[0].h_samp_factor,
                            jpeg_info->comp_info[0].v_samp_factor,
                            jpeg_info->comp_info[1].h_samp_factor,
                            jpeg_info->comp_info[1].v_samp_factor,
                            jpeg_info->comp_info[2].h_samp_factor,
                            jpeg_info->comp_info[2].v_samp_factor,
                            jpeg_info->comp_info[3].h_samp_factor,
                            jpeg_info->comp_info[3].v_samp_factor);
        break;
      }
    }
}

static MagickBool
IsITUFax(const Image* image)
{
  size_t
    profile_length;
  
  const unsigned char
    *profile;
  
  MagickBool
    status;
  
  status=MagickFalse;
  if ((profile=GetImageProfile(image,"APP1",&profile_length)) &&
      (profile_length >= 5))
    {
      if (profile[0] == 0x47 &&
          profile[1] == 0x33 &&
          profile[2] == 0x46 &&
          profile[3] == 0x41 &&
          profile[4] == 0x58)
      status=MagickTrue;
    }

    return status;
}

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

  Image
    *image;

  IndexPacket
    index;

  long
    y;

  JSAMPLE
    *jpeg_pixels;

  JSAMPROW
    scanline[1];

  register long
    i;

  struct jpeg_decompress_struct
    jpeg_info;

  struct jpeg_error_mgr
    jpeg_error;

  register JSAMPLE
    *p;

  MagickPassFail
    status;

  unsigned long
    number_pixels;

  /*
    Open image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  image=AllocateImage(image_info);
  if (image == (Image *) NULL)
    {
      ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,
                           image);
    }
  status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
  if (status == MagickFail)
    ThrowReaderException(FileOpenError,UnableToOpenFile,image);
  /*
    Initialize image structure.
  */
  jpeg_info.err=jpeg_std_error(&jpeg_error);
  jpeg_info.err->emit_message=(void (*)(j_common_ptr,int)) EmitMessage;
  jpeg_info.err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
  jpeg_pixels=(JSAMPLE *) NULL;
  error_manager.image=image;

  /*
    Set initial longjmp based error handler.
  */
  if (setjmp(error_manager.error_recovery))
    {
      jpeg_destroy_decompress(&jpeg_info);
      GetImageException(image,exception);
      CloseBlob(image);
      if (exception->severity < ErrorException)
        return(image);
      DestroyImage(image);
      return((Image *) NULL);
    }
  jpeg_info.client_data=(void *) &error_manager;
  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);
  for (i=1; i < 16; i++)
    if ((i != 2) && (i != 13) && (i != 14))
      jpeg_set_marker_processor(&jpeg_info,JPEG_APP0+i,ReadGenericProfile);
  i=jpeg_read_header(&jpeg_info,True);
  if (IsITUFax(image))
    {
      if (image->logging)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Image colorspace set to LAB");
      image->colorspace=LABColorspace;
      jpeg_info.out_color_space = JCS_YCbCr;
    }
  else if (jpeg_info.out_color_space == JCS_CMYK)
    {
      if (image->logging)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Image colorspace set to CMYK");
      image->colorspace=CMYKColorspace;
    }
  if (jpeg_info.saw_JFIF_marker)
    {
      if ((jpeg_info.X_density != 1U) && (jpeg_info.Y_density != 1U))
        {
          /*
            Set image resolution.
          */
          image->x_resolution=jpeg_info.X_density;
          image->y_resolution=jpeg_info.Y_density;
          if (jpeg_info.density_unit == 1)
            image->units=PixelsPerInchResolution;
          if (jpeg_info.density_unit == 2)
            image->units=PixelsPerCentimeterResolution;
        }
    }
  /*
    If the desired image size is pre-set (e.g. by using -size), then
    let the JPEG library subsample for us.
  */
  number_pixels=image->columns*image->rows;
  if (number_pixels != 0)
    {
      double
        scale_factor;


      if (image->logging)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Requested Geometry: %lux%lu",
                              image->columns,image->rows);
      jpeg_calc_output_dimensions(&jpeg_info);
      image->magick_columns=jpeg_info.output_width;
      image->magick_rows=jpeg_info.output_height;
      scale_factor=(double) jpeg_info.output_width/image->columns;
      if (scale_factor > ((double) jpeg_info.output_height/image->rows))
        scale_factor=(double) jpeg_info.output_height/image->rows;
      jpeg_info.scale_denom *=(unsigned int) scale_factor;
      jpeg_calc_output_dimensions(&jpeg_info);
      if (image->logging)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Scale_factor: %ld (scale_num=%d, "
                              "scale_denom=%d)",
                              (long) scale_factor,
                              jpeg_info.scale_num,jpeg_info.scale_denom);
    }
#if 0
  /*
    The subrange parameter is set by the filename array syntax similar
    to the way an image is requested from a list (e.g. myfile.jpg[2]).
    Argument values other than zero are used to scale the image down
    by that factor.  IJG JPEG 62 (6b) supports values of 1,2,4, or 8
    while IJG JPEG 70 supports all values in the range 1-16.  This
    feature is useful in case you want to view all of the images with
    a consistent ratio.  Unfortunately, it uses the same syntax as
    list member access.
  */
  else if (image_info->subrange != 0)
    {
      jpeg_info.scale_denom *=(int) image_info->subrange;
      jpeg_calc_output_dimensions(&jpeg_info);
      if (image->logging)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Requested Scaling Denominator: %d "
                              "(scale_num=%d, scale_denom=%d)",
                              (int) image_info->subrange,
                              jpeg_info.scale_num,jpeg_info.scale_denom);

    }
#endif
#if (JPEG_LIB_VERSION >= 61) && defined(D_PROGRESSIVE_SUPPORTED)
#ifdef D_LOSSLESS_SUPPORTED
  image->interlace=
    jpeg_info.process == JPROC_PROGRESSIVE ? LineInterlace : NoInterlace;
  image->compression=jpeg_info.process == JPROC_LOSSLESS ?
    LosslessJPEGCompression : JPEGCompression;
  if (jpeg_info.data_precision > 8)
    MagickError2(OptionError,
                 "12-bit JPEG not supported. Reducing pixel data to 8 bits",
                 (char *) NULL);
#else
  image->interlace=jpeg_info.progressive_mode ? LineInterlace : NoInterlace;
  image->compression=JPEGCompression;
#endif
#else
  image->compression=JPEGCompression;
  image->interlace=LineInterlace;
#endif
  {
    const char
      *value;

    /*
      Allow the user to enable/disable block smoothing.
    */
    if ((value=AccessDefinition(image_info,"jpeg","block-smoothing")))
      {
        if (LocaleCompare(value,"FALSE") == 0)
          jpeg_info.do_block_smoothing=False;
        else
          jpeg_info.do_block_smoothing=True;
      }

    /*
      Allow the user to select the DCT decoding algorithm.
    */
    if ((value=AccessDefinition(image_info,"jpeg","dct-method")))
      {
        if (LocaleCompare(value,"ISLOW") == 0)
          jpeg_info.dct_method=JDCT_ISLOW;
        else if (LocaleCompare(value,"IFAST") == 0)
          jpeg_info.dct_method=JDCT_IFAST;
        else if (LocaleCompare(value,"FLOAT") == 0)
          jpeg_info.dct_method=JDCT_FLOAT;
        else if (LocaleCompare(value,"DEFAULT") == 0)
          jpeg_info.dct_method=JDCT_DEFAULT;
        else if (LocaleCompare(value,"FASTEST") == 0)
          jpeg_info.dct_method=JDCT_FASTEST;
      }

    /*
      Allow the user to enable/disable fancy upsampling.
    */
    if ((value=AccessDefinition(image_info,"jpeg","fancy-upsampling")))
      {
        if (LocaleCompare(value,"FALSE") == 0)
          jpeg_info.do_fancy_upsampling=False;
        else
          jpeg_info.do_fancy_upsampling=True;
      }
  }
  (void) jpeg_start_decompress(&jpeg_info);
  image->columns=jpeg_info.output_width;
  image->rows=jpeg_info.output_height;
  if (image->logging)
    {
      if (image->interlace == LineInterlace)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Interlace: progressive");
      else
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Interlace: nonprogressive");
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Data precision: %d",
                            (int) jpeg_info.data_precision);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Components: %d",
                            (int) jpeg_info.output_components);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Geometry: %dx%d",
                            (int) jpeg_info.output_width,
                            (int) jpeg_info.output_height);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"DCT Method: %d",
                            jpeg_info.dct_method);
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Fancy Upsampling: %s",
                            (jpeg_info.do_fancy_upsampling ? "true" : "false"));
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Block Smoothing: %s",
                            (jpeg_info.do_block_smoothing ? "true" : "false"));
    }

  {
    char
      attribute[MaxTextExtent];

    /*
      Estimate and retain JPEG properties as attributes.
    */
    FormatString(attribute,"%d",EstimateJPEGQuality(&jpeg_info,image));
    (void) SetImageAttribute(image,"JPEG-Quality",attribute);

    FormatString(attribute,"%ld",(long)jpeg_info.out_color_space);
    (void) SetImageAttribute(image,"JPEG-Colorspace",attribute);

    FormatJPEGColorSpace(jpeg_info.out_color_space,attribute);
    (void) SetImageAttribute(image,"JPEG-Colorspace-Name",attribute);
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Colorspace: %s (%d)", attribute,
                            jpeg_info.out_color_space);

    FormatJPEGSamplingFactors(&jpeg_info,attribute);
    (void) SetImageAttribute(image,"JPEG-Sampling-factors",attribute);
    if (image->logging)
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                            "Sampling Factors: %s", attribute);
  }
  
  image->depth=Min(jpeg_info.data_precision,QuantumDepth);
  if (jpeg_info.out_color_space == JCS_GRAYSCALE)
    if (!AllocateImageColormap(image,1 << image->depth))
      ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);
  if (image_info->ping)
    {
      jpeg_destroy_decompress(&jpeg_info);
      CloseBlob(image);
      return(image);
    }
  jpeg_pixels=MagickAllocateArray(JSAMPLE *,
                                  jpeg_info.output_components,
                                  image->columns*sizeof(JSAMPLE));
  if (jpeg_pixels == (JSAMPLE *) NULL)
    ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image);

  /*
    Extended longjmp-based error handler (with jpeg_pixels)
  */
  if (setjmp(error_manager.error_recovery))
    {
      /* Error handling code executed if longjmp was invoked */
      MagickFreeMemory(jpeg_pixels);
      jpeg_destroy_decompress(&jpeg_info);
      if (image->exception.severity > exception->severity)
        CopyException(exception,&image->exception);
      CloseBlob(image);
      number_pixels=image->columns*image->rows;
      if (number_pixels != 0)
        return(image);
      DestroyImage(image);
      return((Image *) NULL);
    }

  /*
    Convert JPEG pixels to pixel packets.
  */
  scanline[0]=(JSAMPROW) jpeg_pixels;
  for (y=0; y < (long) image->rows; y++)
    {
      register IndexPacket
        *indexes;

      register long
        x;

      register PixelPacket
        *q;

      q=SetImagePixels(image,0,y,image->columns,1);
      if (q == (PixelPacket *) NULL)
        {
          status=MagickFail;
          break;
        }
      indexes=AccessMutableIndexes(image);

      /* FIXME .... */
      if (jpeg_read_scanlines(&jpeg_info,scanline,1) != 1)
        ThrowReaderException(CorruptImageError,CorruptImage,image);

      p=jpeg_pixels;

      if (jpeg_info.output_components == 1)
        {
          for (x=0; x < (long) image->columns; x++)
            {
              index=(IndexPacket) (GETJSAMPLE(*p++));
              VerifyColormapIndex(image,index);
              indexes[x]=index;
              *q++=image->colormap[index];
            }
        }
      else
        {
          if (jpeg_info.data_precision > 8)
            {
              for (x=0; x < (long) image->columns; x++)
                {
                  q->red=ScaleShortToQuantum(16*GETJSAMPLE(*p++));
                  q->green=ScaleShortToQuantum(16*GETJSAMPLE(*p++));
                  q->blue=ScaleShortToQuantum(16*GETJSAMPLE(*p++));
                  if (jpeg_info.output_components > 3)
                    q->opacity=ScaleShortToQuantum(16*GETJSAMPLE(*p++));
                  else
                    q->opacity=OpaqueOpacity;
                  q++;
                }
            }
          else
            {
              for (x=0; x < (long) image->columns; x++)
                {
                  q->red=ScaleCharToQuantum(GETJSAMPLE(*p++));
                  q->green=ScaleCharToQuantum(GETJSAMPLE(*p++));
                  q->blue=ScaleCharToQuantum(GETJSAMPLE(*p++));
                  if (jpeg_info.output_components > 3)
                    q->opacity=ScaleCharToQuantum(GETJSAMPLE(*p++));
                  else
                    q->opacity=OpaqueOpacity;
                  q++;
                }
            }
          if (image->colorspace == CMYKColorspace)
            {
              /*
                CMYK pixels are inverted.
              */
              q=AccessMutablePixels(image);
              for (x=0; x < (long) image->columns; x++)
                {
                  q->red=MaxRGB-q->red;
                  q->green=MaxRGB-q->green;
                  q->blue=MaxRGB-q->blue;
                  q->opacity=MaxRGB-q->opacity;
                  q++;
                }
            }
        }
      if (!SyncImagePixels(image))
        {
          status=MagickFail;
          break;
        }
      if (QuantumTick(y,image->rows))
        if (!MagickMonitorFormatted(y,image->rows,exception,LoadImageText,
                                    image->filename,
                                    image->columns,image->rows))
          {
            status=MagickFail;
            break;
          }
    }
  /*
    Free jpeg resources.
  */
  if (status == MagickPass)
    (void) jpeg_finish_decompress(&jpeg_info);
  jpeg_destroy_decompress(&jpeg_info);
  MagickFreeMemory(jpeg_pixels);
  CloseBlob(image);

  /*
    Retrieve image orientation from EXIF (if present) and store in
    image.

    EXIF orienation enumerations match TIFF enumerations, which happen
    to match the enumeration values used by GraphicsMagick.
  */
  if (status == MagickPass)
    {
      const ImageAttribute
        *attribute;

      attribute = GetImageAttribute(image,"EXIF:Orientation");
      if ((attribute != (const ImageAttribute *) NULL) &&
          (attribute->value != (char *) NULL))
        {
          int
            orientation;

          orientation=MagickAtoI(attribute->value);
          if ((orientation > UndefinedOrientation) &&
              (orientation <= LeftBottomOrientation))
            image->orientation=(OrientationType) orientation;
        }
    }
  if (image->logging) 
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),"return");
  GetImageException(image,exception);
  return(image);
}
#endif

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e g i s t e r J P E G I m a g e                                         %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method RegisterJPEGImage adds attributes for the JPEG image format to
%  the list of supported formats.  The attributes include the image format
%  tag, a method to read and/or write the format, whether the format
%  supports the saving of more than one frame to the same file or blob,
%  whether the format supports native in-memory I/O, and a brief
%  description of the format.
%
%  The format of the RegisterJPEGImage method is:
%
%      RegisterJPEGImage(void)
%
*/
ModuleExport void RegisterJPEGImage(void)
{
  static const char
    *description="Joint Photographic Experts Group JFIF format";

  static char
    version[MaxTextExtent];

  MagickInfo
    *entry;

  version[0]='\0';
#if defined(HasJPEG)
  FormatString(version,"IJG JPEG %d",JPEG_LIB_VERSION);
#endif

  entry=SetMagickInfo("JPEG");
  entry->thread_support=False; /* libjpeg is not thread safe */
#if defined(HasJPEG)
  entry->decoder=(DecoderHandler) ReadJPEGImage;
  entry->encoder=(EncoderHandler) WriteJPEGImage;
#endif
  entry->magick=(MagickHandler) IsJPEG;
  entry->adjoin=False;
  entry->description=description;
  if (version[0] != '\0')
    entry->version=version;
  entry->module="JPEG";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);

  entry=SetMagickInfo("JPG");
  entry->thread_support=False; /* libjpeg is not thread safe */
#if defined(HasJPEG)
  entry->decoder=(DecoderHandler) ReadJPEGImage;
  entry->encoder=(EncoderHandler) WriteJPEGImage;
#endif
  entry->adjoin=False;
  entry->description=description;
  if (version[0] != '\0')
    entry->version=version;
  entry->module="JPEG";
  entry->coder_class=PrimaryCoderClass;
  (void) RegisterMagickInfo(entry);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   U n r e g i s t e r J P E G I m a g e                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method UnregisterJPEGImage removes format registrations made by the
%  JPEG module from the list of supported formats.
%
%  The format of the UnregisterJPEGImage method is:
%
%      UnregisterJPEGImage(void)
%
*/
ModuleExport void UnregisterJPEGImage(void)
{
  (void) UnregisterMagickInfo("JPEG");
  (void) UnregisterMagickInfo("JPG");
}

#if defined(HasJPEG)
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%  W r i t e J P E G I m a g e                                                %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method WriteJPEGImage writes a JPEG image file and returns it.  It
%  allocates the memory necessary for the new Image structure and returns a
%  pointer to the new image.
%
%  The format of the WriteJPEGImage method is:
%
%      unsigned int WriteJPEGImage(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows:
%
%    o status:  Method WriteJPEGImage return True if the image is written.
%      False is returned is there is of a memory shortage or if the image
%      file cannot be opened for writing.
%
%    o image_info: Specifies a pointer to a ImageInfo structure.
%
%    o jpeg_image:  A pointer to an Image structure.
%
%
*/

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

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

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

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

static unsigned int JPEGWarningHandler(j_common_ptr jpeg_info,int level)
{
  char
    message[JMSG_LENGTH_MAX];

  Image
    *image;

  (jpeg_info->err->format_message)(jpeg_info,message);
  image=(Image *) jpeg_info->client_data;
  if (level < 0)
    {
      if ((jpeg_info->err->num_warnings == 0) ||
          (jpeg_info->err->trace_level >= 3))
        ThrowBinaryException2(CoderError,(char *) message,image->filename);
      jpeg_info->err->num_warnings++;
    }
  else
    if (jpeg_info->err->trace_level >= level)
      ThrowBinaryException2(CoderError,(char *) message,image->filename);
  return(True);
}

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

  destination=(DestinationManager *) cinfo->dest;
  if ((MaxBufferExtent-(int) destination->manager.free_in_buffer) > 0)
    {
      unsigned long
        number_bytes;

      number_bytes=WriteBlob(destination->image,MaxBufferExtent-
        destination->manager.free_in_buffer,(char *) destination->buffer);
      if (number_bytes != (MaxBufferExtent-destination->manager.free_in_buffer))
        ERREXIT(cinfo,JERR_FILE_WRITE);
    }
}

static void WriteICCProfile(j_compress_ptr jpeg_info,Image *image)
{
  register long
    i,
    j;

  const unsigned char
    *color_profile;

  size_t
    profile_length;

  /*
    Retrieve color profile.
  */
  color_profile=GetImageProfile(image,"ICM",&profile_length);
  if (color_profile == ( const unsigned char *) NULL)
    return;

  /*
    Save color profile as a APP marker.
  */
  for (i=0; i < (long) profile_length; i+=65519)
  {
    unsigned char
      *profile;

    size_t
      length=0;


    length=Min(profile_length-i,65519);
    profile=MagickAllocateMemory(unsigned char *,length+14);
    if (profile == (unsigned char *) NULL)
      break;
    (void) strcpy((char *) profile,"ICC_PROFILE");
    profile[12]=(unsigned char) ((i/65519)+1);
    profile[13]=(profile_length/65519)+1;
    for (j=0; j < (long) length; j++)
      profile[j+14]=color_profile[i+j];
    jpeg_write_marker(jpeg_info,ICC_MARKER,profile,(unsigned int) length+14);
    MagickFreeMemory(profile);
  }
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                        "ICC profile: %ld bytes",(long) profile_length);
}

static void WriteIPTCProfile(j_compress_ptr jpeg_info,Image *image)
{
  register long
    i;

  unsigned long
    roundup,
    tag_length;

  const unsigned char
    *iptc_profile;

  size_t
    profile_length;

  /*
    Retrieve IPTC profile from Image.
  */
  iptc_profile=GetImageProfile(image,"IPTC",&profile_length);
  if (iptc_profile == ( const unsigned char *) NULL)
    return;

  /*
    Save binary Photoshop resource data using an APP marker.
  */
#ifdef GET_ONLY_IPTC_DATA
  tag_length=26;
#else
  tag_length=14;
#endif
  for (i=0; i < (long) profile_length; i+=65500)
  {
    size_t
      length;
    
    unsigned char
      *profile;

    length=Min(profile_length-i,65500);
    roundup=(length & 0x01); /* round up for Photoshop */
    profile=MagickAllocateMemory(unsigned char *,length+roundup+tag_length);
    if (profile == (unsigned char *) NULL)
      break;
#ifdef GET_ONLY_IPTC_DATA
    (void) memcpy(profile,"Photoshop 3.0 8BIM\04\04\0\0\0\0",24);
    profile[13]=0x00;
    profile[24]=length >> 8;
    profile[25]=length & 0xff;
#else
    (void) memcpy(profile,"Photoshop 3.0 ",14);
    profile[13]=0x00;
#endif
    (void) memcpy(&(profile[tag_length]),&(iptc_profile[i]),length);
    if (roundup)
      profile[length+tag_length]=0;
    jpeg_write_marker(jpeg_info,IPTC_MARKER,profile,(unsigned int)
      (length+roundup+tag_length));
    MagickFreeMemory(profile);
  }

  (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                        "IPTC profile: %ld bytes",(long) profile_length);
}

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

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

static MagickPassFail WriteJPEGImage(const ImageInfo *image_info,Image *image)
{
  const ImageAttribute
    *attribute;

  JSAMPLE
    *jpeg_pixels;

  JSAMPROW
    scanline[1];

  char
   *sampling_factors,
   *preserve_settings;

  long
    y;

  register const PixelPacket
    *p;

  register JSAMPLE
    *q;

  register long
    i,
    x;

  struct jpeg_compress_struct
    jpeg_info;

  struct jpeg_error_mgr
    jpeg_error;

  MagickPassFail
    status;

  unsigned long
    input_colorspace;

  unsigned long
    quality;

  magick_int64_t
    huffman_memory;

  ImageCharacteristics
    characteristics;

  /*
    Open image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(image != (Image *) NULL);
  assert(image->signature == MagickSignature);
  status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
  if (status == False)
    ThrowWriterException(FileOpenError,UnableToOpenFile,image);
  
  /*
    Analyze image to be written.
  */
  if (!GetImageCharacteristics(image,&characteristics,
                               (OptimizeType == image_info->type),
                               &image->exception))
    {
      CloseBlob(image);
      return MagickFail;
    }

  /*
    Initialize JPEG parameters.
  */
  (void) memset(&jpeg_info,0,sizeof(jpeg_info));
  (void) memset(&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)) JPEGWarningHandler;
  jpeg_create_compress(&jpeg_info);
  JPEGDestinationManager(&jpeg_info,image);
  jpeg_info.image_width=(unsigned int) image->columns;
  jpeg_info.image_height=(unsigned int) image->rows;
  jpeg_info.input_components=3;
  jpeg_info.in_color_space=JCS_RGB;
  switch (image_info->colorspace)
  {
    case CMYKColorspace:
    {
      jpeg_info.input_components=4;
      jpeg_info.in_color_space=JCS_CMYK;
      (void) TransformColorspace(image,CMYKColorspace);
      break;
    }
    case YCbCrColorspace:
    {
      jpeg_info.in_color_space=JCS_YCbCr;
      (void) TransformColorspace(image,YCbCrColorspace);
      break;
    }
    case GRAYColorspace:
    case Rec601LumaColorspace:
    case Rec709LumaColorspace:
    {
      jpeg_info.in_color_space=JCS_GRAYSCALE;
      (void) TransformColorspace(image,image_info->colorspace);
      break;
    }
    default:
    {
      if (image->colorspace == CMYKColorspace)
        {
          jpeg_info.input_components=4;
          jpeg_info.in_color_space=JCS_CMYK;
          break;
        }
      if (image->colorspace == YCbCrColorspace)
        {
          jpeg_info.in_color_space=JCS_YCbCr;
          break;
        }
      (void) TransformColorspace(image,RGBColorspace);
      break;
    }
  }

  input_colorspace=UndefinedColorspace;
  quality=image_info->quality;
  /* Check for -define jpeg:preserve-settings */
  /* ImageMagick:
    GetImageOption();
  */
  /* GraphicsMagick */
  preserve_settings=(char *) AccessDefinition(image_info,"jpeg",
     "preserve-settings");

  sampling_factors=image_info->sampling_factor;

  if (preserve_settings)
    {
      if (image->logging)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "  JPEG:preserve-settings flag is defined.");

      /* Retrieve input file quality */
      attribute=GetImageAttribute(image,"JPEG-Quality");
      if ((attribute != (const ImageAttribute *) NULL) &&
          (attribute->value != (char *) NULL))
        {
          (void) sscanf(attribute->value,"%lu",&quality);
          if (image->logging)
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "  Input quality=%lu",quality);
        }

      /* Retrieve input file colorspace */
      attribute=GetImageAttribute(image,"JPEG-Colorspace");
      if ((attribute != (const ImageAttribute *) NULL) &&
          (attribute->value != (char *) NULL))
        {
          (void) sscanf(attribute->value,"%lu",&input_colorspace);
          if (image->logging)
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "  Input colorspace=%lu",input_colorspace);
        }

      if (input_colorspace == (unsigned long) jpeg_info.in_color_space)
        {
          /* Retrieve input sampling factors */
          attribute=GetImageAttribute(image,"JPEG-Sampling-factors");
          if ((attribute != (const ImageAttribute *) NULL) &&
              (attribute->value != (char *) NULL))
            {
              sampling_factors=attribute->value;
              if (image->logging)
                (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                    "  Input sampling-factors=%s",sampling_factors);
            }
        }
      else
        {
          if (image->logging)
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "  Input colorspace (%lu) != Output colorspace (%d)",
                input_colorspace,jpeg_info.in_color_space);
        }
    }

  if ((image_info->type != TrueColorType) &&
      (image_info->type != TrueColorMatteType) &&
      (image_info->type != ColorSeparationType) &&
      (image_info->type != ColorSeparationMatteType) &&
      (image->colorspace != CMYKColorspace) &&
      (characteristics.grayscale))
    {
      jpeg_info.input_components=1;
      jpeg_info.in_color_space=JCS_GRAYSCALE;
    }
  jpeg_set_defaults(&jpeg_info);
  /*
    Determine bit depth.
  */
  {
    int
      sample_size;
    
    sample_size=sizeof(JSAMPLE)*8;
    if (sample_size > 8)
      sample_size=12;
    if ((jpeg_info.data_precision != 12) &&
        (image->depth <= 8))
      sample_size=8;
    jpeg_info.data_precision=sample_size;
  }
  if ((image->x_resolution == 0) || (image->y_resolution == 0))
    {
      image->x_resolution=72.0;
      image->y_resolution=72.0;
      image->units=PixelsPerInchResolution;
    }
  if (image_info->density != (char *) NULL)
    {
      int
        count;

      /* FIXME: density should not be set via image_info->density
         but removing this support may break some applications. */
      count=GetMagickDimension(image_info->density,&image->x_resolution,
        &image->y_resolution,NULL,NULL);
      if (count == 1 )
        image->y_resolution=image->x_resolution;
    }
  jpeg_info.density_unit=1;  /* default to DPI */
  if (image->logging)
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
      "Image resolution: %ld,%ld",(long) image->x_resolution,
      (long) image->y_resolution);
  if ((image->x_resolution != 0) && (image->y_resolution != 0))
    {
      /*
        Set image resolution.
      */
      jpeg_info.write_JFIF_header=True;
      jpeg_info.X_density=(short) image->x_resolution;
      jpeg_info.Y_density=(short) image->y_resolution;
      if (image->units == PixelsPerInchResolution)
        jpeg_info.density_unit=1;
      if (image->units == PixelsPerCentimeterResolution)
        jpeg_info.density_unit=2;
    }

  {
    const char
      *value;

    /*
      Allow the user to select the DCT encoding algorithm.
    */
    if ((value=AccessDefinition(image_info,"jpeg","dct-method")))
      {
        if (LocaleCompare(value,"ISLOW") == 0)
          jpeg_info.dct_method=JDCT_ISLOW;
        else if (LocaleCompare(value,"IFAST") == 0)
          jpeg_info.dct_method=JDCT_IFAST;
        else if (LocaleCompare(value,"FLOAT") == 0)
          jpeg_info.dct_method=JDCT_FLOAT;
        else if (LocaleCompare(value,"DEFAULT") == 0)
          jpeg_info.dct_method=JDCT_DEFAULT;
        else if (LocaleCompare(value,"FASTEST") == 0)
          jpeg_info.dct_method=JDCT_FASTEST;
      }
  }

{
  const char
    *value;
  
  huffman_memory=0;
  if ((value=AccessDefinition(image_info,"jpeg","optimize-coding")))
    {
      if (LocaleCompare(value,"FALSE") == 0)
        jpeg_info.optimize_coding=MagickFalse;
      else
        jpeg_info.optimize_coding=MagickTrue;
    }
  else
    {
      /*
        Huffman optimization requires that the whole image be buffered in
        memory.  Since this is such a large consumer, obtain a memory
        resource for the memory to be consumed.  If the memory resource
        fails to be acquired, then don't enable huffman optimization.
      */
      huffman_memory=(magick_int64_t) jpeg_info.input_components*
        image->columns*image->rows*sizeof(JSAMPLE);
      jpeg_info.optimize_coding=AcquireMagickResource(MemoryResource,huffman_memory);
    }
  if (!jpeg_info.optimize_coding)
    huffman_memory=0;
  if (image->logging)
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                          "Huffman optimization is %s",
                          (jpeg_info.optimize_coding ? "enabled" : "disabled"));
 }

#if (JPEG_LIB_VERSION >= 61) && defined(C_PROGRESSIVE_SUPPORTED)
 if (image_info->interlace == LineInterlace)
   jpeg_simple_progression(&jpeg_info);
 if (image->logging)
   {
     if (image_info->interlace == LineInterlace)
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                             "Interlace: progressive");
     else
       (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                             "Interlace: nonprogressive");
   }
#else
  if (image->logging)
    (void) LogMagickEvent(CoderEvent,GetMagickModule(),
      "Interlace:  nonprogressive");
#endif
  if ((image->compression == LosslessJPEGCompression) ||
      (quality > 100))
    {
#if defined(C_LOSSLESS_SUPPORTED)
      if (quality < 100)
        ThrowException(&image->exception,CoderWarning,LosslessToLossyJPEGConversion,(char *) NULL);
      else
        {
          int
            point_transform,
            predictor;

          predictor=quality/100;  /* range 1-7 */
          point_transform=quality % 20;  /* range 0-15 */
          jpeg_simple_lossless(&jpeg_info,predictor,point_transform);
          if (image->logging)
            {
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "Compression: lossless");
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "Predictor: %d",predictor);
              (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                "Point Transform: %d",point_transform);
            }
        }
#else
        {
          jpeg_set_quality(&jpeg_info,100,True);
          if (image->logging)
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: 100");
        }
#endif
    }
  else
    {
      jpeg_set_quality(&jpeg_info,(int) quality,True);
      if (image->logging)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: %lu",
          quality);
    }
  if (sampling_factors != (char *) NULL)
    {
      double
        hs[4]={1.0, 1.0, 1.0, 1.0}, 
        vs[4]={1.0, 1.0, 1.0, 1.0};

      long
        count;

      /*
        Set sampling factors.
      */
      count=sscanf(sampling_factors,"%lfx%lf,%lfx%lf,%lfx%lf,%lfx%lf",
        &hs[0],&vs[0],&hs[1],&vs[1],&hs[2],&vs[2],&hs[3],&vs[3]);

      if (count%2 == 1)
        vs[count/2]=hs[count/2];

      for (i=0; i < 4; i++)
      {
        jpeg_info.comp_info[i].h_samp_factor=(int) hs[i];
        jpeg_info.comp_info[i].v_samp_factor=(int) vs[i];
      }
      for (; i < MAX_COMPONENTS; i++)
      {
        jpeg_info.comp_info[i].h_samp_factor=1;
        jpeg_info.comp_info[i].v_samp_factor=1;
      }
    }
   else
    {
      if (quality >= 90)
        for (i=0; i < MAX_COMPONENTS; i++)
        {
          jpeg_info.comp_info[i].h_samp_factor=1;
          jpeg_info.comp_info[i].v_samp_factor=1;
        }
    }
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Starting JPEG compression");
  jpeg_start_compress(&jpeg_info,True);
  if (image->logging)
    {
      if (image->storage_class == PseudoClass)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "Storage class: PseudoClass");
      else
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "Storage class: DirectClass");
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Depth: %u",
        image->depth);
      if (image->colors)
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "Number of colors: %u",image->colors);
      else
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
          "Number of colors: unspecified");
      (void) LogMagickEvent(CoderEvent,GetMagickModule(),
        "JPEG data precision: %d",(int) jpeg_info.data_precision);
      switch (image_info->colorspace)
      {
        case CMYKColorspace:
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Storage class: DirectClass");
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Colorspace: CMYK");
          break;
        }
        case YCbCrColorspace:
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Colorspace: YCbCr");
          break;
        }
          default:
          break;
      }
      switch (image->colorspace)
      {
        case CMYKColorspace:
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Colorspace: CMYK");
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Sampling factors: %dx%d,%dx%d,%dx%d,%dx%d",
            jpeg_info.comp_info[0].h_samp_factor,
            jpeg_info.comp_info[0].v_samp_factor,
            jpeg_info.comp_info[1].h_samp_factor,
            jpeg_info.comp_info[1].v_samp_factor,
            jpeg_info.comp_info[2].h_samp_factor,
            jpeg_info.comp_info[2].v_samp_factor,
            jpeg_info.comp_info[3].h_samp_factor,
            jpeg_info.comp_info[3].v_samp_factor);
          break;
        }
        case Rec601LumaColorspace:
        case Rec709LumaColorspace:
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Colorspace: GRAY");
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Sampling factors: %dx%d",jpeg_info.comp_info[0].h_samp_factor,
            jpeg_info.comp_info[0].v_samp_factor);
          break;
        }
        case RGBColorspace:
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            " Image colorspace is RGB");
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Sampling factors: %dx%d,%dx%d,%dx%d",
            jpeg_info.comp_info[0].h_samp_factor,
            jpeg_info.comp_info[0].v_samp_factor,
            jpeg_info.comp_info[1].h_samp_factor,
            jpeg_info.comp_info[1].v_samp_factor,
            jpeg_info.comp_info[2].h_samp_factor,
            jpeg_info.comp_info[2].v_samp_factor);
          break;
        }
        case YCbCrColorspace:
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Colorspace: YCbCr");
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Sampling factors: %dx%d,%dx%d,%dx%d",
            jpeg_info.comp_info[0].h_samp_factor,
            jpeg_info.comp_info[0].v_samp_factor,
            jpeg_info.comp_info[1].h_samp_factor,
            jpeg_info.comp_info[1].v_samp_factor,
            jpeg_info.comp_info[2].h_samp_factor,
            jpeg_info.comp_info[2].v_samp_factor);
          break;
        }
        default:
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: %d",
            image->colorspace);
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "Sampling factors: %dx%d,%dx%d,%dx%d,%dx%d",
            jpeg_info.comp_info[0].h_samp_factor,
            jpeg_info.comp_info[0].v_samp_factor,
            jpeg_info.comp_info[1].h_samp_factor,
            jpeg_info.comp_info[1].v_samp_factor,
            jpeg_info.comp_info[2].h_samp_factor,
            jpeg_info.comp_info[2].v_samp_factor,
            jpeg_info.comp_info[3].h_samp_factor,
            jpeg_info.comp_info[3].v_samp_factor);
          break;
        }
      }
    }
  /*
    Write JPEG profiles.
  */
  attribute=GetImageAttribute(image,"comment");
  if ((attribute != (const ImageAttribute *) NULL) &&
    (attribute->value != (char *) NULL))
    for (i=0; i < (long) strlen(attribute->value); i+=65533L)
      jpeg_write_marker(&jpeg_info,JPEG_COM,(unsigned char *) attribute->value+
        i,(int) Min(strlen(attribute->value+i),65533L));
  WriteICCProfile(&jpeg_info,image);
  WriteIPTCProfile(&jpeg_info,image);
  {
    const char
      *profile_name;
    
    size_t
      profile_length;
    
    const unsigned char *
      profile_info;
    
    ImageProfileIterator
      profile_iterator;

    profile_iterator=AllocateImageProfileIterator(image);
    while(NextImageProfile(profile_iterator,&profile_name,&profile_info,
                           &profile_length) != MagickFail)
      {
        register long
          j;

        if (LocaleNCompare(profile_name,"APP",3) != 0)
          continue;
        x=MagickAtoL(profile_name+3);
        if (image->logging)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Profile: %s, %lu bytes",profile_name,
                                (unsigned long) profile_length);
        for (j=0; j < (long) profile_length; j+=65533L)
          jpeg_write_marker(&jpeg_info,JPEG_APP0+(int) x,
                            profile_info+j,(int)
                            Min(profile_length-j,65533L));
      }
    DeallocateImageProfileIterator(profile_iterator);
  }
  /*
    Convert MIFF to JPEG raster pixels.
  */
  jpeg_pixels=MagickAllocateArray(JSAMPLE *,
    jpeg_info.input_components*image->columns,sizeof(JSAMPLE));
  if (jpeg_pixels == (JSAMPLE *) NULL)
    {
      if (huffman_memory)
        LiberateMagickResource(MemoryResource,huffman_memory);
      ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image);
    }
  scanline[0]=(JSAMPROW) jpeg_pixels;
  if (jpeg_info.data_precision > 8)
    {
      if (jpeg_info.in_color_space == JCS_GRAYSCALE)
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Writing %d bit JCS_GRAYSCALE samples",
                                jpeg_info.data_precision);
          for (y=0; y < (long) image->rows; y++)
            {
              p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
              if (p == (const PixelPacket *) NULL)
                break;
              q=jpeg_pixels;
              if (image->is_grayscale)
                {
                  for (x=0; x < (long) image->columns; x++)
                    {
                      *q++=(JSAMPLE) (ScaleQuantumToShort(GetGraySample(p))/16U);
                      p++;
                    }
                }
              else
                {
                  for (x=0; x < (long) image->columns; x++)
                    {
                      *q++=(JSAMPLE) (ScaleQuantumToShort(PixelIntensityToQuantum(p))/16U);
                      p++;
                    }
                }
              (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
              if (QuantumTick(y,image->rows))
                if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                            SaveImageText,image->filename,
                                            image->columns,image->rows))
                  break;
            }
        }
      else
        if ((jpeg_info.in_color_space == JCS_RGB) ||
            (jpeg_info.in_color_space == JCS_YCbCr))
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Writing %d bit JCS_RGB or JCS_YCbCr samples",
                                  jpeg_info.data_precision);
            for (y=0; y < (long) image->rows; y++)
              {
                p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
                if (p == (const PixelPacket *) NULL)
                  break;
                q=jpeg_pixels;
                for (x=0; x < (long) image->columns; x++)
                  {
                    *q++=(JSAMPLE) (ScaleQuantumToShort(p->red)/16);
                    *q++=(JSAMPLE) (ScaleQuantumToShort(p->green)/16);
                    *q++=(JSAMPLE) (ScaleQuantumToShort(p->blue)/16);
                    p++;
                  }
                (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
                if (QuantumTick(y,image->rows))
                  if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                              SaveImageText,image->filename,
                                              image->columns,image->rows))
                    break;
              }
          }
        else
          {
            (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                  "Writing %d bit JCS_CMYK samples",
                                  jpeg_info.data_precision);
            for (y=0; y < (long) image->rows; y++)
              {
                p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
                if (p == (const PixelPacket *) NULL)
                  break;
                q=jpeg_pixels;
                for (x=0; x < (long) image->columns; x++)
                  {
                    /*
                      Convert DirectClass packets to contiguous CMYK scanlines.
                    */
                    *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->red)/16);
                    *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->green)/16);
                    *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->blue)/16);
                    *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->opacity)/16);
                    p++;
                  }
                (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
                if (QuantumTick(y,image->rows))
                  if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                              SaveImageText,image->filename,
                                              image->columns,image->rows))
                    break;
              }
          }
    }
  else
    if (jpeg_info.in_color_space == JCS_GRAYSCALE)
      {
        (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                              "Writing %d bit JCS_GRAYSCALE samples",
                              jpeg_info.data_precision);
        for (y=0; y < (long) image->rows; y++)
          {
            p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
            if (p == (const PixelPacket *) NULL)
              break;
            q=jpeg_pixels;
            if (image->is_grayscale)
                {
                  for (x=0; x < (long) image->columns; x++)
                  {
                    *q++=(JSAMPLE) ScaleQuantumToChar(GetGraySample(p));
                    p++;
                  }
                }
            else
              {
                for (x=0; x < (long) image->columns; x++)
                  {
                    *q++=(JSAMPLE) ScaleQuantumToChar(PixelIntensityToQuantum(p));
                    p++;
                  }
              }
            (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
            if (QuantumTick(y,image->rows))
              if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                          SaveImageText,image->filename,
                                          image->columns,image->rows))
                break;
          }
      }
    else
      if ((jpeg_info.in_color_space == JCS_RGB) ||
          (jpeg_info.in_color_space == JCS_YCbCr))
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Writing %d bit JCS_RGB or JCS_YCbCr samples",
                                jpeg_info.data_precision);
          for (y=0; y < (long) image->rows; y++)
            {
              p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
              if (p == (const PixelPacket *) NULL)
                break;
              q=jpeg_pixels;
              for (x=0; x < (long) image->columns; x++)
                {
                  *q++=(JSAMPLE) ScaleQuantumToChar(p->red);
                  *q++=(JSAMPLE) ScaleQuantumToChar(p->green);
                  *q++=(JSAMPLE) ScaleQuantumToChar(p->blue);
                  p++;
                }
              (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
              if (QuantumTick(y,image->rows))
                if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                            SaveImageText,image->filename,
                                            image->columns,image->rows))
                  break;
            }
        }
      else
        {
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
                                "Writing %d bit JCS_CMYK samples",
                                jpeg_info.data_precision);
          for (y=0; y < (long) image->rows; y++)
            {
              p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
              if (p == (const PixelPacket *) NULL)
                break;
              q=jpeg_pixels;
              for (x=0; x < (long) image->columns; x++)
                {
                  /*
                    Convert DirectClass packets to contiguous CMYK scanlines.
                  */
                  *q++=(JSAMPLE) (ScaleQuantumToChar(MaxRGB-p->red));
                  *q++=(JSAMPLE) (ScaleQuantumToChar(MaxRGB-p->green));
                  *q++=(JSAMPLE) (ScaleQuantumToChar(MaxRGB-p->blue));
                  *q++=(JSAMPLE) (ScaleQuantumToChar(MaxRGB-p->opacity));
                  p++;
                }
              (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
              if (QuantumTick(y,image->rows))
                if (!MagickMonitorFormatted(y,image->rows,&image->exception,
                                            SaveImageText,image->filename,
                                            image->columns,image->rows))
                  break;
            }
        }
  (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Finishing JPEG compression");
  jpeg_finish_compress(&jpeg_info);
  /*
    Free memory.
  */
  if (huffman_memory)
    LiberateMagickResource(MemoryResource,huffman_memory);
  jpeg_destroy_compress(&jpeg_info);
  MagickFreeMemory(jpeg_pixels);
  CloseBlob(image);
  return(True);
}
#endif

/* [<][>][^][v][top][bottom][index][help] */