blob: 2a018eac4dfd3be2e723d7e2d27197c11d15735d [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% JJJJJ PPPP EEEEE GGGG %
7% J P P E G %
8% J PPPP EEE G GG %
9% J J P E G G %
10% JJJ P EEEEE GGG %
11% %
12% %
13% Read/Write JPEG Image Format %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy45ef08f2012-12-07 13:13:34 +000020% Copyright 1999-2013 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36% This software is based in part on the work of the Independent JPEG Group.
37% See ftp://ftp.uu.net/graphics/jpeg/jpegsrc.v6b.tar.gz for copyright and
38% licensing restrictions. Blob support contributed by Glenn Randers-Pehrson.
39%
40%
41*/
42
43/*
44 Include declarations.
45*/
cristy4c08aed2011-07-01 19:47:50 +000046#include "MagickCore/studio.h"
cristyc6348612013-04-05 14:24:24 +000047#include "MagickCore/artifact.h"
cristy4c08aed2011-07-01 19:47:50 +000048#include "MagickCore/attribute.h"
49#include "MagickCore/blob.h"
50#include "MagickCore/blob-private.h"
51#include "MagickCore/cache.h"
52#include "MagickCore/color.h"
53#include "MagickCore/colormap-private.h"
54#include "MagickCore/color-private.h"
55#include "MagickCore/colormap.h"
56#include "MagickCore/colorspace.h"
cristy510d06a2011-07-06 23:43:54 +000057#include "MagickCore/colorspace-private.h"
cristy4c08aed2011-07-01 19:47:50 +000058#include "MagickCore/constitute.h"
59#include "MagickCore/exception.h"
60#include "MagickCore/exception-private.h"
61#include "MagickCore/geometry.h"
62#include "MagickCore/image.h"
63#include "MagickCore/image-private.h"
64#include "MagickCore/list.h"
65#include "MagickCore/log.h"
66#include "MagickCore/magick.h"
67#include "MagickCore/memory_.h"
68#include "MagickCore/module.h"
69#include "MagickCore/monitor.h"
70#include "MagickCore/monitor-private.h"
71#include "MagickCore/option.h"
72#include "MagickCore/pixel-accessor.h"
73#include "MagickCore/profile.h"
74#include "MagickCore/property.h"
75#include "MagickCore/quantum-private.h"
76#include "MagickCore/resource_.h"
77#include "MagickCore/splay-tree.h"
78#include "MagickCore/static.h"
79#include "MagickCore/string_.h"
80#include "MagickCore/string-private.h"
cristye40005d2012-03-23 12:18:45 +000081#include "MagickCore/token.h"
cristy4c08aed2011-07-01 19:47:50 +000082#include "MagickCore/utility.h"
cristy1b58f252012-03-01 01:41:41 +000083#include "MagickCore/xml-tree.h"
84#include "MagickCore/xml-tree-private.h"
cristy3ed852e2009-09-05 21:47:34 +000085#include <setjmp.h>
86#if defined(MAGICKCORE_JPEG_DELEGATE)
87#define JPEG_INTERNAL_OPTIONS
cristy07a3cca2012-12-10 13:09:10 +000088#if defined(__MINGW32__) || defined(__MINGW64__)
cristy3ed852e2009-09-05 21:47:34 +000089# define XMD_H 1 /* Avoid conflicting typedef for INT32 */
90#endif
91#undef HAVE_STDLIB_H
92#include "jpeglib.h"
93#include "jerror.h"
94#endif
95
96/*
97 Define declarations.
98*/
99#define ICC_MARKER (JPEG_APP0+2)
100#define ICC_PROFILE "ICC_PROFILE"
101#define IPTC_MARKER (JPEG_APP0+13)
102#define XML_MARKER (JPEG_APP0+1)
cristyeb9759e2012-06-07 23:06:29 +0000103#define MaxBufferExtent 16384
cristy3ed852e2009-09-05 21:47:34 +0000104
105/*
106 Typedef declarations.
107*/
108#if defined(MAGICKCORE_JPEG_DELEGATE)
109typedef struct _DestinationManager
110{
111 struct jpeg_destination_mgr
112 manager;
113
114 Image
115 *image;
116
117 JOCTET
118 *buffer;
119} DestinationManager;
120
121typedef struct _ErrorManager
122{
cristy018f07f2011-09-04 21:15:19 +0000123 ExceptionInfo
124 *exception;
125
cristy3ed852e2009-09-05 21:47:34 +0000126 Image
127 *image;
128
cristyd28b1dd2011-05-14 20:30:38 +0000129 MagickBooleanType
130 finished;
131
cristyf551aee2012-09-27 21:45:23 +0000132 StringInfo
133 *profile;
134
cristy3ed852e2009-09-05 21:47:34 +0000135 jmp_buf
136 error_recovery;
137} ErrorManager;
138
139typedef struct _SourceManager
140{
141 struct jpeg_source_mgr
142 manager;
143
144 Image
145 *image;
146
147 JOCTET
148 *buffer;
149
150 boolean
151 start_of_blob;
152} SourceManager;
153#endif
cristy1b58f252012-03-01 01:41:41 +0000154
155typedef struct _QuantizationTable
156{
157 char
158 *slot,
159 *description;
160
161 size_t
162 width,
163 height;
164
cristy043f3f32012-03-02 17:37:28 +0000165 double
166 divisor;
167
cristy1b58f252012-03-01 01:41:41 +0000168 unsigned int
cristy1b58f252012-03-01 01:41:41 +0000169 *levels;
170} QuantizationTable;
cristy3ed852e2009-09-05 21:47:34 +0000171
172/*
173 Forward declarations.
174*/
175#if defined(MAGICKCORE_JPEG_DELEGATE)
176static MagickBooleanType
cristy1e178e72011-08-28 19:44:34 +0000177 WriteJPEGImage(const ImageInfo *,Image *,ExceptionInfo *);
cristy3ed852e2009-09-05 21:47:34 +0000178#endif
179
180/*
181%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
182% %
183% %
184% %
185% I s J P E G %
186% %
187% %
188% %
189%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
190%
191% IsJPEG() returns MagickTrue if the image format type, identified by the
192% magick string, is JPEG.
193%
194% The format of the IsJPEG method is:
195%
196% MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
197%
198% A description of each parameter follows:
199%
200% o magick: compare image format pattern against these bytes.
201%
202% o length: Specifies the length of the magick string.
203%
204*/
205static MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
206{
207 if (length < 3)
208 return(MagickFalse);
209 if (memcmp(magick,"\377\330\377",3) == 0)
210 return(MagickTrue);
211 return(MagickFalse);
212}
213
214#if defined(MAGICKCORE_JPEG_DELEGATE)
215/*
216%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
217% %
218% %
219% %
220% R e a d J P E G I m a g e %
221% %
222% %
223% %
224%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
225%
226% ReadJPEGImage() reads a JPEG image file and returns it. It allocates
227% the memory necessary for the new Image structure and returns a pointer to
228% the new image.
229%
230% The format of the ReadJPEGImage method is:
231%
232% Image *ReadJPEGImage(const ImageInfo *image_info,
233% ExceptionInfo *exception)
234%
235% A description of each parameter follows:
236%
237% o image_info: the image info.
238%
239% o exception: return any errors or warnings in this structure.
240%
241*/
242
cristy3ed852e2009-09-05 21:47:34 +0000243static boolean FillInputBuffer(j_decompress_ptr cinfo)
244{
245 SourceManager
246 *source;
247
248 source=(SourceManager *) cinfo->src;
cristy524222d2011-04-25 00:37:06 +0000249 source->manager.bytes_in_buffer=(size_t) ReadBlob(source->image,
250 MaxBufferExtent,source->buffer);
cristy3ed852e2009-09-05 21:47:34 +0000251 if (source->manager.bytes_in_buffer == 0)
252 {
cristy453b8202012-06-07 22:57:52 +0000253 if (source->start_of_blob != FALSE)
cristy3ed852e2009-09-05 21:47:34 +0000254 ERREXIT(cinfo,JERR_INPUT_EMPTY);
255 WARNMS(cinfo,JWRN_JPEG_EOF);
256 source->buffer[0]=(JOCTET) 0xff;
257 source->buffer[1]=(JOCTET) JPEG_EOI;
258 source->manager.bytes_in_buffer=2;
259 }
260 source->manager.next_input_byte=source->buffer;
261 source->start_of_blob=FALSE;
262 return(TRUE);
263}
264
265static int GetCharacter(j_decompress_ptr jpeg_info)
266{
267 if (jpeg_info->src->bytes_in_buffer == 0)
268 (void) (*jpeg_info->src->fill_input_buffer)(jpeg_info);
269 jpeg_info->src->bytes_in_buffer--;
270 return((int) GETJOCTET(*jpeg_info->src->next_input_byte++));
271}
272
273static void InitializeSource(j_decompress_ptr cinfo)
274{
275 SourceManager
276 *source;
277
278 source=(SourceManager *) cinfo->src;
279 source->start_of_blob=TRUE;
280}
281
cristye8dd1302009-11-11 02:45:03 +0000282static MagickBooleanType IsITUFaxImage(const Image *image)
283{
284 const StringInfo
285 *profile;
286
287 const unsigned char
288 *datum;
289
cristyace6aa42009-11-11 03:17:33 +0000290 profile=GetImageProfile(image,"8bim");
cristye8dd1302009-11-11 02:45:03 +0000291 if (profile == (const StringInfo *) NULL)
292 return(MagickFalse);
293 if (GetStringInfoLength(profile) < 5)
294 return(MagickFalse);
295 datum=GetStringInfoDatum(profile);
296 if ((datum[0] == 0x47) && (datum[1] == 0x33) && (datum[2] == 0x46) &&
297 (datum[3] == 0x41) && (datum[4] == 0x58))
298 return(MagickTrue);
299 return(MagickFalse);
300}
301
cristy437c3932011-10-12 18:03:46 +0000302static void JPEGErrorHandler(j_common_ptr jpeg_info)
cristy3ed852e2009-09-05 21:47:34 +0000303{
cristyd28b1dd2011-05-14 20:30:38 +0000304 char
305 message[JMSG_LENGTH_MAX];
306
cristy3ed852e2009-09-05 21:47:34 +0000307 ErrorManager
308 *error_manager;
309
cristyc82a27b2011-10-21 01:07:16 +0000310 ExceptionInfo
311 *exception;
312
cristyd28b1dd2011-05-14 20:30:38 +0000313 Image
314 *image;
315
316 *message='\0';
cristy3ed852e2009-09-05 21:47:34 +0000317 error_manager=(ErrorManager *) jpeg_info->client_data;
cristyd28b1dd2011-05-14 20:30:38 +0000318 image=error_manager->image;
cristyc82a27b2011-10-21 01:07:16 +0000319 exception=error_manager->exception;
cristy86f33542011-05-21 22:58:33 +0000320 (jpeg_info->err->format_message)(jpeg_info,message);
cristyd28b1dd2011-05-14 20:30:38 +0000321 if (image->debug != MagickFalse)
cristy86f33542011-05-21 22:58:33 +0000322 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
323 "[%s] JPEG Trace: \"%s\"",image->filename,message);
cristyd28b1dd2011-05-14 20:30:38 +0000324 if (error_manager->finished != MagickFalse)
cristyc82a27b2011-10-21 01:07:16 +0000325 (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageWarning,
326 (char *) message,"`%s'",image->filename);
cristyd28b1dd2011-05-14 20:30:38 +0000327 else
cristyc82a27b2011-10-21 01:07:16 +0000328 (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
329 (char *) message,"`%s'",image->filename);
cristy3ed852e2009-09-05 21:47:34 +0000330 longjmp(error_manager->error_recovery,1);
331}
332
cristyd28b1dd2011-05-14 20:30:38 +0000333static MagickBooleanType JPEGWarningHandler(j_common_ptr jpeg_info,int level)
334{
cristyf162e012012-03-28 12:54:33 +0000335#define JPEGExcessiveWarnings 1000
336
cristyd28b1dd2011-05-14 20:30:38 +0000337 char
338 message[JMSG_LENGTH_MAX];
339
340 ErrorManager
341 *error_manager;
342
cristy018f07f2011-09-04 21:15:19 +0000343 ExceptionInfo
344 *exception;
345
cristyd28b1dd2011-05-14 20:30:38 +0000346 Image
347 *image;
348
349 *message='\0';
350 error_manager=(ErrorManager *) jpeg_info->client_data;
cristy018f07f2011-09-04 21:15:19 +0000351 exception=error_manager->exception;
cristyd28b1dd2011-05-14 20:30:38 +0000352 image=error_manager->image;
353 if (level < 0)
354 {
355 /*
356 Process warning message.
357 */
358 (jpeg_info->err->format_message)(jpeg_info,message);
cristyf162e012012-03-28 12:54:33 +0000359 if (jpeg_info->err->num_warnings++ > JPEGExcessiveWarnings)
360 JPEGErrorHandler(jpeg_info);
cristy8043fb32012-06-28 16:14:12 +0000361 ThrowBinaryException(CorruptImageWarning,(char *) message,
362 image->filename);
cristyd28b1dd2011-05-14 20:30:38 +0000363 }
364 else
365 if ((image->debug != MagickFalse) &&
366 (level >= jpeg_info->err->trace_level))
367 {
368 /*
369 Process trace message.
370 */
371 (jpeg_info->err->format_message)(jpeg_info,message);
372 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
373 "[%s] JPEG Trace: \"%s\"",image->filename,message);
374 }
375 return(MagickTrue);
376}
377
cristy3ed852e2009-09-05 21:47:34 +0000378static boolean ReadComment(j_decompress_ptr jpeg_info)
379{
cristy3ed852e2009-09-05 21:47:34 +0000380 ErrorManager
381 *error_manager;
382
cristy018f07f2011-09-04 21:15:19 +0000383 ExceptionInfo
384 *exception;
385
cristy3ed852e2009-09-05 21:47:34 +0000386 Image
387 *image;
388
cristyf551aee2012-09-27 21:45:23 +0000389 register unsigned char
cristy3ed852e2009-09-05 21:47:34 +0000390 *p;
391
cristybb503372010-05-27 20:51:26 +0000392 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000393 i;
394
395 size_t
396 length;
397
cristyf551aee2012-09-27 21:45:23 +0000398 StringInfo
399 *comment;
400
cristy3ed852e2009-09-05 21:47:34 +0000401 /*
402 Determine length of comment.
403 */
404 error_manager=(ErrorManager *) jpeg_info->client_data;
cristy018f07f2011-09-04 21:15:19 +0000405 exception=error_manager->exception;
cristy3ed852e2009-09-05 21:47:34 +0000406 image=error_manager->image;
cristybb503372010-05-27 20:51:26 +0000407 length=(size_t) ((size_t) GetCharacter(jpeg_info) << 8);
cristy3ed852e2009-09-05 21:47:34 +0000408 length+=GetCharacter(jpeg_info);
409 length-=2;
410 if (length <= 0)
411 return(MagickTrue);
cristyf551aee2012-09-27 21:45:23 +0000412 comment=BlobToStringInfo((const void *) NULL,length);
413 if (comment == (StringInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000414 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
415 image->filename);
416 /*
417 Read comment.
418 */
cristyf551aee2012-09-27 21:45:23 +0000419 error_manager->profile=comment;
420 p=GetStringInfoDatum(comment);
421 for (i=0; i < (ssize_t) GetStringInfoLength(comment); i++)
422 *p++=(unsigned char) GetCharacter(jpeg_info);
cristy3ed852e2009-09-05 21:47:34 +0000423 *p='\0';
cristyf551aee2012-09-27 21:45:23 +0000424 error_manager->profile=NULL;
425 p=GetStringInfoDatum(comment);
426 (void) SetImageProperty(image,"comment",(const char *) p,exception);
427 comment=DestroyStringInfo(comment);
cristy3ed852e2009-09-05 21:47:34 +0000428 return(MagickTrue);
429}
430
431static boolean ReadICCProfile(j_decompress_ptr jpeg_info)
432{
433 char
434 magick[12];
435
436 ErrorManager
437 *error_manager;
438
cristy018f07f2011-09-04 21:15:19 +0000439 ExceptionInfo
440 *exception;
441
cristy3ed852e2009-09-05 21:47:34 +0000442 Image
443 *image;
444
445 MagickBooleanType
446 status;
447
cristybb503372010-05-27 20:51:26 +0000448 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000449 i;
450
451 register unsigned char
452 *p;
453
454 size_t
455 length;
456
457 StringInfo
458 *icc_profile,
459 *profile;
460
461 /*
462 Read color profile.
463 */
cristybb503372010-05-27 20:51:26 +0000464 length=(size_t) ((size_t) GetCharacter(jpeg_info) << 8);
cristy3ed852e2009-09-05 21:47:34 +0000465 length+=(size_t) GetCharacter(jpeg_info);
466 length-=2;
467 if (length <= 14)
468 {
469 while (length-- > 0)
470 (void) GetCharacter(jpeg_info);
471 return(MagickTrue);
472 }
473 for (i=0; i < 12; i++)
474 magick[i]=(char) GetCharacter(jpeg_info);
475 if (LocaleCompare(magick,ICC_PROFILE) != 0)
476 {
477 /*
478 Not a ICC profile, return.
479 */
cristybb503372010-05-27 20:51:26 +0000480 for (i=0; i < (ssize_t) (length-12); i++)
cristy3ed852e2009-09-05 21:47:34 +0000481 (void) GetCharacter(jpeg_info);
482 return(MagickTrue);
483 }
484 (void) GetCharacter(jpeg_info); /* id */
485 (void) GetCharacter(jpeg_info); /* markers */
486 length-=14;
487 error_manager=(ErrorManager *) jpeg_info->client_data;
cristy018f07f2011-09-04 21:15:19 +0000488 exception=error_manager->exception;
cristy3ed852e2009-09-05 21:47:34 +0000489 image=error_manager->image;
cristy8723e4b2011-09-01 13:11:19 +0000490 profile=BlobToStringInfo((const void *) NULL,length);
cristy3ed852e2009-09-05 21:47:34 +0000491 if (profile == (StringInfo *) NULL)
492 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
493 image->filename);
cristyf551aee2012-09-27 21:45:23 +0000494 error_manager->profile=profile;
cristy3ed852e2009-09-05 21:47:34 +0000495 p=GetStringInfoDatum(profile);
cristybb503372010-05-27 20:51:26 +0000496 for (i=(ssize_t) GetStringInfoLength(profile)-1; i >= 0; i--)
cristy3ed852e2009-09-05 21:47:34 +0000497 *p++=(unsigned char) GetCharacter(jpeg_info);
cristyf551aee2012-09-27 21:45:23 +0000498 error_manager->profile=NULL;
cristy3ed852e2009-09-05 21:47:34 +0000499 icc_profile=(StringInfo *) GetImageProfile(image,"icc");
500 if (icc_profile != (StringInfo *) NULL)
501 {
502 ConcatenateStringInfo(icc_profile,profile);
503 profile=DestroyStringInfo(profile);
504 }
505 else
506 {
cristyd15e6592011-10-15 00:13:06 +0000507 status=SetImageProfile(image,"icc",profile,exception);
cristy3ed852e2009-09-05 21:47:34 +0000508 profile=DestroyStringInfo(profile);
509 if (status == MagickFalse)
510 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
511 image->filename);
512 }
513 if (image->debug != MagickFalse)
514 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000515 "Profile: ICC, %.20g bytes",(double) length);
cristy3ed852e2009-09-05 21:47:34 +0000516 return(MagickTrue);
517}
518
519static boolean ReadIPTCProfile(j_decompress_ptr jpeg_info)
520{
521 char
522 magick[MaxTextExtent];
523
524 ErrorManager
525 *error_manager;
526
cristy018f07f2011-09-04 21:15:19 +0000527 ExceptionInfo
528 *exception;
529
cristy3ed852e2009-09-05 21:47:34 +0000530 Image
531 *image;
532
533 MagickBooleanType
534 status;
535
cristybb503372010-05-27 20:51:26 +0000536 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000537 i;
538
539 register unsigned char
540 *p;
541
542 size_t
543 length;
544
545 StringInfo
546 *iptc_profile,
547 *profile;
548
549 /*
550 Determine length of binary data stored here.
551 */
cristybb503372010-05-27 20:51:26 +0000552 length=(size_t) ((size_t) GetCharacter(jpeg_info) << 8);
cristy3ed852e2009-09-05 21:47:34 +0000553 length+=(size_t) GetCharacter(jpeg_info);
554 length-=2;
555 if (length <= 14)
556 {
557 while (length-- > 0)
558 (void) GetCharacter(jpeg_info);
559 return(MagickTrue);
560 }
561 /*
562 Validate that this was written as a Photoshop resource format slug.
563 */
564 for (i=0; i < 10; i++)
565 magick[i]=(char) GetCharacter(jpeg_info);
566 magick[10]='\0';
567 if (length <= 10)
568 return(MagickTrue);
569 length-=10;
570 if (LocaleCompare(magick,"Photoshop ") != 0)
571 {
572 /*
573 Not a IPTC profile, return.
574 */
cristybb503372010-05-27 20:51:26 +0000575 for (i=0; i < (ssize_t) length; i++)
cristy3ed852e2009-09-05 21:47:34 +0000576 (void) GetCharacter(jpeg_info);
577 return(MagickTrue);
578 }
579 /*
580 Remove the version number.
581 */
582 for (i=0; i < 4; i++)
583 (void) GetCharacter(jpeg_info);
584 if (length <= 4)
585 return(MagickTrue);
586 length-=4;
587 if (length == 0)
588 return(MagickTrue);
589 error_manager=(ErrorManager *) jpeg_info->client_data;
cristy018f07f2011-09-04 21:15:19 +0000590 exception=error_manager->exception;
cristy3ed852e2009-09-05 21:47:34 +0000591 image=error_manager->image;
cristy8723e4b2011-09-01 13:11:19 +0000592 profile=BlobToStringInfo((const void *) NULL,length);
cristy3ed852e2009-09-05 21:47:34 +0000593 if (profile == (StringInfo *) NULL)
594 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
595 image->filename);
cristyf551aee2012-09-27 21:45:23 +0000596 error_manager->profile=profile;
cristy3ed852e2009-09-05 21:47:34 +0000597 p=GetStringInfoDatum(profile);
cristy109e5572010-09-16 18:38:17 +0000598 for (i=0; i < (ssize_t) GetStringInfoLength(profile); i++)
cristy3ed852e2009-09-05 21:47:34 +0000599 *p++=(unsigned char) GetCharacter(jpeg_info);
cristyf551aee2012-09-27 21:45:23 +0000600 error_manager->profile=NULL;
cristy3ed852e2009-09-05 21:47:34 +0000601 iptc_profile=(StringInfo *) GetImageProfile(image,"8bim");
602 if (iptc_profile != (StringInfo *) NULL)
603 {
604 ConcatenateStringInfo(iptc_profile,profile);
605 profile=DestroyStringInfo(profile);
606 }
607 else
608 {
cristyd15e6592011-10-15 00:13:06 +0000609 status=SetImageProfile(image,"8bim",profile,exception);
cristy3ed852e2009-09-05 21:47:34 +0000610 profile=DestroyStringInfo(profile);
611 if (status == MagickFalse)
612 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
613 image->filename);
614 }
615 if (image->debug != MagickFalse)
616 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000617 "Profile: iptc, %.20g bytes",(double) length);
cristy3ed852e2009-09-05 21:47:34 +0000618 return(MagickTrue);
619}
620
621static boolean ReadProfile(j_decompress_ptr jpeg_info)
622{
623 char
624 name[MaxTextExtent];
625
cristye23ec9d2011-08-16 18:15:40 +0000626 const StringInfo
627 *previous_profile;
628
cristy3ed852e2009-09-05 21:47:34 +0000629 ErrorManager
630 *error_manager;
631
cristy018f07f2011-09-04 21:15:19 +0000632 ExceptionInfo
633 *exception;
634
cristy3ed852e2009-09-05 21:47:34 +0000635 Image
636 *image;
637
638 int
639 marker;
640
641 MagickBooleanType
642 status;
643
cristybb503372010-05-27 20:51:26 +0000644 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000645 i;
646
647 register unsigned char
648 *p;
649
650 size_t
651 length;
652
653 StringInfo
654 *profile;
655
656 /*
657 Read generic profile.
658 */
cristybb503372010-05-27 20:51:26 +0000659 length=(size_t) ((size_t) GetCharacter(jpeg_info) << 8);
cristy3ed852e2009-09-05 21:47:34 +0000660 length+=(size_t) GetCharacter(jpeg_info);
661 if (length <= 2)
662 return(MagickTrue);
663 length-=2;
664 marker=jpeg_info->unread_marker-JPEG_APP0;
cristyb51dff52011-05-19 16:55:47 +0000665 (void) FormatLocaleString(name,MaxTextExtent,"APP%d",marker);
cristy3ed852e2009-09-05 21:47:34 +0000666 error_manager=(ErrorManager *) jpeg_info->client_data;
cristy018f07f2011-09-04 21:15:19 +0000667 exception=error_manager->exception;
cristy3ed852e2009-09-05 21:47:34 +0000668 image=error_manager->image;
cristy8723e4b2011-09-01 13:11:19 +0000669 profile=BlobToStringInfo((const void *) NULL,length);
cristy3ed852e2009-09-05 21:47:34 +0000670 if (profile == (StringInfo *) NULL)
671 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
672 image->filename);
cristyf551aee2012-09-27 21:45:23 +0000673 error_manager->profile=profile;
cristy3ed852e2009-09-05 21:47:34 +0000674 p=GetStringInfoDatum(profile);
cristy08f255a2011-08-17 01:23:06 +0000675 for (i=0; i < (ssize_t) GetStringInfoLength(profile); i++)
cristy3ed852e2009-09-05 21:47:34 +0000676 *p++=(unsigned char) GetCharacter(jpeg_info);
cristyf551aee2012-09-27 21:45:23 +0000677 error_manager->profile=NULL;
cristy3ed852e2009-09-05 21:47:34 +0000678 if (marker == 1)
679 {
680 p=GetStringInfoDatum(profile);
681 if ((length > 4) && (LocaleNCompare((char *) p,"exif",4) == 0))
682 (void) CopyMagickString(name,"exif",MaxTextExtent);
683 if ((length > 5) && (LocaleNCompare((char *) p,"http:",5) == 0))
684 {
cristybb503372010-05-27 20:51:26 +0000685 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000686 j;
687
688 /*
689 Extract namespace from XMP profile.
690 */
691 p=GetStringInfoDatum(profile);
cristybb503372010-05-27 20:51:26 +0000692 for (j=0; j < (ssize_t) GetStringInfoLength(profile); j++)
cristy3ed852e2009-09-05 21:47:34 +0000693 {
694 if (*p == '\0')
695 break;
696 p++;
697 }
cristybb503372010-05-27 20:51:26 +0000698 if (j < (ssize_t) GetStringInfoLength(profile))
cristy3ed852e2009-09-05 21:47:34 +0000699 (void) DestroyStringInfo(SplitStringInfo(profile,(size_t) (j+1)));
700 (void) CopyMagickString(name,"xmp",MaxTextExtent);
701 }
702 }
cristye23ec9d2011-08-16 18:15:40 +0000703 previous_profile=GetImageProfile(image,name);
704 if (previous_profile != (const StringInfo *) NULL)
cristy08f255a2011-08-17 01:23:06 +0000705 {
cristy1b58f252012-03-01 01:41:41 +0000706 size_t
cristy05c0c9a2011-09-05 23:16:13 +0000707 length;
708
709 length=GetStringInfoLength(profile);
cristy08f255a2011-08-17 01:23:06 +0000710 SetStringInfoLength(profile,GetStringInfoLength(profile)+
711 GetStringInfoLength(previous_profile));
cristy83ab3d82011-09-06 12:05:51 +0000712 (void) memmove(GetStringInfoDatum(profile)+
713 GetStringInfoLength(previous_profile),GetStringInfoDatum(profile),
714 length);
cristy08f255a2011-08-17 01:23:06 +0000715 (void) memcpy(GetStringInfoDatum(profile),
716 GetStringInfoDatum(previous_profile),
717 GetStringInfoLength(previous_profile));
718 }
cristyd15e6592011-10-15 00:13:06 +0000719 status=SetImageProfile(image,name,profile,exception);
cristy3ed852e2009-09-05 21:47:34 +0000720 profile=DestroyStringInfo(profile);
721 if (status == MagickFalse)
722 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
723 image->filename);
724 if (image->debug != MagickFalse)
725 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000726 "Profile: %s, %.20g bytes",name,(double) length);
cristy3ed852e2009-09-05 21:47:34 +0000727 return(MagickTrue);
728}
729
cristyf2faecf2010-05-28 19:19:36 +0000730static void SkipInputData(j_decompress_ptr cinfo,long number_bytes)
cristy3ed852e2009-09-05 21:47:34 +0000731{
732 SourceManager
733 *source;
734
735 if (number_bytes <= 0)
736 return;
737 source=(SourceManager *) cinfo->src;
cristy4cb162a2010-05-30 03:04:47 +0000738 while (number_bytes > (long) source->manager.bytes_in_buffer)
cristy3ed852e2009-09-05 21:47:34 +0000739 {
cristyf2faecf2010-05-28 19:19:36 +0000740 number_bytes-=(long) source->manager.bytes_in_buffer;
cristy3ed852e2009-09-05 21:47:34 +0000741 (void) FillInputBuffer(cinfo);
742 }
cristy4cb162a2010-05-30 03:04:47 +0000743 source->manager.next_input_byte+=number_bytes;
744 source->manager.bytes_in_buffer-=number_bytes;
cristy3ed852e2009-09-05 21:47:34 +0000745}
746
747static void TerminateSource(j_decompress_ptr cinfo)
748{
cristydf0d90e2011-12-12 01:03:55 +0000749 (void) cinfo;
cristy3ed852e2009-09-05 21:47:34 +0000750}
751
752static void JPEGSourceManager(j_decompress_ptr cinfo,Image *image)
753{
754 SourceManager
755 *source;
756
757 cinfo->src=(struct jpeg_source_mgr *) (*cinfo->mem->alloc_small)
758 ((j_common_ptr) cinfo,JPOOL_IMAGE,sizeof(SourceManager));
759 source=(SourceManager *) cinfo->src;
760 source->buffer=(JOCTET *) (*cinfo->mem->alloc_small)
761 ((j_common_ptr) cinfo,JPOOL_IMAGE,MaxBufferExtent*sizeof(JOCTET));
762 source=(SourceManager *) cinfo->src;
763 source->manager.init_source=InitializeSource;
764 source->manager.fill_input_buffer=FillInputBuffer;
765 source->manager.skip_input_data=SkipInputData;
766 source->manager.resync_to_restart=jpeg_resync_to_restart;
767 source->manager.term_source=TerminateSource;
768 source->manager.bytes_in_buffer=0;
769 source->manager.next_input_byte=NULL;
770 source->image=image;
771}
772
773static void JPEGSetImageQuality(struct jpeg_decompress_struct *jpeg_info,
dirk881355a2013-08-19 19:56:50 +0000774 Image *image, ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000775{
776 image->quality=UndefinedCompressionQuality;
777#if defined(D_PROGRESSIVE_SUPPORTED)
778 if (image->compression == LosslessJPEGCompression)
779 {
dirk881355a2013-08-19 19:56:50 +0000780 SetImageProperty(image,"jpeg:quality","100",exception);
cristy3ed852e2009-09-05 21:47:34 +0000781 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
782 "Quality: 100 (lossless)");
783 }
784 else
785#endif
786 {
dirk881355a2013-08-19 19:56:50 +0000787 char
788 quality[4];
789
cristybb503372010-05-27 20:51:26 +0000790 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000791 j,
792 qvalue,
793 sum;
794
cristybb503372010-05-27 20:51:26 +0000795 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000796 i;
797
798 /*
799 Determine the JPEG compression quality from the quantization tables.
800 */
801 sum=0;
802 for (i=0; i < NUM_QUANT_TBLS; i++)
803 {
804 if (jpeg_info->quant_tbl_ptrs[i] != NULL)
805 for (j=0; j < DCTSIZE2; j++)
806 sum+=jpeg_info->quant_tbl_ptrs[i]->quantval[j];
dirk881355a2013-08-19 19:56:50 +0000807 }
808 if ((jpeg_info->quant_tbl_ptrs[0] != NULL) &&
809 (jpeg_info->quant_tbl_ptrs[1] != NULL))
810 {
811 ssize_t
812 hash[101] =
813 {
814 1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645,
815 632, 623, 613, 607, 600, 594, 589, 585, 581, 571,
816 555, 542, 529, 514, 494, 474, 457, 439, 424, 410,
817 397, 386, 373, 364, 351, 341, 334, 324, 317, 309,
818 299, 294, 287, 279, 274, 267, 262, 257, 251, 247,
819 243, 237, 232, 227, 222, 217, 213, 207, 202, 198,
820 192, 188, 183, 177, 173, 168, 163, 157, 153, 148,
821 143, 139, 132, 128, 125, 119, 115, 108, 104, 99,
822 94, 90, 84, 79, 74, 70, 64, 59, 55, 49,
823 45, 40, 34, 30, 25, 20, 15, 11, 6, 4,
824 0
825 },
826 sums[101] =
827 {
828 32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104,
829 27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946,
830 23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998,
831 16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702,
832 12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208,
833 9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458,
834 8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788,
835 6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128,
836 4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509,
837 3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
838 1666, 1483, 1297, 1109, 927, 735, 554, 375, 201,
839 128, 0
840 };
841
842 qvalue=(ssize_t) (jpeg_info->quant_tbl_ptrs[0]->quantval[2]+
843 jpeg_info->quant_tbl_ptrs[0]->quantval[53]+
844 jpeg_info->quant_tbl_ptrs[1]->quantval[0]+
845 jpeg_info->quant_tbl_ptrs[1]->quantval[DCTSIZE2-1]);
846 for (i=0; i < 100; i++)
847 {
848 if ((qvalue < hash[i]) && (sum < sums[i]))
849 continue;
850 if (((qvalue <= hash[i]) && (sum <= sums[i])) || (i >= 50))
851 {
cristya2a5a302013-08-20 11:41:15 +0000852 FormatLocaleString(quality,4,"%.20g",(double) i+1);
dirk881355a2013-08-19 19:56:50 +0000853 SetImageProperty(image,"jpeg:quality",quality,exception);
854 }
855 if (image->debug != MagickFalse)
856 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
857 "Quality: %.20g (%s)",(double) i+1,(qvalue <= hash[i]) &&
858 (sum <= sums[i]) ? "exact" : "approximate");
859 break;
860 }
861 }
862 else
863 if (jpeg_info->quant_tbl_ptrs[0] != NULL)
864 {
865 ssize_t
866 hash[101] =
867 {
868 510, 505, 422, 380, 355, 338, 326, 318, 311, 305,
869 300, 297, 293, 291, 288, 286, 284, 283, 281, 280,
870 279, 278, 277, 273, 262, 251, 243, 233, 225, 218,
871 211, 205, 198, 193, 186, 181, 177, 172, 168, 164,
872 158, 156, 152, 148, 145, 142, 139, 136, 133, 131,
873 129, 126, 123, 120, 118, 115, 113, 110, 107, 105,
874 102, 100, 97, 94, 92, 89, 87, 83, 81, 79,
875 76, 74, 70, 68, 66, 63, 61, 57, 55, 52,
876 50, 48, 44, 42, 39, 37, 34, 31, 29, 26,
877 24, 21, 18, 16, 13, 11, 8, 6, 3, 2,
cristy3ed852e2009-09-05 21:47:34 +0000878 0
dirk881355a2013-08-19 19:56:50 +0000879 },
880 sums[101] =
881 {
882 16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859,
883 12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679,
884 9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823,
885 6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086,
886 4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092,
887 3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396,
888 3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727,
889 2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068,
890 1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398,
891 1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736,
892 667, 592, 518, 441, 369, 292, 221, 151, 86,
893 64, 0
894 };
cristy3ed852e2009-09-05 21:47:34 +0000895
dirk881355a2013-08-19 19:56:50 +0000896 qvalue=(ssize_t) (jpeg_info->quant_tbl_ptrs[0]->quantval[2]+
897 jpeg_info->quant_tbl_ptrs[0]->quantval[53]);
898 for (i=0; i < 100; i++)
899 {
900 if ((qvalue < hash[i]) && (sum < sums[i]))
901 continue;
902 if (((qvalue <= hash[i]) && (sum <= sums[i])) || (i >= 50))
903 {
cristya2a5a302013-08-20 11:41:15 +0000904 FormatLocaleString(quality,4,"%.20g",(double) i+1);
dirk881355a2013-08-19 19:56:50 +0000905 SetImageProperty(image,"jpeg:quality",quality,exception);
906 }
907 if (image->debug != MagickFalse)
908 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
909 "Quality: %.20g (%s)",(double) i+1,(qvalue <= hash[i]) &&
910 (sum <= sums[i]) ? "exact" : "approximate");
911 break;
912 }
913 }
cristy3ed852e2009-09-05 21:47:34 +0000914 }
915}
916
cristyd15e6592011-10-15 00:13:06 +0000917static void JPEGSetImageSamplingFactor(struct jpeg_decompress_struct *jpeg_info, Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000918{
919 char
920 sampling_factor[MaxTextExtent];
921
922 switch (jpeg_info->out_color_space)
923 {
924 case JCS_CMYK:
925 {
926 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: CMYK");
cristyb51dff52011-05-19 16:55:47 +0000927 (void) FormatLocaleString(sampling_factor,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +0000928 "%dx%d,%dx%d,%dx%d,%dx%d",jpeg_info->comp_info[0].h_samp_factor,
929 jpeg_info->comp_info[0].v_samp_factor,
930 jpeg_info->comp_info[1].h_samp_factor,
931 jpeg_info->comp_info[1].v_samp_factor,
932 jpeg_info->comp_info[2].h_samp_factor,
933 jpeg_info->comp_info[2].v_samp_factor,
934 jpeg_info->comp_info[3].h_samp_factor,
935 jpeg_info->comp_info[3].v_samp_factor);
cristy787d4352010-03-06 13:55:58 +0000936 break;
cristy3ed852e2009-09-05 21:47:34 +0000937 }
938 case JCS_GRAYSCALE:
939 {
940 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
941 "Colorspace: GRAYSCALE");
cristyb51dff52011-05-19 16:55:47 +0000942 (void) FormatLocaleString(sampling_factor,MaxTextExtent,"%dx%d",
cristy3ed852e2009-09-05 21:47:34 +0000943 jpeg_info->comp_info[0].h_samp_factor,
944 jpeg_info->comp_info[0].v_samp_factor);
945 break;
946 }
947 case JCS_RGB:
948 {
949 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: RGB");
cristyb51dff52011-05-19 16:55:47 +0000950 (void) FormatLocaleString(sampling_factor,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +0000951 "%dx%d,%dx%d,%dx%d",jpeg_info->comp_info[0].h_samp_factor,
952 jpeg_info->comp_info[0].v_samp_factor,
953 jpeg_info->comp_info[1].h_samp_factor,
954 jpeg_info->comp_info[1].v_samp_factor,
955 jpeg_info->comp_info[2].h_samp_factor,
956 jpeg_info->comp_info[2].v_samp_factor);
957 break;
958 }
959 default:
960 {
961 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: %d",
962 jpeg_info->out_color_space);
cristyb51dff52011-05-19 16:55:47 +0000963 (void) FormatLocaleString(sampling_factor,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +0000964 "%dx%d,%dx%d,%dx%d,%dx%d",jpeg_info->comp_info[0].h_samp_factor,
965 jpeg_info->comp_info[0].v_samp_factor,
966 jpeg_info->comp_info[1].h_samp_factor,
967 jpeg_info->comp_info[1].v_samp_factor,
968 jpeg_info->comp_info[2].h_samp_factor,
969 jpeg_info->comp_info[2].v_samp_factor,
970 jpeg_info->comp_info[3].h_samp_factor,
971 jpeg_info->comp_info[3].v_samp_factor);
972 break;
973 }
974 }
cristyd15e6592011-10-15 00:13:06 +0000975 (void) SetImageProperty(image,"jpeg:sampling-factor",sampling_factor,
976 exception);
cristye90d7402010-03-14 18:21:29 +0000977 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Sampling Factors: %s",
978 sampling_factor);
cristy3ed852e2009-09-05 21:47:34 +0000979}
980
981static Image *ReadJPEGImage(const ImageInfo *image_info,
982 ExceptionInfo *exception)
983{
984 char
985 value[MaxTextExtent];
986
cristycaec74e2009-09-14 02:20:04 +0000987 const char
cristy11151212009-09-14 13:10:15 +0000988 *option;
cristycaec74e2009-09-14 02:20:04 +0000989
cristy3ed852e2009-09-05 21:47:34 +0000990 ErrorManager
991 error_manager;
992
cristy3ed852e2009-09-05 21:47:34 +0000993 Image
994 *image;
995
cristy3ed852e2009-09-05 21:47:34 +0000996 JSAMPLE
cristy75fc68f2012-10-08 16:26:00 +0000997 *volatile jpeg_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000998
999 JSAMPROW
1000 scanline[1];
1001
1002 MagickBooleanType
1003 debug,
1004 status;
1005
1006 MagickSizeType
1007 number_pixels;
1008
cristy22646e22013-06-23 16:34:03 +00001009 MemoryInfo
1010 *memory_info;
1011
cristy4c08aed2011-07-01 19:47:50 +00001012 Quantum
1013 index;
1014
cristybb503372010-05-27 20:51:26 +00001015 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001016 i;
1017
1018 struct jpeg_decompress_struct
1019 jpeg_info;
1020
1021 struct jpeg_error_mgr
1022 jpeg_error;
1023
1024 register JSAMPLE
1025 *p;
1026
cristybb503372010-05-27 20:51:26 +00001027 size_t
cristy3ed852e2009-09-05 21:47:34 +00001028 precision,
1029 units;
1030
cristy524222d2011-04-25 00:37:06 +00001031 ssize_t
1032 y;
1033
cristy3ed852e2009-09-05 21:47:34 +00001034 /*
1035 Open image file.
1036 */
1037 assert(image_info != (const ImageInfo *) NULL);
1038 assert(image_info->signature == MagickSignature);
1039 if (image_info->debug != MagickFalse)
1040 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
1041 image_info->filename);
1042 assert(exception != (ExceptionInfo *) NULL);
1043 assert(exception->signature == MagickSignature);
1044 debug=IsEventLogging();
cristyda16f162011-02-19 23:52:17 +00001045 (void) debug;
cristy9950d572011-10-01 18:22:35 +00001046 image=AcquireImage(image_info,exception);
cristy3ed852e2009-09-05 21:47:34 +00001047 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
1048 if (status == MagickFalse)
1049 {
1050 image=DestroyImageList(image);
1051 return((Image *) NULL);
1052 }
1053 /*
1054 Initialize JPEG parameters.
1055 */
cristy91044972011-04-22 14:21:16 +00001056 (void) ResetMagickMemory(&error_manager,0,sizeof(error_manager));
cristy3ed852e2009-09-05 21:47:34 +00001057 (void) ResetMagickMemory(&jpeg_info,0,sizeof(jpeg_info));
1058 (void) ResetMagickMemory(&jpeg_error,0,sizeof(jpeg_error));
1059 jpeg_info.err=jpeg_std_error(&jpeg_error);
cristyd28b1dd2011-05-14 20:30:38 +00001060 jpeg_info.err->emit_message=(void (*)(j_common_ptr,int)) JPEGWarningHandler;
cristy3ed852e2009-09-05 21:47:34 +00001061 jpeg_info.err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
cristy72f87f82013-06-23 16:43:05 +00001062 memory_info=(MemoryInfo *) NULL;
cristy018f07f2011-09-04 21:15:19 +00001063 error_manager.exception=exception;
cristy3ed852e2009-09-05 21:47:34 +00001064 error_manager.image=image;
1065 if (setjmp(error_manager.error_recovery) != 0)
1066 {
1067 jpeg_destroy_decompress(&jpeg_info);
cristyf551aee2012-09-27 21:45:23 +00001068 if (error_manager.profile != (StringInfo *) NULL)
1069 error_manager.profile=DestroyStringInfo(error_manager.profile);
cristy3ed852e2009-09-05 21:47:34 +00001070 (void) CloseBlob(image);
1071 number_pixels=(MagickSizeType) image->columns*image->rows;
1072 if (number_pixels != 0)
1073 return(GetFirstImageInList(image));
1074 return(DestroyImage(image));
1075 }
1076 jpeg_info.client_data=(void *) &error_manager;
1077 jpeg_create_decompress(&jpeg_info);
1078 JPEGSourceManager(&jpeg_info,image);
1079 jpeg_set_marker_processor(&jpeg_info,JPEG_COM,ReadComment);
1080 jpeg_set_marker_processor(&jpeg_info,ICC_MARKER,ReadICCProfile);
1081 jpeg_set_marker_processor(&jpeg_info,IPTC_MARKER,ReadIPTCProfile);
1082 for (i=1; i < 16; i++)
1083 if ((i != 2) && (i != 13) && (i != 14))
1084 jpeg_set_marker_processor(&jpeg_info,(int) (JPEG_APP0+i),ReadProfile);
cristy4cb162a2010-05-30 03:04:47 +00001085 i=(ssize_t) jpeg_read_header(&jpeg_info,MagickTrue);
cristy53215c82009-09-19 16:32:36 +00001086 if ((image_info->colorspace == YCbCrColorspace) ||
1087 (image_info->colorspace == Rec601YCbCrColorspace) ||
1088 (image_info->colorspace == Rec709YCbCrColorspace))
cristy3ed852e2009-09-05 21:47:34 +00001089 jpeg_info.out_color_space=JCS_YCbCr;
1090 /*
1091 Set image resolution.
1092 */
1093 units=0;
1094 if ((jpeg_info.saw_JFIF_marker != 0) && (jpeg_info.X_density != 1) &&
1095 (jpeg_info.Y_density != 1))
1096 {
cristy2a11bef2011-10-28 18:33:11 +00001097 image->resolution.x=(double) jpeg_info.X_density;
1098 image->resolution.y=(double) jpeg_info.Y_density;
cristybb503372010-05-27 20:51:26 +00001099 units=(size_t) jpeg_info.density_unit;
cristy3ed852e2009-09-05 21:47:34 +00001100 }
1101 if (units == 1)
1102 image->units=PixelsPerInchResolution;
1103 if (units == 2)
1104 image->units=PixelsPerCentimeterResolution;
1105 number_pixels=(MagickSizeType) image->columns*image->rows;
cristy092ec8d2013-04-26 13:46:22 +00001106 option=GetImageOption(image_info,"jpeg:size");
cristy11151212009-09-14 13:10:15 +00001107 if (option != (const char *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001108 {
1109 double
1110 scale_factor;
1111
cristycaec74e2009-09-14 02:20:04 +00001112 GeometryInfo
1113 geometry_info;
1114
cristy0adb4f92009-11-28 18:08:51 +00001115 MagickStatusType
cristycaec74e2009-09-14 02:20:04 +00001116 flags;
1117
cristy3ed852e2009-09-05 21:47:34 +00001118 /*
cristycaec74e2009-09-14 02:20:04 +00001119 Scale the image.
cristy3ed852e2009-09-05 21:47:34 +00001120 */
cristy11151212009-09-14 13:10:15 +00001121 flags=ParseGeometry(option,&geometry_info);
cristycaec74e2009-09-14 02:20:04 +00001122 if ((flags & SigmaValue) == 0)
1123 geometry_info.sigma=geometry_info.rho;
cristy3ed852e2009-09-05 21:47:34 +00001124 jpeg_calc_output_dimensions(&jpeg_info);
1125 image->magick_columns=jpeg_info.output_width;
1126 image->magick_rows=jpeg_info.output_height;
cristycaec74e2009-09-14 02:20:04 +00001127 scale_factor=1.0;
1128 if (geometry_info.rho != 0.0)
1129 scale_factor=jpeg_info.output_width/geometry_info.rho;
1130 if ((geometry_info.sigma != 0.0) &&
1131 (scale_factor > (jpeg_info.output_height/geometry_info.sigma)))
1132 scale_factor=jpeg_info.output_height/geometry_info.sigma;
cristy3ed852e2009-09-05 21:47:34 +00001133 jpeg_info.scale_num=1U;
1134 jpeg_info.scale_denom=(unsigned int) scale_factor;
1135 jpeg_calc_output_dimensions(&jpeg_info);
1136 if (image->debug != MagickFalse)
cristye8c25f92010-06-03 00:53:06 +00001137 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1138 "Scale factor: %.20g",(double) scale_factor);
cristy3ed852e2009-09-05 21:47:34 +00001139 }
cristybb503372010-05-27 20:51:26 +00001140 precision=(size_t) jpeg_info.data_precision;
cristy3ed852e2009-09-05 21:47:34 +00001141#if (JPEG_LIB_VERSION >= 61) && defined(D_PROGRESSIVE_SUPPORTED)
1142#if defined(D_LOSSLESS_SUPPORTED)
1143 image->interlace=jpeg_info.process == JPROC_PROGRESSIVE ?
1144 JPEGInterlace : NoInterlace;
1145 image->compression=jpeg_info.process == JPROC_LOSSLESS ?
1146 LosslessJPEGCompression : JPEGCompression;
1147 if (jpeg_info.data_precision > 8)
1148 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1149 "12-bit JPEG not supported. Reducing pixel data to 8 bits","`%s'",
1150 image->filename);
1151 if (jpeg_info.data_precision == 16)
1152 jpeg_info.data_precision=12;
1153#else
1154 image->interlace=jpeg_info.progressive_mode != 0 ? JPEGInterlace :
1155 NoInterlace;
1156 image->compression=JPEGCompression;
1157#endif
1158#else
1159 image->compression=JPEGCompression;
1160 image->interlace=JPEGInterlace;
1161#endif
cristy092ec8d2013-04-26 13:46:22 +00001162 option=GetImageOption(image_info,"jpeg:colors");
anthony41906552011-10-07 12:15:27 +00001163 if (option != (const char *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001164 {
cristyb4bb39c2012-02-21 18:45:54 +00001165 /*
1166 Let the JPEG library quantize the image.
1167 */
cristy3ed852e2009-09-05 21:47:34 +00001168 jpeg_info.quantize_colors=MagickTrue;
anthony41906552011-10-07 12:15:27 +00001169 jpeg_info.desired_number_of_colors=(int) StringToUnsignedLong(option);
cristy3ed852e2009-09-05 21:47:34 +00001170 }
cristy092ec8d2013-04-26 13:46:22 +00001171 option=GetImageOption(image_info,"jpeg:block-smoothing");
cristy0d737182013-04-04 00:22:57 +00001172 if (option != (const char *) NULL)
1173 jpeg_info.do_block_smoothing=IsStringTrue(option);
cristyb4bb39c2012-02-21 18:45:54 +00001174 jpeg_info.dct_method=JDCT_FLOAT;
cristy092ec8d2013-04-26 13:46:22 +00001175 option=GetImageOption(image_info,"jpeg:dct-method");
cristy787d4352010-03-06 13:55:58 +00001176 if (option != (const char *) NULL)
1177 switch (*option)
1178 {
1179 case 'D':
1180 case 'd':
1181 {
1182 if (LocaleCompare(option,"default") == 0)
1183 jpeg_info.dct_method=JDCT_DEFAULT;
1184 break;
1185 }
1186 case 'F':
1187 case 'f':
1188 {
1189 if (LocaleCompare(option,"fastest") == 0)
1190 jpeg_info.dct_method=JDCT_FASTEST;
1191 if (LocaleCompare(option,"float") == 0)
1192 jpeg_info.dct_method=JDCT_FLOAT;
1193 break;
1194 }
1195 case 'I':
1196 case 'i':
1197 {
1198 if (LocaleCompare(option,"ifast") == 0)
1199 jpeg_info.dct_method=JDCT_IFAST;
1200 if (LocaleCompare(option,"islow") == 0)
1201 jpeg_info.dct_method=JDCT_ISLOW;
1202 break;
1203 }
1204 }
cristy092ec8d2013-04-26 13:46:22 +00001205 option=GetImageOption(image_info,"jpeg:fancy-upsampling");
cristy0d737182013-04-04 00:22:57 +00001206 if (option != (const char *) NULL)
1207 jpeg_info.do_fancy_upsampling=IsStringTrue(option);
cristy3ed852e2009-09-05 21:47:34 +00001208 (void) jpeg_start_decompress(&jpeg_info);
1209 image->columns=jpeg_info.output_width;
1210 image->rows=jpeg_info.output_height;
cristybb503372010-05-27 20:51:26 +00001211 image->depth=(size_t) jpeg_info.data_precision;
cristy93ff2cc2012-05-13 21:03:53 +00001212 switch (jpeg_info.out_color_space)
1213 {
1214 case JCS_RGB:
1215 default:
1216 {
cristyf551aee2012-09-27 21:45:23 +00001217 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy93ff2cc2012-05-13 21:03:53 +00001218 break;
1219 }
1220 case JCS_GRAYSCALE:
1221 {
cristy1a45be72013-04-07 00:16:24 +00001222 (void) SetImageColorspace(image,GRAYColorspace,exception);
cristy93ff2cc2012-05-13 21:03:53 +00001223 break;
1224 }
1225 case JCS_YCbCr:
1226 {
cristyf551aee2012-09-27 21:45:23 +00001227 (void) SetImageColorspace(image,YCbCrColorspace,exception);
cristy93ff2cc2012-05-13 21:03:53 +00001228 break;
1229 }
1230 case JCS_CMYK:
1231 {
cristyf551aee2012-09-27 21:45:23 +00001232 (void) SetImageColorspace(image,CMYKColorspace,exception);
cristy93ff2cc2012-05-13 21:03:53 +00001233 break;
1234 }
1235 }
cristy36fc5502012-05-22 11:32:23 +00001236 if (IsITUFaxImage(image) != MagickFalse)
1237 {
cristyf551aee2012-09-27 21:45:23 +00001238 (void) SetImageColorspace(image,LabColorspace,exception);
cristy36fc5502012-05-22 11:32:23 +00001239 jpeg_info.out_color_space=JCS_YCbCr;
1240 }
cristy092ec8d2013-04-26 13:46:22 +00001241 option=GetImageOption(image_info,"jpeg:colors");
anthony41906552011-10-07 12:15:27 +00001242 if (option != (const char *) NULL)
1243 if (AcquireImageColormap(image,StringToUnsignedLong(option),exception)
1244 == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001245 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
1246 if ((jpeg_info.output_components == 1) &&
1247 (jpeg_info.quantize_colors == MagickFalse))
1248 {
cristybb503372010-05-27 20:51:26 +00001249 size_t
cristy3ed852e2009-09-05 21:47:34 +00001250 colors;
1251
cristybb503372010-05-27 20:51:26 +00001252 colors=(size_t) GetQuantumRange(image->depth)+1;
cristy018f07f2011-09-04 21:15:19 +00001253 if (AcquireImageColormap(image,colors,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001254 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
1255 }
1256 if (image->debug != MagickFalse)
1257 {
1258 if (image->interlace != NoInterlace)
1259 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1260 "Interlace: progressive");
1261 else
1262 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1263 "Interlace: nonprogressive");
1264 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Data precision: %d",
1265 (int) jpeg_info.data_precision);
1266 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Geometry: %dx%d",
1267 (int) jpeg_info.output_width,(int) jpeg_info.output_height);
1268 }
dirk881355a2013-08-19 19:56:50 +00001269 JPEGSetImageQuality(&jpeg_info,image,exception);
cristyd15e6592011-10-15 00:13:06 +00001270 JPEGSetImageSamplingFactor(&jpeg_info,image,exception);
cristyb51dff52011-05-19 16:55:47 +00001271 (void) FormatLocaleString(value,MaxTextExtent,"%.20g",(double)
cristy3ed852e2009-09-05 21:47:34 +00001272 jpeg_info.out_color_space);
cristyd15e6592011-10-15 00:13:06 +00001273 (void) SetImageProperty(image,"jpeg:colorspace",value,exception);
cristy3ed852e2009-09-05 21:47:34 +00001274 if (image_info->ping != MagickFalse)
1275 {
1276 jpeg_destroy_decompress(&jpeg_info);
1277 (void) CloseBlob(image);
1278 return(GetFirstImageInList(image));
1279 }
cristy22646e22013-06-23 16:34:03 +00001280 memory_info=AcquireVirtualMemory((size_t) image->columns,
1281 jpeg_info.output_components*sizeof(*jpeg_pixels));
1282 if (memory_info == (MemoryInfo *) NULL)
cristyee1bdaa2013-07-16 16:45:03 +00001283 {
1284 jpeg_destroy_decompress(&jpeg_info);
1285 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
1286 }
cristy22646e22013-06-23 16:34:03 +00001287 jpeg_pixels=(JSAMPLE *) GetVirtualMemoryBlob(memory_info);
cristy3ed852e2009-09-05 21:47:34 +00001288 /*
1289 Convert JPEG pixels to pixel packets.
1290 */
1291 if (setjmp(error_manager.error_recovery) != 0)
1292 {
cristy22646e22013-06-23 16:34:03 +00001293 if (memory_info != (MemoryInfo *) NULL)
1294 memory_info=RelinquishVirtualMemory(memory_info);
cristy3ed852e2009-09-05 21:47:34 +00001295 jpeg_destroy_decompress(&jpeg_info);
1296 (void) CloseBlob(image);
1297 number_pixels=(MagickSizeType) image->columns*image->rows;
1298 if (number_pixels != 0)
1299 return(GetFirstImageInList(image));
1300 return(DestroyImage(image));
1301 }
1302 if (jpeg_info.quantize_colors != MagickFalse)
1303 {
cristybb503372010-05-27 20:51:26 +00001304 image->colors=(size_t) jpeg_info.actual_number_of_colors;
cristy3ed852e2009-09-05 21:47:34 +00001305 if (jpeg_info.out_color_space == JCS_GRAYSCALE)
cristybb503372010-05-27 20:51:26 +00001306 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001307 {
cristy1b58f252012-03-01 01:41:41 +00001308 image->colormap[i].red=(double) ScaleCharToQuantum(
1309 jpeg_info.colormap[0][i]);
cristy3ed852e2009-09-05 21:47:34 +00001310 image->colormap[i].green=image->colormap[i].red;
1311 image->colormap[i].blue=image->colormap[i].red;
cristy4c08aed2011-07-01 19:47:50 +00001312 image->colormap[i].alpha=OpaqueAlpha;
cristy3ed852e2009-09-05 21:47:34 +00001313 }
1314 else
cristybb503372010-05-27 20:51:26 +00001315 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001316 {
cristy1b58f252012-03-01 01:41:41 +00001317 image->colormap[i].red=(double) ScaleCharToQuantum(
1318 jpeg_info.colormap[0][i]);
1319 image->colormap[i].green=(double) ScaleCharToQuantum(
1320 jpeg_info.colormap[1][i]);
1321 image->colormap[i].blue=(double) ScaleCharToQuantum(
1322 jpeg_info.colormap[2][i]);
cristy4c08aed2011-07-01 19:47:50 +00001323 image->colormap[i].alpha=OpaqueAlpha;
cristy3ed852e2009-09-05 21:47:34 +00001324 }
1325 }
1326 scanline[0]=(JSAMPROW) jpeg_pixels;
cristybb503372010-05-27 20:51:26 +00001327 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001328 {
cristybb503372010-05-27 20:51:26 +00001329 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001330 x;
1331
cristy4c08aed2011-07-01 19:47:50 +00001332 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001333 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001334
1335 if (jpeg_read_scanlines(&jpeg_info,scanline,1) != 1)
1336 {
1337 (void) ThrowMagickException(exception,GetMagickModule(),
1338 CorruptImageWarning,"SkipToSyncByte","`%s'",image->filename);
1339 continue;
1340 }
1341 p=jpeg_pixels;
1342 q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001343 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001344 break;
cristy3ed852e2009-09-05 21:47:34 +00001345 if (jpeg_info.data_precision > 8)
1346 {
1347 if (jpeg_info.output_components == 1)
cristybb503372010-05-27 20:51:26 +00001348 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001349 {
cristybb503372010-05-27 20:51:26 +00001350 size_t
cristy3ed852e2009-09-05 21:47:34 +00001351 pixel;
1352
1353 if (precision != 16)
cristybb503372010-05-27 20:51:26 +00001354 pixel=(size_t) GETJSAMPLE(*p);
cristy3ed852e2009-09-05 21:47:34 +00001355 else
cristybb503372010-05-27 20:51:26 +00001356 pixel=(size_t) ((GETJSAMPLE(*p) ^ 0x80) << 4);
cristyc82a27b2011-10-21 01:07:16 +00001357 index=ConstrainColormapIndex(image,pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00001358 SetPixelIndex(image,index,q);
cristy803640d2011-11-17 02:11:32 +00001359 SetPixelInfoPixel(image,image->colormap+(ssize_t) index,q);
cristy3ed852e2009-09-05 21:47:34 +00001360 p++;
cristyed231572011-07-14 02:18:59 +00001361 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001362 }
1363 else
1364 if (image->colorspace != CMYKColorspace)
cristybb503372010-05-27 20:51:26 +00001365 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001366 {
cristy4c08aed2011-07-01 19:47:50 +00001367 SetPixelRed(image,ScaleShortToQuantum((unsigned char)
1368 (GETJSAMPLE(*p++) << 4)),q);
1369 SetPixelGreen(image,ScaleShortToQuantum((unsigned char)
1370 (GETJSAMPLE(*p++) << 4)),q);
1371 SetPixelBlue(image,ScaleShortToQuantum((unsigned char)
1372 (GETJSAMPLE(*p++) << 4)),q);
1373 SetPixelAlpha(image,OpaqueAlpha,q);
cristyed231572011-07-14 02:18:59 +00001374 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001375 }
1376 else
cristybb503372010-05-27 20:51:26 +00001377 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001378 {
cristy4c08aed2011-07-01 19:47:50 +00001379 SetPixelCyan(image,QuantumRange-ScaleShortToQuantum(
1380 (unsigned char) (GETJSAMPLE(*p++) << 4)),q);
1381 SetPixelMagenta(image,QuantumRange-ScaleShortToQuantum(
1382 (unsigned char) (GETJSAMPLE(*p++) << 4)),q);
1383 SetPixelYellow(image,QuantumRange-ScaleShortToQuantum(
1384 (unsigned char) (GETJSAMPLE(*p++) << 4)),q);
1385 SetPixelBlack(image,QuantumRange-ScaleShortToQuantum(
1386 (unsigned char) (GETJSAMPLE(*p++) << 4)),q);
1387 SetPixelAlpha(image,OpaqueAlpha,q);
cristyed231572011-07-14 02:18:59 +00001388 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001389 }
1390 }
1391 else
1392 if (jpeg_info.output_components == 1)
cristybb503372010-05-27 20:51:26 +00001393 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001394 {
cristyc82a27b2011-10-21 01:07:16 +00001395 index=ConstrainColormapIndex(image,(size_t) GETJSAMPLE(*p),exception);
cristy4c08aed2011-07-01 19:47:50 +00001396 SetPixelIndex(image,index,q);
cristy803640d2011-11-17 02:11:32 +00001397 SetPixelInfoPixel(image,image->colormap+(ssize_t) index,q);
cristy3ed852e2009-09-05 21:47:34 +00001398 p++;
cristyed231572011-07-14 02:18:59 +00001399 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001400 }
1401 else
1402 if (image->colorspace != CMYKColorspace)
cristybb503372010-05-27 20:51:26 +00001403 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001404 {
cristy4c08aed2011-07-01 19:47:50 +00001405 SetPixelRed(image,ScaleCharToQuantum((unsigned char)
1406 GETJSAMPLE(*p++)),q);
1407 SetPixelGreen(image,ScaleCharToQuantum((unsigned char)
1408 GETJSAMPLE(*p++)),q);
1409 SetPixelBlue(image,ScaleCharToQuantum((unsigned char)
1410 GETJSAMPLE(*p++)),q);
1411 SetPixelAlpha(image,OpaqueAlpha,q);
cristyed231572011-07-14 02:18:59 +00001412 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001413 }
1414 else
cristybb503372010-05-27 20:51:26 +00001415 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001416 {
cristy4c08aed2011-07-01 19:47:50 +00001417 SetPixelCyan(image,QuantumRange-ScaleCharToQuantum(
1418 (unsigned char) GETJSAMPLE(*p++)),q);
1419 SetPixelMagenta(image,QuantumRange-ScaleCharToQuantum(
1420 (unsigned char) GETJSAMPLE(*p++)),q);
1421 SetPixelYellow(image,QuantumRange-ScaleCharToQuantum(
1422 (unsigned char) GETJSAMPLE(*p++)),q);
1423 SetPixelBlack(image,QuantumRange-ScaleCharToQuantum(
1424 (unsigned char) GETJSAMPLE(*p++)),q);
1425 SetPixelAlpha(image,OpaqueAlpha,q);
cristyed231572011-07-14 02:18:59 +00001426 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001427 }
1428 if (SyncAuthenticPixels(image,exception) == MagickFalse)
1429 break;
cristy524222d2011-04-25 00:37:06 +00001430 status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
1431 image->rows);
1432 if (status == MagickFalse)
cristyd28b1dd2011-05-14 20:30:38 +00001433 {
1434 jpeg_abort_decompress(&jpeg_info);
1435 break;
1436 }
cristy3ed852e2009-09-05 21:47:34 +00001437 }
cristyd28b1dd2011-05-14 20:30:38 +00001438 if (status != MagickFalse)
1439 {
1440 error_manager.finished=MagickTrue;
1441 if (setjmp(error_manager.error_recovery) == 0)
1442 (void) jpeg_finish_decompress(&jpeg_info);
1443 }
cristy3ed852e2009-09-05 21:47:34 +00001444 /*
1445 Free jpeg resources.
1446 */
cristy3ed852e2009-09-05 21:47:34 +00001447 jpeg_destroy_decompress(&jpeg_info);
cristy22646e22013-06-23 16:34:03 +00001448 memory_info=RelinquishVirtualMemory(memory_info);
cristy3ed852e2009-09-05 21:47:34 +00001449 (void) CloseBlob(image);
1450 return(GetFirstImageInList(image));
1451}
1452#endif
1453
1454/*
1455%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1456% %
1457% %
1458% %
1459% R e g i s t e r J P E G I m a g e %
1460% %
1461% %
1462% %
1463%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1464%
1465% RegisterJPEGImage() adds properties for the JPEG image format to
1466% the list of supported formats. The properties include the image format
1467% tag, a method to read and/or write the format, whether the format
1468% supports the saving of more than one frame to the same file or blob,
1469% whether the format supports native in-memory I/O, and a brief
1470% description of the format.
1471%
1472% The format of the RegisterJPEGImage method is:
1473%
cristybb503372010-05-27 20:51:26 +00001474% size_t RegisterJPEGImage(void)
cristy3ed852e2009-09-05 21:47:34 +00001475%
1476*/
cristybb503372010-05-27 20:51:26 +00001477ModuleExport size_t RegisterJPEGImage(void)
cristy3ed852e2009-09-05 21:47:34 +00001478{
1479 char
1480 version[MaxTextExtent];
1481
1482 MagickInfo
1483 *entry;
1484
1485 static const char
cristy7138c592009-09-08 13:58:52 +00001486 description[] = "Joint Photographic Experts Group JFIF format";
cristy3ed852e2009-09-05 21:47:34 +00001487
1488 *version='\0';
1489#if defined(JPEG_LIB_VERSION)
cristyb51dff52011-05-19 16:55:47 +00001490 (void) FormatLocaleString(version,MaxTextExtent,"%d",JPEG_LIB_VERSION);
cristy3ed852e2009-09-05 21:47:34 +00001491#endif
1492 entry=SetMagickInfo("JPEG");
1493 entry->thread_support=NoThreadSupport;
1494#if defined(MAGICKCORE_JPEG_DELEGATE)
1495 entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
1496 entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
1497#endif
1498 entry->magick=(IsImageFormatHandler *) IsJPEG;
1499 entry->adjoin=MagickFalse;
1500 entry->description=ConstantString(description);
1501 if (*version != '\0')
1502 entry->version=ConstantString(version);
cristy4a6ab482013-08-09 01:29:35 +00001503 entry->mime_type=ConstantString("image/jpeg");
cristy3ed852e2009-09-05 21:47:34 +00001504 entry->module=ConstantString("JPEG");
1505 (void) RegisterMagickInfo(entry);
1506 entry=SetMagickInfo("JPG");
1507 entry->thread_support=NoThreadSupport;
1508#if defined(MAGICKCORE_JPEG_DELEGATE)
1509 entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
1510 entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
1511#endif
1512 entry->adjoin=MagickFalse;
1513 entry->description=ConstantString(description);
1514 if (*version != '\0')
1515 entry->version=ConstantString(version);
cristy4a6ab482013-08-09 01:29:35 +00001516 entry->mime_type=ConstantString("image/jpeg");
cristy3ed852e2009-09-05 21:47:34 +00001517 entry->module=ConstantString("JPEG");
1518 (void) RegisterMagickInfo(entry);
1519 entry=SetMagickInfo("PJPEG");
1520 entry->thread_support=NoThreadSupport;
1521#if defined(MAGICKCORE_JPEG_DELEGATE)
1522 entry->decoder=(DecodeImageHandler *) ReadJPEGImage;
1523 entry->encoder=(EncodeImageHandler *) WriteJPEGImage;
1524#endif
1525 entry->adjoin=MagickFalse;
1526 entry->description=ConstantString(description);
1527 if (*version != '\0')
1528 entry->version=ConstantString(version);
cristy4a6ab482013-08-09 01:29:35 +00001529 entry->mime_type=ConstantString("image/jpeg");
cristy3ed852e2009-09-05 21:47:34 +00001530 entry->module=ConstantString("JPEG");
1531 (void) RegisterMagickInfo(entry);
1532 return(MagickImageCoderSignature);
1533}
1534
1535/*
1536%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1537% %
1538% %
1539% %
1540% U n r e g i s t e r J P E G I m a g e %
1541% %
1542% %
1543% %
1544%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1545%
1546% UnregisterJPEGImage() removes format registrations made by the
1547% JPEG module from the list of supported formats.
1548%
1549% The format of the UnregisterJPEGImage method is:
1550%
1551% UnregisterJPEGImage(void)
1552%
1553*/
1554ModuleExport void UnregisterJPEGImage(void)
1555{
1556 (void) UnregisterMagickInfo("PJPG");
1557 (void) UnregisterMagickInfo("JPEG");
1558 (void) UnregisterMagickInfo("JPG");
1559}
1560
1561#if defined(MAGICKCORE_JPEG_DELEGATE)
1562/*
1563%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1564% %
1565% %
1566% %
1567% W r i t e J P E G I m a g e %
1568% %
1569% %
1570% %
1571%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1572%
1573% WriteJPEGImage() writes a JPEG image file and returns it. It
1574% allocates the memory necessary for the new Image structure and returns a
1575% pointer to the new image.
1576%
1577% The format of the WriteJPEGImage method is:
1578%
cristy91044972011-04-22 14:21:16 +00001579% MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
cristy1e178e72011-08-28 19:44:34 +00001580% Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001581%
1582% A description of each parameter follows:
1583%
1584% o image_info: the image info.
1585%
1586% o jpeg_image: The image.
1587%
cristy1e178e72011-08-28 19:44:34 +00001588% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00001589%
1590*/
1591
cristy1b58f252012-03-01 01:41:41 +00001592static QuantizationTable *DestroyQuantizationTable(QuantizationTable *table)
1593{
1594 assert(table != (QuantizationTable *) NULL);
1595 if (table->slot != (char *) NULL)
1596 table->slot=DestroyString(table->slot);
1597 if (table->description != (char *) NULL)
1598 table->description=DestroyString(table->description);
1599 if (table->levels != (unsigned int *) NULL)
1600 table->levels=(unsigned int *) RelinquishMagickMemory(table->levels);
1601 table=(QuantizationTable *) RelinquishMagickMemory(table);
1602 return(table);
1603}
1604
cristy3ed852e2009-09-05 21:47:34 +00001605static boolean EmptyOutputBuffer(j_compress_ptr cinfo)
1606{
1607 DestinationManager
1608 *destination;
1609
1610 destination=(DestinationManager *) cinfo->dest;
1611 destination->manager.free_in_buffer=(size_t) WriteBlob(destination->image,
1612 MaxBufferExtent,destination->buffer);
1613 if (destination->manager.free_in_buffer != MaxBufferExtent)
1614 ERREXIT(cinfo,JERR_FILE_WRITE);
1615 destination->manager.next_output_byte=destination->buffer;
1616 return(TRUE);
1617}
1618
cristy1b58f252012-03-01 01:41:41 +00001619static QuantizationTable *GetQuantizationTable(const char *filename,
1620 const char *slot,ExceptionInfo *exception)
1621{
1622 char
1623 *p,
1624 *xml;
1625
1626 const char
1627 *attribute,
1628 *content;
1629
1630 double
1631 value;
1632
1633 register ssize_t
1634 i;
1635
1636 ssize_t
1637 j;
1638
1639 QuantizationTable
1640 *table;
1641
cristy1fcc8b62012-03-02 17:25:55 +00001642 size_t
1643 length;
1644
cristy1b58f252012-03-01 01:41:41 +00001645 XMLTreeInfo
1646 *description,
1647 *levels,
1648 *quantization_tables,
1649 *table_iterator;
1650
1651 (void) LogMagickEvent(ConfigureEvent,GetMagickModule(),
1652 "Loading quantization tables \"%s\" ...",filename);
1653 table=(QuantizationTable *) NULL;
1654 xml=FileToString(filename,~0,exception);
1655 if (xml == (char *) NULL)
1656 return(table);
1657 quantization_tables=NewXMLTree(xml,exception);
1658 if (quantization_tables == (XMLTreeInfo *) NULL)
1659 {
1660 xml=DestroyString(xml);
1661 return(table);
1662 }
1663 for (table_iterator=GetXMLTreeChild(quantization_tables,"table");
1664 table_iterator != (XMLTreeInfo *) NULL;
1665 table_iterator=GetNextXMLTreeTag(table_iterator))
1666 {
1667 attribute=GetXMLTreeAttribute(table_iterator,"slot");
1668 if ((attribute != (char *) NULL) && (LocaleCompare(slot,attribute) == 0))
1669 break;
1670 attribute=GetXMLTreeAttribute(table_iterator,"alias");
1671 if ((attribute != (char *) NULL) && (LocaleCompare(slot,attribute) == 0))
1672 break;
1673 }
1674 if (table_iterator == (XMLTreeInfo *) NULL)
1675 {
1676 xml=DestroyString(xml);
1677 return(table);
1678 }
1679 description=GetXMLTreeChild(table_iterator,"description");
1680 if (description == (XMLTreeInfo *) NULL)
1681 {
1682 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1683 "XmlMissingElement", "<description>, slot \"%s\"",slot);
1684 quantization_tables=DestroyXMLTree(quantization_tables);
1685 xml=DestroyString(xml);
1686 return(table);
1687 }
1688 levels=GetXMLTreeChild(table_iterator,"levels");
1689 if (levels == (XMLTreeInfo *) NULL)
1690 {
1691 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1692 "XmlMissingElement", "<levels>, slot \"%s\"", slot);
1693 quantization_tables=DestroyXMLTree(quantization_tables);
1694 xml=DestroyString(xml);
1695 return(table);
1696 }
1697 table=(QuantizationTable *) AcquireMagickMemory(sizeof(*table));
1698 if (table == (QuantizationTable *) NULL)
1699 ThrowFatalException(ResourceLimitFatalError,
1700 "UnableToAcquireQuantizationTable");
1701 table->slot=(char *) NULL;
1702 table->description=(char *) NULL;
1703 table->levels=(unsigned int *) NULL;
1704 attribute=GetXMLTreeAttribute(table_iterator,"slot");
1705 if (attribute != (char *) NULL)
1706 table->slot=ConstantString(attribute);
1707 content=GetXMLTreeContent(description);
1708 if (content != (char *) NULL)
1709 table->description=ConstantString(content);
1710 attribute=GetXMLTreeAttribute(levels,"width");
1711 if (attribute == (char *) NULL)
1712 {
1713 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1714 "XmlMissingAttribute", "<levels width>, slot \"%s\"",slot);
1715 quantization_tables=DestroyXMLTree(quantization_tables);
1716 table=DestroyQuantizationTable(table);
1717 xml=DestroyString(xml);
1718 return(table);
1719 }
1720 table->width=StringToUnsignedLong(attribute);
1721 if (table->width == 0)
1722 {
1723 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1724 "XmlInvalidAttribute", "<levels width>, table \"%s\"",slot);
1725 quantization_tables=DestroyXMLTree(quantization_tables);
1726 table=DestroyQuantizationTable(table);
1727 xml=DestroyString(xml);
1728 return(table);
1729 }
1730 attribute=GetXMLTreeAttribute(levels,"height");
1731 if (attribute == (char *) NULL)
1732 {
1733 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1734 "XmlMissingAttribute", "<levels height>, table \"%s\"",slot);
1735 quantization_tables=DestroyXMLTree(quantization_tables);
1736 table=DestroyQuantizationTable(table);
1737 xml=DestroyString(xml);
1738 return(table);
1739 }
1740 table->height=StringToUnsignedLong(attribute);
1741 if (table->height == 0)
1742 {
1743 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1744 "XmlInvalidAttribute", "<levels height>, table \"%s\"",slot);
1745 quantization_tables=DestroyXMLTree(quantization_tables);
1746 table=DestroyQuantizationTable(table);
1747 xml=DestroyString(xml);
1748 return(table);
1749 }
1750 attribute=GetXMLTreeAttribute(levels,"divisor");
1751 if (attribute == (char *) NULL)
1752 {
1753 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1754 "XmlMissingAttribute", "<levels divisor>, table \"%s\"",slot);
1755 quantization_tables=DestroyXMLTree(quantization_tables);
1756 table=DestroyQuantizationTable(table);
1757 xml=DestroyString(xml);
1758 return(table);
1759 }
cristy043f3f32012-03-02 17:37:28 +00001760 table->divisor=InterpretLocaleValue(attribute,(char **) NULL);
1761 if (table->divisor == 0.0)
cristy1b58f252012-03-01 01:41:41 +00001762 {
1763 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1764 "XmlInvalidAttribute", "<levels divisor>, table \"%s\"",slot);
1765 quantization_tables=DestroyXMLTree(quantization_tables);
1766 table=DestroyQuantizationTable(table);
1767 xml=DestroyString(xml);
1768 return(table);
1769 }
1770 content=GetXMLTreeContent(levels);
1771 if (content == (char *) NULL)
1772 {
1773 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1774 "XmlMissingContent", "<levels>, table \"%s\"",slot);
1775 quantization_tables=DestroyXMLTree(quantization_tables);
1776 table=DestroyQuantizationTable(table);
1777 xml=DestroyString(xml);
1778 return(table);
1779 }
cristy1fcc8b62012-03-02 17:25:55 +00001780 length=(size_t) table->width*table->height;
1781 if (length < 64)
1782 length=64;
cristy092006b2012-03-02 17:26:02 +00001783 table->levels=(unsigned int *) AcquireQuantumMemory(length,
cristy1fcc8b62012-03-02 17:25:55 +00001784 sizeof(*table->levels));
cristy1b58f252012-03-01 01:41:41 +00001785 if (table->levels == (unsigned int *) NULL)
1786 ThrowFatalException(ResourceLimitFatalError,
1787 "UnableToAcquireQuantizationTable");
1788 for (i=0; i < (ssize_t) (table->width*table->height); i++)
1789 {
cristy043f3f32012-03-02 17:37:28 +00001790 table->levels[i]=(unsigned int) (InterpretLocaleValue(content,&p)/
1791 table->divisor+0.5);
cristy1b58f252012-03-01 01:41:41 +00001792 while (isspace((int) ((unsigned char) *p)) != 0)
1793 p++;
1794 if (*p == ',')
1795 p++;
1796 content=p;
1797 }
cristy043f3f32012-03-02 17:37:28 +00001798 value=InterpretLocaleValue(content,&p);
cristy1b58f252012-03-01 01:41:41 +00001799 (void) value;
1800 if (p != content)
1801 {
1802 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
1803 "XmlInvalidContent", "<level> too many values, table \"%s\"",slot);
1804 quantization_tables=DestroyXMLTree(quantization_tables);
1805 table=DestroyQuantizationTable(table);
1806 xml=DestroyString(xml);
1807 return(table);
1808 }
cristy043f3f32012-03-02 17:37:28 +00001809 for (j=i; j < 64; j++)
1810 table->levels[j]=table->levels[j-1];
cristy1b58f252012-03-01 01:41:41 +00001811 quantization_tables=DestroyXMLTree(quantization_tables);
1812 xml=DestroyString(xml);
1813 return(table);
1814}
1815
cristy3ed852e2009-09-05 21:47:34 +00001816static void InitializeDestination(j_compress_ptr cinfo)
1817{
1818 DestinationManager
1819 *destination;
1820
1821 destination=(DestinationManager *) cinfo->dest;
1822 destination->buffer=(JOCTET *) (*cinfo->mem->alloc_small)
1823 ((j_common_ptr) cinfo,JPOOL_IMAGE,MaxBufferExtent*sizeof(JOCTET));
1824 destination->manager.next_output_byte=destination->buffer;
1825 destination->manager.free_in_buffer=MaxBufferExtent;
1826}
1827
1828static inline size_t MagickMin(const size_t x,const size_t y)
1829{
1830 if (x < y)
1831 return(x);
1832 return(y);
1833}
1834
1835static void TerminateDestination(j_compress_ptr cinfo)
1836{
1837 DestinationManager
1838 *destination;
1839
1840 destination=(DestinationManager *) cinfo->dest;
1841 if ((MaxBufferExtent-(int) destination->manager.free_in_buffer) > 0)
1842 {
1843 ssize_t
1844 count;
1845
1846 count=WriteBlob(destination->image,MaxBufferExtent-
1847 destination->manager.free_in_buffer,destination->buffer);
1848 if (count != (ssize_t)
1849 (MaxBufferExtent-destination->manager.free_in_buffer))
1850 ERREXIT(cinfo,JERR_FILE_WRITE);
1851 }
1852}
1853
1854static void WriteProfile(j_compress_ptr jpeg_info,Image *image)
1855{
1856 const char
1857 *name;
1858
1859 const StringInfo
1860 *profile;
1861
1862 MagickBooleanType
1863 iptc;
1864
cristybb503372010-05-27 20:51:26 +00001865 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001866 i;
1867
1868 size_t
cristy524222d2011-04-25 00:37:06 +00001869 length,
1870 tag_length;
cristy3ed852e2009-09-05 21:47:34 +00001871
1872 StringInfo
1873 *custom_profile;
1874
cristy3ed852e2009-09-05 21:47:34 +00001875 /*
1876 Save image profile as a APP marker.
1877 */
1878 iptc=MagickFalse;
1879 custom_profile=AcquireStringInfo(65535L);
1880 ResetImageProfileIterator(image);
1881 for (name=GetNextImageProfile(image); name != (const char *) NULL; )
1882 {
cristy109e5572010-09-16 18:38:17 +00001883 register unsigned char
1884 *p;
1885
cristy3ed852e2009-09-05 21:47:34 +00001886 profile=GetImageProfile(image,name);
cristy109e5572010-09-16 18:38:17 +00001887 p=GetStringInfoDatum(custom_profile);
cristy3ed852e2009-09-05 21:47:34 +00001888 if (LocaleCompare(name,"EXIF") == 0)
cristybb503372010-05-27 20:51:26 +00001889 for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=65533L)
cristy3ed852e2009-09-05 21:47:34 +00001890 {
1891 length=MagickMin(GetStringInfoLength(profile)-i,65533L);
1892 jpeg_write_marker(jpeg_info,XML_MARKER,GetStringInfoDatum(profile)+i,
1893 (unsigned int) length);
1894 }
1895 if (LocaleCompare(name,"ICC") == 0)
1896 {
1897 register unsigned char
1898 *p;
1899
cristy8ba82ae2013-05-29 14:19:49 +00001900 tag_length=strlen(ICC_PROFILE);
cristy35ce5c32010-09-16 23:16:02 +00001901 p=GetStringInfoDatum(custom_profile);
cristy3ed852e2009-09-05 21:47:34 +00001902 (void) CopyMagickMemory(p,ICC_PROFILE,tag_length);
cristyc3696d42013-07-16 20:18:37 +00001903 p[tag_length]='\0';
cristybb503372010-05-27 20:51:26 +00001904 for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=65519L)
cristy3ed852e2009-09-05 21:47:34 +00001905 {
1906 length=MagickMin(GetStringInfoLength(profile)-i,65519L);
cristy3ed852e2009-09-05 21:47:34 +00001907 p[12]=(unsigned char) ((i/65519L)+1);
1908 p[13]=(unsigned char) (GetStringInfoLength(profile)/65519L+1);
cristyc1381ed2013-06-28 18:10:23 +00001909 (void) CopyMagickMemory(p+tag_length+3,GetStringInfoDatum(profile)+i,
cristy3ed852e2009-09-05 21:47:34 +00001910 length);
1911 jpeg_write_marker(jpeg_info,ICC_MARKER,GetStringInfoDatum(
cristyc1381ed2013-06-28 18:10:23 +00001912 custom_profile),(unsigned int) (length+tag_length+3));
cristy3ed852e2009-09-05 21:47:34 +00001913 }
1914 }
1915 if (((LocaleCompare(name,"IPTC") == 0) ||
1916 (LocaleCompare(name,"8BIM") == 0)) && (iptc == MagickFalse))
1917 {
cristybb503372010-05-27 20:51:26 +00001918 size_t
cristy3ed852e2009-09-05 21:47:34 +00001919 roundup;
1920
1921 iptc=MagickTrue;
cristybb503372010-05-27 20:51:26 +00001922 for (i=0; i < (ssize_t) GetStringInfoLength(profile); i+=65500L)
cristy3ed852e2009-09-05 21:47:34 +00001923 {
1924 length=MagickMin(GetStringInfoLength(profile)-i,65500L);
cristybb503372010-05-27 20:51:26 +00001925 roundup=(size_t) (length & 0x01);
cristy109e5572010-09-16 18:38:17 +00001926 if (LocaleNCompare((char *) GetStringInfoDatum(profile),"8BIM",4) == 0)
1927 {
1928 (void) memcpy(p,"Photoshop 3.0 ",14);
1929 tag_length=14;
1930 }
1931 else
1932 {
1933 (void) CopyMagickMemory(p,"Photoshop 3.0 8BIM\04\04\0\0\0\0",24);
1934 tag_length=26;
1935 p[24]=(unsigned char) (length >> 8);
1936 p[25]=(unsigned char) (length & 0xff);
1937 }
1938 p[13]=0x00;
1939 (void) memcpy(p+tag_length,GetStringInfoDatum(profile)+i,length);
cristy3ed852e2009-09-05 21:47:34 +00001940 if (roundup != 0)
1941 p[length+tag_length]='\0';
1942 jpeg_write_marker(jpeg_info,IPTC_MARKER,GetStringInfoDatum(
1943 custom_profile),(unsigned int) (length+tag_length+roundup));
1944 }
1945 }
1946 if (LocaleCompare(name,"XMP") == 0)
1947 {
1948 StringInfo
1949 *xmp_profile;
1950
1951 /*
1952 Add namespace to XMP profile.
1953 */
cristy0615f0e2011-10-12 11:36:46 +00001954 xmp_profile=StringToStringInfo("http://ns.adobe.com/xap/1.0/ ");
cristy3ed852e2009-09-05 21:47:34 +00001955 ConcatenateStringInfo(xmp_profile,profile);
1956 GetStringInfoDatum(xmp_profile)[28]='\0';
cristybb503372010-05-27 20:51:26 +00001957 for (i=0; i < (ssize_t) GetStringInfoLength(xmp_profile); i+=65533L)
cristy3ed852e2009-09-05 21:47:34 +00001958 {
1959 length=MagickMin(GetStringInfoLength(xmp_profile)-i,65533L);
1960 jpeg_write_marker(jpeg_info,XML_MARKER,
1961 GetStringInfoDatum(xmp_profile)+i,(unsigned int) length);
1962 }
1963 xmp_profile=DestroyStringInfo(xmp_profile);
1964 }
cristye8c25f92010-06-03 00:53:06 +00001965 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
1966 "%s profile: %.20g bytes",name,(double) GetStringInfoLength(profile));
cristy3ed852e2009-09-05 21:47:34 +00001967 name=GetNextImageProfile(image);
1968 }
1969 custom_profile=DestroyStringInfo(custom_profile);
1970}
1971
1972static void JPEGDestinationManager(j_compress_ptr cinfo,Image * image)
1973{
1974 DestinationManager
1975 *destination;
1976
1977 cinfo->dest=(struct jpeg_destination_mgr *) (*cinfo->mem->alloc_small)
1978 ((j_common_ptr) cinfo,JPOOL_IMAGE,sizeof(DestinationManager));
1979 destination=(DestinationManager *) cinfo->dest;
1980 destination->manager.init_destination=InitializeDestination;
1981 destination->manager.empty_output_buffer=EmptyOutputBuffer;
1982 destination->manager.term_destination=TerminateDestination;
1983 destination->image=image;
1984}
1985
1986static char **SamplingFactorToList(const char *text)
1987{
1988 char
1989 **textlist;
1990
1991 register char
1992 *q;
1993
1994 register const char
1995 *p;
1996
cristybb503372010-05-27 20:51:26 +00001997 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001998 i;
1999
cristybb503372010-05-27 20:51:26 +00002000 size_t
cristy3ed852e2009-09-05 21:47:34 +00002001 lines;
2002
2003 if (text == (char *) NULL)
2004 return((char **) NULL);
2005 /*
2006 Convert string to an ASCII list.
2007 */
2008 lines=1;
2009 for (p=text; *p != '\0'; p++)
2010 if (*p == ',')
2011 lines++;
2012 textlist=(char **) AcquireQuantumMemory((size_t) lines+MaxTextExtent,
2013 sizeof(*textlist));
2014 if (textlist == (char **) NULL)
2015 ThrowFatalException(ResourceLimitFatalError,"UnableToConvertText");
2016 p=text;
cristybb503372010-05-27 20:51:26 +00002017 for (i=0; i < (ssize_t) lines; i++)
cristy3ed852e2009-09-05 21:47:34 +00002018 {
2019 for (q=(char *) p; *q != '\0'; q++)
2020 if (*q == ',')
2021 break;
2022 textlist[i]=(char *) AcquireQuantumMemory((size_t) (q-p)+MaxTextExtent,
2023 sizeof(*textlist[i]));
2024 if (textlist[i] == (char *) NULL)
2025 ThrowFatalException(ResourceLimitFatalError,"UnableToConvertText");
2026 (void) CopyMagickString(textlist[i],p,(size_t) (q-p+1));
2027 if (*q == '\r')
2028 q++;
2029 p=q+1;
2030 }
2031 textlist[i]=(char *) NULL;
2032 return(textlist);
2033}
2034
2035static MagickBooleanType WriteJPEGImage(const ImageInfo *image_info,
cristy1e178e72011-08-28 19:44:34 +00002036 Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002037{
2038 const char
2039 *option,
2040 *sampling_factor,
2041 *value;
2042
2043 ErrorManager
2044 error_manager;
2045
cristy21749e92012-02-18 02:29:21 +00002046 int
cristy7b8ed292013-05-18 01:13:56 +00002047 colorspace,
cristy21749e92012-02-18 02:29:21 +00002048 quality;
2049
cristy3ed852e2009-09-05 21:47:34 +00002050 JSAMPLE
cristy75fc68f2012-10-08 16:26:00 +00002051 *volatile jpeg_pixels;
cristy3ed852e2009-09-05 21:47:34 +00002052
2053 JSAMPROW
2054 scanline[1];
2055
cristy3ed852e2009-09-05 21:47:34 +00002056 MagickBooleanType
2057 status;
2058
cristy22646e22013-06-23 16:34:03 +00002059 MemoryInfo
2060 *memory_info;
2061
cristy3ed852e2009-09-05 21:47:34 +00002062 register JSAMPLE
2063 *q;
2064
cristybb503372010-05-27 20:51:26 +00002065 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002066 i;
2067
cristy524222d2011-04-25 00:37:06 +00002068 ssize_t
2069 y;
2070
cristy3ed852e2009-09-05 21:47:34 +00002071 struct jpeg_compress_struct
2072 jpeg_info;
2073
2074 struct jpeg_error_mgr
2075 jpeg_error;
2076
2077 /*
2078 Open image file.
2079 */
2080 assert(image_info != (const ImageInfo *) NULL);
2081 assert(image_info->signature == MagickSignature);
2082 assert(image != (Image *) NULL);
2083 assert(image->signature == MagickSignature);
2084 if (image->debug != MagickFalse)
2085 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy3a37efd2011-08-28 20:31:03 +00002086 assert(exception != (ExceptionInfo *) NULL);
2087 assert(exception->signature == MagickSignature);
cristy1e178e72011-08-28 19:44:34 +00002088 status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
cristy3ed852e2009-09-05 21:47:34 +00002089 if (status == MagickFalse)
2090 return(status);
2091 /*
2092 Initialize JPEG parameters.
2093 */
cristy91044972011-04-22 14:21:16 +00002094 (void) ResetMagickMemory(&error_manager,0,sizeof(error_manager));
cristy3ed852e2009-09-05 21:47:34 +00002095 (void) ResetMagickMemory(&jpeg_info,0,sizeof(jpeg_info));
2096 (void) ResetMagickMemory(&jpeg_error,0,sizeof(jpeg_error));
2097 jpeg_info.client_data=(void *) image;
2098 jpeg_info.err=jpeg_std_error(&jpeg_error);
cristyd28b1dd2011-05-14 20:30:38 +00002099 jpeg_info.err->emit_message=(void (*)(j_common_ptr,int)) JPEGWarningHandler;
cristy91044972011-04-22 14:21:16 +00002100 jpeg_info.err->error_exit=(void (*)(j_common_ptr)) JPEGErrorHandler;
cristy018f07f2011-09-04 21:15:19 +00002101 error_manager.exception=exception;
cristy3ed852e2009-09-05 21:47:34 +00002102 error_manager.image=image;
cristy72f87f82013-06-23 16:43:05 +00002103 memory_info=(MemoryInfo *) NULL;
cristy3ed852e2009-09-05 21:47:34 +00002104 if (setjmp(error_manager.error_recovery) != 0)
2105 {
2106 jpeg_destroy_compress(&jpeg_info);
2107 (void) CloseBlob(image);
2108 return(MagickFalse);
2109 }
2110 jpeg_info.client_data=(void *) &error_manager;
2111 jpeg_create_compress(&jpeg_info);
2112 JPEGDestinationManager(&jpeg_info,image);
2113 if ((image->columns != (unsigned int) image->columns) ||
2114 (image->rows != (unsigned int) image->rows))
2115 ThrowWriterException(ImageError,"WidthOrHeightExceedsLimit");
2116 jpeg_info.image_width=(unsigned int) image->columns;
2117 jpeg_info.image_height=(unsigned int) image->rows;
2118 jpeg_info.input_components=3;
2119 jpeg_info.data_precision=8;
2120 jpeg_info.in_color_space=JCS_RGB;
2121 switch (image->colorspace)
2122 {
2123 case CMYKColorspace:
2124 {
2125 jpeg_info.input_components=4;
2126 jpeg_info.in_color_space=JCS_CMYK;
2127 break;
2128 }
2129 case YCbCrColorspace:
2130 case Rec601YCbCrColorspace:
2131 case Rec709YCbCrColorspace:
2132 {
2133 jpeg_info.in_color_space=JCS_YCbCr;
2134 break;
2135 }
2136 case GRAYColorspace:
cristy3ed852e2009-09-05 21:47:34 +00002137 {
2138 jpeg_info.input_components=1;
2139 jpeg_info.in_color_space=JCS_GRAYSCALE;
2140 break;
2141 }
2142 default:
cristy9f396782009-12-21 01:37:45 +00002143 {
cristy3d9f5ba2012-06-26 13:37:31 +00002144 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
cristy8d951092012-02-08 18:54:56 +00002145 (void) TransformImageColorspace(image,sRGBColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +00002146 break;
cristy9f396782009-12-21 01:37:45 +00002147 }
cristy3ed852e2009-09-05 21:47:34 +00002148 }
2149 if ((image_info->type != TrueColorType) &&
cristy1e178e72011-08-28 19:44:34 +00002150 (IsImageGray(image,exception) != MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002151 {
2152 jpeg_info.input_components=1;
2153 jpeg_info.in_color_space=JCS_GRAYSCALE;
2154 }
2155 jpeg_set_defaults(&jpeg_info);
cristy11369092013-02-03 22:38:57 +00002156 if (jpeg_info.in_color_space == JCS_CMYK)
2157 jpeg_set_colorspace(&jpeg_info,JCS_YCCK);
cristy3ed852e2009-09-05 21:47:34 +00002158 if ((jpeg_info.data_precision != 12) && (image->depth <= 8))
2159 jpeg_info.data_precision=8;
2160 else
2161 if (sizeof(JSAMPLE) > 1)
2162 jpeg_info.data_precision=12;
2163 jpeg_info.density_unit=(UINT8) 1;
2164 if (image->debug != MagickFalse)
2165 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
cristy2a11bef2011-10-28 18:33:11 +00002166 "Image resolution: %.20g,%.20g",floor(image->resolution.x+0.5),
2167 floor(image->resolution.y+0.5));
2168 if ((image->resolution.x != 0.0) && (image->resolution.y != 0.0))
cristy3ed852e2009-09-05 21:47:34 +00002169 {
2170 /*
2171 Set image resolution.
2172 */
2173 jpeg_info.write_JFIF_header=MagickTrue;
cristy2a11bef2011-10-28 18:33:11 +00002174 jpeg_info.X_density=(UINT16) floor(image->resolution.x+0.5);
2175 jpeg_info.Y_density=(UINT16) floor(image->resolution.y+0.5);
cristy0c220632013-09-25 22:10:28 +00002176 /*
2177 Set image resolution units.
2178 */
2179 jpeg_info.density_unit=(UINT8) 0;
cristy3ed852e2009-09-05 21:47:34 +00002180 if (image->units == PixelsPerInchResolution)
2181 jpeg_info.density_unit=(UINT8) 1;
2182 if (image->units == PixelsPerCentimeterResolution)
2183 jpeg_info.density_unit=(UINT8) 2;
2184 }
cristy97cb3bf2012-02-21 18:31:20 +00002185 jpeg_info.dct_method=JDCT_FLOAT;
cristy092ec8d2013-04-26 13:46:22 +00002186 option=GetImageOption(image_info,"jpeg:dct-method");
cristy3ed852e2009-09-05 21:47:34 +00002187 if (option != (const char *) NULL)
2188 switch (*option)
2189 {
2190 case 'D':
2191 case 'd':
2192 {
2193 if (LocaleCompare(option,"default") == 0)
2194 jpeg_info.dct_method=JDCT_DEFAULT;
2195 break;
2196 }
2197 case 'F':
2198 case 'f':
2199 {
2200 if (LocaleCompare(option,"fastest") == 0)
2201 jpeg_info.dct_method=JDCT_FASTEST;
2202 if (LocaleCompare(option,"float") == 0)
2203 jpeg_info.dct_method=JDCT_FLOAT;
2204 break;
2205 }
2206 case 'I':
2207 case 'i':
2208 {
2209 if (LocaleCompare(option,"ifast") == 0)
2210 jpeg_info.dct_method=JDCT_IFAST;
2211 if (LocaleCompare(option,"islow") == 0)
2212 jpeg_info.dct_method=JDCT_ISLOW;
2213 break;
2214 }
2215 }
cristy092ec8d2013-04-26 13:46:22 +00002216 option=GetImageOption(image_info,"jpeg:optimize-coding");
cristy3ed852e2009-09-05 21:47:34 +00002217 if (option != (const char *) NULL)
anthony6f201312012-03-30 04:08:15 +00002218 jpeg_info.optimize_coding=IsStringTrue(option);
cristy3ed852e2009-09-05 21:47:34 +00002219 else
2220 {
2221 MagickSizeType
2222 length;
2223
2224 length=(MagickSizeType) jpeg_info.input_components*image->columns*
2225 image->rows*sizeof(JSAMPLE);
2226 if (length == (MagickSizeType) ((size_t) length))
2227 {
2228 /*
2229 Perform optimization only if available memory resources permit it.
2230 */
2231 status=AcquireMagickResource(MemoryResource,length);
cristy3ed852e2009-09-05 21:47:34 +00002232 RelinquishMagickResource(MemoryResource,length);
anthony6f201312012-03-30 04:08:15 +00002233 jpeg_info.optimize_coding=status;
cristy3ed852e2009-09-05 21:47:34 +00002234 }
2235 }
2236#if (JPEG_LIB_VERSION >= 61) && defined(C_PROGRESSIVE_SUPPORTED)
2237 if ((LocaleCompare(image_info->magick,"PJPEG") == 0) ||
2238 (image_info->interlace != NoInterlace))
2239 {
2240 if (image->debug != MagickFalse)
2241 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2242 "Interlace: progressive");
2243 jpeg_simple_progression(&jpeg_info);
2244 }
2245 else
2246 if (image->debug != MagickFalse)
2247 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2248 "Interlace: non-progressive");
2249#else
2250 if (image->debug != MagickFalse)
2251 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2252 "Interlace: nonprogressive");
2253#endif
cristy092ec8d2013-04-26 13:46:22 +00002254 option=GetImageOption(image_info,"jpeg:extent");
cristy0adb4f92009-11-28 18:08:51 +00002255 if (option != (const char *) NULL)
2256 {
2257 Image
2258 *jpeg_image;
2259
2260 ImageInfo
2261 *jpeg_info;
2262
2263 jpeg_info=CloneImageInfo(image_info);
cristy1e178e72011-08-28 19:44:34 +00002264 jpeg_image=CloneImage(image,0,0,MagickTrue,exception);
cristy0adb4f92009-11-28 18:08:51 +00002265 if (jpeg_image != (Image *) NULL)
2266 {
2267 MagickSizeType
2268 extent;
2269
2270 size_t
cristy87e73ab2010-02-12 01:59:12 +00002271 maximum,
2272 minimum;
cristy0adb4f92009-11-28 18:08:51 +00002273
2274 /*
2275 Search for compression quality that does not exceed image extent.
2276 */
2277 jpeg_info->quality=0;
cristyd6495f92011-12-01 19:36:52 +00002278 extent=(MagickSizeType) SiPrefixToDoubleInterval(option,100.0);
cristy0adb4f92009-11-28 18:08:51 +00002279 (void) DeleteImageOption(jpeg_info,"jpeg:extent");
cristy2e5309e2013-04-27 14:48:31 +00002280 (void) DeleteImageArtifact(jpeg_image,"jpeg:extent");
cristy0adb4f92009-11-28 18:08:51 +00002281 (void) AcquireUniqueFilename(jpeg_image->filename);
cristye0a316c2010-02-13 19:15:23 +00002282 maximum=101;
dirkcf845242013-08-11 21:36:19 +00002283 for (minimum=2; minimum < maximum; )
cristy0adb4f92009-11-28 18:08:51 +00002284 {
cristye10c9642013-04-13 12:32:41 +00002285 jpeg_image->quality=minimum+(maximum-minimum+1)/2;
cristy1e178e72011-08-28 19:44:34 +00002286 status=WriteJPEGImage(jpeg_info,jpeg_image,exception);
cristyc8d69b12010-02-13 19:06:26 +00002287 if (GetBlobSize(jpeg_image) <= extent)
cristy87e73ab2010-02-12 01:59:12 +00002288 minimum=jpeg_image->quality+1;
cristy0adb4f92009-11-28 18:08:51 +00002289 else
cristy87e73ab2010-02-12 01:59:12 +00002290 maximum=jpeg_image->quality-1;
cristy0adb4f92009-11-28 18:08:51 +00002291 }
2292 (void) RelinquishUniqueFileResource(jpeg_image->filename);
cristyc8d69b12010-02-13 19:06:26 +00002293 image->quality=minimum-1;
cristy0adb4f92009-11-28 18:08:51 +00002294 jpeg_image=DestroyImage(jpeg_image);
2295 }
2296 jpeg_info=DestroyImageInfo(jpeg_info);
2297 }
cristy21749e92012-02-18 02:29:21 +00002298 quality=92;
cristy3ed852e2009-09-05 21:47:34 +00002299 if ((image_info->compression != LosslessJPEGCompression) &&
2300 (image->quality <= 100))
2301 {
cristy21749e92012-02-18 02:29:21 +00002302 if (image->quality != UndefinedCompressionQuality)
2303 quality=(int) image->quality;
cristy3ed852e2009-09-05 21:47:34 +00002304 if (image->debug != MagickFalse)
cristye8c25f92010-06-03 00:53:06 +00002305 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: %.20g",
2306 (double) image->quality);
cristy3ed852e2009-09-05 21:47:34 +00002307 }
2308 else
2309 {
2310#if !defined(C_LOSSLESS_SUPPORTED)
cristy21749e92012-02-18 02:29:21 +00002311 quality=100;
cristy3ed852e2009-09-05 21:47:34 +00002312 if (image->debug != MagickFalse)
2313 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: 100");
2314#else
2315 if (image->quality < 100)
cristy1e178e72011-08-28 19:44:34 +00002316 (void) ThrowMagickException(exception,GetMagickModule(),CoderWarning,
2317 "LosslessToLossyJPEGConversion",image->filename);
cristy3ed852e2009-09-05 21:47:34 +00002318 else
2319 {
2320 int
2321 point_transform,
2322 predictor;
2323
2324 predictor=image->quality/100; /* range 1-7 */
2325 point_transform=image->quality % 20; /* range 0-15 */
2326 jpeg_simple_lossless(&jpeg_info,predictor,point_transform);
2327 if (image->debug != MagickFalse)
2328 {
2329 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2330 "Compression: lossless");
2331 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2332 "Predictor: %d",predictor);
2333 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2334 "Point Transform: %d",point_transform);
2335 }
2336 }
2337#endif
2338 }
cristy21749e92012-02-18 02:29:21 +00002339 jpeg_set_quality(&jpeg_info,quality,MagickTrue);
cristy16e74722012-07-01 13:01:38 +00002340#if (JPEG_LIB_VERSION >= 70)
cristy092ec8d2013-04-26 13:46:22 +00002341 option=GetImageOption(image_info,"quality");
cristy16e74722012-07-01 13:01:38 +00002342 if (option != (const char *) NULL)
2343 {
2344 GeometryInfo
2345 geometry_info;
2346
2347 int
2348 flags;
2349
2350 /*
2351 Set quality scaling for luminance and chrominance separately.
2352 */
2353 flags=ParseGeometry(option,&geometry_info);
2354 if (((flags & RhoValue) != 0) && ((flags & SigmaValue) != 0))
2355 {
2356 jpeg_info.q_scale_factor[0]=jpeg_quality_scaling((int)
2357 (geometry_info.rho+0.5));
2358 jpeg_info.q_scale_factor[1]=jpeg_quality_scaling((int)
2359 (geometry_info.sigma+0.5));
2360 jpeg_default_qtables(&jpeg_info,MagickTrue);
2361 }
2362 }
2363#endif
cristy7b8ed292013-05-18 01:13:56 +00002364 colorspace=jpeg_info.in_color_space;
2365 value=GetImageOption(image_info,"jpeg:colorspace");
glennrp2a7dbb12012-09-20 12:48:41 +00002366 if (value == (char *) NULL)
cristy7b8ed292013-05-18 01:13:56 +00002367 value=GetImageProperty(image,"jpeg:colorspace",exception);
cristyefb04e22012-09-17 23:29:25 +00002368 if (value != (char *) NULL)
cristy7b8ed292013-05-18 01:13:56 +00002369 colorspace=StringToInteger(value);
2370 sampling_factor=(const char *) NULL;
2371 if (colorspace == jpeg_info.in_color_space)
cristy3ed852e2009-09-05 21:47:34 +00002372 {
cristy7b8ed292013-05-18 01:13:56 +00002373 value=GetImageOption(image_info,"jpeg:sampling-factor");
2374 if (value == (char *) NULL)
2375 value=GetImageProperty(image,"jpeg:sampling-factor",exception);
2376 if (value != (char *) NULL)
2377 {
2378 sampling_factor=value;
2379 if (image->debug != MagickFalse)
2380 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2381 " Input sampling-factors=%s",sampling_factor);
2382 }
cristy3ed852e2009-09-05 21:47:34 +00002383 }
cristy7b8ed292013-05-18 01:13:56 +00002384 value=GetImageOption(image_info,"jpeg:sampling-factor");
cristy3ed852e2009-09-05 21:47:34 +00002385 if (image_info->sampling_factor != (char *) NULL)
2386 sampling_factor=image_info->sampling_factor;
2387 if (sampling_factor == (const char *) NULL)
2388 {
2389 if (image->quality >= 90)
2390 for (i=0; i < MAX_COMPONENTS; i++)
2391 {
2392 jpeg_info.comp_info[i].h_samp_factor=1;
2393 jpeg_info.comp_info[i].v_samp_factor=1;
2394 }
2395 }
2396 else
2397 {
2398 char
2399 **factors;
2400
2401 GeometryInfo
2402 geometry_info;
2403
2404 MagickStatusType
2405 flags;
2406
2407 /*
2408 Set sampling factor.
2409 */
2410 i=0;
2411 factors=SamplingFactorToList(sampling_factor);
2412 if (factors != (char **) NULL)
2413 {
2414 for (i=0; i < MAX_COMPONENTS; i++)
2415 {
2416 if (factors[i] == (char *) NULL)
2417 break;
2418 flags=ParseGeometry(factors[i],&geometry_info);
2419 if ((flags & SigmaValue) == 0)
2420 geometry_info.sigma=geometry_info.rho;
2421 jpeg_info.comp_info[i].h_samp_factor=(int) geometry_info.rho;
2422 jpeg_info.comp_info[i].v_samp_factor=(int) geometry_info.sigma;
2423 factors[i]=(char *) RelinquishMagickMemory(factors[i]);
2424 }
2425 factors=(char **) RelinquishMagickMemory(factors);
2426 }
2427 for ( ; i < MAX_COMPONENTS; i++)
2428 {
2429 jpeg_info.comp_info[i].h_samp_factor=1;
2430 jpeg_info.comp_info[i].v_samp_factor=1;
2431 }
2432 }
cristy092ec8d2013-04-26 13:46:22 +00002433 option=GetImageOption(image_info,"jpeg:q-table");
cristy1b58f252012-03-01 01:41:41 +00002434 if (option != (const char *) NULL)
cristy1445b322012-02-19 18:57:30 +00002435 {
cristy1b58f252012-03-01 01:41:41 +00002436 QuantizationTable
2437 *table;
cristyb4bb39c2012-02-21 18:45:54 +00002438
cristy1445b322012-02-19 18:57:30 +00002439 /*
cristy1b58f252012-03-01 01:41:41 +00002440 Custom quantization tables.
cristy1445b322012-02-19 18:57:30 +00002441 */
cristy1b58f252012-03-01 01:41:41 +00002442 table=GetQuantizationTable(option,"0",exception);
2443 if (table != (QuantizationTable *) NULL)
2444 {
cristyae453b72013-04-21 22:24:20 +00002445 for (i=0; i < MAX_COMPONENTS; i++)
cristyd2fe6d12013-04-21 22:29:56 +00002446 jpeg_info.comp_info[i].quant_tbl_no=0;
2447 jpeg_add_quant_table(&jpeg_info,0,table->levels,
2448 jpeg_quality_scaling(quality),0);
cristy1b58f252012-03-01 01:41:41 +00002449 table=DestroyQuantizationTable(table);
2450 }
2451 table=GetQuantizationTable(option,"1",exception);
2452 if (table != (QuantizationTable *) NULL)
2453 {
cristyae453b72013-04-21 22:24:20 +00002454 for (i=1; i < MAX_COMPONENTS; i++)
cristyd2fe6d12013-04-21 22:29:56 +00002455 jpeg_info.comp_info[i].quant_tbl_no=1;
2456 jpeg_add_quant_table(&jpeg_info,1,table->levels,
2457 jpeg_quality_scaling(quality),0);
cristy1b58f252012-03-01 01:41:41 +00002458 table=DestroyQuantizationTable(table);
2459 }
2460 table=GetQuantizationTable(option,"2",exception);
2461 if (table != (QuantizationTable *) NULL)
2462 {
cristyae453b72013-04-21 22:24:20 +00002463 for (i=2; i < MAX_COMPONENTS; i++)
cristyd2fe6d12013-04-21 22:29:56 +00002464 jpeg_info.comp_info[i].quant_tbl_no=2;
2465 jpeg_add_quant_table(&jpeg_info,2,table->levels,
2466 jpeg_quality_scaling(quality),0);
cristy1b58f252012-03-01 01:41:41 +00002467 table=DestroyQuantizationTable(table);
2468 }
2469 table=GetQuantizationTable(option,"3",exception);
2470 if (table != (QuantizationTable *) NULL)
2471 {
cristyae453b72013-04-21 22:24:20 +00002472 for (i=3; i < MAX_COMPONENTS; i++)
cristyd2fe6d12013-04-21 22:29:56 +00002473 jpeg_info.comp_info[i].quant_tbl_no=3;
2474 jpeg_add_quant_table(&jpeg_info,3,table->levels,
2475 jpeg_quality_scaling(quality),0);
cristy1b58f252012-03-01 01:41:41 +00002476 table=DestroyQuantizationTable(table);
2477 }
cristy1445b322012-02-19 18:57:30 +00002478 }
cristy3ed852e2009-09-05 21:47:34 +00002479 jpeg_start_compress(&jpeg_info,MagickTrue);
2480 if (image->debug != MagickFalse)
2481 {
2482 if (image->storage_class == PseudoClass)
2483 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2484 "Storage class: PseudoClass");
2485 else
2486 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2487 "Storage class: DirectClass");
cristye8c25f92010-06-03 00:53:06 +00002488 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Depth: %.20g",
2489 (double) image->depth);
cristy3ed852e2009-09-05 21:47:34 +00002490 if (image->colors != 0)
2491 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002492 "Number of colors: %.20g",(double) image->colors);
cristy3ed852e2009-09-05 21:47:34 +00002493 else
2494 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2495 "Number of colors: unspecified");
2496 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2497 "JPEG data precision: %d",(int) jpeg_info.data_precision);
2498 switch (image->colorspace)
2499 {
2500 case CMYKColorspace:
2501 {
2502 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2503 "Storage class: DirectClass");
2504 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2505 "Colorspace: CMYK");
2506 break;
2507 }
2508 case YCbCrColorspace:
2509 case Rec601YCbCrColorspace:
2510 case Rec709YCbCrColorspace:
2511 {
2512 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2513 "Colorspace: YCbCr");
2514 break;
2515 }
2516 default:
2517 break;
2518 }
2519 switch (image->colorspace)
2520 {
2521 case CMYKColorspace:
2522 {
2523 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2524 "Colorspace: CMYK");
2525 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2526 "Sampling factors: %dx%d,%dx%d,%dx%d,%dx%d",
2527 jpeg_info.comp_info[0].h_samp_factor,
2528 jpeg_info.comp_info[0].v_samp_factor,
2529 jpeg_info.comp_info[1].h_samp_factor,
2530 jpeg_info.comp_info[1].v_samp_factor,
2531 jpeg_info.comp_info[2].h_samp_factor,
2532 jpeg_info.comp_info[2].v_samp_factor,
2533 jpeg_info.comp_info[3].h_samp_factor,
2534 jpeg_info.comp_info[3].v_samp_factor);
2535 break;
2536 }
2537 case GRAYColorspace:
cristy3ed852e2009-09-05 21:47:34 +00002538 {
2539 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2540 "Colorspace: GRAY");
2541 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2542 "Sampling factors: %dx%d",jpeg_info.comp_info[0].h_samp_factor,
2543 jpeg_info.comp_info[0].v_samp_factor);
2544 break;
2545 }
cristyed8d7852013-08-01 22:44:22 +00002546 case sRGBColorspace:
cristy3ed852e2009-09-05 21:47:34 +00002547 case RGBColorspace:
2548 {
2549 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
cristyfeed8be2013-08-01 22:45:13 +00002550 "Image colorspace is RGB");
cristy3ed852e2009-09-05 21:47:34 +00002551 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2552 "Sampling factors: %dx%d,%dx%d,%dx%d",
2553 jpeg_info.comp_info[0].h_samp_factor,
2554 jpeg_info.comp_info[0].v_samp_factor,
2555 jpeg_info.comp_info[1].h_samp_factor,
2556 jpeg_info.comp_info[1].v_samp_factor,
2557 jpeg_info.comp_info[2].h_samp_factor,
2558 jpeg_info.comp_info[2].v_samp_factor);
2559 break;
2560 }
2561 case YCbCrColorspace:
2562 case Rec601YCbCrColorspace:
2563 case Rec709YCbCrColorspace:
2564 {
2565 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2566 "Colorspace: YCbCr");
2567 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2568 "Sampling factors: %dx%d,%dx%d,%dx%d",
2569 jpeg_info.comp_info[0].h_samp_factor,
2570 jpeg_info.comp_info[0].v_samp_factor,
2571 jpeg_info.comp_info[1].h_samp_factor,
2572 jpeg_info.comp_info[1].v_samp_factor,
2573 jpeg_info.comp_info[2].h_samp_factor,
2574 jpeg_info.comp_info[2].v_samp_factor);
2575 break;
2576 }
2577 default:
2578 {
2579 (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Colorspace: %d",
2580 image->colorspace);
2581 (void) LogMagickEvent(CoderEvent,GetMagickModule(),
2582 "Sampling factors: %dx%d,%dx%d,%dx%d,%dx%d",
2583 jpeg_info.comp_info[0].h_samp_factor,
2584 jpeg_info.comp_info[0].v_samp_factor,
2585 jpeg_info.comp_info[1].h_samp_factor,
2586 jpeg_info.comp_info[1].v_samp_factor,
2587 jpeg_info.comp_info[2].h_samp_factor,
2588 jpeg_info.comp_info[2].v_samp_factor,
2589 jpeg_info.comp_info[3].h_samp_factor,
2590 jpeg_info.comp_info[3].v_samp_factor);
2591 break;
2592 }
2593 }
2594 }
2595 /*
2596 Write JPEG profiles.
2597 */
cristyd15e6592011-10-15 00:13:06 +00002598 value=GetImageProperty(image,"comment",exception);
cristy3ed852e2009-09-05 21:47:34 +00002599 if (value != (char *) NULL)
cristybb503372010-05-27 20:51:26 +00002600 for (i=0; i < (ssize_t) strlen(value); i+=65533L)
cristy3ed852e2009-09-05 21:47:34 +00002601 jpeg_write_marker(&jpeg_info,JPEG_COM,(unsigned char *) value+i,
2602 (unsigned int) MagickMin((size_t) strlen(value+i),65533L));
2603 if (image->profiles != (void *) NULL)
2604 WriteProfile(&jpeg_info,image);
2605 /*
2606 Convert MIFF to JPEG raster pixels.
2607 */
cristy22646e22013-06-23 16:34:03 +00002608 memory_info=AcquireVirtualMemory((size_t) image->columns,
cristy3ed852e2009-09-05 21:47:34 +00002609 jpeg_info.input_components*sizeof(*jpeg_pixels));
cristy22646e22013-06-23 16:34:03 +00002610 if (memory_info == (MemoryInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002611 ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
cristy22646e22013-06-23 16:34:03 +00002612 jpeg_pixels=(JSAMPLE *) GetVirtualMemoryBlob(memory_info);
cristy3ed852e2009-09-05 21:47:34 +00002613 if (setjmp(error_manager.error_recovery) != 0)
2614 {
2615 jpeg_destroy_compress(&jpeg_info);
cristy22646e22013-06-23 16:34:03 +00002616 if (memory_info != (MemoryInfo *) NULL)
2617 memory_info=RelinquishVirtualMemory(memory_info);
cristy3ed852e2009-09-05 21:47:34 +00002618 (void) CloseBlob(image);
2619 return(MagickFalse);
2620 }
2621 scanline[0]=(JSAMPROW) jpeg_pixels;
cristye90d7402010-03-14 18:21:29 +00002622 if (jpeg_info.data_precision <= 8)
cristy3ed852e2009-09-05 21:47:34 +00002623 {
cristy3ed852e2009-09-05 21:47:34 +00002624 if ((jpeg_info.in_color_space == JCS_RGB) ||
2625 (jpeg_info.in_color_space == JCS_YCbCr))
cristybb503372010-05-27 20:51:26 +00002626 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002627 {
cristy4c08aed2011-07-01 19:47:50 +00002628 register const Quantum
cristy3ed852e2009-09-05 21:47:34 +00002629 *p;
2630
cristybb503372010-05-27 20:51:26 +00002631 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002632 x;
2633
cristy1e178e72011-08-28 19:44:34 +00002634 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002635 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002636 break;
2637 q=jpeg_pixels;
cristybb503372010-05-27 20:51:26 +00002638 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002639 {
cristy4c08aed2011-07-01 19:47:50 +00002640 *q++=(JSAMPLE) ScaleQuantumToChar(GetPixelRed(image,p));
2641 *q++=(JSAMPLE) ScaleQuantumToChar(GetPixelGreen(image,p));
2642 *q++=(JSAMPLE) ScaleQuantumToChar(GetPixelBlue(image,p));
cristyed231572011-07-14 02:18:59 +00002643 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002644 }
2645 (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
cristy524222d2011-04-25 00:37:06 +00002646 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
2647 image->rows);
2648 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002649 break;
2650 }
2651 else
cristye90d7402010-03-14 18:21:29 +00002652 if (jpeg_info.in_color_space == JCS_GRAYSCALE)
cristyd0323222013-04-07 16:13:21 +00002653 for (y=0; y < (ssize_t) image->rows; y++)
cristye90d7402010-03-14 18:21:29 +00002654 {
cristyd0323222013-04-07 16:13:21 +00002655 register const Quantum
2656 *p;
2657
2658 register ssize_t
2659 x;
2660
2661 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2662 if (p == (const Quantum *) NULL)
2663 break;
2664 q=jpeg_pixels;
2665 for (x=0; x < (ssize_t) image->columns; x++)
cristye90d7402010-03-14 18:21:29 +00002666 {
cristyd0323222013-04-07 16:13:21 +00002667 *q++=(JSAMPLE) ScaleQuantumToChar(ClampToQuantum(GetPixelLuma(
2668 image,p)));
2669 p+=GetPixelChannels(image);
cristye90d7402010-03-14 18:21:29 +00002670 }
cristyd0323222013-04-07 16:13:21 +00002671 (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
2672 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
2673 image->rows);
2674 if (status == MagickFalse)
2675 break;
2676 }
cristye90d7402010-03-14 18:21:29 +00002677 else
cristybb503372010-05-27 20:51:26 +00002678 for (y=0; y < (ssize_t) image->rows; y++)
cristye90d7402010-03-14 18:21:29 +00002679 {
cristy4c08aed2011-07-01 19:47:50 +00002680 register const Quantum
cristye90d7402010-03-14 18:21:29 +00002681 *p;
2682
cristybb503372010-05-27 20:51:26 +00002683 register ssize_t
cristye90d7402010-03-14 18:21:29 +00002684 x;
2685
cristy1e178e72011-08-28 19:44:34 +00002686 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002687 if (p == (const Quantum *) NULL)
cristye90d7402010-03-14 18:21:29 +00002688 break;
2689 q=jpeg_pixels;
cristybb503372010-05-27 20:51:26 +00002690 for (x=0; x < (ssize_t) image->columns; x++)
cristye90d7402010-03-14 18:21:29 +00002691 {
2692 /*
2693 Convert DirectClass packets to contiguous CMYK scanlines.
2694 */
2695 *q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
cristy679eaad2013-02-03 22:35:12 +00002696 GetPixelCyan(image,p))));
cristye90d7402010-03-14 18:21:29 +00002697 *q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
cristy679eaad2013-02-03 22:35:12 +00002698 GetPixelMagenta(image,p))));
cristye90d7402010-03-14 18:21:29 +00002699 *q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
cristy679eaad2013-02-03 22:35:12 +00002700 GetPixelYellow(image,p))));
cristye90d7402010-03-14 18:21:29 +00002701 *q++=(JSAMPLE) (ScaleQuantumToChar((Quantum) (QuantumRange-
cristy4c08aed2011-07-01 19:47:50 +00002702 GetPixelBlack(image,p))));
cristyed231572011-07-14 02:18:59 +00002703 p+=GetPixelChannels(image);
cristye90d7402010-03-14 18:21:29 +00002704 }
2705 (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
cristy524222d2011-04-25 00:37:06 +00002706 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
2707 image->rows);
2708 if (status == MagickFalse)
cristye90d7402010-03-14 18:21:29 +00002709 break;
2710 }
2711 }
2712 else
2713 if (jpeg_info.in_color_space == JCS_GRAYSCALE)
cristybb503372010-05-27 20:51:26 +00002714 for (y=0; y < (ssize_t) image->rows; y++)
cristye90d7402010-03-14 18:21:29 +00002715 {
cristy4c08aed2011-07-01 19:47:50 +00002716 register const Quantum
cristye90d7402010-03-14 18:21:29 +00002717 *p;
2718
cristybb503372010-05-27 20:51:26 +00002719 register ssize_t
cristye90d7402010-03-14 18:21:29 +00002720 x;
2721
cristy1e178e72011-08-28 19:44:34 +00002722 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002723 if (p == (const Quantum *) NULL)
cristye90d7402010-03-14 18:21:29 +00002724 break;
2725 q=jpeg_pixels;
cristybb503372010-05-27 20:51:26 +00002726 for (x=0; x < (ssize_t) image->columns; x++)
cristye90d7402010-03-14 18:21:29 +00002727 {
cristyd0323222013-04-07 16:13:21 +00002728 *q++=(JSAMPLE) (ScaleQuantumToShort(ClampToQuantum(GetPixelLuma(
2729 image,p))) >> 4);
cristyed231572011-07-14 02:18:59 +00002730 p+=GetPixelChannels(image);
cristye90d7402010-03-14 18:21:29 +00002731 }
2732 (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
cristy524222d2011-04-25 00:37:06 +00002733 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
2734 image->rows);
2735 if (status == MagickFalse)
cristye90d7402010-03-14 18:21:29 +00002736 break;
2737 }
2738 else
2739 if ((jpeg_info.in_color_space == JCS_RGB) ||
2740 (jpeg_info.in_color_space == JCS_YCbCr))
cristybb503372010-05-27 20:51:26 +00002741 for (y=0; y < (ssize_t) image->rows; y++)
cristye90d7402010-03-14 18:21:29 +00002742 {
cristy4c08aed2011-07-01 19:47:50 +00002743 register const Quantum
cristye90d7402010-03-14 18:21:29 +00002744 *p;
2745
cristybb503372010-05-27 20:51:26 +00002746 register ssize_t
cristye90d7402010-03-14 18:21:29 +00002747 x;
2748
cristy1e178e72011-08-28 19:44:34 +00002749 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002750 if (p == (const Quantum *) NULL)
cristye90d7402010-03-14 18:21:29 +00002751 break;
2752 q=jpeg_pixels;
cristybb503372010-05-27 20:51:26 +00002753 for (x=0; x < (ssize_t) image->columns; x++)
cristye90d7402010-03-14 18:21:29 +00002754 {
cristy4c08aed2011-07-01 19:47:50 +00002755 *q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelRed(image,p)) >> 4);
2756 *q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelGreen(image,p)) >> 4);
2757 *q++=(JSAMPLE) (ScaleQuantumToShort(GetPixelBlue(image,p)) >> 4);
cristyed231572011-07-14 02:18:59 +00002758 p+=GetPixelChannels(image);
cristye90d7402010-03-14 18:21:29 +00002759 }
2760 (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
cristycee97112010-05-28 00:44:52 +00002761 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
cristy524222d2011-04-25 00:37:06 +00002762 image->rows);
cristye90d7402010-03-14 18:21:29 +00002763 if (status == MagickFalse)
2764 break;
2765 }
2766 else
cristybb503372010-05-27 20:51:26 +00002767 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002768 {
cristy4c08aed2011-07-01 19:47:50 +00002769 register const Quantum
cristy3ed852e2009-09-05 21:47:34 +00002770 *p;
2771
cristybb503372010-05-27 20:51:26 +00002772 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002773 x;
2774
cristy1e178e72011-08-28 19:44:34 +00002775 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002776 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002777 break;
2778 q=jpeg_pixels;
cristybb503372010-05-27 20:51:26 +00002779 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002780 {
2781 /*
2782 Convert DirectClass packets to contiguous CMYK scanlines.
2783 */
cristye90d7402010-03-14 18:21:29 +00002784 *q++=(JSAMPLE) (4095-(ScaleQuantumToShort(
cristy4c08aed2011-07-01 19:47:50 +00002785 GetPixelRed(image,p)) >> 4));
cristye90d7402010-03-14 18:21:29 +00002786 *q++=(JSAMPLE) (4095-(ScaleQuantumToShort(
cristy4c08aed2011-07-01 19:47:50 +00002787 GetPixelGreen(image,p)) >> 4));
cristye90d7402010-03-14 18:21:29 +00002788 *q++=(JSAMPLE) (4095-(ScaleQuantumToShort(
cristy4c08aed2011-07-01 19:47:50 +00002789 GetPixelBlue(image,p)) >> 4));
cristy524222d2011-04-25 00:37:06 +00002790 *q++=(JSAMPLE) (4095-(ScaleQuantumToShort(
cristy4c08aed2011-07-01 19:47:50 +00002791 GetPixelBlack(image,p)) >> 4));
cristyed231572011-07-14 02:18:59 +00002792 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002793 }
2794 (void) jpeg_write_scanlines(&jpeg_info,scanline,1);
cristycee97112010-05-28 00:44:52 +00002795 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
cristy524222d2011-04-25 00:37:06 +00002796 image->rows);
cristye90d7402010-03-14 18:21:29 +00002797 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002798 break;
2799 }
cristybb503372010-05-27 20:51:26 +00002800 if (y == (ssize_t) image->rows)
cristy3ed852e2009-09-05 21:47:34 +00002801 jpeg_finish_compress(&jpeg_info);
2802 /*
2803 Relinquish resources.
2804 */
2805 jpeg_destroy_compress(&jpeg_info);
cristy22646e22013-06-23 16:34:03 +00002806 memory_info=RelinquishVirtualMemory(memory_info);
cristy3ed852e2009-09-05 21:47:34 +00002807 (void) CloseBlob(image);
2808 return(MagickTrue);
2809}
2810#endif