You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3003 lines
94 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% JJJJJ PPPP EEEEE GGGG %
% J P P E G %
% J PPPP EEE G GG %
% J J P E G G %
% JJJ P EEEEE GGG %
% %
% %
% Read/Write JPEG Image Format %
% %
% Software Design %
% John Cristy %
% July 1992 %
% %
% %
% Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
% dedicated to making software imaging solutions freely available. %
% %
% You may not use this file except in compliance with the License. You may %
% obtain a copy of the License at %
% %
% https://imagemagick.org/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, %
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
% See the License for the specific language governing permissions and %
% limitations under the License. %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% This software is based in part on the work of the Independent JPEG Group.
% See ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz for copyright and
% licensing restrictions. Blob support contributed by Glenn Randers-Pehrson.
%
%
*/
/*
Include declarations.
*/
#include "MagickCore/studio.h"
#include "MagickCore/artifact.h"
#include "MagickCore/attribute.h"
#include "MagickCore/blob.h"
#include "MagickCore/blob-private.h"
#include "MagickCore/cache.h"
#include "MagickCore/color.h"
#include "MagickCore/colormap-private.h"
#include "MagickCore/color-private.h"
#include "MagickCore/colormap.h"
#include "MagickCore/colorspace.h"
#include "MagickCore/colorspace-private.h"
#include "MagickCore/constitute.h"
#include "MagickCore/exception.h"
#include "MagickCore/exception-private.h"
#include "MagickCore/geometry.h"
#include "MagickCore/image.h"
#include "MagickCore/image-private.h"
#include "MagickCore/list.h"
#include "MagickCore/log.h"
#include "MagickCore/magick.h"
#include "MagickCore/memory_.h"
#include "MagickCore/memory-private.h"
#include "MagickCore/module.h"
#include "MagickCore/monitor.h"
#include "MagickCore/monitor-private.h"
#include "MagickCore/option.h"
#include "MagickCore/option-private.h"
#include "MagickCore/pixel-accessor.h"
#include "MagickCore/profile.h"
#include "MagickCore/property.h"
#include "MagickCore/quantum-private.h"
#include "MagickCore/resource_.h"
#include "MagickCore/semaphore.h"
#include "MagickCore/splay-tree.h"
#include "MagickCore/static.h"
#include "MagickCore/string_.h"
#include "MagickCore/string-private.h"
#include "MagickCore/token.h"
#include "MagickCore/utility.h"
#include "MagickCore/xml-tree.h"
#include "MagickCore/xml-tree-private.h"
#include <setjmp.h>
#if defined(MAGICKCORE_JPEG_DELEGATE)
#define JPEG_INTERNAL_OPTIONS
#if defined(__MINGW32__)
# define XMD_H 1 /* Avoid conflicting typedef for INT32 */
#endif
#undef HAVE_STDLIB_H
#include "jpeglib.h"
#include "jerror.h"
#endif
/*
Define declarations.
*/
#define COMMENT_INDEX 0
#define ICC_INDEX 2
#define ICC_MARKER (JPEG_APP0+ICC_INDEX)
#define ICC_PROFILE "ICC_PROFILE"
#define IPTC_INDEX 13
#define IPTC_MARKER (JPEG_APP0+IPTC_INDEX)
#define XML_INDEX 1
#define XML_MARKER (JPEG_APP0+XML_INDEX)
#define MaxJPEGScans 1024
/*
Typedef declarations.
*/
#if defined(MAGICKCORE_JPEG_DELEGATE)
typedef struct _DestinationManager
{
struct jpeg_destination_mgr
manager;
Image
*image;
JOCTET
*buffer;
} DestinationManager;
typedef struct _ErrorManager
{
jmp_buf
error_recovery;
Image
*image;
MagickBooleanType
finished;
StringInfo
*profiles[16];
ExceptionInfo
*exception;
} ErrorManager;
typedef struct _SourceManager
{
struct jpeg_source_mgr
manager;
Image
*image;
JOCTET
*buffer;
boolean
start_of_blob;
} SourceManager;
#endif
typedef struct _QuantizationTable
{
char
*slot,
*description;
size_t
width,
height;
double
divisor;
unsigned int
*levels;
} QuantizationTable;
/*
Const declarations.
*/
static const char
xmp_namespace[] = "http://ns.adobe.com/xap/1.0/ ";
#define XmpNamespaceExtent 28
/*
Forward declarations.
*/
#if defined(MAGICKCORE_JPEG_DELEGATE)
static MagickBooleanType
WriteJPEGImage(const ImageInfo *,Image *,ExceptionInfo *);
#endif
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% I s J P E G %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% IsJPEG() returns MagickTrue if the image format type, identified by the
% magick string, is JPEG.
%
% The format of the IsJPEG method is:
%
% MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
%
% A description of each parameter follows:
%
% o magick: compare image format pattern against these bytes.
%
% o length: Specifies the length of the magick string.
%
*/
static MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
{
if (length < 3)
return(MagickFalse);
if (memcmp(magick,"\377\330\377",3) == 0)
return(MagickTrue);
return(MagickFalse);
}
#if defined(MAGICKCORE_JPEG_DELEGATE)
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e a d J P E G I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% ReadJPEGImage() reads a JPEG image file and returns it. It allocates
% the memory necessary for the new Image structure and returns a pointer to
% the new image.
%
% The format of the ReadJPEGImage method is:
%
% Image *ReadJPEGImage(const ImageInfo *image_info,
% ExceptionInfo *exception)
%
% A description of each parameter follows:
%
% o image_info: the image info.
%
% o exception: return any errors or warnings in this structure.
%
*/
static boolean FillInputBuffer(j_decompress_ptr cinfo)
{
SourceManager
*source;
source=(SourceManager *) cinfo->src;
source->manager.bytes_in_buffer=(size_t) ReadBlob(source->image,
MagickMinBufferExtent,source->buffer);
if (source->manager.bytes_in_buffer == 0)
{
if (source->start_of_blob != FALSE)
ERREXIT(cinfo,JERR_INPUT_EMPTY);
WARNMS(cinfo,JWRN_JPEG_EOF);
source->buffer[0]=(JOCTET) 0xff;
source->buffer[1]=(JOCTET) JPEG_EOI;
source->manager.bytes_in_buffer=2;
}
source->manager.next_input_byte=source->buffer;
source->start_of_blob=FALSE;
return(TRUE);
}
static int GetCharacter(j_decompress_ptr jpeg_info)
{
if (jpeg_info->src->bytes_in_buffer == 0)
{
(void) (*jpeg_info->src->fill_input_buffer)(jpeg_info);
if (jpeg_info->err->msg_code == JWRN_JPEG_EOF)
return(EOF);
}
jpeg_info->src->bytes_in_buffer--;
return((int) GETJOCTET(*jpeg_info->src->next_input_byte++));
}
static void InitializeSource(j_decompress_ptr cinfo)
{
SourceManager
*source;
source=(SourceManager *) cinfo->src;
source->start_of_blob=TRUE;
}
static MagickBooleanType IsITUFaxImage(const Image *image)
{
const StringInfo
*profile;
const unsigned char
*datum;
profile=GetImageProfile(image,"8bim");
if (profile == (const StringInfo *) NULL)
return(MagickFalse);
if (GetStringInfoLength(profile) < 5)
return(MagickFalse);
datum=GetStringInfoDatum(profile);
if ((datum[0] == 0x47) && (datum[1] == 0x33) && (datum[2] == 0x46) &&
(datum[3] == 0x41) && (datum[4] == 0x58))
return(MagickTrue);
return(MagickFalse);
}
static void JPEGErrorHandler(j_common_ptr jpeg_info)
{
char
message[JMSG_LENGTH_MAX];
ErrorManager
*error_manager;
ExceptionInfo
*exception;
Image
*image;
*message='\0';
error_manager=(ErrorManager *) jpeg_info->client_data;
image=error_manager->image;
exception=error_manager->exception;
(jpeg_info->err->format_message)(jpeg_info,message);
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"[%s] JPEG Trace: \"%s\"",image->filename,message);
if (error_manager->finished != MagickFalse)
(void) ThrowMagickException(exception,GetMagickModule(),CorruptImageWarning,
(char *) message,"`%s'",image->filename);
else
(void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
(char *) message,"`%s'",image->filename);
longjmp(error_manager->error_recovery,1);
}
static void JPEGProgressHandler(j_common_ptr jpeg_info)
{
ErrorManager
*error_manager;
ExceptionInfo
*exception;
Image
*image;
error_manager=(ErrorManager *) jpeg_info->client_data;
image=error_manager->image;
exception=error_manager->exception;
if (jpeg_info->is_decompressor == 0)
return;
if (((j_decompress_ptr) jpeg_info)->input_scan_number < MaxJPEGScans)
return;
(void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
"too many scans","`%s'",image->filename);
longjmp(error_manager->error_recovery,1);
}
static MagickBooleanType JPEGWarningHandler(j_common_ptr jpeg_info,int level)
{
#define JPEGExcessiveWarnings 1000
char
message[JMSG_LENGTH_MAX];
ErrorManager
*error_manager;
ExceptionInfo
*exception;
Image
*image;
*message='\0';
error_manager=(ErrorManager *) jpeg_info->client_data;
exception=error_manager->exception;
image=error_manager->image;
if (level < 0)
{
/*
Process warning message.
*/
(jpeg_info->err->format_message)(jpeg_info,message);
if (jpeg_info->err->num_warnings++ < JPEGExcessiveWarnings)
ThrowBinaryException(CorruptImageWarning,(char *) message,
image->filename);
}
else
if ((image->debug != MagickFalse) &&
(level >= jpeg_info->err->trace_level))
{
/*
Process trace message.
*/
(jpeg_info->err->format_message)(jpeg_info,message);
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"[%s] JPEG Trace: \"%s\"",image->filename,message);
}
return(MagickTrue);
}
static boolean ReadProfileData(j_decompress_ptr jpeg_info,const size_t index,
const size_t length)
{
ErrorManager
*error_manager;
ExceptionInfo
*exception;
Image
*image;
unsigned char
*p;
ssize_t
i;
error_manager=(ErrorManager *) jpeg_info->client_data;
exception=error_manager->exception;
image=error_manager->image;
if (error_manager->profiles[index] == (StringInfo *) NULL)
{
error_manager->profiles[index]=BlobToStringInfo((const void *) NULL,
length);
if (error_manager->profiles[index] == (StringInfo *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"MemoryAllocationFailed","`%s'",
image->filename);
return(FALSE);
}
p=GetStringInfoDatum(error_manager->profiles[index]);
}
else
{
size_t
current_length;
current_length=GetStringInfoLength(error_manager->profiles[index]);
SetStringInfoLength(error_manager->profiles[index],current_length+
length);
p=GetStringInfoDatum(error_manager->profiles[index])+current_length;
}
for (i=0; i < (ssize_t) length; i++)
{
int
c;
c=GetCharacter(jpeg_info);
if (c == EOF)
break;
*p++=(unsigned char) c;
}
if (i != (ssize_t) length)
{
(void) ThrowMagickException(exception,GetMagickModule(),
CorruptImageError,"InsufficientImageDataInFile","`%s'",
image->filename);
return(FALSE);
}
*p='\0';
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Profile[%.20g]: %.20g bytes",(double) index,(double) length);
return(TRUE);
}
static boolean ReadComment(j_decompress_ptr jpeg_info)
{
#define GetProfileLength(jpeg_info,length) \
{ \
int \
c[2]; \
\
length=0; \
c[0]=GetCharacter(jpeg_info); \
c[1]=GetCharacter(jpeg_info); \
if ((c[0] >= 0) && (c[1] >= 0)) \
length=(size_t) ((c[0] << 8) | c[1]); \
}
size_t
length;
/*
Determine length of comment.
*/
GetProfileLength(jpeg_info,length);
if (length <= 2)
return(TRUE);
length-=2;
return(ReadProfileData(jpeg_info,COMMENT_INDEX,length));
}
static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
{
char
magick[13];
ssize_t
i;
size_t
length;
/*
Read color profile.
*/
GetProfileLength(jpeg_info,length);
if (length <= 2)
return(TRUE);
length-=2;
if (length <= 14)
{
while (length-- > 0)
if (GetCharacter(jpeg_info) == EOF)
break;
return(TRUE);
}
for (i=0; i < 12; i++)
magick[i]=(char) GetCharacter(jpeg_info);
magick[i]='\0';
if (LocaleCompare(magick,ICC_PROFILE) != 0)
{
/*
Not a ICC profile, return.
*/
for (i=0; i < (ssize_t) (length-12); i++)
if (GetCharacter(jpeg_info) == EOF)
break;
return(TRUE);
}
(void) GetCharacter(jpeg_info); /* id */
(void) GetCharacter(jpeg_info); /* markers */
length-=14;
return(ReadProfileData(jpeg_info,ICC_INDEX,length));
}
static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
{
char
magick[MagickPathExtent];
ssize_t
i;
size_t
length;
/*
Determine length of binary data stored here.
*/
GetProfileLength(jpeg_info,length);
if (length <= 2)
return(TRUE);
length-=2;
if (length <= 14)
{
while (length-- > 0)
if (GetCharacter(jpeg_info) == EOF)
break;
return(TRUE);
}
/*
Validate that this was written as a Photoshop resource format slug.
*/
for (i=0; i < 10; i++)
magick[i]=(char) GetCharacter(jpeg_info);
magick[10]='\0';
length-=10;
if (length <= 10)
return(TRUE);
if (LocaleCompare(magick,"Photoshop ") != 0)
{
/*
Not a IPTC profile, return.
*/
for (i=0; i < (ssize_t) length; i++)
if (GetCharacter(jpeg_info) == EOF)
break;
return(TRUE);
}
/*
Remove the version number.
*/
for (i=0; i < 4; i++)
if (GetCharacter(jpeg_info) == EOF)
break;
if (length <= 11)
return(TRUE);
length-=4;
return(ReadProfileData(jpeg_info,IPTC_INDEX,length));
}
static boolean ReadProfile(j_decompress_ptr jpeg_info)
{
int
marker;
size_t
length;
/*
Read generic profile.
*/
GetProfileLength(jpeg_info,length);
if (length <= 2)
return(TRUE);
length-=2;
marker=jpeg_info->unread_marker-JPEG_APP0;
return(ReadProfileData(jpeg_info,marker,length));
}
static boolean ReadXmlProfile(j_decompress_ptr jpeg_info)
{
ErrorManager
*error_manager;
ExceptionInfo
*exception;
Image
*image;
MagickBooleanType
status;
unsigned char
*p;
size_t
length;
StringInfo
*profile;
GetProfileLength(jpeg_info,length);
if (length <= 2)
return(TRUE);
length-=2;
if (ReadProfileData(jpeg_info,XML_INDEX,length) == FALSE)
return(FALSE);
error_manager=(ErrorManager *) jpeg_info->client_data;
exception=error_manager->exception;
image=error_manager->image;
profile=error_manager->profiles[XML_INDEX];
p=GetStringInfoDatum(profile);
length=GetStringInfoLength(profile);
status=MagickTrue;
if ((length > XmpNamespaceExtent) &&
(LocaleNCompare((char *) p,xmp_namespace,XmpNamespaceExtent-1) == 0))
{
ssize_t
j;
/*
Extract namespace from XMP profile.
*/
p=GetStringInfoDatum(profile)+XmpNamespaceExtent;
for (j=XmpNamespaceExtent; j < (ssize_t) length; j++)
{
if (*p == '\0')
break;
p++;
}
if (j < (ssize_t) length)
(void) DestroyStringInfo(SplitStringInfo(profile,(size_t) (j+1)));
status=SetImageProfile(image,"xmp",profile,exception);
}
else
if (length > 4)
{
if ((LocaleNCompare((char *) p,"exif",4) == 0) ||
(LocaleNCompare((char *) p,"MM",2) == 0) ||
(LocaleNCompare((char *) p,"II",2) == 0))
status=SetImageProfile(image,"exif",profile,exception);
}
else
status=SetImageProfile(image,"app1",profile,exception);
error_manager->profiles[XML_INDEX]=DestroyStringInfo(
error_manager->profiles[XML_INDEX]);
return(status != MagickFalse ? TRUE : FALSE);
}
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+=number_bytes;
source->manager.bytes_in_buffer-=number_bytes;
}
static void TerminateSource(j_decompress_ptr cinfo)
{
(void) cinfo;
}
static void JPEGSourceManager(j_decompress_ptr cinfo,Image *image)
{
SourceManager
*source;
cinfo->src=(struct jpeg_source_mgr *) (*cinfo->mem->alloc_small)
((j_common_ptr) cinfo,JPOOL_IMAGE,sizeof(SourceManager));
source=(SourceManager *) cinfo->src;
source->buffer=(JOCTET *) (*cinfo->mem->alloc_small)
((j_common_ptr) cinfo,JPOOL_IMAGE,MagickMinBufferExtent*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;
}
static void JPEGSetImageQuality(struct jpeg_decompress_struct *jpeg_info,
Image *image)
{
image->quality=UndefinedCompressionQuality;
#if defined(D_PROGRESSIVE_SUPPORTED)
if (image->compression == LosslessJPEGCompression)
{
image->quality=100;
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Quality: 100 (lossless)");
}
else
#endif
{
ssize_t
j,
qvalue,
sum;
ssize_t
i;
/*
Determine the JPEG compression quality from the quantization tables.
*/
sum=0;
for (i=0; i < NUM_QUANT_TBLS; i++)
{
if (jpeg_info->quant_tbl_ptrs[i] != NULL)
for (j=0; j < DCTSIZE2; j++)
sum+=jpeg_info->quant_tbl_ptrs[i]->quantval[j];
}
if ((jpeg_info->quant_tbl_ptrs[0] != NULL) &&
(jpeg_info->quant_tbl_ptrs[1] != NULL))
{
ssize_t
hash[101] =
{
1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645,
632, 623, 613, 607, 600, 594, 589, 585, 581, 571,
555, 542, 529, 514, 494, 474, 457, 439, 424, 410,
397, 386, 373, 364, 351, 341, 334, 324, 317, 309,
299, 294, 287, 279, 274, 267, 262, 257, 251, 247,
243, 237, 232, 227, 222, 217, 213, 207, 202, 198,
192, 188, 183, 177, 173, 168, 163, 157, 153, 148,
143, 139, 132, 128, 125, 119, 115, 108, 104, 99,
94, 90, 84, 79, 74, 70, 64, 59, 55, 49,
45, 40, 34, 30, 25, 20, 15, 11, 6, 4,
0
},
sums[101] =
{
32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104,
27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946,
23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998,
16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702,
12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208,
9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458,
8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788,
6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128,
4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509,
3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
1666, 1483, 1297, 1109, 927, 735, 554, 375, 201,
128, 0
};
qvalue=(ssize_t) (jpeg_info->quant_tbl_ptrs[0]->quantval[2]+
jpeg_info->quant_tbl_ptrs[0]->quantval[53]+
jpeg_info->quant_tbl_ptrs[1]->quantval[0]+
jpeg_info->quant_tbl_ptrs[1]->quantval[DCTSIZE2-1]);
for (i=0; i < 100; i++)
{
if ((qvalue < hash[i]) && (sum < sums[i]))
continue;
if (((qvalue <= hash[i]) && (sum <= sums[i])) || (i >= 50))
image->quality=(size_t) i+1;
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Quality: %.20g (%s)",(double) i+1,(qvalue <= hash[i]) &&
(sum <= sums[i]) ? "exact" : "approximate");
break;
}
}
else
if (jpeg_info->quant_tbl_ptrs[0] != NULL)
{
ssize_t
hash[101] =
{
510, 505, 422, 380, 355, 338, 326, 318, 311, 305,
300, 297, 293, 291, 288, 286, 284, 283, 281, 280,
279, 278, 277, 273, 262, 251, 243, 233, 225, 218,
211, 205, 198, 193, 186, 181, 177, 172, 168, 164,
158, 156, 152, 148, 145, 142, 139, 136, 133, 131,
129, 126, 123, 120, 118, 115, 113, 110, 107, 105,
102, 100, 97, 94, 92, 89, 87, 83, 81, 79,
76, 74, 70, 68, 66, 63, 61, 57, 55, 52,
50, 48, 44, 42, 39, 37, 34, 31, 29, 26,
24, 21, 18, 16, 13, 11, 8, 6, 3, 2,
0
},
sums[101] =
{
16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859,
12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679,
9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823,
6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086,
4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092,
3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396,
3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727,
2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068,
1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398,
1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736,
667, 592, 518, 441, 369, 292, 221, 151, 86,
64, 0
};
qvalue=(ssize_t) (jpeg_info->quant_tbl_ptrs[0]->quantval[2]+
jpeg_info->quant_tbl_ptrs[0]->quantval[53]);
for (i=0; i < 100; i++)
{
if ((qvalue < hash[i]) && (sum < sums[i]))
continue;
if (((qvalue <= hash[i]) && (sum <= sums[i])) || (i >= 50))
image->quality=(size_t)i+1;
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Quality: %.20g (%s)",(double) i+1,(qvalue <= hash[i]) &&
(sum <= sums[i]) ? "exact" : "approximate");
break;
}
}
}
}
static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info, Image *image,ExceptionInfo *exception)
{
char
sampling_factor[MagickPathExtent];
switch (jpeg_info->out_color_space)
{
case JCS_CMYK:
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: CMYK");
(void) FormatLocaleString(sampling_factor,MagickPathExtent,
"%dx%d,%dx%d,%dx%d,%dx%d",jpeg_info->comp_info[0].h_samp_factor,
jpeg_info->comp_info[0].v_samp_factor,
jpeg_info->comp_info[1].h_samp_factor,
jpeg_info->comp_info[1].v_samp_factor,
jpeg_info->comp_info[2].h_samp_factor,
jpeg_info->comp_info[2].v_samp_factor,
jpeg_info->comp_info[3].h_samp_factor,
jpeg_info->comp_info[3].v_samp_factor);
break;
}
case JCS_GRAYSCALE:
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Colorspace: GRAYSCALE");
(void) FormatLocaleString(sampling_factor,MagickPathExtent,"%dx%d",
jpeg_info->comp_info[0].h_samp_factor,
jpeg_info->comp_info[0].v_samp_factor);
break;
}
case JCS_RGB:
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: RGB");
(void) FormatLocaleString(sampling_factor,MagickPathExtent,
"%dx%d,%dx%d,%dx%d",jpeg_info->comp_info[0].h_samp_factor,
jpeg_info->comp_info[0].v_samp_factor,
jpeg_info->comp_info[1].h_samp_factor,
jpeg_info->comp_info[1].v_samp_factor,
jpeg_info->comp_info[2].h_samp_factor,
jpeg_info->comp_info[2].v_samp_factor);
break;
}
default:
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: %d",
jpeg_info->out_color_space);
(void) FormatLocaleString(sampling_factor,MagickPathExtent,
"%dx%d,%dx%d,%dx%d,%dx%d",jpeg_info->comp_info[0].h_samp_factor,
jpeg_info->comp_info[0].v_samp_factor,
jpeg_info->comp_info[1].h_samp_factor,
jpeg_info->comp_info[1].v_samp_factor,
jpeg_info->comp_info[2].h_samp_factor,
jpeg_info->comp_info[2].v_samp_factor,
jpeg_info->comp_info[3].h_samp_factor,
jpeg_info->comp_info[3].v_samp_factor);
break;
}
}
(void) SetImageProperty(image,"jpeg:sampling-factor",sampling_factor,
exception);
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"Sampling Factors: %s",
sampling_factor);
}
static void JPEGDestroyDecompress(j_decompress_ptr jpeg_info)
{
ErrorManager
*error_manager;
size_t
i;
error_manager=(ErrorManager *) jpeg_info->client_data;
for (i=0; i < 16; i++)
{
if (error_manager->profiles[i] != (StringInfo *) NULL)
error_manager->profiles[i]=DestroyStringInfo(error_manager->profiles[i]);
}
jpeg_destroy_decompress(jpeg_info);
}
static MagickBooleanType JPEGSetImageProfiles(ErrorManager *error_manager)
{
ExceptionInfo
*exception;
Image
*image;
MagickBooleanType
status;
unsigned char
*p;
ssize_t
i;
StringInfo
*profile;
exception=error_manager->exception;
image=error_manager->image;
status=MagickTrue;
for (i=0; i < 16; i++)
{
profile=error_manager->profiles[i];
if (profile == (StringInfo *) NULL)
continue;
switch (i)
{
case COMMENT_INDEX:
{
p=GetStringInfoDatum(profile);
status=SetImageProperty(image,"comment",(const char *) p,exception);
break;
}
case ICC_INDEX:
{
status=SetImageProfile(image,"icc",profile,exception);
break;
}
case IPTC_INDEX:
{
/*
The IPTC profile is actually an 8bim.
*/
status=SetImageProfile(image,"8bim",profile,exception);
break;
}
default:
{
char
name[6];
(void) FormatLocaleString(name,sizeof(name),"APP%d",(int) i);
status=SetImageProfile(image,name,profile,exception);
break;
}
}
error_manager->profiles[i]=DestroyStringInfo(error_manager->profiles[i]);
if (status == MagickFalse)
break;
}
return(status);
}
static Image *ReadJPEGImage_(const ImageInfo *image_info,
struct jpeg_decompress_struct *jpeg_info,ExceptionInfo *exception)
{
char
value[MagickPathExtent];
const char
*dct_method,
*option;
ErrorManager
error_manager;
Image
*image;
JSAMPLE
*volatile jpeg_pixels;
JSAMPROW
scanline[1];
MagickBooleanType
debug,
status;
MagickSizeType
number_pixels;
MemoryInfo
*memory_info;
Quantum
index;
ssize_t
i;
struct jpeg_error_mgr
jpeg_error;
struct jpeg_progress_mgr
jpeg_progress;
JSAMPLE
*p;
size_t
units;
ssize_t
y;
/*
Open image file.
*/
assert(image_info != (const ImageInfo *) NULL);
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 == MagickCoreSignature);
debug=IsEventLogging();
(void) debug;
image=AcquireImage(image_info,exception);
status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
if (status == MagickFalse)
{
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.
*/
(void) memset(&error_manager,0,sizeof(error_manager));
(void) memset(jpeg_info,0,sizeof(*jpeg_info));
(void) memset(&jpeg_error,0,sizeof(jpeg_error));
(void) memset(&jpeg_progress,0,sizeof(jpeg_progress));
jpeg_info->err=jpeg_std_error(&jpeg_error);
jpeg_info->err->emit_message=(void (*)(j_common_ptr,int)) JPEGWarningHandler;
jpeg_info->err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
memory_info=(MemoryInfo *) NULL;
error_manager.exception=exception;
error_manager.image=image;
if (setjmp(error_manager.error_recovery) != 0)
{
JPEGDestroyDecompress(jpeg_info);
(void) CloseBlob(image);
if (exception->severity < ErrorException)
return(GetFirstImageInList(image));
return(DestroyImage(image));
}
jpeg_info->client_data=(void *) &error_manager;
jpeg_create_decompress(jpeg_info);
if (GetMaxMemoryRequest() != ~0UL)
jpeg_info->mem->max_memory_to_use=(long) GetMaxMemoryRequest();
jpeg_progress.progress_monitor=(void (*)(j_common_ptr)) JPEGProgressHandler;
jpeg_info->progress=(&jpeg_progress);
JPEGSourceManager(jpeg_info,image);
jpeg_set_marker_processor(jpeg_info,JPEG_COM,ReadComment);
option=GetImageOption(image_info,"profile:skip");
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);
if (IsOptionMember("APP",option) == MagickFalse)
jpeg_set_marker_processor(jpeg_info,XML_MARKER,ReadXmlProfile);
for (i=3; i < 16; i++)
/* APP14 is ignored because this will change the colors of the image */
if (i != IPTC_INDEX && i != 14)
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 (IsYCbCrCompatibleColorspace(image_info->colorspace) != MagickFalse)
jpeg_info->out_color_space=JCS_YCbCr;
/*
Set image resolution.
*/
units=0;
if ((jpeg_info->saw_JFIF_marker != 0) && (jpeg_info->X_density != 1) &&
(jpeg_info->Y_density != 1))
{
image->resolution.x=(double) jpeg_info->X_density;
image->resolution.y=(double) jpeg_info->Y_density;
units=(size_t) jpeg_info->density_unit;
}
if (units == 1)
image->units=PixelsPerInchResolution;
if (units == 2)
image->units=PixelsPerCentimeterResolution;
number_pixels=(MagickSizeType) image->columns*image->rows;
option=GetImageOption(image_info,"jpeg:size");
if ((option != (const char *) NULL) &&
(jpeg_info->out_color_space != JCS_YCbCr))
{
double
scale_factor;
GeometryInfo
geometry_info;
MagickStatusType
flags;
/*
Scale the image.
*/
flags=ParseGeometry(option,&geometry_info);
if ((flags & SigmaValue) == 0)
geometry_info.sigma=geometry_info.rho;
jpeg_calc_output_dimensions(jpeg_info);
image->magick_columns=jpeg_info->output_width;
image->magick_rows=jpeg_info->output_height;
scale_factor=1.0;
if (geometry_info.rho != 0.0)
scale_factor=jpeg_info->output_width/geometry_info.rho;
if ((geometry_info.sigma != 0.0) &&
(scale_factor > (jpeg_info->output_height/geometry_info.sigma)))
scale_factor=jpeg_info->output_height/geometry_info.sigma;
jpeg_info->scale_num=1U;
jpeg_info->scale_denom=(unsigned int) scale_factor;
jpeg_calc_output_dimensions(jpeg_info);
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Scale factor: %.20g",(double) scale_factor);
}
#if (JPEG_LIB_VERSION >= 61) && defined(D_PROGRESSIVE_SUPPORTED)
#if defined(D_LOSSLESS_SUPPORTED)
image->interlace=jpeg_info->process == JPROC_PROGRESSIVE ?
JPEGInterlace : NoInterlace;
image->compression=jpeg_info->process == JPROC_LOSSLESS ?
LosslessJPEGCompression : JPEGCompression;
if (jpeg_info->data_precision > 8)
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"12-bit JPEG not supported. Reducing pixel data to 8 bits","`%s'",
image->filename);
if (jpeg_info->data_precision == 16)
jpeg_info->data_precision=12;
#else
image->interlace=jpeg_info->progressive_mode != 0 ? JPEGInterlace :
NoInterlace;
image->compression=JPEGCompression;
#endif
#else
image->compression=JPEGCompression;
image->interlace=JPEGInterlace;
#endif
option=GetImageOption(image_info,"jpeg:colors");
if (option != (const char *) NULL)
{
/*
Let the JPEG library quantize the image.
*/
jpeg_info->quantize_colors=TRUE;
jpeg_info->desired_number_of_colors=(int) StringToUnsignedLong(option);
}
option=GetImageOption(image_info,"jpeg:block-smoothing");
if (option != (const char *) NULL)
jpeg_info->do_block_smoothing=IsStringTrue(option) != MagickFalse ? TRUE :
FALSE;
dct_method=GetImageOption(image_info,"jpeg:dct-method");
if (dct_method != (const char *) NULL)
switch (*dct_method)
{
case 'D':
case 'd':
{
if (LocaleCompare(dct_method,"default") == 0)
jpeg_info->dct_method=JDCT_DEFAULT;
break;
}
case 'F':
case 'f':
{
if (LocaleCompare(dct_method,"fastest") == 0)
jpeg_info->dct_method=JDCT_FASTEST;
if (LocaleCompare(dct_method,"float") == 0)
jpeg_info->dct_method=JDCT_FLOAT;
break;
}
case 'I':
case 'i':
{
if (LocaleCompare(dct_method,"ifast") == 0)
jpeg_info->dct_method=JDCT_IFAST;
if (LocaleCompare(dct_method,"islow") == 0)
jpeg_info->dct_method=JDCT_ISLOW;
break;
}
}
option=GetImageOption(image_info,"jpeg:fancy-upsampling");
if (option != (const char *) NULL)
jpeg_info->do_fancy_upsampling=IsStringTrue(option) != MagickFalse ? TRUE :
FALSE;
jpeg_calc_output_dimensions(jpeg_info);
image->columns=jpeg_info->output_width;
image->rows=jpeg_info->output_height;
image->depth=(size_t) jpeg_info->data_precision;
switch (jpeg_info->out_color_space)
{
case JCS_RGB:
default:
{
(void) SetImageColorspace(image,sRGBColorspace,exception);
break;
}
case JCS_GRAYSCALE:
{
(void) SetImageColorspace(image,GRAYColorspace,exception);
break;
}
case JCS_YCbCr:
{
(void) SetImageColorspace(image,YCbCrColorspace,exception);
break;
}
case JCS_CMYK:
{
(void) SetImageColorspace(image,CMYKColorspace,exception);
break;
}
}
if (IsITUFaxImage(image) != MagickFalse)
{
(void) SetImageColorspace(image,LabColorspace,exception);
jpeg_info->out_color_space=JCS_YCbCr;
}
option=GetImageOption(image_info,"jpeg:colors");
if (option != (const char *) NULL)
if (AcquireImageColormap(image,StringToUnsignedLong(option),exception) == MagickFalse)
{
JPEGDestroyDecompress(jpeg_info);
ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
}
if ((jpeg_info->output_components == 1) && (jpeg_info->quantize_colors == 0))
{
size_t
colors;
colors=(size_t) GetQuantumRange(image->depth)+1;
if (AcquireImageColormap(image,colors,exception) == MagickFalse)
{
JPEGDestroyDecompress(jpeg_info);
ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
}
}
if (image->debug != MagickFalse)
{
if (image->interlace != NoInterlace)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Interlace: progressive");
else
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Interlace: nonprogressive");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"Data precision: %d",
(int) jpeg_info->data_precision);
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"Geometry: %dx%d",
(int) jpeg_info->output_width,(int) jpeg_info->output_height);
}
JPEGSetImageQuality(jpeg_info,image);
JPEGSetImageSamplingFactor(jpeg_info,image,exception);
(void) FormatLocaleString(value,MagickPathExtent,"%.20g",(double)
jpeg_info->out_color_space);
(void) SetImageProperty(image,"jpeg:colorspace",value,exception);
#if defined(D_ARITH_CODING_SUPPORTED)
if (jpeg_info->arith_code == TRUE)
(void) SetImageProperty(image,"jpeg:coding","arithmetic",exception);
#endif
if (JPEGSetImageProfiles(&error_manager) == MagickFalse)
{
JPEGDestroyDecompress(jpeg_info);
return(DestroyImageList(image));
}
if (image_info->ping != MagickFalse)
{
JPEGDestroyDecompress(jpeg_info);
(void) CloseBlob(image);
return(GetFirstImageInList(image));
}
status=SetImageExtent(image,image->columns,image->rows,exception);
if (status == MagickFalse)
{
JPEGDestroyDecompress(jpeg_info);
return(DestroyImageList(image));
}
(void) jpeg_start_decompress(jpeg_info);
if ((jpeg_info->output_components != 1) &&
(jpeg_info->output_components != 3) && (jpeg_info->output_components != 4))
{
JPEGDestroyDecompress(jpeg_info);
ThrowReaderException(CorruptImageError,"ImageTypeNotSupported");
}
memory_info=AcquireVirtualMemory((size_t) image->columns,
jpeg_info->output_components*sizeof(*jpeg_pixels));
if (memory_info == (MemoryInfo *) NULL)
{
JPEGDestroyDecompress(jpeg_info);
ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
}
jpeg_pixels=(JSAMPLE *) GetVirtualMemoryBlob(memory_info);
(void) memset(jpeg_pixels,0,image->columns*
jpeg_info->output_components*sizeof(*jpeg_pixels));
/*
Convert JPEG pixels to pixel packets.
*/
if (setjmp(error_manager.error_recovery) != 0)
{
if (memory_info != (MemoryInfo *) NULL)
memory_info=RelinquishVirtualMemory(memory_info);
JPEGDestroyDecompress(jpeg_info);
(void) CloseBlob(image);
number_pixels=(MagickSizeType) image->columns*image->rows;
if (number_pixels != 0)
return(GetFirstImageInList(image));
return(DestroyImage(image));
}
if (jpeg_info->quantize_colors != 0)
{
image->colors=(size_t) jpeg_info->actual_number_of_colors;
if (jpeg_info->out_color_space == JCS_GRAYSCALE)
for (i=0; i < (ssize_t) image->colors; i++)
{
image->colormap[i].red=(double) ScaleCharToQuantum(
jpeg_info->colormap[0][i]);
image->colormap[i].green=image->colormap[i].red;
image->colormap[i].blue=image->colormap[i].red;
image->colormap[i].alpha=(MagickRealType) OpaqueAlpha;
}
else
for (i=0; i < (ssize_t) image->colors; i++)
{
image->colormap[i].red=(double) ScaleCharToQuantum(
jpeg_info->colormap[0][i]);
image->colormap[i].green=(double) ScaleCharToQuantum(
jpeg_info->colormap[1][i]);
image->colormap[i].blue=(double) ScaleCharToQuantum(
jpeg_info->colormap[2][i]);
image->colormap[i].alpha=(MagickRealType) OpaqueAlpha;
}
}
scanline[0]=(JSAMPROW) jpeg_pixels;
for (y=0; y < (ssize_t) image->rows; y++)
{
ssize_t
x;
Quantum
*magick_restrict q;
if (jpeg_read_scanlines(jpeg_info,scanline,1) != 1)
{
(void) ThrowMagickException(exception,GetMagickModule(),
CorruptImageWarning,"SkipToSyncByte","`%s'",image->filename);
continue;
}
p=jpeg_pixels;
q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
if (q == (Quantum *) NULL)
break;
if (jpeg_info->data_precision > 8)
{
unsigned short
scale;
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++)
{
ssize_t
pixel;
pixel=(ssize_t) (scale*GETJSAMPLE(*p));
index=(Quantum) ConstrainColormapIndex(image,pixel,exception);
SetPixelIndex(image,index,q);
SetPixelViaPixelInfo(image,image->colormap+(ssize_t) index,q);
p++;
q+=GetPixelChannels(image);
}
else
if (image->colorspace != CMYKColorspace)
for (x=0; x < (ssize_t) image->columns; x++)
{
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(
(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);
}
}
else
if (jpeg_info->output_components == 1)
for (x=0; x < (ssize_t) image->columns; x++)
{
ssize_t
pixel;
pixel=(ssize_t) GETJSAMPLE(*p);
index=(Quantum) ConstrainColormapIndex(image,pixel,exception);
SetPixelIndex(image,index,q);
SetPixelViaPixelInfo(image,image->colormap+(ssize_t) index,q);
p++;
q+=GetPixelChannels(image);
}
else
if (image->colorspace != CMYKColorspace)
for (x=0; x < (ssize_t) image->columns; x++)
{
SetPixelRed(image,ScaleCharToQuantum((unsigned char)
GETJSAMPLE(*p++)),q);
SetPixelGreen(image,ScaleCharToQuantum((unsigned char)
GETJSAMPLE(*p++)),q);
SetPixelBlue(image,ScaleCharToQuantum((unsigned char)
GETJSAMPLE(*p++)),q);
SetPixelAlpha(image,OpaqueAlpha,q);
q+=GetPixelChannels(image);
}
else
for (x=0; x < (ssize_t) image->columns; x++)
{
SetPixelCyan(image,QuantumRange-ScaleCharToQuantum(
(unsigned char) GETJSAMPLE(*p++)),q);
SetPixelMagenta(image,QuantumRange-ScaleCharToQuantum(
(unsigned char) GETJSAMPLE(*p++)),q);
SetPixelYellow(image,QuantumRange-ScaleCharToQuantum(
(unsigned char) GETJSAMPLE(*p++)),q);
SetPixelBlack(image,QuantumRange-ScaleCharToQuantum(
(unsigned char) GETJSAMPLE(*p++)),q);
SetPixelAlpha(image,OpaqueAlpha,q);
q+=GetPixelChannels(image);
}
if (SyncAuthenticPixels(image,exception) == MagickFalse)
break;
status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
image->rows);
if (status == MagickFalse)
{
jpeg_abort_decompress(jpeg_info);
break;
}
}
if (status != MagickFalse)
{
error_manager.finished=MagickTrue;
if (setjmp(error_manager.error_recovery) == 0)
(void) jpeg_finish_decompress(jpeg_info);
}
/*
Free jpeg resources.
*/
JPEGDestroyDecompress(jpeg_info);
memory_info=RelinquishVirtualMemory(memory_info);
(void) CloseBlob(image);
return(GetFirstImageInList(image));
}
static Image *ReadJPEGImage(const ImageInfo *image_info,
ExceptionInfo *exception)
{
struct jpeg_decompress_struct
jpeg_info;
return(ReadJPEGImage_(image_info,&jpeg_info,exception));
}
#endif
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e g i s t e r J P E G I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% RegisterJPEGImage() adds properties for the JPEG image format to
% the list of supported formats. The properties include the image format
% tag, a method to read and/or write the format, whether the format
% supports the saving of more than one frame to the same file or blob,
% whether the format supports native in-memory I/O, and a brief
% description of the format.
%
% The format of the RegisterJPEGImage method is:
%
% size_t RegisterJPEGImage(void)
%
*/
ModuleExport size_t RegisterJPEGImage(void)
{
#define JPEGDescription "Joint Photographic Experts Group JFIF format"
#define JPEGStringify(macro_or_string) JPEGStringifyArg(macro_or_string)
#define JPEGStringifyArg(contents) #contents
char
version[MagickPathExtent];
MagickInfo
*entry;
*version='\0';
#if defined(LIBJPEG_TURBO_VERSION)
(void) CopyMagickString(version,"libjpeg-turbo " JPEGStringify(
LIBJPEG_TURBO_VERSION),MagickPathExtent);
#elif defined(JPEG_LIB_VERSION)
(void) FormatLocaleString(version,MagickPathExtent,"libjpeg %d",
JPEG_LIB_VERSION);
#endif
entry=AcquireMagickInfo("JPEG","JPE",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
entry->magick=(IsImageFormatHandler *) IsJPEG;
entry->flags|=CoderDecoderSeekableStreamFlag;
entry->flags^=CoderAdjoinFlag;
entry->flags^=CoderUseExtensionFlag;
if (*version != '\0')
entry->version=ConstantString(version);
entry->mime_type=ConstantString("image/jpeg");
(void) RegisterMagickInfo(entry);
entry=AcquireMagickInfo("JPEG","JPEG",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
entry->magick=(IsImageFormatHandler *) IsJPEG;
entry->flags|=CoderDecoderSeekableStreamFlag;
entry->flags^=CoderAdjoinFlag;
if (*version != '\0')
entry->version=ConstantString(version);
entry->mime_type=ConstantString("image/jpeg");
(void) RegisterMagickInfo(entry);
entry=AcquireMagickInfo("JPEG","JPG",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
entry->flags|=CoderDecoderSeekableStreamFlag;
entry->flags^=CoderAdjoinFlag;
entry->flags^=CoderUseExtensionFlag;
if (*version != '\0')
entry->version=ConstantString(version);
entry->mime_type=ConstantString("image/jpeg");
(void) RegisterMagickInfo(entry);
entry=AcquireMagickInfo("JPEG","JPS",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
entry->flags|=CoderDecoderSeekableStreamFlag;
entry->flags^=CoderAdjoinFlag;
entry->flags^=CoderUseExtensionFlag;
if (*version != '\0')
entry->version=ConstantString(version);
entry->mime_type=ConstantString("image/jpeg");
(void) RegisterMagickInfo(entry);
entry=AcquireMagickInfo("JPEG","PJPEG",JPEGDescription);
#if (JPEG_LIB_VERSION < 80) && !defined(LIBJPEG_TURBO_VERSION)
entry->flags^=CoderDecoderThreadSupportFlag;
#endif
#if defined(MAGICKCORE_JPEG_DELEGATE)
entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
#endif
entry->flags|=CoderDecoderSeekableStreamFlag;
entry->flags^=CoderAdjoinFlag;
entry->flags^=CoderUseExtensionFlag;
if (*version != '\0')
entry->version=ConstantString(version);
entry->mime_type=ConstantString("image/jpeg");
(void) RegisterMagickInfo(entry);
return(MagickImageCoderSignature);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% U n r e g i s t e r J P E G I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% UnregisterJPEGImage() removes format registrations made by the
% JPEG module from the list of supported formats.
%
% The format of the UnregisterJPEGImage method is:
%
% UnregisterJPEGImage(void)
%
*/
ModuleExport void UnregisterJPEGImage(void)
{
(void) UnregisterMagickInfo("PJPG");
(void) UnregisterMagickInfo("JPS");
(void) UnregisterMagickInfo("JPG");
(void) UnregisterMagickInfo("JPEG");
(void) UnregisterMagickInfo("JPE");
}
#if defined(MAGICKCORE_JPEG_DELEGATE)
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% W r i t e J P E G I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% WriteJPEGImage() writes a JPEG image file and returns it. It
% allocates the memory necessary for the new Image structure and returns a
% pointer to the new image.
%
% The format of the WriteJPEGImage method is:
%
% MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
% Image *image,ExceptionInfo *exception)
%
% A description of each parameter follows:
%
% o image_info: the image info.
%
% o jpeg_image: The image.
%
% o exception: return any errors or warnings in this structure.
%
*/
static QuantizationTable *DestroyQuantizationTable(QuantizationTable *table)
{
assert(table != (QuantizationTable *) NULL);
if (table->slot != (char *) NULL)
table->slot=DestroyString(table->slot);
if (table->description != (char *) NULL)
table->description=DestroyString(table->description);
if (table->levels != (unsigned int *) NULL)
table->levels=(unsigned int *) RelinquishMagickMemory(table->levels);
table=(QuantizationTable *) RelinquishMagickMemory(table);
return(table);
}
static boolean EmptyOutputBuffer(j_compress_ptr cinfo)
{
DestinationManager
*destination;
destination=(DestinationManager *) cinfo->dest;
destination->manager.free_in_buffer=(size_t) WriteBlob(destination->image,
MagickMinBufferExtent,destination->buffer);
if (destination->manager.free_in_buffer != MagickMinBufferExtent)
ERREXIT(cinfo,JERR_FILE_WRITE);
destination->manager.next_output_byte=destination->buffer;
return(TRUE);
}
static QuantizationTable *GetQuantizationTable(const char *filename,
const char *slot,ExceptionInfo *exception)
{
char
*p,
*xml;
const char
*attribute,
*content;
double
value;
ssize_t
i;
ssize_t
j;
QuantizationTable
*table;
size_t
length;
XMLTreeInfo
*description,
*levels,
*quantization_tables,
*table_iterator;
(void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
"Loading quantization tables \"%s\" ...",filename);
table=(QuantizationTable *) NULL;
xml=FileToString(filename,~0UL,exception);
if (xml == (char *) NULL)
return(table);
quantization_tables=NewXMLTree(xml,exception);
if (quantization_tables == (XMLTreeInfo *) NULL)
{
xml=DestroyString(xml);
return(table);
}
for (table_iterator=GetXMLTreeChild(quantization_tables,"table");
table_iterator != (XMLTreeInfo *) NULL;
table_iterator=GetNextXMLTreeTag(table_iterator))
{
attribute=GetXMLTreeAttribute(table_iterator,"slot");
if ((attribute != (char *) NULL) && (LocaleCompare(slot,attribute) == 0))
break;
attribute=GetXMLTreeAttribute(table_iterator,"alias");
if ((attribute != (char *) NULL) && (LocaleCompare(slot,attribute) == 0))
break;
}
if (table_iterator == (XMLTreeInfo *) NULL)
{
xml=DestroyString(xml);
return(table);
}
description=GetXMLTreeChild(table_iterator,"description");
if (description == (XMLTreeInfo *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlMissingElement","<description>, slot \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
xml=DestroyString(xml);
return(table);
}
levels=GetXMLTreeChild(table_iterator,"levels");
if (levels == (XMLTreeInfo *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlMissingElement","<levels>, slot \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
xml=DestroyString(xml);
return(table);
}
table=(QuantizationTable *) AcquireCriticalMemory(sizeof(*table));
table->slot=(char *) NULL;
table->description=(char *) NULL;
table->levels=(unsigned int *) NULL;
attribute=GetXMLTreeAttribute(table_iterator,"slot");
if (attribute != (char *) NULL)
table->slot=ConstantString(attribute);
content=GetXMLTreeContent(description);
if (content != (char *) NULL)
table->description=ConstantString(content);
attribute=GetXMLTreeAttribute(levels,"width");
if (attribute == (char *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlMissingAttribute","<levels width>, slot \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
table=DestroyQuantizationTable(table);
xml=DestroyString(xml);
return(table);
}
table->width=StringToUnsignedLong(attribute);
if (table->width == 0)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlInvalidAttribute","<levels width>, table \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
table=DestroyQuantizationTable(table);
xml=DestroyString(xml);
return(table);
}
attribute=GetXMLTreeAttribute(levels,"height");
if (attribute == (char *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlMissingAttribute","<levels height>, table \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
table=DestroyQuantizationTable(table);
xml=DestroyString(xml);
return(table);
}
table->height=StringToUnsignedLong(attribute);
if (table->height == 0)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlInvalidAttribute","<levels height>, table \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
table=DestroyQuantizationTable(table);
xml=DestroyString(xml);
return(table);
}
attribute=GetXMLTreeAttribute(levels,"divisor");
if (attribute == (char *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlMissingAttribute","<levels divisor>, table \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
table=DestroyQuantizationTable(table);
xml=DestroyString(xml);
return(table);
}
table->divisor=InterpretLocaleValue(attribute,(char **) NULL);
if (table->divisor == 0.0)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlInvalidAttribute","<levels divisor>, table \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
table=DestroyQuantizationTable(table);
xml=DestroyString(xml);
return(table);
}
content=GetXMLTreeContent(levels);
if (content == (char *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlMissingContent","<levels>, table \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
table=DestroyQuantizationTable(table);
xml=DestroyString(xml);
return(table);
}
length=(size_t) table->width*table->height;
if (length < 64)
length=64;
table->levels=(unsigned int *) AcquireQuantumMemory(length,
sizeof(*table->levels));
if (table->levels == (unsigned int *) NULL)
ThrowFatalException(ResourceLimitFatalError,
"UnableToAcquireQuantizationTable");
for (i=0; i < (ssize_t) (table->width*table->height); i++)
{
table->levels[i]=(unsigned int) (InterpretLocaleValue(content,&p)/
table->divisor+0.5);
while (isspace((int) ((unsigned char) *p)) != 0)
p++;
if (*p == ',')
p++;
content=p;
}
value=InterpretLocaleValue(content,&p);
(void) value;
if (p != content)
{
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
"XmlInvalidContent","<level> too many values, table \"%s\"",slot);
quantization_tables=DestroyXMLTree(quantization_tables);
table=DestroyQuantizationTable(table);
xml=DestroyString(xml);
return(table);
}
for (j=i; j < 64; j++)
table->levels[j]=table->levels[j-1];
quantization_tables=DestroyXMLTree(quantization_tables);
xml=DestroyString(xml);
return(table);
}
static void InitializeDestination(j_compress_ptr cinfo)
{
DestinationManager
*destination;
destination=(DestinationManager *) cinfo->dest;
destination->buffer=(JOCTET *) (*cinfo->mem->alloc_small)
((j_common_ptr) cinfo,JPOOL_IMAGE,MagickMinBufferExtent*sizeof(JOCTET));
destination->manager.next_output_byte=destination->buffer;
destination->manager.free_in_buffer=MagickMinBufferExtent;
}
static void TerminateDestination(j_compress_ptr cinfo)
{
DestinationManager
*destination;
destination=(DestinationManager *) cinfo->dest;
if ((MagickMinBufferExtent-(int) destination->manager.free_in_buffer) > 0)
{
ssize_t
count;
count=WriteBlob(destination->image,MagickMinBufferExtent-
destination->manager.free_in_buffer,destination->buffer);
if (count != (ssize_t)
(MagickMinBufferExtent-destination->manager.free_in_buffer))
ERREXIT(cinfo,JERR_FILE_WRITE);
}
}
static void WriteProfile(j_compress_ptr jpeg_info,Image *image,
ExceptionInfo *exception)
{
const char
*name;
const StringInfo
*profile;
MagickBooleanType
iptc;
ssize_t
i;
size_t
length,
tag_length;
StringInfo
*custom_profile;
/*
Save image profile as a APP marker.
*/
iptc=MagickFalse;
custom_profile=AcquireStringInfo(65535L);
ResetImageProfileIterator(image);
for (name=GetNextImageProfile(image); name != (const char *) NULL; )
{
profile=GetImageProfile(image,name);
length=GetStringInfoLength(profile);
if (LocaleNCompare(name,"APP",3) == 0)
{
int
id;
id=JPEG_APP0+StringToInteger(name+3);
for (i=0; i < (ssize_t) length; i+=65533L)
jpeg_write_marker(jpeg_info,id,GetStringInfoDatum(profile)+i,
(unsigned int) MagickMin(length-i,65533));
}
if (LocaleCompare(name,"EXIF") == 0)
{
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)
{
unsigned char
*p;
tag_length=strlen(ICC_PROFILE);
p=GetStringInfoDatum(custom_profile);
(void) memcpy(p,ICC_PROFILE,tag_length);
p[tag_length]='\0';
for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=65519L)
{
length=MagickMin(GetStringInfoLength(profile)-i,65519L);
p[12]=(unsigned char) ((i/65519L)+1);
p[13]=(unsigned char) (GetStringInfoLength(profile)/65519L+1);
(void) memcpy(p+tag_length+3,GetStringInfoDatum(profile)+i,
length);
jpeg_write_marker(jpeg_info,ICC_MARKER,GetStringInfoDatum(
custom_profile),(unsigned int) (length+tag_length+3));
}
}
if (((LocaleCompare(name,"IPTC") == 0) ||
(LocaleCompare(name,"8BIM") == 0)) && (iptc == MagickFalse))
{
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);
roundup=(size_t) (length & 0x01);
if (LocaleNCompare((char *) GetStringInfoDatum(profile),"8BIM",4) == 0)
{
(void) memcpy(p,"Photoshop 3.0 ",14);
tag_length=14;
}
else
{
(void) memcpy(p,"Photoshop 3.0 8BIM\04\04\0\0\0\0",24);
tag_length=26;
p[24]=(unsigned char) (length >> 8);
p[25]=(unsigned char) (length & 0xff);
}
p[13]=0x00;
(void) memcpy(p+tag_length,GetStringInfoDatum(profile)+i,length);
if (roundup != 0)
p[length+tag_length]='\0';
jpeg_write_marker(jpeg_info,IPTC_MARKER,GetStringInfoDatum(
custom_profile),(unsigned int) (length+tag_length+roundup));
}
}
if ((LocaleCompare(name,"XMP") == 0) &&
(GetStringInfoLength(profile) < (65533 - sizeof(xmp_namespace))))
{
StringInfo
*xmp_profile;
/*
Add namespace to XMP profile.
*/
xmp_profile=StringToStringInfo(xmp_namespace);
if (xmp_profile != (StringInfo *) NULL)
{
ConcatenateStringInfo(xmp_profile,profile);
GetStringInfoDatum(xmp_profile)[XmpNamespaceExtent]='\0';
length=GetStringInfoLength(xmp_profile);
jpeg_write_marker(jpeg_info,XML_MARKER,
GetStringInfoDatum(xmp_profile),(unsigned int) length);
xmp_profile=DestroyStringInfo(xmp_profile);
}
}
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"%s profile: %.20g bytes",name,(double) GetStringInfoLength(profile));
name=GetNextImageProfile(image);
}
custom_profile=DestroyStringInfo(custom_profile);
}
static void JPEGDestinationManager(j_compress_ptr 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 char **SamplingFactorToList(const char *text)
{
char
**textlist;
char
*q;
const char
*p;
ssize_t
i;
if (text == (char *) NULL)
return((char **) NULL);
/*
Convert string to an ASCII list.
*/
textlist=(char **) AcquireQuantumMemory((size_t) MAX_COMPONENTS,
sizeof(*textlist));
if (textlist == (char **) NULL)
ThrowFatalException(ResourceLimitFatalError,"UnableToConvertText");
p=text;
for (i=0; i < (ssize_t) MAX_COMPONENTS; i++)
{
for (q=(char *) p; *q != '\0'; q++)
if (*q == ',')
break;
textlist[i]=(char *) AcquireQuantumMemory((size_t) (q-p)+MagickPathExtent,
sizeof(*textlist[i]));
if (textlist[i] == (char *) NULL)
ThrowFatalException(ResourceLimitFatalError,"UnableToConvertText");
(void) CopyMagickString(textlist[i],p,(size_t) (q-p+1));
if (*q == '\r')
q++;
if (*q == '\0')
break;
p=q+1;
}
for (i++; i < (ssize_t) MAX_COMPONENTS; i++)
textlist[i]=ConstantString("1x1");
return(textlist);
}
static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
Image *image,struct jpeg_compress_struct *jpeg_info,ExceptionInfo *exception)
{
const char
*dct_method,
*option,
*sampling_factor,
*value;
ErrorManager
error_manager;
Image
*volatile volatile_image;
int
colorspace,
quality;
JSAMPLE
*volatile jpeg_pixels;
JSAMPROW
scanline[1];
MagickBooleanType
status;
MemoryInfo
*memory_info;
JSAMPLE
*q;
ssize_t
i;
ssize_t
y;
struct jpeg_error_mgr
jpeg_error;
unsigned short
scale;
/*
Open image file.
*/
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickCoreSignature);
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(exception != (ExceptionInfo *) NULL);
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);
/*
Initialize JPEG parameters.
*/
(void) memset(&error_manager,0,sizeof(error_manager));
(void) memset(jpeg_info,0,sizeof(*jpeg_info));
(void) memset(&jpeg_error,0,sizeof(jpeg_error));
volatile_image=image;
jpeg_info->client_data=(void *) volatile_image;
jpeg_info->err=jpeg_std_error(&jpeg_error);
jpeg_info->err->emit_message=(void (*)(j_common_ptr,int)) JPEGWarningHandler;
jpeg_info->err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
error_manager.exception=exception;
error_manager.image=volatile_image;
memory_info=(MemoryInfo *) NULL;
if (setjmp(error_manager.error_recovery) != 0)
{
jpeg_destroy_compress(jpeg_info);
(void) CloseBlob(volatile_image);
return(MagickFalse);
}
jpeg_info->client_data=(void *) &error_manager;
jpeg_create_compress(jpeg_info);
JPEGDestinationManager(jpeg_info,image);
if ((image->columns != (unsigned int) image->columns) ||
(image->rows != (unsigned int) image->rows))
ThrowWriterException(ImageError,"WidthOrHeightExceedsLimit");
jpeg_info->image_width=(unsigned int) image->columns;
jpeg_info->image_height=(unsigned int) image->rows;
jpeg_info->input_components=3;
jpeg_info->data_precision=8;
jpeg_info->in_color_space=JCS_RGB;
switch (image->colorspace)
{
case CMYKColorspace:
{
jpeg_info->input_components=4;
jpeg_info->in_color_space=JCS_CMYK;
break;
}
case YCbCrColorspace:
case Rec601YCbCrColorspace:
case Rec709YCbCrColorspace:
{
jpeg_info->in_color_space=JCS_YCbCr;
break;
}
case LinearGRAYColorspace:
case GRAYColorspace:
{
if (image_info->type == TrueColorType)
break;
jpeg_info->input_components=1;
jpeg_info->in_color_space=JCS_GRAYSCALE;
break;
}
default:
{
(void) TransformImageColorspace(image,sRGBColorspace,exception);
if (image_info->type == TrueColorType)
break;
if (SetImageGray(image,exception) != MagickFalse)
{
jpeg_info->input_components=1;
jpeg_info->in_color_space=JCS_GRAYSCALE;
}
break;
}
}
jpeg_set_defaults(jpeg_info);
if (jpeg_info->in_color_space == JCS_CMYK)
jpeg_set_colorspace(jpeg_info,JCS_YCCK);
if ((jpeg_info->data_precision != 12) && (image->depth <= 8))
jpeg_info->data_precision=8;
else
jpeg_info->data_precision=BITS_IN_JSAMPLE;
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"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) image->resolution.x;
jpeg_info->Y_density=(UINT16) image->resolution.y;
/*
Set image resolution units.
*/
if (image->units == PixelsPerInchResolution)
jpeg_info->density_unit=(UINT8) 1;
if (image->units == PixelsPerCentimeterResolution)
jpeg_info->density_unit=(UINT8) 2;
}
dct_method=GetImageOption(image_info,"jpeg:dct-method");
if (dct_method != (const char *) NULL)
switch (*dct_method)
{
case 'D':
case 'd':
{
if (LocaleCompare(dct_method,"default") == 0)
jpeg_info->dct_method=JDCT_DEFAULT;
break;
}
case 'F':
case 'f':
{
if (LocaleCompare(dct_method,"fastest") == 0)
jpeg_info->dct_method=JDCT_FASTEST;
if (LocaleCompare(dct_method,"float") == 0)
jpeg_info->dct_method=JDCT_FLOAT;
break;
}
case 'I':
case 'i':
{
if (LocaleCompare(dct_method,"ifast") == 0)
jpeg_info->dct_method=JDCT_IFAST;
if (LocaleCompare(dct_method,"islow") == 0)
jpeg_info->dct_method=JDCT_ISLOW;
break;
}
}
option=GetImageOption(image_info,"jpeg:optimize-coding");
if (option != (const char *) NULL)
jpeg_info->optimize_coding=IsStringTrue(option) != MagickFalse ? TRUE :
FALSE;
else
{
MagickSizeType
length;
length=(MagickSizeType) jpeg_info->input_components*image->columns*
image->rows*sizeof(JSAMPLE);
if (length == (MagickSizeType) ((size_t) length))
{
/*
Perform optimization only if available memory resources permit it.
*/
status=AcquireMagickResource(MemoryResource,length);
if (status != MagickFalse)
RelinquishMagickResource(MemoryResource,length);
jpeg_info->optimize_coding=status == MagickFalse ? FALSE : TRUE;
}
}
#if defined(C_ARITH_CODING_SUPPORTED)
option=GetImageOption(image_info,"jpeg:arithmetic-coding");
if (IsStringTrue(option) != MagickFalse)
{
jpeg_info->arith_code=TRUE;
jpeg_info->optimize_coding=FALSE; /* not supported */
}
#endif
#if (JPEG_LIB_VERSION >= 61) && defined(C_PROGRESSIVE_SUPPORTED)
if ((LocaleCompare(image_info->magick,"PJPEG") == 0) ||
(image_info->interlace != NoInterlace))
{
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Interlace: progressive");
jpeg_simple_progression(jpeg_info);
}
else
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Interlace: non-progressive");
#else
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Interlace: nonprogressive");
#endif
quality=92;
if ((image_info->compression != LosslessJPEGCompression) &&
(image->quality <= 100))
{
if (image->quality != UndefinedCompressionQuality)
quality=(int) image->quality;
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: %.20g",
(double) image->quality);
}
else
{
#if !defined(C_LOSSLESS_SUPPORTED)
quality=100;
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: 100");
#else
if (image->quality < 100)
(void) ThrowMagickException(exception,GetMagickModule(),CoderWarning,
"LosslessToLossyJPEGConversion","`%s'",image->filename);
else
{
int
point_transform,
predictor;
predictor=image->quality/100; /* range 1-7 */
point_transform=image->quality % 20; /* range 0-15 */
jpeg_simple_lossless(jpeg_info,predictor,point_transform);
if (image->debug != MagickFalse)
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Compression: lossless");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Predictor: %d",predictor);
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Point Transform: %d",point_transform);
}
}
#endif
}
option=GetImageOption(image_info,"jpeg:extent");
if (option != (const char *) NULL)
{
Image
*jpeg_image;
ImageInfo
*extent_info;
extent_info=CloneImageInfo(image_info);
extent_info->blob=NULL;
jpeg_image=CloneImage(image,0,0,MagickTrue,exception);
if (jpeg_image != (Image *) NULL)
{
MagickSizeType
extent;
size_t
maximum,
minimum;
/*
Search for compression quality that does not exceed image extent.
*/
extent_info->quality=0;
extent=(MagickSizeType) SiPrefixToDoubleInterval(option,100.0);
(void) DeleteImageOption(extent_info,"jpeg:extent");
(void) DeleteImageArtifact(jpeg_image,"jpeg:extent");
maximum=image_info->quality;
if (maximum < 2)
maximum=101;
for (minimum=2; minimum < maximum; )
{
(void) AcquireUniqueFilename(jpeg_image->filename);
jpeg_image->quality=minimum+(maximum-minimum+1)/2;
status=WriteJPEGImage(extent_info,jpeg_image,exception);
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 ((dct_method == (const char *) NULL) && (quality <= 90))
jpeg_info->dct_method=JDCT_IFAST;
#if (JPEG_LIB_VERSION >= 70)
option=GetImageOption(image_info,"quality");
if (option != (const char *) NULL)
{
GeometryInfo
geometry_info;
int
flags;
/*
Set quality scaling for luminance and chrominance separately.
*/
flags=ParseGeometry(option,&geometry_info);
if (((flags & RhoValue) != 0) && ((flags & SigmaValue) != 0))
{
jpeg_info->q_scale_factor[0]=jpeg_quality_scaling((int)
(geometry_info.rho+0.5));
jpeg_info->q_scale_factor[1]=jpeg_quality_scaling((int)
(geometry_info.sigma+0.5));
jpeg_default_qtables(jpeg_info,TRUE);
}
}
#endif
colorspace=jpeg_info->in_color_space;
value=GetImageOption(image_info,"jpeg:colorspace");
if (value == (char *) NULL)
value=GetImageProperty(image,"jpeg:colorspace",exception);
if (value != (char *) NULL)
colorspace=StringToInteger(value);
sampling_factor=(const char *) NULL;
if ((J_COLOR_SPACE) colorspace == jpeg_info->in_color_space)
{
value=GetImageOption(image_info,"jpeg:sampling-factor");
if (value == (char *) NULL)
value=GetImageProperty(image,"jpeg:sampling-factor",exception);
if (value != (char *) NULL)
{
sampling_factor=value;
if (image->debug != MagickFalse)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
" Input sampling-factors=%s",sampling_factor);
}
}
value=GetImageOption(image_info,"jpeg:sampling-factor");
if (image_info->sampling_factor != (char *) NULL)
sampling_factor=image_info->sampling_factor;
if (sampling_factor == (const char *) NULL)
{
if (quality >= 90)
for (i=0; i < MAX_COMPONENTS; i++)
{
jpeg_info->comp_info[i].h_samp_factor=1;
jpeg_info->comp_info[i].v_samp_factor=1;
}
}
else
{
char
**factors;
GeometryInfo
geometry_info;
MagickStatusType
flags;
/*
Set sampling factor.
*/
i=0;
factors=SamplingFactorToList(sampling_factor);
if (factors != (char **) NULL)
{
for (i=0; i < MAX_COMPONENTS; i++)
{
if (factors[i] == (char *) NULL)
break;
flags=ParseGeometry(factors[i],&geometry_info);
if ((flags & SigmaValue) == 0)
geometry_info.sigma=geometry_info.rho;
jpeg_info->comp_info[i].h_samp_factor=(int) geometry_info.rho;
jpeg_info->comp_info[i].v_samp_factor=(int) geometry_info.sigma;
factors[i]=(char *) RelinquishMagickMemory(factors[i]);
}
factors=(char **) RelinquishMagickMemory(factors);
}
for ( ; i < MAX_COMPONENTS; i++)
{
jpeg_info->comp_info[i].h_samp_factor=1;
jpeg_info->comp_info[i].v_samp_factor=1;
}
}
option=GetImageOption(image_info,"jpeg:q-table");
if (option != (const char *) NULL)
{
QuantizationTable
*table;
/*
Custom quantization tables.
*/
table=GetQuantizationTable(option,"0",exception);
if (table != (QuantizationTable *) NULL)
{
for (i=0; i < MAX_COMPONENTS; i++)
jpeg_info->comp_info[i].quant_tbl_no=0;
jpeg_add_quant_table(jpeg_info,0,table->levels,
jpeg_quality_scaling(quality),0);
table=DestroyQuantizationTable(table);
}
table=GetQuantizationTable(option,"1",exception);
if (table != (QuantizationTable *) NULL)
{
for (i=1; i < MAX_COMPONENTS; i++)
jpeg_info->comp_info[i].quant_tbl_no=1;
jpeg_add_quant_table(jpeg_info,1,table->levels,
jpeg_quality_scaling(quality),0);
table=DestroyQuantizationTable(table);
}
table=GetQuantizationTable(option,"2",exception);
if (table != (QuantizationTable *) NULL)
{
for (i=2; i < MAX_COMPONENTS; i++)
jpeg_info->comp_info[i].quant_tbl_no=2;
jpeg_add_quant_table(jpeg_info,2,table->levels,
jpeg_quality_scaling(quality),0);
table=DestroyQuantizationTable(table);
}
table=GetQuantizationTable(option,"3",exception);
if (table != (QuantizationTable *) NULL)
{
for (i=3; i < MAX_COMPONENTS; i++)
jpeg_info->comp_info[i].quant_tbl_no=3;
jpeg_add_quant_table(jpeg_info,3,table->levels,
jpeg_quality_scaling(quality),0);
table=DestroyQuantizationTable(table);
}
}
jpeg_start_compress(jpeg_info,TRUE);
if (image->debug != MagickFalse)
{
if (image->storage_class == PseudoClass)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Storage class: PseudoClass");
else
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Storage class: DirectClass");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),"Depth: %.20g",
(double) image->depth);
if (image->colors != 0)
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Number of colors: %.20g",(double) image->colors);
else
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Number of colors: unspecified");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"JPEG data precision: %d",(int) jpeg_info->data_precision);
switch (image->colorspace)
{
case CMYKColorspace:
{
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Storage class: DirectClass");
(void) LogMagickEvent(CoderEvent,GetMagickModule(),
"Colorspace: CMYK");
break;
}
case YCbCrColorspace:
case Rec601YCbCrColorspace:
case Rec709YCbCrColorspace:
{
(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 GRAYColorspace:
{
(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 sRGBColorspace:
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:
case Rec601YCbCrColorspace:
case Rec709YCbCrColorspace:
{
(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.
*/
value=GetImageProperty(image,"comment",exception);
if (value != (char *) NULL)
{
size_t
length;
length=strlen(value);
for (i=0; i < (ssize_t) length; i+=65533L)
jpeg_write_marker(jpeg_info,JPEG_COM,(unsigned char *) value+i,
(unsigned int) MagickMin((size_t) strlen(value+i),65533L));
}
if (image->profiles != (void *) NULL)
WriteProfile(jpeg_info,image,exception);
/*
Convert MIFF to JPEG raster pixels.
*/
memory_info=AcquireVirtualMemory((size_t) image->columns,
jpeg_info->input_components*sizeof(*jpeg_pixels));
if (memory_info == (MemoryInfo *) NULL)
ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
jpeg_pixels=(JSAMPLE *) GetVirtualMemoryBlob(memory_info);
if (setjmp(error_manager.error_recovery) != 0)
{
jpeg_destroy_compress(jpeg_info);
if (memory_info != (MemoryInfo *) NULL)
memory_info=RelinquishVirtualMemory(memory_info);
(void) CloseBlob(image);
return(MagickFalse);
}
scanline[0]=(JSAMPROW) jpeg_pixels;
scale=65535/(unsigned short) GetQuantumRange((size_t)
jpeg_info->data_precision);
if (scale == 0)
scale=1;
if (jpeg_info->data_precision <= 8)
{
if ((jpeg_info->in_color_space == JCS_RGB) ||
(jpeg_info->in_color_space == JCS_YCbCr))
for (y=0; y < (ssize_t) image->rows; y++)
{
const Quantum
*p;
ssize_t
x;
p=GetVirtualPixels(image,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
q=jpeg_pixels;
for (x=0; x < (ssize_t) image->columns; x++)
{
*q++=(JSAMPLE) ScaleQuantumToChar(GetPixelRed(image,p));
*q++=(JSAMPLE) ScaleQuantumToChar(GetPixelGreen(image,p));
*q++=(JSAMPLE) ScaleQuantumToChar(GetPixelBlue(image,p));
p+=GetPixelChannels(image);
}
(void) jpeg_write_scanlines(jpeg_info,scanline,1);
status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
image->rows);
if (status == MagickFalse)
break;
}
else
if (jpeg_info->in_color_space == JCS_GRAYSCALE)
for (y=0; y < (ssize_t) image->rows; y++)
{
const Quantum
*p;
ssize_t
x;
p=GetVirtualPixels(image,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
q=jpeg_pixels;
for (x=0; x < (ssize_t) image->columns; x++)
{
*q++=(JSAMPLE) ScaleQuantumToChar(ClampToQuantum(GetPixelLuma(
image,p)));
p+=GetPixelChannels(image);
}
(void) jpeg_write_scanlines(jpeg_info,scanline,1);
status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
image->rows);
if (status == MagickFalse)
break;
}
else
for (y=0; y < (ssize_t) image->rows; y++)
{
const Quantum
*p;
ssize_t
x;
p=GetVirtualPixels(image,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
q=jpeg_pixels;
for (x=0; x < (ssize_t) image->columns; x++)
{
/*
Convert DirectClass packets to contiguous CMYK scanlines.
*/
*q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
GetPixelCyan(image,p))));
*q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
GetPixelMagenta(image,p))));
*q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
GetPixelYellow(image,p))));
*q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
GetPixelBlack(image,p))));
p+=GetPixelChannels(image);
}
(void) jpeg_write_scanlines(jpeg_info,scanline,1);
status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
image->rows);
if (status == MagickFalse)
break;
}
}
else
if (jpeg_info->in_color_space == JCS_GRAYSCALE)
for (y=0; y < (ssize_t) image->rows; y++)
{
const Quantum
*p;
ssize_t
x;
p=GetVirtualPixels(image,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
q=jpeg_pixels;
for (x=0; x < (ssize_t) image->columns; x++)
{
*q++=(JSAMPLE) (ScaleQuantumToShort(ClampToQuantum(GetPixelLuma(image,
p)))/scale);
p+=GetPixelChannels(image);
}
(void) jpeg_write_scanlines(jpeg_info,scanline,1);
status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
image->rows);
if (status == MagickFalse)
break;
}
else
if ((jpeg_info->in_color_space == JCS_RGB) ||
(jpeg_info->in_color_space == JCS_YCbCr))
for (y=0; y < (ssize_t) image->rows; y++)
{
const Quantum
*p;
ssize_t
x;
p=GetVirtualPixels(image,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
q=jpeg_pixels;
for (x=0; x < (ssize_t) image->columns; x++)
{
*q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelRed(image,p))/scale);
*q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelGreen(image,p))/scale);
*q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelBlue(image,p))/scale);
p+=GetPixelChannels(image);
}
(void) jpeg_write_scanlines(jpeg_info,scanline,1);
status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
image->rows);
if (status == MagickFalse)
break;
}
else
for (y=0; y < (ssize_t) image->rows; y++)
{
const Quantum
*p;
ssize_t
x;
p=GetVirtualPixels(image,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
break;
q=jpeg_pixels;
for (x=0; x < (ssize_t) image->columns; x++)
{
/*
Convert DirectClass packets to contiguous CMYK scanlines.
*/
*q++=(JSAMPLE) (ScaleQuantumToShort(QuantumRange-GetPixelRed(
image,p))/scale);
*q++=(JSAMPLE) (ScaleQuantumToShort(QuantumRange-GetPixelGreen(
image,p))/scale);
*q++=(JSAMPLE) (ScaleQuantumToShort(QuantumRange-GetPixelBlue(
image,p))/scale);
*q++=(JSAMPLE) (ScaleQuantumToShort(QuantumRange-GetPixelBlack(
image,p))/scale);
p+=GetPixelChannels(image);
}
(void) jpeg_write_scanlines(jpeg_info,scanline,1);
status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
image->rows);
if (status == MagickFalse)
break;
}
if (y == (ssize_t) image->rows)
jpeg_finish_compress(jpeg_info);
/*
Relinquish resources.
*/
jpeg_destroy_compress(jpeg_info);
memory_info=RelinquishVirtualMemory(memory_info);
(void) CloseBlob(image);
return(MagickTrue);
}
static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
Image *image,ExceptionInfo *exception)
{
struct jpeg_compress_struct
jpeg_info;
return(WriteJPEGImage_(image_info,image,&jpeg_info,exception));
}
#endif